2013-10-11 08:18:13

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 00/17] ARM: at91: move to common clk framework

Hello,

This patch series is the 4th version of the new at91 clock implementation
(using common clk framework).

Most of the clock provided by the PMC (Power Management Controller) are
implemented :
- main clock (main oscillator)
- pll clocks
- master clock
- programmable clocks
- utmi clock
- peripheral clocks
- system clocks

This implementation is only compatible with device tree definition.
The goal is to define the whole clock tree in the device tree.

Could a dt maintainer take a look at these dt bindinds.

This patch series is based on linus tree (but should apply correctly on
linux-next) and has been tested on sama5d31ek.

Best Regards,
Boris

Changes since v3:
- simplify master clk implementation (drop set_rate/parent support)
- fix bug in set_rate function of pll driver
- fix coding style issues
- define macros and constants where needed
- remove peripheral id macro references
- remove sam9g35 specific handling (sam9g35 = sam9x5)
- rework main clk prepare function to handle automatic rate calculation

Changes since v2:
- fix several bugs in clk implementations
- drop non-dt boards support
- split the series to ease review and tests:
* 1 patch series for new clk implementations (this series)
* 1 patch series to move each at91 SoC to common clk framework (coming soon)
- modify dt-bindings (add atmel,clk- prefix to atmel specific properties)
- add clk macros for dt-bindings
- add pmc framework (helper function to access pmc registers)
- add interrupt support to enable passive wait in clk_prepare functions

Changes since v1:
- fix bugs in pll, programmable and system clock implementations
(wrong bit position).
- add usb clock configuration support (ohci and udc drivers +
clk_lookup for non dt boards)
- rework of the system clock interfaces (no need to define a parent clock,
system clock is a gate with no rate info)
- change system, peripheral and programmable clk dt bindings (1 master node
and multiple child nodes each defining a system/peripheral or prog clock)
- fix bugs in sama5 dt definition

Boris BREZILLON (17):
ARM: at91: move at91_pmc.h to include/linux/clk/at91_pmc.h
ARM: at91: add Kconfig options for common clk support
clk: at91: add PMC base support
clk: at91: add PMC macro file for dt definitions
clk: at91: add PMC main clock
clk: at91: add PMC pll clocks
clk: at91: add PMC master clock
clk: at91: add PMC system clocks
clk: at91: add PMC peripheral clocks
clk: at91: add peripheral clk macros for peripheral clk dt bindings
clk: at91: add PMC programmable clocks
clk: at91: add PMC utmi clock
clk: at91: add PMC usb clock
clk: at91: add PMC smd clock
clk: at91: add PMC clk device tree binding doc.
ARM: at91: move pit timer to common clk framework
ARM: at91: add new compatible strings for pmc driver

.../devicetree/bindings/clock/at91-clock.txt | 328 ++++++++++++
arch/arm/mach-at91/Kconfig | 44 ++
arch/arm/mach-at91/Kconfig.non_dt | 6 +
arch/arm/mach-at91/Makefile | 2 +-
arch/arm/mach-at91/at91rm9200.c | 2 +-
arch/arm/mach-at91/at91sam9260.c | 2 +-
arch/arm/mach-at91/at91sam9261.c | 2 +-
arch/arm/mach-at91/at91sam9263.c | 2 +-
arch/arm/mach-at91/at91sam926x_time.c | 14 +-
arch/arm/mach-at91/at91sam9g45.c | 2 +-
arch/arm/mach-at91/at91sam9n12.c | 2 +-
arch/arm/mach-at91/at91sam9rl.c | 2 +-
arch/arm/mach-at91/at91sam9x5.c | 2 +-
arch/arm/mach-at91/clock.c | 7 +-
arch/arm/mach-at91/generic.h | 3 +-
arch/arm/mach-at91/pm.c | 2 +-
arch/arm/mach-at91/pm_slowclock.S | 2 +-
arch/arm/mach-at91/sama5d3.c | 2 +-
arch/arm/mach-at91/setup.c | 8 +-
drivers/clk/Makefile | 1 +
drivers/clk/at91/Makefile | 12 +
drivers/clk/at91/clk-main.c | 189 +++++++
drivers/clk/at91/clk-master.c | 278 ++++++++++
drivers/clk/at91/clk-peripheral.c | 407 +++++++++++++++
drivers/clk/at91/clk-pll.c | 539 ++++++++++++++++++++
drivers/clk/at91/clk-plldiv.c | 137 +++++
drivers/clk/at91/clk-programmable.c | 423 +++++++++++++++
drivers/clk/at91/clk-smd.c | 173 +++++++
drivers/clk/at91/clk-system.c | 193 +++++++
drivers/clk/at91/clk-usb.c | 400 +++++++++++++++
drivers/clk/at91/clk-utmi.c | 162 ++++++
drivers/clk/at91/pmc.c | 372 ++++++++++++++
drivers/clk/at91/pmc.h | 113 ++++
drivers/usb/gadget/atmel_usba_udc.c | 2 +-
include/dt-bindings/clk/at91.h | 28 +
.../include/mach => include/linux/clk}/at91_pmc.h | 4 +-
36 files changed, 3847 insertions(+), 20 deletions(-)
create mode 100644 Documentation/devicetree/bindings/clock/at91-clock.txt
create mode 100644 drivers/clk/at91/Makefile
create mode 100644 drivers/clk/at91/clk-main.c
create mode 100644 drivers/clk/at91/clk-master.c
create mode 100644 drivers/clk/at91/clk-peripheral.c
create mode 100644 drivers/clk/at91/clk-pll.c
create mode 100644 drivers/clk/at91/clk-plldiv.c
create mode 100644 drivers/clk/at91/clk-programmable.c
create mode 100644 drivers/clk/at91/clk-smd.c
create mode 100644 drivers/clk/at91/clk-system.c
create mode 100644 drivers/clk/at91/clk-usb.c
create mode 100644 drivers/clk/at91/clk-utmi.c
create mode 100644 drivers/clk/at91/pmc.c
create mode 100644 drivers/clk/at91/pmc.h
create mode 100644 include/dt-bindings/clk/at91.h
rename {arch/arm/mach-at91/include/mach => include/linux/clk}/at91_pmc.h (98%)

--
1.7.9.5


2013-10-11 07:48:49

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 02/17] ARM: at91: add Kconfig options for common clk support

This patch adds the following Kconfig options to prepare the transition to
common clk framework:

- AT91_USE_OLD_CLK: this option is selected by every SoC which does not
support new at91 clks based on common clk framework (SoC which does not
define the clock tree in its device tree).
This options is also selected when the user choose non dt boards support
(new at91 clks can only be registered from a device tree definition).

- COMMON_CLK_AT91: this option cannot be selected directly. Instead it is
enabled if these 3 conditions are met:
* at least one of the selected SoCs have a PMC (Power Management
Controller) Unit
* device tree support is enabled
* the old at91 clk implementation is disabled (every selected SoC define
its clks in its device tree and non dt boards support is disabled)

- OLD_CLK_AT91: this option cannot be selected directly. Instead it is
enabled if these 2 conditions are met:
* at least one of the selected SoCs have a PMC (Power Management
Controller) Unit
* at least one of the selected SoCs does not define its clks in its
device tree or non dt-boards support is enabled

This patch selects AT91_USE_OLD_CLK in all currently supported SoCs. These
selects will be removed after clk definitions are properly added in each
soc's device tree.
It also selects AT91_USE_OLD_CLK in all non-dt boards support.

AT91_PMC_UNIT references are replaced by OLD_CLK_AT91, because PMC Unit is
enabled for both old and common clk implementations, and old clk
implementation should not be compiled if COMMON_CLK is enabled.

To avoid future link errors, a new stub is created for at91_dt_clock_init
function if OLD_CLK_AT91 is disabled.

A new check is added in dt init functions (setup.c) to prepare for SoCs
supporting new clk implementation. These SoCs won't setup the
register_clocks callback (clk registration is done using of_clk_init).

Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/mach-at91/Kconfig | 21 +++++++++++++++++++++
arch/arm/mach-at91/Kconfig.non_dt | 6 ++++++
arch/arm/mach-at91/Makefile | 2 +-
arch/arm/mach-at91/generic.h | 3 ++-
arch/arm/mach-at91/setup.c | 6 ++++--
5 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
index 699b71e..85b53a4 100644
--- a/arch/arm/mach-at91/Kconfig
+++ b/arch/arm/mach-at91/Kconfig
@@ -6,10 +6,22 @@ config HAVE_AT91_DBGU0
config HAVE_AT91_DBGU1
bool

+config AT91_USE_OLD_CLK
+ bool
+
config AT91_PMC_UNIT
bool
default !ARCH_AT91X40

+config COMMON_CLK_AT91
+ bool
+ default AT91_PMC_UNIT && USE_OF && !AT91_USE_OLD_CLK
+ select COMMON_CLK
+
+config OLD_CLK_AT91
+ bool
+ default AT91_PMC_UNIT && AT91_USE_OLD_CLK
+
config AT91_SAM9_ALT_RESET
bool
default !ARCH_AT91X40
@@ -65,6 +77,7 @@ config SOC_SAMA5D3
select SOC_SAMA5
select HAVE_FB_ATMEL
select HAVE_AT91_DBGU1
+ select AT91_USE_OLD_CLK
help
Select this if you are using one of Atmel's SAMA5D3 family SoC.
This support covers SAMA5D31, SAMA5D33, SAMA5D34, SAMA5D35.
@@ -78,11 +91,13 @@ config SOC_AT91RM9200
select HAVE_AT91_DBGU0
select MULTI_IRQ_HANDLER
select SPARSE_IRQ
+ select AT91_USE_OLD_CLK

config SOC_AT91SAM9260
bool "AT91SAM9260, AT91SAM9XE or AT91SAM9G20"
select HAVE_AT91_DBGU0
select SOC_AT91SAM9
+ select AT91_USE_OLD_CLK
help
Select this if you are using one of Atmel's AT91SAM9260, AT91SAM9XE
or AT91SAM9G20 SoC.
@@ -92,6 +107,7 @@ config SOC_AT91SAM9261
select HAVE_AT91_DBGU0
select HAVE_FB_ATMEL
select SOC_AT91SAM9
+ select AT91_USE_OLD_CLK
help
Select this if you are using one of Atmel's AT91SAM9261 or AT91SAM9G10 SoC.

@@ -100,18 +116,21 @@ config SOC_AT91SAM9263
select HAVE_AT91_DBGU1
select HAVE_FB_ATMEL
select SOC_AT91SAM9
+ select AT91_USE_OLD_CLK

config SOC_AT91SAM9RL
bool "AT91SAM9RL"
select HAVE_AT91_DBGU0
select HAVE_FB_ATMEL
select SOC_AT91SAM9
+ select AT91_USE_OLD_CLK

config SOC_AT91SAM9G45
bool "AT91SAM9G45 or AT91SAM9M10 families"
select HAVE_AT91_DBGU1
select HAVE_FB_ATMEL
select SOC_AT91SAM9
+ select AT91_USE_OLD_CLK
help
Select this if you are using one of Atmel's AT91SAM9G45 family SoC.
This support covers AT91SAM9G45, AT91SAM9G46, AT91SAM9M10 and AT91SAM9M11.
@@ -121,6 +140,7 @@ config SOC_AT91SAM9X5
select HAVE_AT91_DBGU0
select HAVE_FB_ATMEL
select SOC_AT91SAM9
+ select AT91_USE_OLD_CLK
help
Select this if you are using one of Atmel's AT91SAM9x5 family SoC.
This means that your SAM9 name finishes with a '5' (except if it is
@@ -133,6 +153,7 @@ config SOC_AT91SAM9N12
select HAVE_AT91_DBGU0
select HAVE_FB_ATMEL
select SOC_AT91SAM9
+ select AT91_USE_OLD_CLK
help
Select this if you are using Atmel's AT91SAM9N12 SoC.

diff --git a/arch/arm/mach-at91/Kconfig.non_dt b/arch/arm/mach-at91/Kconfig.non_dt
index ca900be..b736b57 100644
--- a/arch/arm/mach-at91/Kconfig.non_dt
+++ b/arch/arm/mach-at91/Kconfig.non_dt
@@ -12,26 +12,32 @@ config ARCH_AT91_NONE
config ARCH_AT91RM9200
bool "AT91RM9200"
select SOC_AT91RM9200
+ select AT91_USE_OLD_CLK

config ARCH_AT91SAM9260
bool "AT91SAM9260 or AT91SAM9XE or AT91SAM9G20"
select SOC_AT91SAM9260
+ select AT91_USE_OLD_CLK

config ARCH_AT91SAM9261
bool "AT91SAM9261 or AT91SAM9G10"
select SOC_AT91SAM9261
+ select AT91_USE_OLD_CLK

config ARCH_AT91SAM9263
bool "AT91SAM9263"
select SOC_AT91SAM9263
+ select AT91_USE_OLD_CLK

config ARCH_AT91SAM9RL
bool "AT91SAM9RL"
select SOC_AT91SAM9RL
+ select AT91_USE_OLD_CLK

config ARCH_AT91SAM9G45
bool "AT91SAM9G45"
select SOC_AT91SAM9G45
+ select AT91_USE_OLD_CLK

config ARCH_AT91X40
bool "AT91x40"
diff --git a/arch/arm/mach-at91/Makefile b/arch/arm/mach-at91/Makefile
index 3b0a953..8539411 100644
--- a/arch/arm/mach-at91/Makefile
+++ b/arch/arm/mach-at91/Makefile
@@ -7,7 +7,7 @@ obj-m :=
obj-n :=
obj- :=

-obj-$(CONFIG_AT91_PMC_UNIT) += clock.o
+obj-$(CONFIG_OLD_CLK_AT91) += clock.o
obj-$(CONFIG_AT91_SAM9_ALT_RESET) += at91sam9_alt_reset.o
obj-$(CONFIG_AT91_SAM9G45_RESET) += at91sam9g45_reset.o
obj-$(CONFIG_AT91_SAM9_TIME) += at91sam926x_time.o
diff --git a/arch/arm/mach-at91/generic.h b/arch/arm/mach-at91/generic.h
index dc6e2f5..375c42e 100644
--- a/arch/arm/mach-at91/generic.h
+++ b/arch/arm/mach-at91/generic.h
@@ -44,11 +44,12 @@ extern void at91sam926x_pit_init(void);
extern void at91x40_timer_init(void);

/* Clocks */
-#ifdef CONFIG_AT91_PMC_UNIT
+#ifdef CONFIG_OLD_CLK_AT91
extern int __init at91_clock_init(unsigned long main_clock);
extern int __init at91_dt_clock_init(void);
#else
static int inline at91_clock_init(unsigned long main_clock) { return 0; }
+static int inline at91_dt_clock_init(void) { return 0; }
#endif
struct device;

diff --git a/arch/arm/mach-at91/setup.c b/arch/arm/mach-at91/setup.c
index 41aa68b..06052fc 100644
--- a/arch/arm/mach-at91/setup.c
+++ b/arch/arm/mach-at91/setup.c
@@ -483,7 +483,8 @@ void __init at91rm9200_dt_initialize(void)
at91_dt_clock_init();

/* Register the processor-specific clocks */
- at91_boot_soc.register_clocks();
+ if (at91_boot_soc.register_clocks)
+ at91_boot_soc.register_clocks();

at91_boot_soc.init();
}
@@ -498,7 +499,8 @@ void __init at91_dt_initialize(void)
at91_dt_clock_init();

/* Register the processor-specific clocks */
- at91_boot_soc.register_clocks();
+ if (at91_boot_soc.register_clocks)
+ at91_boot_soc.register_clocks();

if (at91_boot_soc.init)
at91_boot_soc.init();
--
1.7.9.5

2013-10-11 07:53:06

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 01/17] ARM: at91: move at91_pmc.h to include/linux/clk/at91_pmc.h

This patch moves at91_pmc.h header from machine specific directory
(arch/arm/mach-at91/include/mach/at91_pmc.h) to clk include directory
(include/linux/clk/at91_pmc.h).
We need this to avoid reference to machine specific headers in clk
drivers.

Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/mach-at91/at91rm9200.c | 2 +-
arch/arm/mach-at91/at91sam9260.c | 2 +-
arch/arm/mach-at91/at91sam9261.c | 2 +-
arch/arm/mach-at91/at91sam9263.c | 2 +-
arch/arm/mach-at91/at91sam9g45.c | 2 +-
arch/arm/mach-at91/at91sam9n12.c | 2 +-
arch/arm/mach-at91/at91sam9rl.c | 2 +-
arch/arm/mach-at91/at91sam9x5.c | 2 +-
arch/arm/mach-at91/clock.c | 2 +-
arch/arm/mach-at91/pm.c | 2 +-
arch/arm/mach-at91/pm_slowclock.S | 2 +-
arch/arm/mach-at91/sama5d3.c | 2 +-
arch/arm/mach-at91/setup.c | 2 +-
drivers/usb/gadget/atmel_usba_udc.c | 2 +-
.../include/mach => include/linux/clk}/at91_pmc.h | 2 +-
15 files changed, 15 insertions(+), 15 deletions(-)
rename {arch/arm/mach-at91/include/mach => include/linux/clk}/at91_pmc.h (99%)

diff --git a/arch/arm/mach-at91/at91rm9200.c b/arch/arm/mach-at91/at91rm9200.c
index 4aad93d..102dac6 100644
--- a/arch/arm/mach-at91/at91rm9200.c
+++ b/arch/arm/mach-at91/at91rm9200.c
@@ -12,13 +12,13 @@

#include <linux/module.h>
#include <linux/reboot.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/irq.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <asm/system_misc.h>
#include <mach/at91rm9200.h>
-#include <mach/at91_pmc.h>
#include <mach/at91_st.h>
#include <mach/cpu.h>

diff --git a/arch/arm/mach-at91/at91sam9260.c b/arch/arm/mach-at91/at91sam9260.c
index 5de6074..16d7817 100644
--- a/arch/arm/mach-at91/at91sam9260.c
+++ b/arch/arm/mach-at91/at91sam9260.c
@@ -11,6 +11,7 @@
*/

#include <linux/module.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/proc-fns.h>
#include <asm/irq.h>
@@ -20,7 +21,6 @@
#include <mach/cpu.h>
#include <mach/at91_dbgu.h>
#include <mach/at91sam9260.h>
-#include <mach/at91_pmc.h>

#include "at91_aic.h"
#include "at91_rstc.h"
diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
index 0e07932..c8f5958 100644
--- a/arch/arm/mach-at91/at91sam9261.c
+++ b/arch/arm/mach-at91/at91sam9261.c
@@ -11,6 +11,7 @@
*/

#include <linux/module.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/proc-fns.h>
#include <asm/irq.h>
@@ -19,7 +20,6 @@
#include <asm/system_misc.h>
#include <mach/cpu.h>
#include <mach/at91sam9261.h>
-#include <mach/at91_pmc.h>

#include "at91_aic.h"
#include "at91_rstc.h"
diff --git a/arch/arm/mach-at91/at91sam9263.c b/arch/arm/mach-at91/at91sam9263.c
index 6ce7d18..cd8ad95 100644
--- a/arch/arm/mach-at91/at91sam9263.c
+++ b/arch/arm/mach-at91/at91sam9263.c
@@ -11,6 +11,7 @@
*/

#include <linux/module.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/proc-fns.h>
#include <asm/irq.h>
@@ -18,7 +19,6 @@
#include <asm/mach/map.h>
#include <asm/system_misc.h>
#include <mach/at91sam9263.h>
-#include <mach/at91_pmc.h>

#include "at91_aic.h"
#include "at91_rstc.h"
diff --git a/arch/arm/mach-at91/at91sam9g45.c b/arch/arm/mach-at91/at91sam9g45.c
index 474ee04..9794b20 100644
--- a/arch/arm/mach-at91/at91sam9g45.c
+++ b/arch/arm/mach-at91/at91sam9g45.c
@@ -12,13 +12,13 @@

#include <linux/module.h>
#include <linux/dma-mapping.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/irq.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <asm/system_misc.h>
#include <mach/at91sam9g45.h>
-#include <mach/at91_pmc.h>
#include <mach/cpu.h>

#include "at91_aic.h"
diff --git a/arch/arm/mach-at91/at91sam9n12.c b/arch/arm/mach-at91/at91sam9n12.c
index c7d670d..5b83c66 100644
--- a/arch/arm/mach-at91/at91sam9n12.c
+++ b/arch/arm/mach-at91/at91sam9n12.c
@@ -8,12 +8,12 @@

#include <linux/module.h>
#include <linux/dma-mapping.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/irq.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <mach/at91sam9n12.h>
-#include <mach/at91_pmc.h>
#include <mach/cpu.h>

#include "board.h"
diff --git a/arch/arm/mach-at91/at91sam9rl.c b/arch/arm/mach-at91/at91sam9rl.c
index d4ec0d9..947da95 100644
--- a/arch/arm/mach-at91/at91sam9rl.c
+++ b/arch/arm/mach-at91/at91sam9rl.c
@@ -10,6 +10,7 @@
*/

#include <linux/module.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/proc-fns.h>
#include <asm/irq.h>
@@ -19,7 +20,6 @@
#include <mach/cpu.h>
#include <mach/at91_dbgu.h>
#include <mach/at91sam9rl.h>
-#include <mach/at91_pmc.h>

#include "at91_aic.h"
#include "at91_rstc.h"
diff --git a/arch/arm/mach-at91/at91sam9x5.c b/arch/arm/mach-at91/at91sam9x5.c
index 916e5a1..a930d73 100644
--- a/arch/arm/mach-at91/at91sam9x5.c
+++ b/arch/arm/mach-at91/at91sam9x5.c
@@ -8,12 +8,12 @@

#include <linux/module.h>
#include <linux/dma-mapping.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/irq.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <mach/at91sam9x5.h>
-#include <mach/at91_pmc.h>
#include <mach/cpu.h>

#include "board.h"
diff --git a/arch/arm/mach-at91/clock.c b/arch/arm/mach-at91/clock.c
index 6b2630a..5f02aea 100644
--- a/arch/arm/mach-at91/clock.c
+++ b/arch/arm/mach-at91/clock.c
@@ -24,9 +24,9 @@
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/of_address.h>
+#include <linux/clk/at91_pmc.h>

#include <mach/hardware.h>
-#include <mach/at91_pmc.h>
#include <mach/cpu.h>

#include <asm/proc-fns.h>
diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c
index 15afb5d..692cacd 100644
--- a/arch/arm/mach-at91/pm.c
+++ b/arch/arm/mach-at91/pm.c
@@ -19,13 +19,13 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/irq.h>
#include <linux/atomic.h>
#include <asm/mach/time.h>
#include <asm/mach/irq.h>

-#include <mach/at91_pmc.h>
#include <mach/cpu.h>

#include "at91_aic.h"
diff --git a/arch/arm/mach-at91/pm_slowclock.S b/arch/arm/mach-at91/pm_slowclock.S
index 098c28d..2001877 100644
--- a/arch/arm/mach-at91/pm_slowclock.S
+++ b/arch/arm/mach-at91/pm_slowclock.S
@@ -13,8 +13,8 @@
*/

#include <linux/linkage.h>
+#include <linux/clk/at91_pmc.h>
#include <mach/hardware.h>
-#include <mach/at91_pmc.h>
#include <mach/at91_ramc.h>


diff --git a/arch/arm/mach-at91/sama5d3.c b/arch/arm/mach-at91/sama5d3.c
index 4012797..0003949 100644
--- a/arch/arm/mach-at91/sama5d3.c
+++ b/arch/arm/mach-at91/sama5d3.c
@@ -9,12 +9,12 @@

#include <linux/module.h>
#include <linux/dma-mapping.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/irq.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <mach/sama5d3.h>
-#include <mach/at91_pmc.h>
#include <mach/cpu.h>

#include "soc.h"
diff --git a/arch/arm/mach-at91/setup.c b/arch/arm/mach-at91/setup.c
index b17fbcf..41aa68b 100644
--- a/arch/arm/mach-at91/setup.c
+++ b/arch/arm/mach-at91/setup.c
@@ -11,6 +11,7 @@
#include <linux/pm.h>
#include <linux/of_address.h>
#include <linux/pinctrl/machine.h>
+#include <linux/clk/at91_pmc.h>

#include <asm/system_misc.h>
#include <asm/mach/map.h>
@@ -18,7 +19,6 @@
#include <mach/hardware.h>
#include <mach/cpu.h>
#include <mach/at91_dbgu.h>
-#include <mach/at91_pmc.h>

#include "at91_shdwc.h"
#include "soc.h"
diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c
index 2cb52e0..9f71d9f 100644
--- a/drivers/usb/gadget/atmel_usba_udc.c
+++ b/drivers/usb/gadget/atmel_usba_udc.c
@@ -326,7 +326,7 @@ static int vbus_is_present(struct usba_udc *udc)

#if defined(CONFIG_ARCH_AT91SAM9RL)

-#include <mach/at91_pmc.h>
+#include <linux/clk/at91_pmc.h>

static void toggle_bias(int is_on)
{
diff --git a/arch/arm/mach-at91/include/mach/at91_pmc.h b/include/linux/clk/at91_pmc.h
similarity index 99%
rename from arch/arm/mach-at91/include/mach/at91_pmc.h
rename to include/linux/clk/at91_pmc.h
index c604cc6..00c45b3 100644
--- a/arch/arm/mach-at91/include/mach/at91_pmc.h
+++ b/include/linux/clk/at91_pmc.h
@@ -1,5 +1,5 @@
/*
- * arch/arm/mach-at91/include/mach/at91_pmc.h
+ * include/linux/clk/at91_pmc.h
*
* Copyright (C) 2005 Ivan Kokshaysky
* Copyright (C) SAN People
--
1.7.9.5

2013-10-11 07:54:20

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 03/17] clk: at91: add PMC base support

This patch adds at91 PMC (Power Management Controller) base support.

All at91 clocks managed by the PMC unit will use this framework.

This framework provides the following fonctionalities:
- define a new struct at91_pmc to hide PMC internals (lock, PMC memory
mapping, irq domain, ...)
- read/write helper functions (pmc_read/write) to access PMC registers
- lock/unlock helper functions (pmc_lock/unlock) to lock/unlock access to
pmc registers
- a new irq domain and its associated irq chip to request PMC specific
interrupts (useful for clk prepare callbacks)

The PMC unit is declared as a dt clk provider (CLK_OF_DECLARE), and every
clk using this framework will declare a table of of_at91_clk_init_cb_t
and add it to the pmc_clk_ids table.

When the pmc dt clock setup function is called (by of_clk_init function),
it triggers the registration of every supported child clk (those matching
the definitions in pmc_clk_ids).

This patch copies at91_pmc_base (memory mapping) and at91sam9_idle
(function) from arch/arm/mach-at91/clock.c (which is not compiled if
COMMON_CLK_AT91 is enabled).

Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/clk/Makefile | 1 +
drivers/clk/at91/Makefile | 5 +
drivers/clk/at91/pmc.c | 283 +++++++++++++++++++++++++++++++++++++++++++++
drivers/clk/at91/pmc.h | 58 ++++++++++
4 files changed, 347 insertions(+)
create mode 100644 drivers/clk/at91/Makefile
create mode 100644 drivers/clk/at91/pmc.c
create mode 100644 drivers/clk/at91/pmc.h

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 7b11106..28c2678 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -32,6 +32,7 @@ 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_AT91) += at91/

obj-$(CONFIG_X86) += x86/

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
new file mode 100644
index 0000000..1d4fb21
--- /dev/null
+++ b/drivers/clk/at91/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for at91 specific clk
+#
+
+obj-y += pmc.o
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
new file mode 100644
index 0000000..d92e46c
--- /dev/null
+++ b/drivers/clk/at91/pmc.c
@@ -0,0 +1,283 @@
+/*
+ * drivers/clk/at91/pmc.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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 <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of_irq.h>
+
+#include <asm/proc-fns.h>
+
+#include "pmc.h"
+
+void __iomem *at91_pmc_base;
+EXPORT_SYMBOL_GPL(at91_pmc_base);
+
+void at91sam9_idle(void)
+{
+ at91_pmc_write(AT91_PMC_SCDR, AT91_PMC_PCK);
+ cpu_do_idle();
+}
+
+static void pmc_irq_mask(struct irq_data *d)
+{
+ struct at91_pmc *pmc = irq_data_get_irq_chip_data(d);
+
+ pmc_write(pmc, AT91_PMC_IDR, 1 << d->hwirq);
+}
+
+static void pmc_irq_unmask(struct irq_data *d)
+{
+ struct at91_pmc *pmc = irq_data_get_irq_chip_data(d);
+
+ pmc_write(pmc, AT91_PMC_IER, 1 << d->hwirq);
+}
+
+static int pmc_irq_set_type(struct irq_data *d, unsigned type)
+{
+ if (type != IRQ_TYPE_LEVEL_HIGH) {
+ pr_warn("PMC: type not supported (support only IRQ_TYPE_LEVEL_HIGH type)\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct irq_chip pmc_irq = {
+ .name = "PMC",
+ .irq_disable = pmc_irq_mask,
+ .irq_mask = pmc_irq_mask,
+ .irq_unmask = pmc_irq_unmask,
+ .irq_set_type = pmc_irq_set_type,
+};
+
+static struct lock_class_key pmc_lock_class;
+
+static int pmc_irq_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct at91_pmc *pmc = h->host_data;
+
+ irq_set_lockdep_class(virq, &pmc_lock_class);
+
+ irq_set_chip_and_handler(virq, &pmc_irq,
+ handle_level_irq);
+ set_irq_flags(virq, IRQF_VALID);
+ irq_set_chip_data(virq, pmc);
+
+ return 0;
+}
+
+static int pmc_irq_domain_xlate(struct irq_domain *d,
+ struct device_node *ctrlr,
+ const u32 *intspec, unsigned int intsize,
+ irq_hw_number_t *out_hwirq,
+ unsigned int *out_type)
+{
+ struct at91_pmc *pmc = d->host_data;
+ const struct at91_pmc_caps *caps = pmc->caps;
+
+ if (WARN_ON(intsize < 1))
+ return -EINVAL;
+
+ *out_hwirq = intspec[0];
+
+ if (!(caps->available_irqs & (1 << *out_hwirq)))
+ return -EINVAL;
+
+ *out_type = IRQ_TYPE_LEVEL_HIGH;
+
+ return 0;
+}
+
+static struct irq_domain_ops pmc_irq_ops = {
+ .map = pmc_irq_map,
+ .xlate = pmc_irq_domain_xlate,
+};
+
+static irqreturn_t pmc_irq_handler(int irq, void *data)
+{
+ struct at91_pmc *pmc = (struct at91_pmc *)data;
+ unsigned long sr;
+ int n;
+
+ sr = pmc_read(pmc, AT91_PMC_SR) & pmc_read(pmc, AT91_PMC_IMR);
+ if (!sr)
+ return IRQ_NONE;
+
+ for_each_set_bit(n, &sr, BITS_PER_LONG)
+ generic_handle_irq(irq_find_mapping(pmc->irqdomain, n));
+
+ return IRQ_HANDLED;
+}
+
+static const struct at91_pmc_caps at91rm9200_caps = {
+ .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB |
+ AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY |
+ AT91_PMC_PCK1RDY | AT91_PMC_PCK2RDY |
+ AT91_PMC_PCK3RDY,
+};
+
+static const struct at91_pmc_caps at91sam9260_caps = {
+ .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB |
+ AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY |
+ AT91_PMC_PCK1RDY,
+};
+
+static const struct at91_pmc_caps at91sam9g45_caps = {
+ .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY |
+ AT91_PMC_LOCKU | AT91_PMC_PCK0RDY |
+ AT91_PMC_PCK1RDY,
+};
+
+static const struct at91_pmc_caps at91sam9n12_caps = {
+ .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB |
+ AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY |
+ AT91_PMC_PCK1RDY | AT91_PMC_MOSCSELS |
+ AT91_PMC_MOSCRCS | AT91_PMC_CFDEV,
+};
+
+static const struct at91_pmc_caps at91sam9x5_caps = {
+ .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY |
+ AT91_PMC_LOCKU | AT91_PMC_PCK0RDY |
+ AT91_PMC_PCK1RDY | AT91_PMC_MOSCSELS |
+ AT91_PMC_MOSCRCS | AT91_PMC_CFDEV,
+};
+
+static const struct at91_pmc_caps sama5d3_caps = {
+ .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY |
+ AT91_PMC_LOCKU | AT91_PMC_PCK0RDY |
+ AT91_PMC_PCK1RDY | AT91_PMC_PCK2RDY |
+ AT91_PMC_MOSCSELS | AT91_PMC_MOSCRCS |
+ AT91_PMC_CFDEV,
+};
+
+static struct at91_pmc *__init at91_pmc_init(struct device_node *np,
+ void __iomem *regbase, int virq,
+ const struct at91_pmc_caps *caps)
+{
+ struct at91_pmc *pmc;
+
+ if (!regbase || !virq || !caps)
+ return NULL;
+
+ at91_pmc_base = regbase;
+
+ pmc = kzalloc(sizeof(*pmc), GFP_KERNEL);
+ if (!pmc)
+ return NULL;
+
+ spin_lock_init(&pmc->lock);
+ pmc->regbase = regbase;
+ pmc->virq = virq;
+ pmc->caps = caps;
+
+ pmc->irqdomain = irq_domain_add_linear(np, 32, &pmc_irq_ops, pmc);
+
+ if (!pmc->irqdomain)
+ goto out_free_pmc;
+
+ pmc_write(pmc, AT91_PMC_IDR, 0xffffffff);
+ if (request_irq(pmc->virq, pmc_irq_handler, IRQF_SHARED, "pmc", pmc))
+ goto out_remove_irqdomain;
+
+ return pmc;
+
+out_remove_irqdomain:
+ irq_domain_remove(pmc->irqdomain);
+out_free_pmc:
+ kfree(pmc);
+
+ return NULL;
+}
+
+static const struct of_device_id pmc_clk_ids[] __initdata = {
+ { /*sentinel*/ }
+};
+
+static void __init of_at91_pmc_setup(struct device_node *np,
+ const struct at91_pmc_caps *caps)
+{
+ struct at91_pmc *pmc;
+ struct device_node *childnp;
+ void (*clk_setup)(struct device_node *, struct at91_pmc *);
+ const struct of_device_id *clk_id;
+ void __iomem *regbase = of_iomap(np, 0);
+ int virq;
+
+ if (!regbase)
+ return;
+
+ virq = irq_of_parse_and_map(np, 0);
+ if (!virq)
+ return;
+
+ pmc = at91_pmc_init(np, regbase, virq, caps);
+ if (!pmc)
+ return;
+ for_each_child_of_node(np, childnp) {
+ clk_id = of_match_node(pmc_clk_ids, childnp);
+ if (!clk_id)
+ continue;
+ clk_setup = clk_id->data;
+ clk_setup(childnp, pmc);
+ }
+}
+
+static void __init of_at91rm9200_pmc_setup(struct device_node *np)
+{
+ of_at91_pmc_setup(np, &at91rm9200_caps);
+}
+CLK_OF_DECLARE(at91rm9200_clk_main, "atmel,at91rm9200-pmc",
+ of_at91rm9200_pmc_setup);
+
+static void __init of_at91sam9260_pmc_setup(struct device_node *np)
+{
+ of_at91_pmc_setup(np, &at91sam9260_caps);
+}
+CLK_OF_DECLARE(at91sam9260_clk_main, "atmel,at91sam9260-pmc",
+ of_at91sam9260_pmc_setup);
+
+static void __init of_at91sam9g45_pmc_setup(struct device_node *np)
+{
+ of_at91_pmc_setup(np, &at91sam9g45_caps);
+}
+CLK_OF_DECLARE(at91sam9g45_clk_main, "atmel,at91sam9g45-pmc",
+ of_at91sam9g45_pmc_setup);
+
+static void __init of_at91sam9n12_pmc_setup(struct device_node *np)
+{
+ of_at91_pmc_setup(np, &at91sam9n12_caps);
+}
+CLK_OF_DECLARE(at91sam9n12_clk_main, "atmel,at91sam9n12-pmc",
+ of_at91sam9n12_pmc_setup);
+
+static void __init of_at91sam9x5_pmc_setup(struct device_node *np)
+{
+ of_at91_pmc_setup(np, &at91sam9x5_caps);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_main, "atmel,at91sam9x5-pmc",
+ of_at91sam9x5_pmc_setup);
+
+static void __init of_sama5d3_pmc_setup(struct device_node *np)
+{
+ of_at91_pmc_setup(np, &sama5d3_caps);
+}
+CLK_OF_DECLARE(sama5d3_clk_main, "atmel,sama5d3-pmc",
+ of_sama5d3_pmc_setup);
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
new file mode 100644
index 0000000..b7e8397
--- /dev/null
+++ b/drivers/clk/at91/pmc.h
@@ -0,0 +1,58 @@
+/*
+ * drivers/clk/at91/pmc.h
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef __PMC_H_
+#define __PMC_H_
+
+#include <linux/io.h>
+#include <linux/irqdomain.h>
+#include <linux/spinlock.h>
+
+struct clk_range {
+ unsigned long min;
+ unsigned long max;
+};
+
+#define CLK_RANGE(MIN, MAX) {.min = MIN, .max = MAX,}
+
+struct at91_pmc_caps {
+ u32 available_irqs;
+};
+
+struct at91_pmc {
+ void __iomem *regbase;
+ int virq;
+ spinlock_t lock;
+ const struct at91_pmc_caps *caps;
+ struct irq_domain *irqdomain;
+};
+
+static inline void pmc_lock(struct at91_pmc *pmc)
+{
+ spin_lock(&pmc->lock);
+}
+
+static inline void pmc_unlock(struct at91_pmc *pmc)
+{
+ spin_unlock(&pmc->lock);
+}
+
+static inline u32 pmc_read(struct at91_pmc *pmc, int offset)
+{
+ return readl_relaxed(pmc->regbase + offset);
+}
+
+static inline void pmc_write(struct at91_pmc *pmc, int offset, u32 value)
+{
+ return writel_relaxed(value, pmc->regbase + offset);
+}
+
+#endif /* __PMC_H_ */
--
1.7.9.5

2013-10-11 08:44:18

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 04/17] clk: at91: add PMC macro file for dt definitions

This patch adds a new macro file for PMC macros.

This macro file includes the definitions of SR (status register) bit
offsets and will be use to reference PMC irqs.

Signed-off-by: Boris BREZILLON <[email protected]>
---
include/dt-bindings/clk/at91.h | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 include/dt-bindings/clk/at91.h

diff --git a/include/dt-bindings/clk/at91.h b/include/dt-bindings/clk/at91.h
new file mode 100644
index 0000000..0b4cb99
--- /dev/null
+++ b/include/dt-bindings/clk/at91.h
@@ -0,0 +1,22 @@
+/*
+ * This header provides constants for AT91 pmc status.
+ *
+ * The constants defined in this header are being used in dts.
+ *
+ * Licensed under GPLv2 or later.
+ */
+
+#ifndef _DT_BINDINGS_CLK_AT91_H
+#define _DT_BINDINGS_CLK_AT91_H
+
+#define AT91_PMC_MOSCS 0 /* MOSCS Flag */
+#define AT91_PMC_LOCKA 1 /* PLLA Lock */
+#define AT91_PMC_LOCKB 2 /* PLLB Lock */
+#define AT91_PMC_MCKRDY 3 /* Master Clock */
+#define AT91_PMC_LOCKU 6 /* UPLL Lock */
+#define AT91_PMC_PCKRDY(id) (8 + (id)) /* Programmable Clock */
+#define AT91_PMC_MOSCSELS 16 /* Main Oscillator Selection */
+#define AT91_PMC_MOSCRCS 17 /* Main On-Chip RC */
+#define AT91_PMC_CFDEV 18 /* Clock Failure Detector Event */
+
+#endif
--
1.7.9.5

2013-10-11 08:53:52

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 07/17] clk: at91: add PMC master clock

This patch adds new at91 master clock implementation using common clk
framework.

The master clock layout describe the MCKR register layout.
There are 2 master clock layouts:
- at91rm9200
- at91sam9x5

Master clocks are given characteristics:
- min/max clock output rate

These characteristics are checked during rate change to avoid
over/underclocking.

These characteristics are described in atmel's SoC datasheet in
"Electrical Characteristics" paragraph.

Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/clk/at91/Makefile | 2 +-
drivers/clk/at91/clk-master.c | 278 +++++++++++++++++++++++++++++++++++++++++
drivers/clk/at91/clk-pll.c | 10 +-
drivers/clk/at91/pmc.c | 9 ++
drivers/clk/at91/pmc.h | 5 +
5 files changed, 295 insertions(+), 9 deletions(-)
create mode 100644 drivers/clk/at91/clk-master.c

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 902bbf1..e28fb2b 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -3,4 +3,4 @@
#

obj-y += pmc.o
-obj-y += clk-main.o clk-pll.o clk-plldiv.o
+obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
new file mode 100644
index 0000000..58af23d
--- /dev/null
+++ b/drivers/clk/at91/clk-master.c
@@ -0,0 +1,278 @@
+/*
+ * drivers/clk/at91/clk-master.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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/of_irq.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include "pmc.h"
+
+#define MASTER_SOURCE_MAX 4
+
+#define MASTER_PRES_MASK 0x7
+#define MASTER_PRES_MAX MASTER_PRES_MASK
+#define MASTER_DIV_SHIFT 8
+#define MASTER_DIV_MASK 0x3
+
+struct clk_master_characteristics {
+ struct clk_range output;
+ u32 divisors[4];
+ u8 have_div3_pres;
+};
+
+struct clk_master_layout {
+ u32 mask;
+ u8 pres_shift;
+};
+
+#define to_clk_master(hw) container_of(hw, struct clk_master, hw)
+
+struct clk_master {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ unsigned int irq;
+ wait_queue_head_t wait;
+ const struct clk_master_layout *layout;
+ const struct clk_master_characteristics *characteristics;
+};
+
+static irqreturn_t clk_master_irq_handler(int irq, void *dev_id)
+{
+ struct clk_master *master = (struct clk_master *)dev_id;
+
+ wake_up(&master->wait);
+ disable_irq_nosync(master->irq);
+
+ return IRQ_HANDLED;
+}
+static int clk_master_prepare(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ struct at91_pmc *pmc = master->pmc;
+
+ while (!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY)) {
+ enable_irq(master->irq);
+ wait_event(master->wait,
+ pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY);
+ }
+
+ return 0;
+}
+
+static int clk_master_is_prepared(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+
+ return !!(pmc_read(master->pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY);
+}
+
+static unsigned long clk_master_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ u8 pres;
+ u8 div;
+ unsigned long rate = parent_rate;
+ struct clk_master *master = to_clk_master(hw);
+ struct at91_pmc *pmc = master->pmc;
+ const struct clk_master_layout *layout = master->layout;
+ const struct clk_master_characteristics *characteristics =
+ master->characteristics;
+ u32 tmp;
+
+ pmc_lock(pmc);
+ tmp = pmc_read(pmc, AT91_PMC_MCKR) & layout->mask;
+ pmc_unlock(pmc);
+
+ pres = (tmp >> layout->pres_shift) & MASTER_PRES_MASK;
+ div = (tmp >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+
+ if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
+ rate /= 3;
+ else
+ rate >>= pres;
+
+ rate /= characteristics->divisors[div];
+
+ if (rate < characteristics->output.min)
+ pr_warn("master clk is underclocked");
+ else if (rate > characteristics->output.max)
+ pr_warn("master clk is overclocked");
+
+ return rate;
+}
+
+static u8 clk_master_get_parent(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ struct at91_pmc *pmc = master->pmc;
+
+ return pmc_read(pmc, AT91_PMC_MCKR) & AT91_PMC_CSS;
+}
+
+static const struct clk_ops master_ops = {
+ .prepare = clk_master_prepare,
+ .is_prepared = clk_master_is_prepared,
+ .recalc_rate = clk_master_recalc_rate,
+ .get_parent = clk_master_get_parent,
+};
+
+static struct clk * __init
+at91_clk_register_master(struct at91_pmc *pmc, unsigned int irq,
+ const char *name, int num_parents,
+ const char **parent_names,
+ const struct clk_master_layout *layout,
+ const struct clk_master_characteristics *characteristics)
+{
+ int ret;
+ struct clk_master *master;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ if (!pmc || !irq || !name || !num_parents || !parent_names)
+ return ERR_PTR(-EINVAL);
+
+ master = kzalloc(sizeof(*master), GFP_KERNEL);
+ if (!master)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &master_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = 0;
+
+ master->hw.init = &init;
+ master->layout = layout;
+ master->characteristics = characteristics;
+ master->pmc = pmc;
+ master->irq = irq;
+ init_waitqueue_head(&master->wait);
+ irq_set_status_flags(master->irq, IRQ_NOAUTOEN);
+ ret = request_irq(master->irq, clk_master_irq_handler,
+ IRQF_TRIGGER_HIGH, "clk-master", master);
+ if (ret)
+ return ERR_PTR(ret);
+
+ clk = clk_register(NULL, &master->hw);
+ if (IS_ERR(clk))
+ kfree(master);
+
+ return clk;
+}
+
+
+static const struct clk_master_layout at91rm9200_master_layout = {
+ .mask = 0x31F,
+ .pres_shift = 2,
+};
+
+static const struct clk_master_layout at91sam9x5_master_layout = {
+ .mask = 0x373,
+ .pres_shift = 4,
+};
+
+
+static struct clk_master_characteristics * __init
+of_at91_clk_master_get_characteristics(struct device_node *np)
+{
+ u32 tmp;
+ struct clk_master_characteristics *characteristics;
+
+ characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL);
+ if (!characteristics)
+ return NULL;
+
+ if (of_property_read_u32_index(np, "atmel,clk-output-range", 0, &tmp))
+ goto out_free_characteristics;
+ characteristics->output.min = tmp;
+
+ if (of_property_read_u32_index(np, "atmel,clk-output-range", 1, &tmp))
+ goto out_free_characteristics;
+ characteristics->output.max = tmp;
+
+ of_property_read_u32_array(np, "atmel,clk-divisors",
+ characteristics->divisors, 4);
+
+ characteristics->have_div3_pres =
+ of_property_read_bool(np, "atmel,master-clk-have-div3-pres");
+
+ return characteristics;
+
+out_free_characteristics:
+ kfree(characteristics);
+ return NULL;
+}
+
+static void __init
+of_at91_clk_master_setup(struct device_node *np, struct at91_pmc *pmc,
+ const struct clk_master_layout *layout)
+{
+ struct clk *clk;
+ int num_parents;
+ int i;
+ unsigned int irq;
+ const char *parent_names[MASTER_SOURCE_MAX];
+ const char *name = np->name;
+ struct clk_master_characteristics *characteristics;
+
+ num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells");
+ if (num_parents <= 0 || num_parents > MASTER_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);
+
+ characteristics = of_at91_clk_master_get_characteristics(np);
+ if (!characteristics)
+ return;
+
+ irq = irq_of_parse_and_map(np, 0);
+ if (!irq)
+ return;
+
+ clk = at91_clk_register_master(pmc, irq, name, num_parents,
+ parent_names, layout,
+ characteristics);
+ if (IS_ERR(clk))
+ goto out_free_characteristics;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+ return;
+
+out_free_characteristics:
+ kfree(characteristics);
+}
+
+void __init of_at91rm9200_clk_master_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_master_setup(np, pmc, &at91rm9200_master_layout);
+}
+
+void __init of_at91sam9x5_clk_master_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_master_setup(np, pmc, &at91sam9x5_master_layout);
+}
diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c
index 44caddf..351adc5 100644
--- a/drivers/clk/at91/clk-pll.c
+++ b/drivers/clk/at91/clk-pll.c
@@ -82,6 +82,8 @@ static int clk_pll_prepare(struct clk_hw *hw)
struct clk_pll *pll = to_clk_pll(hw);
struct at91_pmc *pmc = pll->pmc;
const struct clk_pll_layout *layout = pll->layout;
+ const struct clk_pll_characteristics *characteristics =
+ pll->characteristics;
u8 id = pll->id;
u32 mask = PLL_STATUS_MASK(id);
int offset = PLL_REG(id);
@@ -271,18 +273,10 @@ static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_pll *pll = to_clk_pll(hw);
- struct at91_pmc *pmc = pll->pmc;
- const struct clk_pll_layout *layout = pll->layout;
- const struct clk_pll_characteristics *characteristics =
- pll->characteristics;
- u8 id = pll->id;
- int offset = PLL_REG(id);
long ret;
u32 div;
u32 mul;
u32 index;
- u32 tmp;
- u8 out = 0;

ret = clk_pll_get_best_div_mul(pll, rate, parent_rate,
&div, &mul, &index);
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index c882e97..6f08879 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -234,6 +234,15 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
.compatible = "atmel,at91sam9x5-clk-plldiv",
.data = of_at91sam9x5_clk_plldiv_setup,
},
+ /* Master clock */
+ {
+ .compatible = "atmel,at91rm9200-clk-master",
+ .data = of_at91rm9200_clk_master_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-clk-master",
+ .data = of_at91sam9x5_clk_master_setup,
+ },
{ /*sentinel*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 35ccbbc..2e1dcb8 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -69,4 +69,9 @@ extern void __init of_sama5d3_clk_pll_setup(struct device_node *np,
extern void __init of_at91sam9x5_clk_plldiv_setup(struct device_node *np,
struct at91_pmc *pmc);

+extern void __init of_at91rm9200_clk_master_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9x5_clk_master_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+
#endif /* __PMC_H_ */
--
1.7.9.5

2013-10-11 09:03:07

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 05/17] clk: at91: add PMC main clock

This patch adds new at91 main oscillator clock implementation using common
clk framework.

If rate is not provided during clock registration it is calculated using
the slow clock (main clk parent in this case) rate and MCFR register.

Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/clk/at91/Makefile | 1 +
drivers/clk/at91/clk-main.c | 189 +++++++++++++++++++++++++++++++++++++++++++
drivers/clk/at91/pmc.c | 5 ++
drivers/clk/at91/pmc.h | 3 +
4 files changed, 198 insertions(+)
create mode 100644 drivers/clk/at91/clk-main.c

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 1d4fb21..44105bd 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -3,3 +3,4 @@
#

obj-y += pmc.o
+obj-y += clk-main.o
diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c
new file mode 100644
index 0000000..905ceff
--- /dev/null
+++ b/drivers/clk/at91/clk-main.c
@@ -0,0 +1,189 @@
+/*
+ * drivers/clk/at91/clk-main.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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 SLOW_CLOCK_FREQ 32768
+#define MAINF_DIV 16
+#define MAINFRDY_TIMEOUT (((MAINF_DIV + 1) * USEC_PER_SEC) / \
+ SLOW_CLOCK_FREQ)
+#define MAINF_LOOP_MIN_WAIT (USEC_PER_SEC / SLOW_CLOCK_FREQ)
+#define MAINF_LOOP_MAX_WAIT MAINFRDY_TIMEOUT
+
+struct clk_main {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ unsigned long rate;
+ unsigned int irq;
+ wait_queue_head_t wait;
+};
+
+#define to_clk_main(hw) container_of(hw, struct clk_main, hw)
+
+static irqreturn_t clk_main_irq_handler(int irq, void *dev_id)
+{
+ struct clk_main *clkmain = (struct clk_main *)dev_id;
+
+ wake_up(&clkmain->wait);
+ disable_irq_nosync(clkmain->irq);
+
+ return IRQ_HANDLED;
+}
+
+static int clk_main_prepare(struct clk_hw *hw)
+{
+ struct clk_main *clkmain = to_clk_main(hw);
+ struct at91_pmc *pmc = clkmain->pmc;
+ unsigned long halt_time, timeout;
+ u32 tmp;
+
+ while (!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MOSCS)) {
+ enable_irq(clkmain->irq);
+ wait_event(clkmain->wait,
+ pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MOSCS);
+ }
+
+ if (clkmain->rate)
+ return 0;
+
+ timeout = jiffies + usecs_to_jiffies(MAINFRDY_TIMEOUT);
+ do {
+ halt_time = jiffies;
+ tmp = pmc_read(pmc, AT91_CKGR_MCFR);
+ if (tmp & AT91_PMC_MAINRDY)
+ return 0;
+ usleep_range(MAINF_LOOP_MIN_WAIT, MAINF_LOOP_MAX_WAIT);
+ } while (time_before(halt_time, timeout));
+
+ return 0;
+}
+
+static int clk_main_is_prepared(struct clk_hw *hw)
+{
+ struct clk_main *clkmain = to_clk_main(hw);
+
+ return !!(pmc_read(clkmain->pmc, AT91_PMC_SR) & AT91_PMC_MOSCS);
+}
+
+static unsigned long clk_main_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ u32 tmp;
+ struct clk_main *clkmain = to_clk_main(hw);
+ struct at91_pmc *pmc = clkmain->pmc;
+
+ if (clkmain->rate)
+ return clkmain->rate;
+
+ tmp = pmc_read(pmc, AT91_CKGR_MCFR) & AT91_PMC_MAINF;
+ clkmain->rate = (tmp * parent_rate) / MAINF_DIV;
+
+ return clkmain->rate;
+}
+
+static const struct clk_ops main_ops = {
+ .prepare = clk_main_prepare,
+ .is_prepared = clk_main_is_prepared,
+ .recalc_rate = clk_main_recalc_rate,
+};
+
+static struct clk * __init
+at91_clk_register_main(struct at91_pmc *pmc,
+ unsigned int irq,
+ const char *name,
+ const char *parent_name,
+ unsigned long rate)
+{
+ int ret;
+ struct clk_main *clkmain;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ if (!pmc || !irq || !name)
+ return ERR_PTR(-EINVAL);
+
+ if (!rate && !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ clkmain = kzalloc(sizeof(*clkmain), GFP_KERNEL);
+ if (!clkmain)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &main_ops;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+ init.flags = parent_name ? 0 : CLK_IS_ROOT;
+
+ clkmain->hw.init = &init;
+ clkmain->rate = rate;
+ clkmain->pmc = pmc;
+ clkmain->irq = irq;
+ init_waitqueue_head(&clkmain->wait);
+ irq_set_status_flags(clkmain->irq, IRQ_NOAUTOEN);
+ ret = request_irq(clkmain->irq, clk_main_irq_handler,
+ IRQF_TRIGGER_HIGH, "clk-main", clkmain);
+ if (ret)
+ return ERR_PTR(ret);
+
+ clk = clk_register(NULL, &clkmain->hw);
+ if (IS_ERR(clk)) {
+ free_irq(clkmain->irq, clkmain);
+ kfree(clkmain);
+ }
+
+ return clk;
+}
+
+
+
+static void __init
+of_at91_clk_main_setup(struct device_node *np, struct at91_pmc *pmc)
+{
+ struct clk *clk;
+ unsigned int irq;
+ const char *parent_name;
+ const char *name = np->name;
+ u32 rate = 0;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ of_property_read_string(np, "clock-output-names", &name);
+ of_property_read_u32(np, "clock-frequency", &rate);
+ irq = irq_of_parse_and_map(np, 0);
+ if (!irq)
+ return;
+
+ clk = at91_clk_register_main(pmc, irq, name, parent_name, rate);
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+void __init of_at91rm9200_clk_main_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_main_setup(np, pmc);
+}
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index d92e46c..ccfe506 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -208,6 +208,11 @@ out_free_pmc:
}

static const struct of_device_id pmc_clk_ids[] __initdata = {
+ /* Main clock */
+ {
+ .compatible = "atmel,at91rm9200-clk-main",
+ .data = of_at91rm9200_clk_main_setup
+ },
{ /*sentinel*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index b7e8397..294e230 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -55,4 +55,7 @@ static inline void pmc_write(struct at91_pmc *pmc, int offset, u32 value)
return writel_relaxed(value, pmc->regbase + offset);
}

+extern void __init of_at91rm9200_clk_main_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+
#endif /* __PMC_H_ */
--
1.7.9.5

2013-10-11 09:47:06

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 09/17] clk: at91: add PMC peripheral clocks

This patch adds new at91 peripheral clock implementation using common clk
framework.

Almost all peripherals provided by at91 SoCs need a clock to work properly.
This clock is enabled/disabled using PCER/PCDR resgisters.

Each peripheral is given an id (see atmel's datasheets) which is used to
define and reference peripheral clocks.

Some new SoCs (at91sam9x5 and sama5d3) provide a new register (PCR) where you
can configure the peripheral clock as a division of the master clock.
This will help reducing the peripherals power comsumption.

Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/clk/at91/Makefile | 2 +-
drivers/clk/at91/clk-peripheral.c | 407 +++++++++++++++++++++++++++++++++++++
drivers/clk/at91/pmc.c | 9 +
drivers/clk/at91/pmc.h | 5 +
4 files changed, 422 insertions(+), 1 deletion(-)
create mode 100644 drivers/clk/at91/clk-peripheral.c

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index c2b7068..04deba3 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -4,4 +4,4 @@

obj-y += pmc.o
obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
-obj-y += clk-system.o
+obj-y += clk-system.o clk-peripheral.o
diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c
new file mode 100644
index 0000000..2dce2e2
--- /dev/null
+++ b/drivers/clk/at91/clk-peripheral.c
@@ -0,0 +1,407 @@
+/*
+ * drivers/clk/at91/clk-peripheral.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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 PERIPHERAL_MAX 64
+
+#define PERIPHERAL_AT91RM9200 0
+#define PERIPHERAL_AT91SAM9X5 1
+
+#define PERIPHERAL_ID_MIN 2
+#define PERIPHERAL_ID_MAX 31
+#define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX))
+
+#define PERIPHERAL_RSHIFT_MASK 0x3
+#define PERIPHERAL_RSHIFT(val) (((val) >> 16) & PERIPHERAL_RSHIFT_MASK)
+
+struct clk_peripheral {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ u32 id;
+};
+
+#define to_clk_peripheral(hw) container_of(hw, struct clk_peripheral, hw)
+
+struct clk_sam9x5_peripheral {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ u32 id;
+ u8 div;
+ u8 have_div_support;
+};
+
+#define to_clk_sam9x5_peripheral(hw) \
+ container_of(hw, struct clk_sam9x5_peripheral, hw)
+
+static int clk_peripheral_enable(struct clk_hw *hw)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(hw);
+ struct at91_pmc *pmc = periph->pmc;
+ int offset = AT91_PMC_PCER;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return 0;
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCER1;
+ pmc_write(pmc, offset, PERIPHERAL_MASK(id));
+ return 0;
+}
+
+static void clk_peripheral_disable(struct clk_hw *hw)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(hw);
+ struct at91_pmc *pmc = periph->pmc;
+ int offset = AT91_PMC_PCDR;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return;
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCDR1;
+ pmc_write(pmc, offset, PERIPHERAL_MASK(id));
+}
+
+static int clk_peripheral_is_enabled(struct clk_hw *hw)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(hw);
+ struct at91_pmc *pmc = periph->pmc;
+ int offset = AT91_PMC_PCSR;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return 1;
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCSR1;
+ return !!(pmc_read(pmc, offset) & PERIPHERAL_MASK(id));
+}
+
+static const struct clk_ops peripheral_ops = {
+ .enable = clk_peripheral_enable,
+ .disable = clk_peripheral_disable,
+ .is_enabled = clk_peripheral_is_enabled,
+};
+
+static struct clk * __init
+at91_clk_register_peripheral(struct at91_pmc *pmc, const char *name,
+ const char *parent_name, u32 id)
+{
+ struct clk_peripheral *periph;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ if (!pmc || !name || !parent_name || id > PERIPHERAL_ID_MAX)
+ return ERR_PTR(-EINVAL);
+
+ periph = kzalloc(sizeof(*periph), GFP_KERNEL);
+ if (!periph)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &peripheral_ops;
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+ init.flags = 0;
+
+ periph->id = id;
+ periph->hw.init = &init;
+ periph->pmc = pmc;
+
+ clk = clk_register(NULL, &periph->hw);
+ if (IS_ERR(clk))
+ kfree(periph);
+
+ return clk;
+}
+
+static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ struct at91_pmc *pmc = periph->pmc;
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return 0;
+ pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID) |
+ AT91_PMC_PCR_CMD |
+ AT91_PMC_PCR_DIV(periph->div) |
+ AT91_PMC_PCR_EN);
+ return 0;
+}
+
+static void clk_sam9x5_peripheral_disable(struct clk_hw *hw)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ struct at91_pmc *pmc = periph->pmc;
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return;
+
+ pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID) |
+ AT91_PMC_PCR_CMD);
+}
+
+static int clk_sam9x5_peripheral_is_enabled(struct clk_hw *hw)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ struct at91_pmc *pmc = periph->pmc;
+ int ret;
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return 1;
+ pmc_lock(pmc);
+ pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID));
+ ret = !!(pmc_read(pmc, AT91_PMC_PCR) & AT91_PMC_PCR_EN);
+ pmc_unlock(pmc);
+
+ return ret;
+}
+
+static unsigned long
+clk_sam9x5_peripheral_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ struct at91_pmc *pmc = periph->pmc;
+ u32 shift;
+
+ if (periph->id < PERIPHERAL_ID_MIN || !periph->have_div_support)
+ return parent_rate;
+ pmc_lock(pmc);
+ pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID));
+ shift = PERIPHERAL_RSHIFT(pmc_read(pmc, AT91_PMC_PCR));
+ pmc_unlock(pmc);
+ return parent_rate >> shift;
+}
+
+static long clk_sam9x5_peripheral_round_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long *parent_rate)
+{
+ int shift;
+ unsigned long best_rate;
+ unsigned long best_diff;
+ unsigned long cur_rate;
+ unsigned long cur_diff;
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+
+ if (periph->id < 2 || !periph->have_div_support)
+ return *parent_rate;
+ if (rate >= *parent_rate)
+ return rate;
+ best_diff = *parent_rate - rate;
+ best_rate = *parent_rate;
+ for (shift = 1; shift < 4; shift++) {
+ cur_rate = *parent_rate >> shift;
+ if (cur_rate < rate)
+ cur_diff = rate - cur_rate;
+ else
+ cur_diff = cur_rate - rate;
+ if (cur_diff < best_diff) {
+ best_diff = cur_diff;
+ best_rate = cur_rate;
+ }
+ if (!best_diff || cur_rate < rate)
+ break;
+ }
+ return best_rate;
+}
+
+static int clk_sam9x5_peripheral_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ int shift;
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ if (periph->id < 2 || !periph->have_div_support) {
+ if (parent_rate == rate)
+ return 0;
+ else
+ return -EINVAL;
+ }
+
+ for (shift = 0; shift < 4; shift++) {
+ if (parent_rate >> shift == rate) {
+ periph->div = shift;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const struct clk_ops sam9x5_peripheral_ops = {
+ .enable = clk_sam9x5_peripheral_enable,
+ .disable = clk_sam9x5_peripheral_disable,
+ .is_enabled = clk_sam9x5_peripheral_is_enabled,
+ .recalc_rate = clk_sam9x5_peripheral_recalc_rate,
+ .round_rate = clk_sam9x5_peripheral_round_rate,
+ .set_rate = clk_sam9x5_peripheral_set_rate,
+};
+
+static struct clk * __init
+at91_clk_register_sam9x5_peripheral(struct at91_pmc *pmc, const char *name,
+ const char *parent_name, u32 id,
+ u32 default_div)
+{
+ struct clk_sam9x5_peripheral *periph;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ if (!pmc || !name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ periph = kzalloc(sizeof(*periph), GFP_KERNEL);
+ if (!periph)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &sam9x5_peripheral_ops;
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+ init.flags = CLK_SET_RATE_GATE;
+
+ periph->id = id;
+ periph->hw.init = &init;
+ periph->div = default_div;
+ periph->pmc = pmc;
+
+ clk = clk_register(NULL, &periph->hw);
+ if (IS_ERR(clk))
+ kfree(periph);
+
+ return clk;
+}
+
+struct clk_periph_data {
+ struct clk **clks;
+ u8 *ids;
+ unsigned int clk_num;
+};
+
+static struct clk * __init
+of_clk_src_periph_get(struct of_phandle_args *clkspec, void *data)
+{
+ struct clk_periph_data *clk_data = data;
+ unsigned int id = clkspec->args[0];
+ int i;
+
+ if (id >= PERIPHERAL_MAX)
+ goto err;
+
+ for (i = 0; i < clk_data->clk_num; i++) {
+ if (clk_data->ids[i] == id)
+ return clk_data->clks[i];
+ }
+
+err:
+ pr_err("%s: invalid clock id %d\n", __func__, id);
+ return ERR_PTR(-EINVAL);
+}
+
+static void __init
+of_at91_clk_periph_setup(struct device_node *np, struct at91_pmc *pmc, u8 type)
+{
+ int num;
+ int i;
+ u32 id;
+ struct clk *clk;
+ const char *parent_name;
+ const char *name;
+ u32 divisor;
+ struct clk **clks;
+ u8 *ids;
+ struct clk_periph_data *clktab;
+ struct device_node *periphclknp;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return;
+
+ num = of_get_child_count(np);
+ if (!num || num > PERIPHERAL_MAX)
+ return;
+
+ clktab = kzalloc(sizeof(*clktab), GFP_KERNEL);
+ if (!clktab)
+ return;
+
+ ids = kzalloc(num * sizeof(*ids), GFP_KERNEL);
+ if (!ids)
+ goto out_free_clktab;
+
+ clks = kzalloc(num * sizeof(*clks), GFP_KERNEL);
+ if (!clks)
+ goto out_free_ids;
+
+ i = 0;
+ for_each_child_of_node(np, periphclknp) {
+ name = periphclknp->name;
+
+ if (of_property_read_u32(periphclknp, "atmel,clk-id", &id))
+ goto out_free_clks;
+ if (id >= PERIPHERAL_MAX)
+ goto out_free_clks;
+
+ if (type == PERIPHERAL_AT91RM9200) {
+ clk = at91_clk_register_peripheral(pmc, name,
+ parent_name, id);
+ } else {
+ if (of_property_read_u32(periphclknp,
+ "atmel,clk-default-divisor",
+ &divisor))
+ divisor = 0;
+
+ clk = at91_clk_register_sam9x5_peripheral(pmc, name,
+ parent_name,
+ id,
+ divisor);
+ }
+ if (IS_ERR(clk))
+ goto out_free_clks;
+
+ clks[i] = clk;
+ ids[i++] = id;
+ }
+
+ clktab->clk_num = num;
+ clktab->clks = clks;
+ clktab->ids = ids;
+ of_clk_add_provider(np, of_clk_src_periph_get, clktab);
+ return;
+
+out_free_clks:
+ kfree(clks);
+out_free_ids:
+ kfree(ids);
+out_free_clktab:
+ kfree(clktab);
+}
+
+void __init of_at91rm9200_clk_periph_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_periph_setup(np, pmc, PERIPHERAL_AT91RM9200);
+}
+
+void __init of_at91sam9x5_clk_periph_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_periph_setup(np, pmc, PERIPHERAL_AT91SAM9X5);
+}
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index ce76ba6..8e27e92 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -248,6 +248,15 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
.compatible = "atmel,at91rm9200-clk-system",
.data = of_at91rm9200_clk_sys_setup,
},
+ /* Peripheral clocks */
+ {
+ .compatible = "atmel,at91rm9200-clk-peripheral",
+ .data = of_at91rm9200_clk_periph_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-clk-peripheral",
+ .data = of_at91sam9x5_clk_periph_setup,
+ },
{ /*sentinel*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index f88ef41..08c30fb 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -77,4 +77,9 @@ extern void __init of_at91sam9x5_clk_master_setup(struct device_node *np,
extern void __init of_at91rm9200_clk_sys_setup(struct device_node *np,
struct at91_pmc *pmc);

+extern void __init of_at91rm9200_clk_periph_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9x5_clk_periph_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+
#endif /* __PMC_H_ */
--
1.7.9.5

2013-10-11 09:53:07

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 08/17] clk: at91: add PMC system clocks

This patch adds new at91 system clock implementation using common clk
framework.

Some peripherals need to enable a "system" clock in order to work properly.
Each system clock is given an id based on the bit position in SCER/SCDR
registers.

Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/clk/at91/Makefile | 1 +
drivers/clk/at91/clk-system.c | 193 +++++++++++++++++++++++++++++++++++++++++
drivers/clk/at91/pmc.c | 5 ++
drivers/clk/at91/pmc.h | 3 +
4 files changed, 202 insertions(+)
create mode 100644 drivers/clk/at91/clk-system.c

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index e28fb2b..c2b7068 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -4,3 +4,4 @@

obj-y += pmc.o
obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
+obj-y += clk-system.o
diff --git a/drivers/clk/at91/clk-system.c b/drivers/clk/at91/clk-system.c
new file mode 100644
index 0000000..603b4b1
--- /dev/null
+++ b/drivers/clk/at91/clk-system.c
@@ -0,0 +1,193 @@
+/*
+ * drivers/clk/at91/clk-system.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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 SYSTEM_MAX_ID 31
+
+#define to_clk_system(hw) container_of(hw, struct clk_system, hw)
+struct clk_system {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ u8 id;
+};
+
+static int clk_system_enable(struct clk_hw *hw)
+{
+ struct clk_system *sys = to_clk_system(hw);
+ struct at91_pmc *pmc = sys->pmc;
+
+ pmc_write(pmc, AT91_PMC_SCER, 1 << sys->id);
+ return 0;
+}
+
+static void clk_system_disable(struct clk_hw *hw)
+{
+ struct clk_system *sys = to_clk_system(hw);
+ struct at91_pmc *pmc = sys->pmc;
+
+ pmc_write(pmc, AT91_PMC_SCDR, 1 << sys->id);
+}
+
+static int clk_system_is_enabled(struct clk_hw *hw)
+{
+ struct clk_system *sys = to_clk_system(hw);
+ struct at91_pmc *pmc = sys->pmc;
+
+ return !!(pmc_read(pmc, AT91_PMC_SCSR) & (1 << sys->id));
+}
+
+static const struct clk_ops system_ops = {
+ .enable = clk_system_enable,
+ .disable = clk_system_disable,
+ .is_enabled = clk_system_is_enabled,
+};
+
+static struct clk * __init
+at91_clk_register_system(struct at91_pmc *pmc, const char *name,
+ const char *parent_name, u8 id)
+{
+ struct clk_system *sys;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ if (!parent_name || id > SYSTEM_MAX_ID)
+ return ERR_PTR(-EINVAL);
+
+ sys = kzalloc(sizeof(*sys), GFP_KERNEL);
+ if (!sys)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &system_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ /*
+ * CLK_IGNORE_UNUSED is used to avoid ddrck switch off.
+ * TODO : we should implement a driver supporting at91 ddr controller
+ * (see drivers/memory) which would request and enable the ddrck clock.
+ * When this is done we will be able to remove CLK_IGNORE_UNUSED flag.
+ */
+ init.flags = CLK_IGNORE_UNUSED;
+
+ sys->id = id;
+ sys->hw.init = &init;
+ sys->pmc = pmc;
+
+ clk = clk_register(NULL, &sys->hw);
+ if (IS_ERR(clk))
+ kfree(sys);
+
+ return clk;
+}
+
+struct clk_system_data {
+ struct clk **clks;
+ u8 *ids;
+ unsigned int clk_num;
+};
+
+static struct clk * __init
+of_clk_src_system_get(struct of_phandle_args *clkspec, void *data)
+{
+ struct clk_system_data *clk_data = data;
+ unsigned int id = clkspec->args[0];
+ int i;
+
+ if (id > SYSTEM_MAX_ID)
+ goto err;
+
+ for (i = 0; i < clk_data->clk_num; i++) {
+ if (clk_data->ids[i] == id)
+ return clk_data->clks[i];
+ }
+
+err:
+ pr_err("%s: invalid clock id %d\n", __func__, id);
+ return ERR_PTR(-EINVAL);
+}
+
+static void __init
+of_at91_clk_sys_setup(struct device_node *np, struct at91_pmc *pmc)
+{
+ int i;
+ int num;
+ u32 id;
+ struct clk *clk;
+ u8 *ids;
+ struct clk **clks;
+ struct clk_system_data *clktab;
+ const char *name;
+ struct device_node *sysclknp;
+ const char *parent_name;
+
+ num = of_get_child_count(np);
+ if (num > (SYSTEM_MAX_ID + 1))
+ return;
+
+ clktab = kzalloc(sizeof(*clktab), GFP_KERNEL);
+ if (!clktab)
+ return;
+
+ ids = kzalloc(num * sizeof(*ids), GFP_KERNEL);
+ if (!ids)
+ goto out_free_clktab;
+
+ clks = kzalloc(num * sizeof(*clks), GFP_KERNEL);
+ if (!clks)
+ goto out_free_ids;
+
+ i = 0;
+ for_each_child_of_node(np, sysclknp) {
+ name = sysclknp->name;
+
+ if (of_property_read_u32(sysclknp, "atmel,clk-id", &id))
+ goto out_free_clks;
+
+ parent_name = of_clk_get_parent_name(sysclknp, 0);
+
+ clk = at91_clk_register_system(pmc, name, parent_name, id);
+ if (IS_ERR(clk))
+ goto out_free_clks;
+
+ clks[i] = clk;
+ ids[i] = id;
+
+ i++;
+ }
+
+ clktab->clk_num = num;
+ clktab->clks = clks;
+ clktab->ids = ids;
+ of_clk_add_provider(np, of_clk_src_system_get, clktab);
+ return;
+
+out_free_clks:
+ kfree(clks);
+out_free_ids:
+ kfree(ids);
+out_free_clktab:
+ kfree(clktab);
+}
+
+void __init of_at91rm9200_clk_sys_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_sys_setup(np, pmc);
+}
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index 6f08879..ce76ba6 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -243,6 +243,11 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
.compatible = "atmel,at91sam9x5-clk-master",
.data = of_at91sam9x5_clk_master_setup,
},
+ /* System clocks */
+ {
+ .compatible = "atmel,at91rm9200-clk-system",
+ .data = of_at91rm9200_clk_sys_setup,
+ },
{ /*sentinel*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 2e1dcb8..f88ef41 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -74,4 +74,7 @@ extern void __init of_at91rm9200_clk_master_setup(struct device_node *np,
extern void __init of_at91sam9x5_clk_master_setup(struct device_node *np,
struct at91_pmc *pmc);

+extern void __init of_at91rm9200_clk_sys_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+
#endif /* __PMC_H_ */
--
1.7.9.5

2013-10-11 09:55:27

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 10/17] clk: at91: add peripheral clk macros for peripheral clk dt bindings

This patch adds the peripheral divisors macros (for sam9x5 compatible IPs)
which will be used by peripheral clk dt definitions.

Signed-off-by: Boris BREZILLON <[email protected]>
---
include/dt-bindings/clk/at91.h | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/include/dt-bindings/clk/at91.h b/include/dt-bindings/clk/at91.h
index 0b4cb99..a3b07ca 100644
--- a/include/dt-bindings/clk/at91.h
+++ b/include/dt-bindings/clk/at91.h
@@ -19,4 +19,10 @@
#define AT91_PMC_MOSCRCS 17 /* Main On-Chip RC */
#define AT91_PMC_CFDEV 18 /* Clock Failure Detector Event */

+/* sam9x5 peripheral divisors */
+#define AT91SAM9X5_PERIPH_CLK_DIV1 0
+#define AT91SAM9X5_PERIPH_CLK_DIV2 1
+#define AT91SAM9X5_PERIPH_CLK_DIV4 2
+#define AT91SAM9X5_PERIPH_CLK_DIV8 3
+
#endif
--
1.7.9.5

2013-10-11 09:59:33

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 11/17] clk: at91: add PMC programmable clocks

This patch adds new at91 programmable clocks implementation using common clk
framework.
A programmable clock is a clock which can be exported on a given pin to clock
external devices.
Each programmable clock is given an id (from 0 to 8).
The number of available programmable clocks depends on the SoC you're using.
Programmable clock driver only implements the clock setting (clock rate and
parent setting). It must be chained to a system clock in order to
enable/disable the generated clock.
The PCKX pins used to output the clock signals must be assigned to the
appropriate peripheral (see atmel's datasheets).

Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/clk/at91/Makefile | 2 +
drivers/clk/at91/clk-programmable.c | 423 +++++++++++++++++++++++++++++++++++
drivers/clk/at91/pmc.c | 15 ++
drivers/clk/at91/pmc.h | 9 +
4 files changed, 449 insertions(+)
create mode 100644 drivers/clk/at91/clk-programmable.c

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 04deba3..3873b62 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -5,3 +5,5 @@
obj-y += pmc.o
obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
obj-y += clk-system.o clk-peripheral.o
+
+obj-$(CONFIG_AT91_PROGRAMMABLE_CLOCKS) += clk-programmable.o
diff --git a/drivers/clk/at91/clk-programmable.c b/drivers/clk/at91/clk-programmable.c
new file mode 100644
index 0000000..b34f80b
--- /dev/null
+++ b/drivers/clk/at91/clk-programmable.c
@@ -0,0 +1,423 @@
+/*
+ * drivers/clk/at91/clk-programmable.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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/of_irq.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include "pmc.h"
+
+#define PROG_SOURCE_MAX 5
+#define PROG_ID_MAX 7
+
+#define PROG_STATUS_MASK(id) (1 << ((id) + 8))
+#define PROG_PRES_MASK 0x7
+#define PROG_MAX_RM9200_CSS 3
+
+struct clk_programmable_layout {
+ u8 pres_shift;
+ u8 css_mask;
+ u8 have_slck_mck;
+};
+
+struct clk_programmable {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ unsigned int irq;
+ wait_queue_head_t wait;
+ u8 id;
+ u8 css;
+ u8 pres;
+ u8 slckmck;
+ const struct clk_programmable_layout *layout;
+};
+
+#define to_clk_programmable(hw) container_of(hw, struct clk_programmable, hw)
+
+
+static irqreturn_t clk_programmable_irq_handler(int irq, void *dev_id)
+{
+ struct clk_programmable *prog = (struct clk_programmable *)dev_id;
+
+ wake_up(&prog->wait);
+
+ return IRQ_HANDLED;
+}
+
+static int clk_programmable_prepare(struct clk_hw *hw)
+{
+ u32 tmp;
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ struct at91_pmc *pmc = prog->pmc;
+ const struct clk_programmable_layout *layout = prog->layout;
+ u8 id = prog->id;
+ u32 mask = PROG_STATUS_MASK(id);
+
+ tmp = prog->css | (prog->pres << layout->pres_shift);
+ if (layout->have_slck_mck && prog->slckmck)
+ tmp |= AT91_PMC_CSSMCK_MCK;
+
+ pmc_write(pmc, AT91_PMC_PCKR(id), tmp);
+
+ while (!(pmc_read(pmc, AT91_PMC_SR) & mask))
+ wait_event(prog->wait, pmc_read(pmc, AT91_PMC_SR) & mask);
+
+ return 0;
+}
+
+static int clk_programmable_is_ready(struct clk_hw *hw)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ struct at91_pmc *pmc = prog->pmc;
+
+ return !!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_PCKR(prog->id));
+}
+
+static unsigned long clk_programmable_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ u32 tmp;
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ struct at91_pmc *pmc = prog->pmc;
+ const struct clk_programmable_layout *layout = prog->layout;
+
+ tmp = pmc_read(pmc, AT91_PMC_PCKR(prog->id));
+ prog->pres = (tmp >> layout->pres_shift) & PROG_PRES_MASK;
+
+ return parent_rate >> prog->pres;
+}
+
+static long clk_programmable_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long best_rate = *parent_rate;
+ unsigned long best_diff;
+ unsigned long new_diff;
+ unsigned long cur_rate;
+ int shift = shift;
+
+ if (rate > *parent_rate)
+ return *parent_rate;
+ else
+ best_diff = *parent_rate - rate;
+
+ if (!best_diff)
+ return best_rate;
+
+ for (shift = 1; shift < PROG_PRES_MASK; shift++) {
+ cur_rate = *parent_rate >> shift;
+
+ if (cur_rate > rate)
+ new_diff = cur_rate - rate;
+ else
+ new_diff = rate - cur_rate;
+
+ if (!new_diff)
+ return cur_rate;
+
+ if (new_diff < best_diff) {
+ best_diff = new_diff;
+ best_rate = cur_rate;
+ }
+
+ if (rate > cur_rate)
+ break;
+ }
+
+ return best_rate;
+}
+
+static int clk_programmable_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ const struct clk_programmable_layout *layout = prog->layout;
+ if (index > layout->css_mask) {
+ if (index > PROG_MAX_RM9200_CSS && layout->have_slck_mck) {
+ prog->css = 0;
+ prog->slckmck = 1;
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+ }
+
+ prog->css = index;
+ return 0;
+}
+
+static u8 clk_programmable_get_parent(struct clk_hw *hw)
+{
+ u32 tmp;
+ u8 ret;
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ struct at91_pmc *pmc = prog->pmc;
+ const struct clk_programmable_layout *layout = prog->layout;
+
+ tmp = pmc_read(pmc, AT91_PMC_PCKR(prog->id));
+ prog->css = tmp & layout->css_mask;
+ ret = prog->css;
+ if (layout->have_slck_mck) {
+ prog->slckmck = !!(tmp & AT91_PMC_CSSMCK_MCK);
+ if (prog->slckmck && !ret)
+ ret = PROG_MAX_RM9200_CSS + 1;
+ }
+
+ return ret;
+}
+
+static int clk_programmable_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ unsigned long best_rate = parent_rate;
+ unsigned long best_diff;
+ unsigned long new_diff;
+ unsigned long cur_rate;
+ int shift = 0;
+
+ if (rate > parent_rate)
+ return parent_rate;
+ else
+ best_diff = parent_rate - rate;
+
+ if (!best_diff) {
+ prog->pres = shift;
+ return 0;
+ }
+
+ for (shift = 1; shift < PROG_PRES_MASK; shift++) {
+ cur_rate = parent_rate >> shift;
+
+ if (cur_rate > rate)
+ new_diff = cur_rate - rate;
+ else
+ new_diff = rate - cur_rate;
+
+ if (!new_diff)
+ break;
+
+ if (new_diff < best_diff) {
+ best_diff = new_diff;
+ best_rate = cur_rate;
+ }
+
+ if (rate > cur_rate)
+ break;
+ }
+
+ prog->pres = shift;
+ return 0;
+}
+
+static const struct clk_ops programmable_ops = {
+ .prepare = clk_programmable_prepare,
+ .is_prepared = clk_programmable_is_ready,
+ .recalc_rate = clk_programmable_recalc_rate,
+ .round_rate = clk_programmable_round_rate,
+ .get_parent = clk_programmable_get_parent,
+ .set_parent = clk_programmable_set_parent,
+ .set_rate = clk_programmable_set_rate,
+};
+
+static struct clk * __init
+at91_clk_register_programmable(struct at91_pmc *pmc, unsigned int irq,
+ const char *name, const char **parent_names,
+ u8 num_parents, u8 id,
+ const struct clk_programmable_layout *layout)
+{
+ int ret;
+ struct clk_programmable *prog;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+ char irq_name[11];
+
+ if (id > PROG_ID_MAX)
+ return ERR_PTR(-EINVAL);
+
+ prog = kzalloc(sizeof(*prog), GFP_KERNEL);
+ if (!prog)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &programmable_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+
+ prog->id = id;
+ prog->layout = layout;
+ prog->hw.init = &init;
+ prog->pmc = pmc;
+ prog->irq = irq;
+ init_waitqueue_head(&prog->wait);
+ irq_set_status_flags(prog->irq, IRQ_NOAUTOEN);
+ snprintf(irq_name, sizeof(irq_name), "clk-prog%d", id);
+ ret = request_irq(prog->irq, clk_programmable_irq_handler,
+ IRQF_TRIGGER_HIGH, irq_name, prog);
+ if (ret)
+ return ERR_PTR(ret);
+
+ clk = clk_register(NULL, &prog->hw);
+ if (IS_ERR(clk))
+ kfree(prog);
+
+ return clk;
+}
+
+static const struct clk_programmable_layout at91rm9200_programmable_layout = {
+ .pres_shift = 2,
+ .css_mask = 0x3,
+ .have_slck_mck = 0,
+};
+
+static const struct clk_programmable_layout at91sam9g45_programmable_layout = {
+ .pres_shift = 2,
+ .css_mask = 0x3,
+ .have_slck_mck = 1,
+};
+
+static const struct clk_programmable_layout at91sam9x5_programmable_layout = {
+ .pres_shift = 4,
+ .css_mask = 0x7,
+ .have_slck_mck = 0,
+};
+
+struct clk_prog_data {
+ struct clk **clks;
+ u8 *ids;
+ unsigned int clk_num;
+};
+
+static struct clk * __init
+of_clk_src_periph_get(struct of_phandle_args *clkspec, void *data)
+{
+ struct clk_prog_data *clk_data = data;
+ unsigned int id = clkspec->args[0];
+ int i;
+
+ if (id > PROG_ID_MAX)
+ goto err;
+
+ for (i = 0; i < clk_data->clk_num; i++) {
+ if (clk_data->ids[i] == id)
+ return clk_data->clks[i];
+ }
+
+err:
+ pr_err("%s: invalid clock id %d\n", __func__, id);
+ return ERR_PTR(-EINVAL);
+}
+
+static void __init
+of_at91_clk_prog_setup(struct device_node *np, struct at91_pmc *pmc,
+ const struct clk_programmable_layout *layout)
+{
+ int num;
+ u32 id;
+ int i;
+ unsigned int irq;
+ struct clk *clk;
+ int num_parents;
+ u8 *ids;
+ struct clk **clks;
+ struct clk_prog_data *clktab;
+ const char *parent_names[PROG_SOURCE_MAX];
+ const char *name;
+ struct device_node *progclknp;
+
+ num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells");
+ if (num_parents <= 0 || num_parents > PROG_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;
+ }
+
+ num = of_get_child_count(np);
+ if (!num || num > (PROG_ID_MAX + 1))
+ return;
+
+ clktab = kzalloc(sizeof(*clktab), GFP_KERNEL);
+ if (!clktab)
+ return;
+
+ ids = kzalloc(num * sizeof(*ids), GFP_KERNEL);
+ if (!ids)
+ goto out_free_clktab;
+
+ clks = kzalloc(num * sizeof(*clks), GFP_KERNEL);
+ if (!clks)
+ goto out_free_ids;
+
+ i = 0;
+ for_each_child_of_node(np, progclknp) {
+ name = progclknp->name;
+
+ if (of_property_read_u32(progclknp, "atmel,clk-id", &id))
+ goto out_free_clks;
+
+ irq = irq_of_parse_and_map(np, 0);
+ if (!irq)
+ goto out_free_clks;
+
+ clk = at91_clk_register_programmable(pmc, irq, name,
+ parent_names, num_parents,
+ id, layout);
+ if (IS_ERR(clk))
+ goto out_free_clks;
+
+ clks[i] = clk;
+ ids[i++] = id;
+ }
+
+ clktab->clk_num = num;
+ clktab->clks = clks;
+ clktab->ids = ids;
+ of_clk_add_provider(np, of_clk_src_periph_get, clktab);
+ return;
+
+out_free_clks:
+ kfree(clks);
+out_free_ids:
+ kfree(ids);
+out_free_clktab:
+ kfree(clktab);
+}
+
+
+void __init of_at91rm9200_clk_prog_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_prog_setup(np, pmc, &at91rm9200_programmable_layout);
+}
+
+void __init of_at91sam9g45_clk_prog_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_prog_setup(np, pmc, &at91sam9g45_programmable_layout);
+}
+
+void __init of_at91sam9x5_clk_prog_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_prog_setup(np, pmc, &at91sam9x5_programmable_layout);
+}
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index 8e27e92..5b8a959e 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -257,6 +257,21 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
.compatible = "atmel,at91sam9x5-clk-peripheral",
.data = of_at91sam9x5_clk_periph_setup,
},
+ /* Programmable clocks */
+#if defined(CONFIG_AT91_PROGRAMMABLE_CLOCKS)
+ {
+ .compatible = "atmel,at91rm9200-clk-programmable",
+ .data = of_at91rm9200_clk_prog_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9g45-clk-programmable",
+ .data = of_at91sam9g45_clk_prog_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-clk-programmable",
+ .data = of_at91sam9x5_clk_prog_setup,
+ },
+#endif
{ /*sentinel*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 08c30fb..5f20dc1 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -82,4 +82,13 @@ extern void __init of_at91rm9200_clk_periph_setup(struct device_node *np,
extern void __init of_at91sam9x5_clk_periph_setup(struct device_node *np,
struct at91_pmc *pmc);

+#if defined(CONFIG_AT91_PROGRAMMABLE_CLOCKS)
+extern void __init of_at91rm9200_clk_prog_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9g45_clk_prog_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9x5_clk_prog_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+#endif
+
#endif /* __PMC_H_ */
--
1.7.9.5

2013-10-11 10:08:05

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 06/17] clk: at91: add PMC pll clocks

This patch adds new at91 pll clock implementation using common clk framework.

The pll clock layout describe the PLLX register layout.
There are four pll clock layouts:
- at91rm9200
- at91sam9g20
- at91sam9g45
- sama5d3

PLL clocks are given characteristics:
- min/max clock source rate
- ranges of valid clock output rates
- values to set in out and icpll fields for each supported output range

These characteristics are checked during rate change to avoid
over/underclocking.

These characteristics are described in atmel's SoC datasheet in
"Electrical Characteristics" paragraph.

Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/clk/at91/Makefile | 2 +-
drivers/clk/at91/clk-pll.c | 545 +++++++++++++++++++++++++++++++++++++++++
drivers/clk/at91/clk-plldiv.c | 137 +++++++++++
drivers/clk/at91/pmc.c | 21 ++
drivers/clk/at91/pmc.h | 11 +
include/linux/clk/at91_pmc.h | 2 +
6 files changed, 717 insertions(+), 1 deletion(-)
create mode 100644 drivers/clk/at91/clk-pll.c
create mode 100644 drivers/clk/at91/clk-plldiv.c

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 44105bd..902bbf1 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -3,4 +3,4 @@
#

obj-y += pmc.o
-obj-y += clk-main.o
+obj-y += clk-main.o clk-pll.o clk-plldiv.o
diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c
new file mode 100644
index 0000000..44caddf
--- /dev/null
+++ b/drivers/clk/at91/clk-pll.c
@@ -0,0 +1,545 @@
+/*
+ * drivers/clk/at91/clk-pll.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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/of_irq.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include "pmc.h"
+
+#define PLL_STATUS_MASK(id) (1 << (1 + (id)))
+#define PLL_REG(id) (AT91_CKGR_PLLAR + ((id) * 4))
+#define PLL_DIV_MASK 0xff
+#define PLL_DIV_MAX PLL_DIV_MASK
+#define PLL_DIV(reg) ((reg) & PLL_DIV_MASK)
+#define PLL_MUL(reg, layout) (((reg) >> (layout)->mul_shift) & \
+ (layout)->mul_mask)
+#define PLL_ICPR_SHIFT(id) ((id) * 16)
+#define PLL_ICPR_MASK(id) (0xffff << PLL_ICPR_SHIFT(id))
+#define PLL_MAX_COUNT 0x3ff
+#define PLL_COUNT_SHIFT 8
+#define PLL_OUT_SHIFT 14
+#define PLL_MAX_ID 1
+
+struct clk_pll_characteristics {
+ struct clk_range input;
+ int num_output;
+ struct clk_range *output;
+ u16 *icpll;
+ u8 *out;
+};
+
+struct clk_pll_layout {
+ u32 pllr_mask;
+ u16 mul_mask;
+ u8 mul_shift;
+};
+
+#define to_clk_pll(hw) container_of(hw, struct clk_pll, hw)
+
+struct clk_pll {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ unsigned int irq;
+ wait_queue_head_t wait;
+ u8 id;
+ u8 div;
+ u8 range;
+ u16 mul;
+ const struct clk_pll_layout *layout;
+ const struct clk_pll_characteristics *characteristics;
+};
+
+static irqreturn_t clk_pll_irq_handler(int irq, void *dev_id)
+{
+ struct clk_pll *pll = (struct clk_pll *)dev_id;
+
+ wake_up(&pll->wait);
+ disable_irq_nosync(pll->irq);
+
+ return IRQ_HANDLED;
+}
+
+static int clk_pll_prepare(struct clk_hw *hw)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ struct at91_pmc *pmc = pll->pmc;
+ const struct clk_pll_layout *layout = pll->layout;
+ u8 id = pll->id;
+ u32 mask = PLL_STATUS_MASK(id);
+ int offset = PLL_REG(id);
+ u8 out = 0;
+ u32 pllr, icpr;
+ u8 div;
+ u16 mul;
+
+ pllr = pmc_read(pmc, offset);
+ div = PLL_DIV(pllr);
+ mul = PLL_MUL(pllr, layout);
+
+ if ((pmc_read(pmc, AT91_PMC_SR) & mask) &&
+ (div == pll->div && mul == pll->mul))
+ return 0;
+
+ if (characteristics->out)
+ out = characteristics->out[pll->range];
+ if (characteristics->icpll) {
+ icpr = pmc_read(pmc, AT91_PMC_PLLICPR) & ~PLL_ICPR_MASK(id);
+ icpr |= (characteristics->icpll[pll->range] <<
+ PLL_ICPR_SHIFT(id));
+ pmc_write(pmc, AT91_PMC_PLLICPR, icpr);
+ }
+
+ pllr &= ~layout->pllr_mask;
+ pllr |= layout->pllr_mask &
+ (pll->div | (PLL_MAX_COUNT << PLL_COUNT_SHIFT) |
+ (out << PLL_OUT_SHIFT) |
+ ((pll->mul & layout->mul_mask) << layout->mul_shift));
+ pmc_write(pmc, offset, pllr);
+
+ while (!(pmc_read(pmc, AT91_PMC_SR) & mask)) {
+ enable_irq(pll->irq);
+ wait_event(pll->wait,
+ pmc_read(pmc, AT91_PMC_SR) & mask);
+ }
+
+ return 0;
+}
+
+static int clk_pll_is_ready(struct clk_hw *hw)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ struct at91_pmc *pmc = pll->pmc;
+
+ return !!(pmc_read(pmc, AT91_PMC_SR) &
+ PLL_STATUS_MASK(pll->id));
+}
+
+static void clk_pll_disable(struct clk_hw *hw)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ struct at91_pmc *pmc = pll->pmc;
+ const struct clk_pll_layout *layout = pll->layout;
+ int offset = PLL_REG(pll->id);
+ u32 tmp = pmc_read(pmc, offset) & ~(layout->pllr_mask);
+
+ pmc_write(pmc, offset, tmp);
+}
+
+static unsigned long clk_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ const struct clk_pll_layout *layout = pll->layout;
+ struct at91_pmc *pmc = pll->pmc;
+ int offset = PLL_REG(pll->id);
+ u32 tmp = pmc_read(pmc, offset) & layout->pllr_mask;
+ u8 div = PLL_DIV(tmp);
+ u16 mul = PLL_MUL(tmp, layout);
+ if (!div || !mul)
+ return 0;
+
+ return (parent_rate * (mul + 1)) / div;
+}
+
+static long clk_pll_get_best_div_mul(struct clk_pll *pll, unsigned long rate,
+ unsigned long parent_rate,
+ u32 *div, u32 *mul,
+ u32 *index) {
+ unsigned long maxrate;
+ unsigned long minrate;
+ unsigned long divrate;
+ unsigned long bestdiv = 1;
+ unsigned long bestmul;
+ unsigned long tmpdiv;
+ unsigned long roundup;
+ unsigned long rounddown;
+ unsigned long remainder;
+ unsigned long bestremainder;
+ unsigned long maxmul;
+ unsigned long maxdiv;
+ unsigned long mindiv;
+ int i = 0;
+ const struct clk_pll_layout *layout = pll->layout;
+ const struct clk_pll_characteristics *characteristics =
+ pll->characteristics;
+
+ /* Minimum divider = 1 */
+ /* Maximum multiplier = max_mul */
+ maxmul = layout->mul_mask + 1;
+ maxrate = (parent_rate * maxmul) / 1;
+
+ /* Maximum divider = max_div */
+ /* Minimum multiplier = 2 */
+ maxdiv = PLL_DIV_MAX;
+ minrate = (parent_rate * 2) / maxdiv;
+
+ if (parent_rate < characteristics->input.min ||
+ parent_rate < characteristics->input.max)
+ return -ERANGE;
+
+ if (parent_rate < minrate || parent_rate > maxrate)
+ return -ERANGE;
+
+ for (i = 0; i < characteristics->num_output; i++) {
+ if (parent_rate >= characteristics->output[i].min &&
+ parent_rate <= characteristics->output[i].max)
+ break;
+ }
+
+ if (i >= characteristics->num_output)
+ return -ERANGE;
+
+ bestmul = rate / parent_rate;
+ rounddown = parent_rate % rate;
+ roundup = rate - rounddown;
+ bestremainder = roundup < rounddown ? roundup : rounddown;
+
+ if (!bestremainder) {
+ if (div)
+ *div = bestdiv;
+ if (mul)
+ *mul = bestmul;
+ if (index)
+ *index = i;
+ return rate;
+ }
+
+ maxdiv = 255 / (bestmul + 1);
+ if (parent_rate / maxdiv < characteristics->input.min)
+ maxdiv = parent_rate / characteristics->input.min;
+ mindiv = parent_rate / characteristics->input.max;
+ if (parent_rate % characteristics->input.max)
+ mindiv++;
+
+ for (tmpdiv = mindiv; tmpdiv < maxdiv; tmpdiv++) {
+ divrate = parent_rate / tmpdiv;
+
+ rounddown = rate % divrate;
+ roundup = divrate - rounddown;
+ remainder = roundup < rounddown ? roundup : rounddown;
+
+ if (remainder < bestremainder) {
+ bestremainder = remainder;
+ bestmul = rate / divrate;
+ bestdiv = tmpdiv;
+ }
+
+ if (!remainder)
+ break;
+ }
+
+ rate = (parent_rate / bestdiv) * bestmul;
+
+ if (div)
+ *div = bestdiv;
+ if (mul)
+ *mul = bestmul;
+ if (index)
+ *index = i;
+
+ return rate;
+}
+
+static long clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+
+ return clk_pll_get_best_div_mul(pll, rate, *parent_rate,
+ NULL, NULL, NULL);
+}
+
+static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ struct at91_pmc *pmc = pll->pmc;
+ const struct clk_pll_layout *layout = pll->layout;
+ const struct clk_pll_characteristics *characteristics =
+ pll->characteristics;
+ u8 id = pll->id;
+ int offset = PLL_REG(id);
+ long ret;
+ u32 div;
+ u32 mul;
+ u32 index;
+ u32 tmp;
+ u8 out = 0;
+
+ ret = clk_pll_get_best_div_mul(pll, rate, parent_rate,
+ &div, &mul, &index);
+ if (ret < 0)
+ return ret;
+
+ pll->range = index;
+ pll->div = div;
+ pll->mul = mul;
+
+ return 0;
+}
+
+static const struct clk_ops pll_ops = {
+ .prepare = clk_pll_prepare,
+ .is_prepared = clk_pll_is_ready,
+ .disable = clk_pll_disable,
+ .is_enabled = clk_pll_is_ready,
+ .recalc_rate = clk_pll_recalc_rate,
+ .round_rate = clk_pll_round_rate,
+ .set_rate = clk_pll_set_rate,
+};
+
+static struct clk * __init
+at91_clk_register_pll(struct at91_pmc *pmc, unsigned int irq, const char *name,
+ const char *parent_name, u8 id,
+ const struct clk_pll_layout *layout,
+ const struct clk_pll_characteristics *characteristics)
+{
+ struct clk_pll *pll;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+ int ret;
+ int offset = PLL_REG(id);
+ u32 tmp;
+
+ if (id > PLL_MAX_ID)
+ return ERR_PTR(-EINVAL);
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &pll_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE;
+
+ pll->id = id;
+ pll->hw.init = &init;
+ pll->layout = layout;
+ pll->characteristics = characteristics;
+ pll->pmc = pmc;
+ pll->irq = irq;
+ tmp = pmc_read(pmc, offset) & layout->pllr_mask;
+ pll->div = PLL_DIV(tmp);
+ pll->mul = PLL_MUL(tmp, layout);
+ init_waitqueue_head(&pll->wait);
+ irq_set_status_flags(pll->irq, IRQ_NOAUTOEN);
+ ret = request_irq(pll->irq, clk_pll_irq_handler, IRQF_TRIGGER_HIGH,
+ id ? "clk-pllb" : "clk-plla", pll);
+ if (ret)
+ return ERR_PTR(ret);
+
+ clk = clk_register(NULL, &pll->hw);
+ if (IS_ERR(clk))
+ kfree(pll);
+
+ return clk;
+}
+
+
+static const struct clk_pll_layout at91rm9200_pll_layout = {
+ .pllr_mask = 0x7FFFFFF,
+ .mul_shift = 16,
+ .mul_mask = 0x7FF,
+};
+
+static const struct clk_pll_layout at91sam9g45_pll_layout = {
+ .pllr_mask = 0xFFFFFF,
+ .mul_shift = 16,
+ .mul_mask = 0xFF,
+};
+
+static const struct clk_pll_layout at91sam9g20_pllb_layout = {
+ .pllr_mask = 0x3FFFFF,
+ .mul_shift = 16,
+ .mul_mask = 0x3F,
+};
+
+static const struct clk_pll_layout sama5d3_pll_layout = {
+ .pllr_mask = 0x1FFFFFF,
+ .mul_shift = 18,
+ .mul_mask = 0x7F,
+};
+
+
+static struct clk_pll_characteristics * __init
+of_at91_clk_pll_get_characteristics(struct device_node *np)
+{
+ int i;
+ int offset;
+ u32 tmp;
+ int num_output;
+ u32 num_cells;
+ struct clk_range input;
+ struct clk_range *output;
+ u8 *out = NULL;
+ u16 *icpll = NULL;
+ struct clk_pll_characteristics *characteristics;
+
+ if (of_property_read_u32_index(np, "atmel,clk-input-range", 0, &tmp))
+ return NULL;
+ input.min = tmp;
+
+ if (of_property_read_u32_index(np, "atmel,clk-input-range", 1, &tmp))
+ return NULL;
+ input.max = tmp;
+
+ if (of_property_read_u32(np, "#atmel,pll-clk-output-range-cells",
+ &num_cells))
+ return NULL;
+
+ if (num_cells < 2 || num_cells > 4)
+ return NULL;
+
+ if (!of_get_property(np, "atmel,pll-clk-output-ranges", &tmp))
+ return NULL;
+ num_output = tmp / (sizeof(u32) * num_cells);
+
+ characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL);
+ if (!characteristics)
+ return NULL;
+
+ output = kzalloc(sizeof(*output) * num_output, GFP_KERNEL);
+ if (!output)
+ goto out_free_characteristics;
+
+ if (num_cells > 2) {
+ out = kzalloc(sizeof(*out) * num_output, GFP_KERNEL);
+ if (!out)
+ goto out_free_output;
+ }
+
+ if (num_cells > 3) {
+ icpll = kzalloc(sizeof(*icpll) * num_output, GFP_KERNEL);
+ if (!icpll)
+ goto out_free_output;
+ }
+
+ for (i = 0; i < num_output; i++) {
+ offset = i * num_cells;
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset, &tmp))
+ goto out_free_output;
+ output[i].min = tmp;
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset + 1, &tmp))
+ goto out_free_output;
+ output[i].max = tmp;
+
+ if (num_cells == 2)
+ continue;
+
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset + 2, &tmp))
+ goto out_free_output;
+ out[i] = tmp;
+
+ if (num_cells == 3)
+ continue;
+
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset + 3, &tmp))
+ goto out_free_output;
+ icpll[i] = tmp;
+ }
+
+ characteristics->input = input;
+ characteristics->num_output = num_output;
+ characteristics->output = output;
+ characteristics->out = out;
+ characteristics->icpll = icpll;
+ return characteristics;
+
+out_free_output:
+ kfree(icpll);
+ kfree(out);
+ kfree(output);
+out_free_characteristics:
+ kfree(characteristics);
+ return NULL;
+}
+
+static void __init
+of_at91_clk_pll_setup(struct device_node *np, struct at91_pmc *pmc,
+ const struct clk_pll_layout *layout)
+{
+ u32 id;
+ unsigned int irq;
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+ struct clk_pll_characteristics *characteristics;
+
+ if (of_property_read_u32(np, "atmel,clk-id", &id))
+ return;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ characteristics = of_at91_clk_pll_get_characteristics(np);
+ if (!characteristics)
+ return;
+
+ irq = irq_of_parse_and_map(np, 0);
+ if (!irq)
+ return;
+
+ clk = at91_clk_register_pll(pmc, irq, name, parent_name, id, layout,
+ characteristics);
+ if (IS_ERR(clk))
+ goto out_free_characteristics;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+ return;
+
+out_free_characteristics:
+ kfree(characteristics);
+}
+
+void __init of_at91rm9200_clk_pll_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_pll_setup(np, pmc, &at91rm9200_pll_layout);
+}
+
+void __init of_at91sam9g45_clk_pll_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_pll_setup(np, pmc, &at91sam9g45_pll_layout);
+}
+
+void __init of_at91sam9g20_clk_pllb_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_pll_setup(np, pmc, &at91sam9g20_pllb_layout);
+}
+
+void __init of_sama5d3_clk_pll_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_pll_setup(np, pmc, &sama5d3_pll_layout);
+}
diff --git a/drivers/clk/at91/clk-plldiv.c b/drivers/clk/at91/clk-plldiv.c
new file mode 100644
index 0000000..2bbb22b
--- /dev/null
+++ b/drivers/clk/at91/clk-plldiv.c
@@ -0,0 +1,137 @@
+/*
+ * drivers/clk/at91/clk-plldiv.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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 to_clk_plldiv(hw) container_of(hw, struct clk_plldiv, hw)
+
+struct clk_plldiv {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+};
+
+static unsigned long clk_plldiv_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_plldiv *plldiv = to_clk_plldiv(hw);
+ struct at91_pmc *pmc = plldiv->pmc;
+
+ if (pmc_read(pmc, AT91_PMC_MCKR) & AT91_PMC_PLLADIV2)
+ return parent_rate / 2;
+
+ return parent_rate;
+}
+
+static long clk_plldiv_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_plldiv_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_plldiv *plldiv = to_clk_plldiv(hw);
+ struct at91_pmc *pmc = plldiv->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_PLLADIV2;
+ if ((parent_rate / 2) == rate)
+ tmp |= AT91_PMC_PLLADIV2;
+ pmc_write(pmc, AT91_PMC_MCKR, tmp);
+ pmc_unlock(pmc);
+
+ return 0;
+}
+
+static const struct clk_ops plldiv_ops = {
+ .recalc_rate = clk_plldiv_recalc_rate,
+ .round_rate = clk_plldiv_round_rate,
+ .set_rate = clk_plldiv_set_rate,
+};
+
+static struct clk * __init
+at91_clk_register_plldiv(struct at91_pmc *pmc, const char *name,
+ const char *parent_name)
+{
+ struct clk_plldiv *plldiv;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ plldiv = kzalloc(sizeof(*plldiv), GFP_KERNEL);
+ if (!plldiv)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &plldiv_ops;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+ init.flags = CLK_SET_RATE_GATE;
+
+ plldiv->hw.init = &init;
+ plldiv->pmc = pmc;
+
+ clk = clk_register(NULL, &plldiv->hw);
+
+ if (IS_ERR(clk))
+ kfree(plldiv);
+
+ return clk;
+}
+
+static void __init
+of_at91_clk_plldiv_setup(struct device_node *np, struct at91_pmc *pmc)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ clk = at91_clk_register_plldiv(pmc, name, parent_name);
+
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+ return;
+}
+
+void __init of_at91sam9x5_clk_plldiv_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_plldiv_setup(np, pmc);
+}
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index ccfe506..c882e97 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -213,6 +213,27 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
.compatible = "atmel,at91rm9200-clk-main",
.data = of_at91rm9200_clk_main_setup
},
+ /* PLL clocks */
+ {
+ .compatible = "atmel,at91rm9200-clk-pll",
+ .data = of_at91rm9200_clk_pll_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9g45-clk-pll",
+ .data = of_at91sam9g45_clk_pll_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9g20-clk-pllb",
+ .data = of_at91sam9g20_clk_pllb_setup,
+ },
+ {
+ .compatible = "atmel,sama5d3-clk-pll",
+ .data = of_sama5d3_clk_pll_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-clk-plldiv",
+ .data = of_at91sam9x5_clk_plldiv_setup,
+ },
{ /*sentinel*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 294e230..35ccbbc 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -58,4 +58,15 @@ static inline void pmc_write(struct at91_pmc *pmc, int offset, u32 value)
extern void __init of_at91rm9200_clk_main_setup(struct device_node *np,
struct at91_pmc *pmc);

+extern void __init of_at91rm9200_clk_pll_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9g45_clk_pll_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9g20_clk_pllb_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_sama5d3_clk_pll_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9x5_clk_plldiv_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+
#endif /* __PMC_H_ */
diff --git a/include/linux/clk/at91_pmc.h b/include/linux/clk/at91_pmc.h
index 00c45b3..a6911eb 100644
--- a/include/linux/clk/at91_pmc.h
+++ b/include/linux/clk/at91_pmc.h
@@ -164,6 +164,8 @@ extern void __iomem *at91_pmc_base;
#define AT91_PMC_CFDEV (1 << 18) /* Clock Failure Detector Event [some SAM9] */
#define AT91_PMC_IMR 0x6c /* Interrupt Mask Register */

+#define AT91_PMC_PLLICPR 0x80 /* PLL Charge Pump Current Register */
+
#define AT91_PMC_PROT 0xe4 /* Write Protect Mode Register [some SAM9] */
#define AT91_PMC_WPEN (0x1 << 0) /* Write Protect Enable */
#define AT91_PMC_WPKEY (0xffffff << 8) /* Write Protect Key */
--
1.7.9.5

2013-10-11 10:24:38

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 12/17] clk: at91: add PMC utmi clock

This adds new at91 utmi clock implementation using common clk framework.

This clock is a pll with a fixed factor (x40).
It is used as a source for usb clock.

Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/mach-at91/Kconfig | 7 ++
drivers/clk/at91/Makefile | 1 +
drivers/clk/at91/clk-utmi.c | 162 +++++++++++++++++++++++++++++++++++++++++++
drivers/clk/at91/pmc.c | 7 ++
drivers/clk/at91/pmc.h | 5 ++
5 files changed, 182 insertions(+)
create mode 100644 drivers/clk/at91/clk-utmi.c

diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
index 85b53a4..6ad37da 100644
--- a/arch/arm/mach-at91/Kconfig
+++ b/arch/arm/mach-at91/Kconfig
@@ -1,5 +1,8 @@
if ARCH_AT91

+config HAVE_AT91_UTMI
+ bool
+
config HAVE_AT91_DBGU0
bool

@@ -78,6 +81,7 @@ config SOC_SAMA5D3
select HAVE_FB_ATMEL
select HAVE_AT91_DBGU1
select AT91_USE_OLD_CLK
+ select HAVE_AT91_UTMI
help
Select this if you are using one of Atmel's SAMA5D3 family SoC.
This support covers SAMA5D31, SAMA5D33, SAMA5D34, SAMA5D35.
@@ -124,6 +128,7 @@ config SOC_AT91SAM9RL
select HAVE_FB_ATMEL
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_UTMI

config SOC_AT91SAM9G45
bool "AT91SAM9G45 or AT91SAM9M10 families"
@@ -131,6 +136,7 @@ config SOC_AT91SAM9G45
select HAVE_FB_ATMEL
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_UTMI
help
Select this if you are using one of Atmel's AT91SAM9G45 family SoC.
This support covers AT91SAM9G45, AT91SAM9G46, AT91SAM9M10 and AT91SAM9M11.
@@ -141,6 +147,7 @@ config SOC_AT91SAM9X5
select HAVE_FB_ATMEL
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_UTMI
help
Select this if you are using one of Atmel's AT91SAM9x5 family SoC.
This means that your SAM9 name finishes with a '5' (except if it is
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 3873b62..a824883 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -7,3 +7,4 @@ obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
obj-y += clk-system.o clk-peripheral.o

obj-$(CONFIG_AT91_PROGRAMMABLE_CLOCKS) += clk-programmable.o
+obj-$(CONFIG_HAVE_AT91_UTMI) += clk-utmi.o
diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c
new file mode 100644
index 0000000..9a133df
--- /dev/null
+++ b/drivers/clk/at91/clk-utmi.c
@@ -0,0 +1,162 @@
+/*
+ * drivers/clk/at91/clk-utmi.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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/interrupt.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+#include "pmc.h"
+
+#define UTMI_FIXED_MUL 40
+
+struct clk_utmi {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ unsigned int irq;
+ wait_queue_head_t wait;
+};
+
+#define to_clk_utmi(hw) container_of(hw, struct clk_utmi, hw)
+
+static irqreturn_t clk_utmi_irq_handler(int irq, void *dev_id)
+{
+ struct clk_utmi *utmi = (struct clk_utmi *)dev_id;
+
+ wake_up(&utmi->wait);
+ disable_irq_nosync(utmi->irq);
+
+ return IRQ_HANDLED;
+}
+
+static int clk_utmi_prepare(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+ struct at91_pmc *pmc = utmi->pmc;
+ u32 tmp = at91_pmc_read(AT91_CKGR_UCKR) | AT91_PMC_UPLLEN |
+ AT91_PMC_UPLLCOUNT | AT91_PMC_BIASEN;
+
+ pmc_write(pmc, AT91_CKGR_UCKR, tmp);
+
+ while (!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_LOCKU)) {
+ enable_irq(utmi->irq);
+ wait_event(utmi->wait,
+ pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_LOCKU);
+ }
+
+ return 0;
+}
+
+static int clk_utmi_is_ready(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+ struct at91_pmc *pmc = utmi->pmc;
+
+ return !!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_LOCKU);
+}
+
+static void clk_utmi_disable(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+ struct at91_pmc *pmc = utmi->pmc;
+ u32 tmp = at91_pmc_read(AT91_CKGR_UCKR) & ~AT91_PMC_UPLLEN;
+
+ pmc_write(pmc, AT91_CKGR_UCKR, tmp);
+}
+
+static unsigned long clk_utmi_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ /* UTMI clk is a fixed clk multiplier */
+ return parent_rate * UTMI_FIXED_MUL;
+}
+
+static const struct clk_ops utmi_ops = {
+ .prepare = clk_utmi_prepare,
+ .is_prepared = clk_utmi_is_ready,
+ .disable = clk_utmi_disable,
+ .is_enabled = clk_utmi_is_ready,
+ .recalc_rate = clk_utmi_recalc_rate,
+};
+
+static struct clk * __init
+at91_clk_register_utmi(struct at91_pmc *pmc, unsigned int irq,
+ const char *name, const char *parent_name)
+{
+ int ret;
+ struct clk_utmi *utmi;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ utmi = kzalloc(sizeof(*utmi), GFP_KERNEL);
+ if (!utmi)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &utmi_ops;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+ init.flags = CLK_SET_RATE_GATE;
+
+ utmi->hw.init = &init;
+ utmi->pmc = pmc;
+ utmi->irq = irq;
+ init_waitqueue_head(&utmi->wait);
+ irq_set_status_flags(utmi->irq, IRQ_NOAUTOEN);
+ ret = request_irq(utmi->irq, clk_utmi_irq_handler,
+ IRQF_TRIGGER_HIGH, "clk-utmi", utmi);
+ if (ret)
+ return ERR_PTR(ret);
+
+ clk = clk_register(NULL, &utmi->hw);
+ if (IS_ERR(clk))
+ kfree(utmi);
+
+ return clk;
+}
+
+static void __init
+of_at91_clk_utmi_setup(struct device_node *np, struct at91_pmc *pmc)
+{
+ unsigned int irq;
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ irq = irq_of_parse_and_map(np, 0);
+ if (!irq)
+ return;
+
+ clk = at91_clk_register_utmi(pmc, irq, name, parent_name);
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+ return;
+}
+
+void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ of_at91_clk_utmi_setup(np, pmc);
+}
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index 5b8a959e..8802999 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -272,6 +272,13 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
.data = of_at91sam9x5_clk_prog_setup,
},
#endif
+ /* UTMI clock */
+#if defined(CONFIG_HAVE_AT91_UTMI)
+ {
+ .compatible = "atmel,at91sam9x5-clk-utmi",
+ .data = of_at91sam9x5_clk_utmi_setup,
+ },
+#endif
{ /*sentinel*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 5f20dc1..3ba9503 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -91,4 +91,9 @@ extern void __init of_at91sam9x5_clk_prog_setup(struct device_node *np,
struct at91_pmc *pmc);
#endif

+#if defined(CONFIG_HAVE_AT91_UTMI)
+extern void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+#endif
+
#endif /* __PMC_H_ */
--
1.7.9.5

2013-10-11 11:17:52

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 13/17] clk: at91: add PMC usb clock

This patch adds new at91 usb clock implementation using common clk framework.
This clock is used to clock usb ports (ohci, ehci and udc).

Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/mach-at91/Kconfig | 11 ++
drivers/clk/at91/Makefile | 1 +
drivers/clk/at91/clk-usb.c | 400 ++++++++++++++++++++++++++++++++++++++++++++
drivers/clk/at91/pmc.c | 11 ++
drivers/clk/at91/pmc.h | 9 +
5 files changed, 432 insertions(+)
create mode 100644 drivers/clk/at91/clk-usb.c

diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
index 6ad37da..b76dc4c 100644
--- a/arch/arm/mach-at91/Kconfig
+++ b/arch/arm/mach-at91/Kconfig
@@ -3,6 +3,9 @@ if ARCH_AT91
config HAVE_AT91_UTMI
bool

+config HAVE_AT91_USB_CLK
+ bool
+
config HAVE_AT91_DBGU0
bool

@@ -82,6 +85,7 @@ config SOC_SAMA5D3
select HAVE_AT91_DBGU1
select AT91_USE_OLD_CLK
select HAVE_AT91_UTMI
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's SAMA5D3 family SoC.
This support covers SAMA5D31, SAMA5D33, SAMA5D34, SAMA5D35.
@@ -96,12 +100,14 @@ config SOC_AT91RM9200
select MULTI_IRQ_HANDLER
select SPARSE_IRQ
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK

config SOC_AT91SAM9260
bool "AT91SAM9260, AT91SAM9XE or AT91SAM9G20"
select HAVE_AT91_DBGU0
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's AT91SAM9260, AT91SAM9XE
or AT91SAM9G20 SoC.
@@ -112,6 +118,7 @@ config SOC_AT91SAM9261
select HAVE_FB_ATMEL
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's AT91SAM9261 or AT91SAM9G10 SoC.

@@ -121,6 +128,7 @@ config SOC_AT91SAM9263
select HAVE_FB_ATMEL
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK

config SOC_AT91SAM9RL
bool "AT91SAM9RL"
@@ -137,6 +145,7 @@ config SOC_AT91SAM9G45
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
select HAVE_AT91_UTMI
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's AT91SAM9G45 family SoC.
This support covers AT91SAM9G45, AT91SAM9G46, AT91SAM9M10 and AT91SAM9M11.
@@ -148,6 +157,7 @@ config SOC_AT91SAM9X5
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
select HAVE_AT91_UTMI
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's AT91SAM9x5 family SoC.
This means that your SAM9 name finishes with a '5' (except if it is
@@ -161,6 +171,7 @@ config SOC_AT91SAM9N12
select HAVE_FB_ATMEL
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK
help
Select this if you are using Atmel's AT91SAM9N12 SoC.

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index a824883..61db058 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -8,3 +8,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
diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c
new file mode 100644
index 0000000..0454555
--- /dev/null
+++ b/drivers/clk/at91/clk-usb.c
@@ -0,0 +1,400 @@
+/*
+ * drivers/clk/at91/clk-usb.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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 USB_SOURCE_MAX 2
+
+#define SAM9X5_USB_DIV_SHIFT 8
+#define SAM9X5_USB_MAX_DIV 0xf
+
+#define RM9200_USB_DIV_SHIFT 28
+#define RM9200_USB_DIV_TAB_SIZE 4
+
+struct at91sam9x5_clk_usb {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+};
+
+#define to_at91sam9x5_clk_usb(hw) \
+ container_of(hw, struct at91sam9x5_clk_usb, hw)
+
+struct at91rm9200_clk_usb {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ u32 divisors[4];
+};
+
+#define to_at91rm9200_clk_usb(hw) \
+ container_of(hw, struct at91rm9200_clk_usb, hw)
+
+static unsigned long at91sam9x5_clk_usb_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ u32 tmp;
+ u8 usbdiv;
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ tmp = pmc_read(pmc, AT91_PMC_USB);
+ usbdiv = (tmp & AT91_PMC_OHCIUSBDIV) >> SAM9X5_USB_DIV_SHIFT;
+ return parent_rate / (usbdiv + 1);
+}
+
+static long at91sam9x5_clk_usb_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 >= SAM9X5_USB_MAX_DIV)
+ return *parent_rate / (SAM9X5_USB_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_usb_set_parent(struct clk_hw *hw, u8 index)
+{
+ u32 tmp;
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ if (index > 1)
+ return -EINVAL;
+ tmp = pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_USBS;
+ if (index)
+ tmp |= AT91_PMC_USBS;
+ pmc_write(pmc, AT91_PMC_USB, tmp);
+ return 0;
+}
+
+static u8 at91sam9x5_clk_usb_get_parent(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ return pmc_read(pmc, AT91_PMC_USB) & AT91_PMC_USBS;
+}
+
+static int at91sam9x5_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ u32 tmp;
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+ unsigned long div = parent_rate / rate;
+
+ if (parent_rate % rate || div < 1 || div >= SAM9X5_USB_MAX_DIV)
+ return -EINVAL;
+
+ tmp = pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_OHCIUSBDIV;
+ tmp |= (div - 1) << SAM9X5_USB_DIV_SHIFT;
+ pmc_write(pmc, AT91_PMC_USB, tmp);
+
+ return 0;
+}
+
+static const struct clk_ops at91sam9x5_usb_ops = {
+ .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
+ .round_rate = at91sam9x5_clk_usb_round_rate,
+ .get_parent = at91sam9x5_clk_usb_get_parent,
+ .set_parent = at91sam9x5_clk_usb_set_parent,
+ .set_rate = at91sam9x5_clk_usb_set_rate,
+};
+
+static int at91sam9n12_clk_usb_enable(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ pmc_write(pmc, AT91_PMC_USB,
+ pmc_read(pmc, AT91_PMC_USB) | AT91_PMC_USBS);
+ return 0;
+}
+
+static void at91sam9n12_clk_usb_disable(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ pmc_write(pmc, AT91_PMC_USB,
+ pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_USBS);
+}
+
+static int at91sam9n12_clk_usb_is_enabled(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ return !!(pmc_read(pmc, AT91_PMC_USB) & AT91_PMC_USBS);
+}
+
+static const struct clk_ops at91sam9n12_usb_ops = {
+ .enable = at91sam9n12_clk_usb_enable,
+ .disable = at91sam9n12_clk_usb_disable,
+ .is_enabled = at91sam9n12_clk_usb_is_enabled,
+ .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
+ .round_rate = at91sam9x5_clk_usb_round_rate,
+ .set_rate = at91sam9x5_clk_usb_set_rate,
+};
+
+static struct clk * __init
+at91sam9x5_clk_register_usb(struct at91_pmc *pmc, const char *name,
+ const char **parent_names, u8 num_parents)
+{
+ struct at91sam9x5_clk_usb *usb;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91sam9x5_usb_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+
+ usb->hw.init = &init;
+ usb->pmc = pmc;
+
+ clk = clk_register(NULL, &usb->hw);
+ if (IS_ERR(clk))
+ kfree(usb);
+
+ return clk;
+}
+
+static struct clk * __init
+at91sam9n12_clk_register_usb(struct at91_pmc *pmc, const char *name,
+ const char *parent_name)
+{
+ struct at91sam9x5_clk_usb *usb;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91sam9n12_usb_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE;
+
+ usb->hw.init = &init;
+ usb->pmc = pmc;
+
+ clk = clk_register(NULL, &usb->hw);
+ if (IS_ERR(clk))
+ kfree(usb);
+
+ return clk;
+}
+
+static unsigned long at91rm9200_clk_usb_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+ u32 tmp;
+ u8 usbdiv;
+
+ tmp = pmc_read(pmc, AT91_CKGR_PLLBR);
+ usbdiv = (tmp & AT91_PMC_USBDIV) >> RM9200_USB_DIV_SHIFT;
+ if (usb->divisors[usbdiv])
+ return parent_rate / usb->divisors[usbdiv];
+
+ return 0;
+}
+
+static long at91rm9200_clk_usb_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
+ unsigned long bestrate = 0;
+ int bestdiff = -1;
+ unsigned long tmprate;
+ int tmpdiff;
+ int i = 0;
+
+ for (i = 0; i < 4; i++) {
+ if (!usb->divisors[i])
+ continue;
+ tmprate = *parent_rate / usb->divisors[i];
+ if (tmprate < rate)
+ tmpdiff = rate - tmprate;
+ else
+ tmpdiff = tmprate - rate;
+
+ if (bestdiff < 0 || bestdiff > tmpdiff) {
+ bestrate = tmprate;
+ bestdiff = tmpdiff;
+ }
+
+ if (!bestdiff)
+ break;
+ }
+
+ return bestrate;
+}
+
+static int at91rm9200_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ u32 tmp;
+ int i;
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+ unsigned long div = parent_rate / rate;
+
+ if (parent_rate % rate)
+ return -EINVAL;
+ for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) {
+ if (usb->divisors[i] == div) {
+ tmp = pmc_read(pmc, AT91_CKGR_PLLBR) &
+ ~AT91_PMC_USBDIV;
+ tmp |= i << RM9200_USB_DIV_SHIFT;
+ pmc_write(pmc, AT91_CKGR_PLLBR, tmp);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const struct clk_ops at91rm9200_usb_ops = {
+ .recalc_rate = at91rm9200_clk_usb_recalc_rate,
+ .round_rate = at91rm9200_clk_usb_round_rate,
+ .set_rate = at91rm9200_clk_usb_set_rate,
+};
+
+static struct clk * __init
+at91rm9200_clk_register_usb(struct at91_pmc *pmc, const char *name,
+ const char *parent_name, const u32 *divisors)
+{
+ struct at91rm9200_clk_usb *usb;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91rm9200_usb_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = 0;
+
+ usb->hw.init = &init;
+ usb->pmc = pmc;
+ memcpy(usb->divisors, divisors, sizeof(usb->divisors));
+
+ clk = clk_register(NULL, &usb->hw);
+ if (IS_ERR(clk))
+ kfree(usb);
+
+ return clk;
+}
+
+void __init of_at91sam9x5_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ struct clk *clk;
+ int i;
+ int num_parents;
+ const char *parent_names[USB_SOURCE_MAX];
+ const char *name = np->name;
+
+ num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells");
+ if (num_parents <= 0 || num_parents > USB_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_usb(pmc, name, parent_names, num_parents);
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+void __init of_at91sam9n12_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ clk = at91sam9n12_clk_register_usb(pmc, name, parent_name);
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+void __init of_at91rm9200_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+ u32 divisors[4] = {0, 0, 0, 0};
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return;
+
+ of_property_read_u32_array(np, "atmel,clk-divisors", divisors, 4);
+ if (!divisors[0])
+ return;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ clk = at91rm9200_clk_register_usb(pmc, name, parent_name, divisors);
+ if (IS_ERR(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 8802999..dc0b1b6 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -279,6 +279,17 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
.data = of_at91sam9x5_clk_utmi_setup,
},
#endif
+ /* USB clock */
+#if defined(CONFIG_HAVE_AT91_USB_CLK)
+ {
+ .compatible = "atmel,at91rm9200-clk-usb",
+ .data = of_at91rm9200_clk_usb_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-clk-usb",
+ .data = of_at91sam9x5_clk_usb_setup,
+ },
+#endif
{ /*sentinel*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 3ba9503..eba7334 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -96,4 +96,13 @@ extern void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np,
struct at91_pmc *pmc);
#endif

+#if defined(CONFIG_HAVE_AT91_USB_CLK)
+extern void __init of_at91rm9200_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9x5_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9n12_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+#endif
+
#endif /* __PMC_H_ */
--
1.7.9.5

2013-10-11 11:41:20

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 15/17] clk: at91: add PMC clk device tree binding doc.

This patch adds new at91 clks dt bindings documentation.

Signed-off-by: Boris BREZILLON <[email protected]>
---
.../devicetree/bindings/clock/at91-clock.txt | 328 ++++++++++++++++++++
1 file changed, 328 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/at91-clock.txt

diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt
new file mode 100644
index 0000000..360d8fe
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/at91-clock.txt
@@ -0,0 +1,328 @@
+Device Tree Clock bindings for arch-at91
+
+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:
+ "atmel,at91rm9200-pmc" or
+ "atmel,at91sam9g45-pmc" or
+ "atmel,at91sam9n12-pmc" or
+ "atmel,at91sam9x5-pmc" or
+ "atmel,sama5d3-pmc":
+ at91 PMC (Power Management Controller)
+ All at91 specific clocks (clocks defined below) must be child
+ node of the PMC node.
+
+ "atmel,at91rm9200-clk-main":
+ at91 main oscillator
+
+ "atmel,at91rm9200-clk-master" or
+ "atmel,at91sam9x5-clk-master":
+ at91 master clock
+
+ "atmel,at91sam9x5-clk-peripheral" or
+ "atmel,at91rm9200-clk-peripheral":
+ at91 peripheral clocks
+
+ "atmel,at91rm9200-clk-pll" or
+ "atmel,at91sam9g45-clk-pll" or
+ "atmel,at91sam9g20-clk-pllb" or
+ "atmel,sama5d3-clk-pll":
+ at91 pll clocks
+
+ "atmel,at91sam9x5-clk-plldiv":
+ at91 plla divisor
+
+ "atmel,at91rm9200-clk-programmable" or
+ "atmel,at91sam9g45-clk-programmable" or
+ "atmel,at91sam9x5-clk-programmable":
+ at91 programmable clocks
+
+ "atmel,at91sam9x5-clk-smd":
+ at91 SMD (Soft Modem) clock
+
+ "atmel,at91rm9200-clk-system":
+ at91 system clocks
+
+ "atmel,at91rm9200-clk-usb" or
+ "atmel,at91sam9x5-clk-usb" or
+ "atmel,at91sam9n12-clk-usb":
+ at91 usb clock
+
+ "atmel,at91sam9x5-clk-utmi":
+ at91 utmi clock
+
+Required properties for PMC node:
+- reg : defines the IO memory reserved for the PMC.
+- interrupts : shall be set to PMC interrupt line.
+- interrupt-controller : tell that the PMC is an interrupt controller.
+- #interrupt-cells : must be set to 1. The first cell encodes the interrupt id,
+ and reflect the bit position in the PMC_ER/DR/SR registers.
+ You can use the dt macros defined in dt-bindings/clk/at91.h.
+ 0 (AT91_PMC_MOSCS) -> main oscillator ready
+ 1 (AT91_PMC_LOCKA) -> PLL A ready
+ 2 (AT91_PMC_LOCKB) -> PLL B ready
+ 3 (AT91_PMC_MCKRDY) -> master clock ready
+ 6 (AT91_PMC_LOCKU) -> UTMI PLL clock ready
+ 8 .. 15 (AT91_PMC_PCKRDY(id)) -> programmable clock ready
+ 16 (AT91_PMC_MOSCSELS) -> main oscillator selected
+ 17 (AT91_PMC_MOSCRCS) -> RC main oscillator stabilized
+ 18 (AT91_PMC_CFDEV) -> clock failure detected
+
+For example:
+ pmc: pmc@fffffc00 {
+ compatible = "atmel,sama5d3-pmc";
+ interrupts = <1 4 7>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+
+ /* put at91 clocks here */
+ };
+
+Required properties for main clock:
+- interrupt-parent : must reference the PMC node.
+- interrupts : shall be set to "<0>".
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks (optional if clock-frequency is provided) : shall be the slow clock
+ phandle. This clock is used to calculate the main clock rate if
+ "clock-frequency" is not provided.
+- clock-frequency : the main oscillator frequency.Prefer the use of
+ "clock-frequency" over automatic clock rate calculation.
+
+For example:
+ main: mainck {
+ compatible = "atmel,at91rm9200-clk-main";
+ interrupt-parent = <&pmc>;
+ interrupts = <0>;
+ #clock-cells = <0>;
+ clocks = <&ck32k>;
+ clock-frequency = <18432000>;
+ };
+
+Required properties for master clock:
+- interrupt-parent : must reference the PMC node.
+- interrupts : shall be set to "<3>".
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : shall be the master clock sources (see atmel datasheet) phandles.
+ e.g. "<&ck32k>, <&main>, <&plla>, <&pllb>".
+- atmel,clk-output-range : minimum and maximum clock frequency (two u32
+ fields).
+ e.g. output = <0 133000000>; <=> 0 to 133MHz.
+- atmel,clk-divisors : master clock divisors table (four u32 fields).
+ 0 <=> reserved value.
+ e.g. divisors = <1 2 4 6>;
+- atmel,master-clk-have-div3-pres : some SoC use the reserved value 7 in the
+ PRES field as CLOCK_DIV3 (e.g sam9x5).
+
+For example:
+ mck: mck {
+ compatible = "atmel,at91rm9200-clk-master";
+ interrupt-parent = <&pmc>;
+ interrupts = <3>;
+ #clock-cells = <0>;
+ atmel,clk-output-range = <0 133000000>;
+ atmel,clk-divisors = <1 2 4 0>;
+ };
+
+Required properties for peripheral clocks:
+- #clock-cells : from common clock binding; shall be set to 1. The second cell
+ is used to encode the peripheral id. Peripheral ids are defined in
+ atmel's SoC datasheets.
+- clocks : shall be the master clock phandle.
+ e.g. clocks = <&mck>;
+- name: device tree node describing a specific system clock.
+ * atmel,clk-id: peripheral id. See Atmel's datasheets to get a full
+ list of peripheral ids.
+ * atmel,clk-default-divisor (optional, only available for
+ "atmel,at91sam9x5-clk-peripheral"): sam9x5 and sama5d3 SoC provides
+ configurable peripheral clock divisor. If you define this property
+ (u32), the default divisor will be applied when enabling
+ peripheral clock. If not provided the peripheral clock is not
+ divided.
+
+For example:
+ periph: periphck {
+ compatible = "atmel,at91sam9x5-clk-peripheral";
+ #clock-cells = <1>;
+ clocks = <&mck>;
+
+ pioAB_clk {
+ atmel,clk-id = <2>;
+ atmel,clk-default-divisor = <1>;
+ };
+
+ pioCD_clk {
+ atmel,clk-id = <3>;
+ atmel,clk-default-divisor = <2>;
+ };
+ };
+
+
+Required properties for pll clocks:
+- interrupt-parent : must reference the PMC node.
+- interrupts : shall be set to "<1>".
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : shall be the main clock phandle.
+- atmel,clk-id : pll id.
+ 0 -> PLL A
+ 1 -> PLL B
+- atmel,clk-input-range : minimum and maximum source clock frequency (two u32
+ fields).
+ e.g. input = <1 32000000>; <=> 1 to 32MHz.
+- #atmel,pll-clk-output-range-cells : number of cells reserved for pll output
+ range description. Sould be set to 2, 3
+ or 4.
+ * 1st and 2nd cells represent the frequency range (min-max).
+ * 3rd cell is optional and represents the OUT field value for the given
+ range.
+ * 4th cell is optional and represents the ICPLL field (PLLICPR
+ register)
+- atmel,pll-clk-output-ranges : pll output frequency ranges + optional parameter
+ depending on #atmel,pll-output-range-cells
+ property value.
+
+For example:
+ plla: pllack {
+ compatible = "atmel,at91sam9g45-clk-pll";
+ interrupt-parent = <&pmc>;
+ interrupts = <1>;
+ #clock-cells = <0>;
+ clocks = <&main>;
+ atmel,clk-id = <0>;
+ atmel,clk-input-range = <2000000 32000000>;
+ #atmel,pll-clk-output-range-cells = <4>;
+ atmel,pll-clk-output-ranges = <74500000 800000000 0 0
+ 69500000 750000000 1 0
+ 64500000 700000000 2 0
+ 59500000 650000000 3 0
+ 54500000 600000000 0 1
+ 49500000 550000000 1 1
+ 44500000 500000000 2 1
+ 40000000 450000000 3 1>;
+ };
+
+Required properties for plldiv clocks (plldiv = pll / 2):
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : shall be the plla clock phandle.
+
+The pll divisor is equal to 2 and cannot be changed.
+
+For example:
+ plladiv: plladivck {
+ compatible = "atmel,at91sam9x5-clk-plldiv";
+ #clock-cells = <0>;
+ clocks = <&plla>;
+ };
+
+Required properties for programmable clocks:
+- interrupt-parent : must reference the PMC node.
+- #clock-cells : from common clock binding; shall be set to 1. The second cell
+ is used to encode the programmable clock id.
+ Peripheral ids are in atmel's SoC
+ datasheets.
+- clocks : shall be the programmable clock source phandles.
+ e.g. clocks = <&clk32k>, <&main>, <&plla>, <&pllb>;
+- name: device tree node describing a specific prog clock.
+ * atmel,clk-id : programmable clock id (register offset from PCKx
+ register).
+ * interrupts : shall be set to "<(8 + id)>".
+
+For example:
+ prog: progck {
+ compatible = "atmel,at91sam9g45-clk-programmable";
+ interrupt-parent = <&pmc>;
+ #clock-cells = <1>;
+ clocks = <&clk32k>, <&main>, <&plladiv>, <&utmi>, <&mck>;
+
+ prog0 {
+ atmel,clk-id = <0>;
+ interrupts = <8>;
+ };
+
+ prog1 {
+ atmel,clk-id = <1>;
+ interrupts = <9>;
+ };
+ };
+
+
+Required properties for smd clock:
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : shall be the smd clock source phandles.
+ e.g. clocks = <&plladiv>, <&utmi>;
+
+For example:
+ smd: smdck {
+ compatible = "atmel,at91sam9x5-clk-smd";
+ #clock-cells = <0>;
+ clocks = <&plladiv>, <&utmi>;
+ };
+
+Required properties for system clocks:
+- #clock-cells : from common clock binding; shall be set to 1. The second cell
+ is used to encode the system clock id (bit used in SCER/SCDR register).
+- name: device tree node describing a specific system clock.
+ * id: system clock id (bit position in SCER/SCDR/SCSR registers).
+ See Atmel's datasheet to get a full list of system clock ids.
+
+For example:
+ system: systemck {
+ compatible = "atmel,at91rm9200-clk-system";
+ #clock-cells = <1>;
+
+ ddrck {
+ atmel,clk-id = <2>;
+ clocks = <&mck>;
+ };
+
+ uhpck {
+ atmel,clk-id = <6>;
+ clocks = <&usb>;
+ };
+
+ udpck {
+ atmel,clk-id = <7>;
+ clocks = <&usb>;
+ };
+ };
+
+
+Required properties for usb clock:
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : shall be the smd clock source phandles.
+ e.g. clocks = <&pllb>;
+- atmel,clk-divisors (only available for "atmel,at91rm9200-clk-usb"):
+ usb clock divisor table.
+ e.g. divisors = <1 2 4 0>;
+
+For example:
+ usb: usbck {
+ compatible = "atmel,at91sam9x5-clk-usb";
+ #clock-cells = <0>;
+ clocks = <&plladiv>, <&utmi>;
+ };
+
+ usb: usbck {
+ compatible = "atmel,at91rm9200-clk-usb";
+ #clock-cells = <0>;
+ clocks = <&pllb>;
+ atmel,clk-divisors = <1 2 4 0>;
+ };
+
+
+Required properties for utmi clock:
+- interrupt-parent : must reference the PMC node.
+- interrupts : shall be set to "<AT91_PMC_LOCKU IRQ_TYPE_LEVEL_HIGH>".
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : shall be the main clock source phandle.
+
+For example:
+ utmi: utmick {
+ compatible = "atmel,at91sam9x5-clk-utmi";
+ interrupt-parent = <&pmc>;
+ interrupts = <AT91_PMC_LOCKU IRQ_TYPE_LEVEL_HIGH>;
+ #clock-cells = <0>;
+ clocks = <&main>;
+ };
--
1.7.9.5

2013-10-11 11:48:08

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 14/17] 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: Boris BREZILLON <[email protected]>
---
arch/arm/mach-at91/Kconfig | 5 ++
drivers/clk/at91/Makefile | 1 +
drivers/clk/at91/clk-smd.c | 173 ++++++++++++++++++++++++++++++++++++++++++++
drivers/clk/at91/pmc.c | 7 ++
drivers/clk/at91/pmc.h | 5 ++
5 files changed, 191 insertions(+)
create mode 100644 drivers/clk/at91/clk-smd.c

diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
index b76dc4c..97033f7 100644
--- a/arch/arm/mach-at91/Kconfig
+++ b/arch/arm/mach-at91/Kconfig
@@ -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.
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 61db058..0e92b71 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -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
diff --git a/drivers/clk/at91/clk-smd.c b/drivers/clk/at91/clk-smd.c
new file mode 100644
index 0000000..9f3fa39
--- /dev/null
+++ b/drivers/clk/at91/clk-smd.c
@@ -0,0 +1,173 @@
+/*
+ * drivers/clk/at91/clk-smd.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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);
+}
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index dc0b1b6..00a4a51 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -290,6 +290,13 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
.data = of_at91sam9x5_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*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index eba7334..d0b8b38 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -105,4 +105,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_ */
--
1.7.9.5

2013-10-11 11:48:57

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 16/17] ARM: at91: move pit timer to common clk framework

Use device tree to get the source clock of the PIT (Periodic Interval Timer).
If the clock is not found in device tree (or dt is not enabled) we'll try to
get it using clk_lookup definitions.

Signed-off-by: Boris BREZILLON <[email protected]>
Acked-by: Nicolas Ferre <[email protected]>
---
arch/arm/mach-at91/at91sam926x_time.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/arch/arm/mach-at91/at91sam926x_time.c b/arch/arm/mach-at91/at91sam926x_time.c
index bb39232..0f04ffe 100644
--- a/arch/arm/mach-at91/at91sam926x_time.c
+++ b/arch/arm/mach-at91/at91sam926x_time.c
@@ -39,6 +39,7 @@
static u32 pit_cycle; /* write-once */
static u32 pit_cnt; /* access only w/system irq blocked */
static void __iomem *pit_base_addr __read_mostly;
+static struct clk *mck;

static inline unsigned int pit_read(unsigned int reg_offset)
{
@@ -195,10 +196,14 @@ static int __init of_at91sam926x_pit_init(void)
if (!pit_base_addr)
goto node_err;

+ mck = of_clk_get(np, 0);
+
/* Get the interrupts property */
ret = irq_of_parse_and_map(np, 0);
if (!ret) {
pr_crit("AT91: PIT: Unable to get IRQ from DT\n");
+ if (!IS_ERR(mck))
+ clk_put(mck);
goto ioremap_err;
}
at91sam926x_pit_irq.irq = ret;
@@ -230,6 +235,8 @@ void __init at91sam926x_pit_init(void)
unsigned bits;
int ret;

+ mck = ERR_PTR(-ENOENT);
+
/* For device tree enabled device: initialize here */
of_at91sam926x_pit_init();

@@ -237,7 +244,12 @@ void __init at91sam926x_pit_init(void)
* Use our actual MCK to figure out how many MCK/16 ticks per
* 1/HZ period (instead of a compile-time constant LATCH).
*/
- pit_rate = clk_get_rate(clk_get(NULL, "mck")) / 16;
+ if (IS_ERR(mck))
+ mck = clk_get(NULL, "mck");
+
+ if (IS_ERR(mck))
+ panic("AT91: PIT: Unable to get mck clk\n");
+ pit_rate = clk_get_rate(mck) / 16;
pit_cycle = (pit_rate + HZ/2) / HZ;
WARN_ON(((pit_cycle - 1) & ~AT91_PIT_PIV) != 0);

--
1.7.9.5

2013-10-11 11:51:37

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v4 17/17] ARM: at91: add new compatible strings for pmc driver

This patch adds new compatible string for PMC node to prepare the
transition to common clk.

These compatible string come from pmc driver in clk subsystem and are
needed to provide new device tree compatibility with old at91 clks
(device tree using common clks will use the new compatible strings).

Signed-off-by: Boris BREZILLON <[email protected]>
Acked-by: Nicolas Ferre <[email protected]>
---
arch/arm/mach-at91/clock.c | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/arch/arm/mach-at91/clock.c b/arch/arm/mach-at91/clock.c
index 5f02aea..72b2579 100644
--- a/arch/arm/mach-at91/clock.c
+++ b/arch/arm/mach-at91/clock.c
@@ -884,6 +884,11 @@ static int __init at91_pmc_init(unsigned long main_clock)
#if defined(CONFIG_OF)
static struct of_device_id pmc_ids[] = {
{ .compatible = "atmel,at91rm9200-pmc" },
+ { .compatible = "atmel,at91sam9260-pmc" },
+ { .compatible = "atmel,at91sam9g45-pmc" },
+ { .compatible = "atmel,at91sam9n12-pmc" },
+ { .compatible = "atmel,at91sam9x5-pmc" },
+ { .compatible = "atmel,sama5d3-pmc" },
{ /*sentinel*/ }
};

--
1.7.9.5

2013-10-11 16:06:35

by Felipe Balbi

[permalink] [raw]
Subject: Re: [PATCH v4 01/17] ARM: at91: move at91_pmc.h to include/linux/clk/at91_pmc.h

On Fri, Oct 11, 2013 at 09:37:45AM +0200, Boris BREZILLON wrote:
> This patch moves at91_pmc.h header from machine specific directory
> (arch/arm/mach-at91/include/mach/at91_pmc.h) to clk include directory
> (include/linux/clk/at91_pmc.h).
> We need this to avoid reference to machine specific headers in clk
> drivers.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

for drivers/usb/gadget:

Acked-by: Felipe Balbi <[email protected]>


--
balbi


Attachments:
(No filename) (458.00 B)
signature.asc (836.00 B)
Digital signature
Download all attachments

2013-10-16 15:30:05

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 01/17] ARM: at91: move at91_pmc.h to include/linux/clk/at91_pmc.h

On 11/10/2013 09:37, Boris BREZILLON :
> This patch moves at91_pmc.h header from machine specific directory
> (arch/arm/mach-at91/include/mach/at91_pmc.h) to clk include directory
> (include/linux/clk/at91_pmc.h).
> We need this to avoid reference to machine specific headers in clk
> drivers.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> arch/arm/mach-at91/at91rm9200.c | 2 +-
> arch/arm/mach-at91/at91sam9260.c | 2 +-
> arch/arm/mach-at91/at91sam9261.c | 2 +-
> arch/arm/mach-at91/at91sam9263.c | 2 +-
> arch/arm/mach-at91/at91sam9g45.c | 2 +-
> arch/arm/mach-at91/at91sam9n12.c | 2 +-
> arch/arm/mach-at91/at91sam9rl.c | 2 +-
> arch/arm/mach-at91/at91sam9x5.c | 2 +-
> arch/arm/mach-at91/clock.c | 2 +-
> arch/arm/mach-at91/pm.c | 2 +-
> arch/arm/mach-at91/pm_slowclock.S | 2 +-
> arch/arm/mach-at91/sama5d3.c | 2 +-
> arch/arm/mach-at91/setup.c | 2 +-
> drivers/usb/gadget/atmel_usba_udc.c | 2 +-
> .../include/mach => include/linux/clk}/at91_pmc.h | 2 +-
> 15 files changed, 15 insertions(+), 15 deletions(-)
> rename {arch/arm/mach-at91/include/mach => include/linux/clk}/at91_pmc.h (99%)
>
> diff --git a/arch/arm/mach-at91/at91rm9200.c b/arch/arm/mach-at91/at91rm9200.c
> index 4aad93d..102dac6 100644
> --- a/arch/arm/mach-at91/at91rm9200.c
> +++ b/arch/arm/mach-at91/at91rm9200.c
> @@ -12,13 +12,13 @@
>
> #include <linux/module.h>
> #include <linux/reboot.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/irq.h>
> #include <asm/mach/arch.h>
> #include <asm/mach/map.h>
> #include <asm/system_misc.h>
> #include <mach/at91rm9200.h>
> -#include <mach/at91_pmc.h>
> #include <mach/at91_st.h>
> #include <mach/cpu.h>
>
> diff --git a/arch/arm/mach-at91/at91sam9260.c b/arch/arm/mach-at91/at91sam9260.c
> index 5de6074..16d7817 100644
> --- a/arch/arm/mach-at91/at91sam9260.c
> +++ b/arch/arm/mach-at91/at91sam9260.c
> @@ -11,6 +11,7 @@
> */
>
> #include <linux/module.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/proc-fns.h>
> #include <asm/irq.h>
> @@ -20,7 +21,6 @@
> #include <mach/cpu.h>
> #include <mach/at91_dbgu.h>
> #include <mach/at91sam9260.h>
> -#include <mach/at91_pmc.h>
>
> #include "at91_aic.h"
> #include "at91_rstc.h"
> diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
> index 0e07932..c8f5958 100644
> --- a/arch/arm/mach-at91/at91sam9261.c
> +++ b/arch/arm/mach-at91/at91sam9261.c
> @@ -11,6 +11,7 @@
> */
>
> #include <linux/module.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/proc-fns.h>
> #include <asm/irq.h>
> @@ -19,7 +20,6 @@
> #include <asm/system_misc.h>
> #include <mach/cpu.h>
> #include <mach/at91sam9261.h>
> -#include <mach/at91_pmc.h>
>
> #include "at91_aic.h"
> #include "at91_rstc.h"
> diff --git a/arch/arm/mach-at91/at91sam9263.c b/arch/arm/mach-at91/at91sam9263.c
> index 6ce7d18..cd8ad95 100644
> --- a/arch/arm/mach-at91/at91sam9263.c
> +++ b/arch/arm/mach-at91/at91sam9263.c
> @@ -11,6 +11,7 @@
> */
>
> #include <linux/module.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/proc-fns.h>
> #include <asm/irq.h>
> @@ -18,7 +19,6 @@
> #include <asm/mach/map.h>
> #include <asm/system_misc.h>
> #include <mach/at91sam9263.h>
> -#include <mach/at91_pmc.h>
>
> #include "at91_aic.h"
> #include "at91_rstc.h"
> diff --git a/arch/arm/mach-at91/at91sam9g45.c b/arch/arm/mach-at91/at91sam9g45.c
> index 474ee04..9794b20 100644
> --- a/arch/arm/mach-at91/at91sam9g45.c
> +++ b/arch/arm/mach-at91/at91sam9g45.c
> @@ -12,13 +12,13 @@
>
> #include <linux/module.h>
> #include <linux/dma-mapping.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/irq.h>
> #include <asm/mach/arch.h>
> #include <asm/mach/map.h>
> #include <asm/system_misc.h>
> #include <mach/at91sam9g45.h>
> -#include <mach/at91_pmc.h>
> #include <mach/cpu.h>
>
> #include "at91_aic.h"
> diff --git a/arch/arm/mach-at91/at91sam9n12.c b/arch/arm/mach-at91/at91sam9n12.c
> index c7d670d..5b83c66 100644
> --- a/arch/arm/mach-at91/at91sam9n12.c
> +++ b/arch/arm/mach-at91/at91sam9n12.c
> @@ -8,12 +8,12 @@
>
> #include <linux/module.h>
> #include <linux/dma-mapping.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/irq.h>
> #include <asm/mach/arch.h>
> #include <asm/mach/map.h>
> #include <mach/at91sam9n12.h>
> -#include <mach/at91_pmc.h>
> #include <mach/cpu.h>
>
> #include "board.h"
> diff --git a/arch/arm/mach-at91/at91sam9rl.c b/arch/arm/mach-at91/at91sam9rl.c
> index d4ec0d9..947da95 100644
> --- a/arch/arm/mach-at91/at91sam9rl.c
> +++ b/arch/arm/mach-at91/at91sam9rl.c
> @@ -10,6 +10,7 @@
> */
>
> #include <linux/module.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/proc-fns.h>
> #include <asm/irq.h>
> @@ -19,7 +20,6 @@
> #include <mach/cpu.h>
> #include <mach/at91_dbgu.h>
> #include <mach/at91sam9rl.h>
> -#include <mach/at91_pmc.h>
>
> #include "at91_aic.h"
> #include "at91_rstc.h"
> diff --git a/arch/arm/mach-at91/at91sam9x5.c b/arch/arm/mach-at91/at91sam9x5.c
> index 916e5a1..a930d73 100644
> --- a/arch/arm/mach-at91/at91sam9x5.c
> +++ b/arch/arm/mach-at91/at91sam9x5.c
> @@ -8,12 +8,12 @@
>
> #include <linux/module.h>
> #include <linux/dma-mapping.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/irq.h>
> #include <asm/mach/arch.h>
> #include <asm/mach/map.h>
> #include <mach/at91sam9x5.h>
> -#include <mach/at91_pmc.h>
> #include <mach/cpu.h>
>
> #include "board.h"
> diff --git a/arch/arm/mach-at91/clock.c b/arch/arm/mach-at91/clock.c
> index 6b2630a..5f02aea 100644
> --- a/arch/arm/mach-at91/clock.c
> +++ b/arch/arm/mach-at91/clock.c
> @@ -24,9 +24,9 @@
> #include <linux/clk.h>
> #include <linux/io.h>
> #include <linux/of_address.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <mach/hardware.h>
> -#include <mach/at91_pmc.h>
> #include <mach/cpu.h>
>
> #include <asm/proc-fns.h>
> diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c
> index 15afb5d..692cacd 100644
> --- a/arch/arm/mach-at91/pm.c
> +++ b/arch/arm/mach-at91/pm.c
> @@ -19,13 +19,13 @@
> #include <linux/module.h>
> #include <linux/platform_device.h>
> #include <linux/io.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/irq.h>
> #include <linux/atomic.h>
> #include <asm/mach/time.h>
> #include <asm/mach/irq.h>
>
> -#include <mach/at91_pmc.h>
> #include <mach/cpu.h>
>
> #include "at91_aic.h"
> diff --git a/arch/arm/mach-at91/pm_slowclock.S b/arch/arm/mach-at91/pm_slowclock.S
> index 098c28d..2001877 100644
> --- a/arch/arm/mach-at91/pm_slowclock.S
> +++ b/arch/arm/mach-at91/pm_slowclock.S
> @@ -13,8 +13,8 @@
> */
>
> #include <linux/linkage.h>
> +#include <linux/clk/at91_pmc.h>
> #include <mach/hardware.h>
> -#include <mach/at91_pmc.h>
> #include <mach/at91_ramc.h>
>
>
> diff --git a/arch/arm/mach-at91/sama5d3.c b/arch/arm/mach-at91/sama5d3.c
> index 4012797..0003949 100644
> --- a/arch/arm/mach-at91/sama5d3.c
> +++ b/arch/arm/mach-at91/sama5d3.c
> @@ -9,12 +9,12 @@
>
> #include <linux/module.h>
> #include <linux/dma-mapping.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/irq.h>
> #include <asm/mach/arch.h>
> #include <asm/mach/map.h>
> #include <mach/sama5d3.h>
> -#include <mach/at91_pmc.h>
> #include <mach/cpu.h>
>
> #include "soc.h"
> diff --git a/arch/arm/mach-at91/setup.c b/arch/arm/mach-at91/setup.c
> index b17fbcf..41aa68b 100644
> --- a/arch/arm/mach-at91/setup.c
> +++ b/arch/arm/mach-at91/setup.c
> @@ -11,6 +11,7 @@
> #include <linux/pm.h>
> #include <linux/of_address.h>
> #include <linux/pinctrl/machine.h>
> +#include <linux/clk/at91_pmc.h>
>
> #include <asm/system_misc.h>
> #include <asm/mach/map.h>
> @@ -18,7 +19,6 @@
> #include <mach/hardware.h>
> #include <mach/cpu.h>
> #include <mach/at91_dbgu.h>
> -#include <mach/at91_pmc.h>
>
> #include "at91_shdwc.h"
> #include "soc.h"
> diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c
> index 2cb52e0..9f71d9f 100644
> --- a/drivers/usb/gadget/atmel_usba_udc.c
> +++ b/drivers/usb/gadget/atmel_usba_udc.c
> @@ -326,7 +326,7 @@ static int vbus_is_present(struct usba_udc *udc)
>
> #if defined(CONFIG_ARCH_AT91SAM9RL)
>
> -#include <mach/at91_pmc.h>
> +#include <linux/clk/at91_pmc.h>
>
> static void toggle_bias(int is_on)
> {
> diff --git a/arch/arm/mach-at91/include/mach/at91_pmc.h b/include/linux/clk/at91_pmc.h
> similarity index 99%
> rename from arch/arm/mach-at91/include/mach/at91_pmc.h
> rename to include/linux/clk/at91_pmc.h
> index c604cc6..00c45b3 100644
> --- a/arch/arm/mach-at91/include/mach/at91_pmc.h
> +++ b/include/linux/clk/at91_pmc.h
> @@ -1,5 +1,5 @@
> /*
> - * arch/arm/mach-at91/include/mach/at91_pmc.h
> + * include/linux/clk/at91_pmc.h
> *
> * Copyright (C) 2005 Ivan Kokshaysky
> * Copyright (C) SAN People
>


--
Nicolas Ferre

2013-10-16 15:33:14

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 02/17] ARM: at91: add Kconfig options for common clk support

On 11/10/2013 09:37, Boris BREZILLON :
> This patch adds the following Kconfig options to prepare the transition to
> common clk framework:
>
> - AT91_USE_OLD_CLK: this option is selected by every SoC which does not
> support new at91 clks based on common clk framework (SoC which does not
> define the clock tree in its device tree).
> This options is also selected when the user choose non dt boards support
> (new at91 clks can only be registered from a device tree definition).
>
> - COMMON_CLK_AT91: this option cannot be selected directly. Instead it is
> enabled if these 3 conditions are met:
> * at least one of the selected SoCs have a PMC (Power Management
> Controller) Unit
> * device tree support is enabled
> * the old at91 clk implementation is disabled (every selected SoC define
> its clks in its device tree and non dt boards support is disabled)
>
> - OLD_CLK_AT91: this option cannot be selected directly. Instead it is
> enabled if these 2 conditions are met:
> * at least one of the selected SoCs have a PMC (Power Management
> Controller) Unit
> * at least one of the selected SoCs does not define its clks in its
> device tree or non dt-boards support is enabled
>
> This patch selects AT91_USE_OLD_CLK in all currently supported SoCs. These
> selects will be removed after clk definitions are properly added in each
> soc's device tree.
> It also selects AT91_USE_OLD_CLK in all non-dt boards support.
>
> AT91_PMC_UNIT references are replaced by OLD_CLK_AT91, because PMC Unit is
> enabled for both old and common clk implementations, and old clk
> implementation should not be compiled if COMMON_CLK is enabled.
>
> To avoid future link errors, a new stub is created for at91_dt_clock_init
> function if OLD_CLK_AT91 is disabled.
>
> A new check is added in dt init functions (setup.c) to prepare for SoCs
> supporting new clk implementation. These SoCs won't setup the
> register_clocks callback (clk registration is done using of_clk_init).
>
> Signed-off-by: Boris BREZILLON <[email protected]>

It seems that all cases are covered for a smooth transition to common
clock framework.

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

Thanks

> ---
> arch/arm/mach-at91/Kconfig | 21 +++++++++++++++++++++
> arch/arm/mach-at91/Kconfig.non_dt | 6 ++++++
> arch/arm/mach-at91/Makefile | 2 +-
> arch/arm/mach-at91/generic.h | 3 ++-
> arch/arm/mach-at91/setup.c | 6 ++++--
> 5 files changed, 34 insertions(+), 4 deletions(-)
>
> diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
> index 699b71e..85b53a4 100644
> --- a/arch/arm/mach-at91/Kconfig
> +++ b/arch/arm/mach-at91/Kconfig
> @@ -6,10 +6,22 @@ config HAVE_AT91_DBGU0
> config HAVE_AT91_DBGU1
> bool
>
> +config AT91_USE_OLD_CLK
> + bool
> +
> config AT91_PMC_UNIT
> bool
> default !ARCH_AT91X40
>
> +config COMMON_CLK_AT91
> + bool
> + default AT91_PMC_UNIT && USE_OF && !AT91_USE_OLD_CLK
> + select COMMON_CLK
> +
> +config OLD_CLK_AT91
> + bool
> + default AT91_PMC_UNIT && AT91_USE_OLD_CLK
> +
> config AT91_SAM9_ALT_RESET
> bool
> default !ARCH_AT91X40
> @@ -65,6 +77,7 @@ config SOC_SAMA5D3
> select SOC_SAMA5
> select HAVE_FB_ATMEL
> select HAVE_AT91_DBGU1
> + select AT91_USE_OLD_CLK
> help
> Select this if you are using one of Atmel's SAMA5D3 family SoC.
> This support covers SAMA5D31, SAMA5D33, SAMA5D34, SAMA5D35.
> @@ -78,11 +91,13 @@ config SOC_AT91RM9200
> select HAVE_AT91_DBGU0
> select MULTI_IRQ_HANDLER
> select SPARSE_IRQ
> + select AT91_USE_OLD_CLK
>
> config SOC_AT91SAM9260
> bool "AT91SAM9260, AT91SAM9XE or AT91SAM9G20"
> select HAVE_AT91_DBGU0
> select SOC_AT91SAM9
> + select AT91_USE_OLD_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9260, AT91SAM9XE
> or AT91SAM9G20 SoC.
> @@ -92,6 +107,7 @@ config SOC_AT91SAM9261
> select HAVE_AT91_DBGU0
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> + select AT91_USE_OLD_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9261 or AT91SAM9G10 SoC.
>
> @@ -100,18 +116,21 @@ config SOC_AT91SAM9263
> select HAVE_AT91_DBGU1
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> + select AT91_USE_OLD_CLK
>
> config SOC_AT91SAM9RL
> bool "AT91SAM9RL"
> select HAVE_AT91_DBGU0
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> + select AT91_USE_OLD_CLK
>
> config SOC_AT91SAM9G45
> bool "AT91SAM9G45 or AT91SAM9M10 families"
> select HAVE_AT91_DBGU1
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> + select AT91_USE_OLD_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9G45 family SoC.
> This support covers AT91SAM9G45, AT91SAM9G46, AT91SAM9M10 and AT91SAM9M11.
> @@ -121,6 +140,7 @@ config SOC_AT91SAM9X5
> select HAVE_AT91_DBGU0
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> + select AT91_USE_OLD_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9x5 family SoC.
> This means that your SAM9 name finishes with a '5' (except if it is
> @@ -133,6 +153,7 @@ config SOC_AT91SAM9N12
> select HAVE_AT91_DBGU0
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> + select AT91_USE_OLD_CLK
> help
> Select this if you are using Atmel's AT91SAM9N12 SoC.
>
> diff --git a/arch/arm/mach-at91/Kconfig.non_dt b/arch/arm/mach-at91/Kconfig.non_dt
> index ca900be..b736b57 100644
> --- a/arch/arm/mach-at91/Kconfig.non_dt
> +++ b/arch/arm/mach-at91/Kconfig.non_dt
> @@ -12,26 +12,32 @@ config ARCH_AT91_NONE
> config ARCH_AT91RM9200
> bool "AT91RM9200"
> select SOC_AT91RM9200
> + select AT91_USE_OLD_CLK
>
> config ARCH_AT91SAM9260
> bool "AT91SAM9260 or AT91SAM9XE or AT91SAM9G20"
> select SOC_AT91SAM9260
> + select AT91_USE_OLD_CLK
>
> config ARCH_AT91SAM9261
> bool "AT91SAM9261 or AT91SAM9G10"
> select SOC_AT91SAM9261
> + select AT91_USE_OLD_CLK
>
> config ARCH_AT91SAM9263
> bool "AT91SAM9263"
> select SOC_AT91SAM9263
> + select AT91_USE_OLD_CLK
>
> config ARCH_AT91SAM9RL
> bool "AT91SAM9RL"
> select SOC_AT91SAM9RL
> + select AT91_USE_OLD_CLK
>
> config ARCH_AT91SAM9G45
> bool "AT91SAM9G45"
> select SOC_AT91SAM9G45
> + select AT91_USE_OLD_CLK
>
> config ARCH_AT91X40
> bool "AT91x40"
> diff --git a/arch/arm/mach-at91/Makefile b/arch/arm/mach-at91/Makefile
> index 3b0a953..8539411 100644
> --- a/arch/arm/mach-at91/Makefile
> +++ b/arch/arm/mach-at91/Makefile
> @@ -7,7 +7,7 @@ obj-m :=
> obj-n :=
> obj- :=
>
> -obj-$(CONFIG_AT91_PMC_UNIT) += clock.o
> +obj-$(CONFIG_OLD_CLK_AT91) += clock.o
> obj-$(CONFIG_AT91_SAM9_ALT_RESET) += at91sam9_alt_reset.o
> obj-$(CONFIG_AT91_SAM9G45_RESET) += at91sam9g45_reset.o
> obj-$(CONFIG_AT91_SAM9_TIME) += at91sam926x_time.o
> diff --git a/arch/arm/mach-at91/generic.h b/arch/arm/mach-at91/generic.h
> index dc6e2f5..375c42e 100644
> --- a/arch/arm/mach-at91/generic.h
> +++ b/arch/arm/mach-at91/generic.h
> @@ -44,11 +44,12 @@ extern void at91sam926x_pit_init(void);
> extern void at91x40_timer_init(void);
>
> /* Clocks */
> -#ifdef CONFIG_AT91_PMC_UNIT
> +#ifdef CONFIG_OLD_CLK_AT91
> extern int __init at91_clock_init(unsigned long main_clock);
> extern int __init at91_dt_clock_init(void);
> #else
> static int inline at91_clock_init(unsigned long main_clock) { return 0; }
> +static int inline at91_dt_clock_init(void) { return 0; }
> #endif
> struct device;
>
> diff --git a/arch/arm/mach-at91/setup.c b/arch/arm/mach-at91/setup.c
> index 41aa68b..06052fc 100644
> --- a/arch/arm/mach-at91/setup.c
> +++ b/arch/arm/mach-at91/setup.c
> @@ -483,7 +483,8 @@ void __init at91rm9200_dt_initialize(void)
> at91_dt_clock_init();
>
> /* Register the processor-specific clocks */
> - at91_boot_soc.register_clocks();
> + if (at91_boot_soc.register_clocks)
> + at91_boot_soc.register_clocks();
>
> at91_boot_soc.init();
> }
> @@ -498,7 +499,8 @@ void __init at91_dt_initialize(void)
> at91_dt_clock_init();
>
> /* Register the processor-specific clocks */
> - at91_boot_soc.register_clocks();
> + if (at91_boot_soc.register_clocks)
> + at91_boot_soc.register_clocks();
>
> if (at91_boot_soc.init)
> at91_boot_soc.init();
>


--
Nicolas Ferre

2013-10-17 15:46:52

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 03/17] clk: at91: add PMC base support

On 11/10/2013 09:37, Boris BREZILLON :
> This patch adds at91 PMC (Power Management Controller) base support.
>
> All at91 clocks managed by the PMC unit will use this framework.
>
> This framework provides the following fonctionalities:
> - define a new struct at91_pmc to hide PMC internals (lock, PMC memory
> mapping, irq domain, ...)
> - read/write helper functions (pmc_read/write) to access PMC registers
> - lock/unlock helper functions (pmc_lock/unlock) to lock/unlock access to
> pmc registers
> - a new irq domain and its associated irq chip to request PMC specific
> interrupts (useful for clk prepare callbacks)
>
> The PMC unit is declared as a dt clk provider (CLK_OF_DECLARE), and every
> clk using this framework will declare a table of of_at91_clk_init_cb_t
> and add it to the pmc_clk_ids table.
>
> When the pmc dt clock setup function is called (by of_clk_init function),
> it triggers the registration of every supported child clk (those matching
> the definitions in pmc_clk_ids).
>
> This patch copies at91_pmc_base (memory mapping) and at91sam9_idle
> (function) from arch/arm/mach-at91/clock.c (which is not compiled if
> COMMON_CLK_AT91 is enabled).
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> drivers/clk/Makefile | 1 +
> drivers/clk/at91/Makefile | 5 +
> drivers/clk/at91/pmc.c | 283 +++++++++++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/pmc.h | 58 ++++++++++
> 4 files changed, 347 insertions(+)
> create mode 100644 drivers/clk/at91/Makefile
> create mode 100644 drivers/clk/at91/pmc.c
> create mode 100644 drivers/clk/at91/pmc.h
>
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 7b11106..28c2678 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -32,6 +32,7 @@ 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_AT91) += at91/
>
> obj-$(CONFIG_X86) += x86/
>
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> new file mode 100644
> index 0000000..1d4fb21
> --- /dev/null
> +++ b/drivers/clk/at91/Makefile
> @@ -0,0 +1,5 @@
> +#
> +# Makefile for at91 specific clk
> +#
> +
> +obj-y += pmc.o
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> new file mode 100644
> index 0000000..d92e46c
> --- /dev/null
> +++ b/drivers/clk/at91/pmc.c
> @@ -0,0 +1,283 @@
> +/*
> + * drivers/clk/at91/pmc.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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 <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/of_irq.h>
> +
> +#include <asm/proc-fns.h>
> +
> +#include "pmc.h"
> +
> +void __iomem *at91_pmc_base;
> +EXPORT_SYMBOL_GPL(at91_pmc_base);
> +
> +void at91sam9_idle(void)
> +{
> + at91_pmc_write(AT91_PMC_SCDR, AT91_PMC_PCK);
> + cpu_do_idle();
> +}
> +
> +static void pmc_irq_mask(struct irq_data *d)
> +{
> + struct at91_pmc *pmc = irq_data_get_irq_chip_data(d);
> +
> + pmc_write(pmc, AT91_PMC_IDR, 1 << d->hwirq);
> +}
> +
> +static void pmc_irq_unmask(struct irq_data *d)
> +{
> + struct at91_pmc *pmc = irq_data_get_irq_chip_data(d);
> +
> + pmc_write(pmc, AT91_PMC_IER, 1 << d->hwirq);
> +}
> +
> +static int pmc_irq_set_type(struct irq_data *d, unsigned type)
> +{
> + if (type != IRQ_TYPE_LEVEL_HIGH) {
> + pr_warn("PMC: type not supported (support only IRQ_TYPE_LEVEL_HIGH type)\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static struct irq_chip pmc_irq = {
> + .name = "PMC",
> + .irq_disable = pmc_irq_mask,
> + .irq_mask = pmc_irq_mask,
> + .irq_unmask = pmc_irq_unmask,
> + .irq_set_type = pmc_irq_set_type,
> +};
> +
> +static struct lock_class_key pmc_lock_class;
> +
> +static int pmc_irq_map(struct irq_domain *h, unsigned int virq,
> + irq_hw_number_t hw)
> +{
> + struct at91_pmc *pmc = h->host_data;
> +
> + irq_set_lockdep_class(virq, &pmc_lock_class);
> +
> + irq_set_chip_and_handler(virq, &pmc_irq,
> + handle_level_irq);
> + set_irq_flags(virq, IRQF_VALID);
> + irq_set_chip_data(virq, pmc);
> +
> + return 0;
> +}
> +
> +static int pmc_irq_domain_xlate(struct irq_domain *d,
> + struct device_node *ctrlr,
> + const u32 *intspec, unsigned int intsize,
> + irq_hw_number_t *out_hwirq,
> + unsigned int *out_type)
> +{
> + struct at91_pmc *pmc = d->host_data;
> + const struct at91_pmc_caps *caps = pmc->caps;
> +
> + if (WARN_ON(intsize < 1))
> + return -EINVAL;
> +
> + *out_hwirq = intspec[0];
> +
> + if (!(caps->available_irqs & (1 << *out_hwirq)))
> + return -EINVAL;
> +
> + *out_type = IRQ_TYPE_LEVEL_HIGH;
> +
> + return 0;
> +}
> +
> +static struct irq_domain_ops pmc_irq_ops = {
> + .map = pmc_irq_map,
> + .xlate = pmc_irq_domain_xlate,
> +};
> +
> +static irqreturn_t pmc_irq_handler(int irq, void *data)
> +{
> + struct at91_pmc *pmc = (struct at91_pmc *)data;
> + unsigned long sr;
> + int n;
> +
> + sr = pmc_read(pmc, AT91_PMC_SR) & pmc_read(pmc, AT91_PMC_IMR);
> + if (!sr)
> + return IRQ_NONE;
> +
> + for_each_set_bit(n, &sr, BITS_PER_LONG)
> + generic_handle_irq(irq_find_mapping(pmc->irqdomain, n));
> +
> + return IRQ_HANDLED;
> +}
> +
> +static const struct at91_pmc_caps at91rm9200_caps = {
> + .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB |
> + AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY |
> + AT91_PMC_PCK1RDY | AT91_PMC_PCK2RDY |
> + AT91_PMC_PCK3RDY,
> +};
> +
> +static const struct at91_pmc_caps at91sam9260_caps = {
> + .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB |
> + AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY |
> + AT91_PMC_PCK1RDY,
> +};
> +
> +static const struct at91_pmc_caps at91sam9g45_caps = {
> + .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY |
> + AT91_PMC_LOCKU | AT91_PMC_PCK0RDY |
> + AT91_PMC_PCK1RDY,
> +};
> +
> +static const struct at91_pmc_caps at91sam9n12_caps = {
> + .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB |
> + AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY |
> + AT91_PMC_PCK1RDY | AT91_PMC_MOSCSELS |
> + AT91_PMC_MOSCRCS | AT91_PMC_CFDEV,
> +};
> +
> +static const struct at91_pmc_caps at91sam9x5_caps = {
> + .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY |
> + AT91_PMC_LOCKU | AT91_PMC_PCK0RDY |
> + AT91_PMC_PCK1RDY | AT91_PMC_MOSCSELS |
> + AT91_PMC_MOSCRCS | AT91_PMC_CFDEV,
> +};
> +
> +static const struct at91_pmc_caps sama5d3_caps = {
> + .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY |
> + AT91_PMC_LOCKU | AT91_PMC_PCK0RDY |
> + AT91_PMC_PCK1RDY | AT91_PMC_PCK2RDY |
> + AT91_PMC_MOSCSELS | AT91_PMC_MOSCRCS |
> + AT91_PMC_CFDEV,
> +};
> +
> +static struct at91_pmc *__init at91_pmc_init(struct device_node *np,
> + void __iomem *regbase, int virq,
> + const struct at91_pmc_caps *caps)
> +{
> + struct at91_pmc *pmc;
> +
> + if (!regbase || !virq || !caps)
> + return NULL;
> +
> + at91_pmc_base = regbase;
> +
> + pmc = kzalloc(sizeof(*pmc), GFP_KERNEL);
> + if (!pmc)
> + return NULL;
> +
> + spin_lock_init(&pmc->lock);
> + pmc->regbase = regbase;
> + pmc->virq = virq;
> + pmc->caps = caps;
> +
> + pmc->irqdomain = irq_domain_add_linear(np, 32, &pmc_irq_ops, pmc);
> +
> + if (!pmc->irqdomain)
> + goto out_free_pmc;
> +
> + pmc_write(pmc, AT91_PMC_IDR, 0xffffffff);
> + if (request_irq(pmc->virq, pmc_irq_handler, IRQF_SHARED, "pmc", pmc))
> + goto out_remove_irqdomain;
> +
> + return pmc;
> +
> +out_remove_irqdomain:
> + irq_domain_remove(pmc->irqdomain);
> +out_free_pmc:
> + kfree(pmc);
> +
> + return NULL;
> +}
> +
> +static const struct of_device_id pmc_clk_ids[] __initdata = {
> + { /*sentinel*/ }
> +};
> +
> +static void __init of_at91_pmc_setup(struct device_node *np,
> + const struct at91_pmc_caps *caps)
> +{
> + struct at91_pmc *pmc;
> + struct device_node *childnp;
> + void (*clk_setup)(struct device_node *, struct at91_pmc *);
> + const struct of_device_id *clk_id;
> + void __iomem *regbase = of_iomap(np, 0);
> + int virq;
> +
> + if (!regbase)
> + return;
> +
> + virq = irq_of_parse_and_map(np, 0);
> + if (!virq)
> + return;
> +
> + pmc = at91_pmc_init(np, regbase, virq, caps);
> + if (!pmc)
> + return;
> + for_each_child_of_node(np, childnp) {
> + clk_id = of_match_node(pmc_clk_ids, childnp);
> + if (!clk_id)
> + continue;
> + clk_setup = clk_id->data;
> + clk_setup(childnp, pmc);
> + }
> +}
> +
> +static void __init of_at91rm9200_pmc_setup(struct device_node *np)
> +{
> + of_at91_pmc_setup(np, &at91rm9200_caps);
> +}
> +CLK_OF_DECLARE(at91rm9200_clk_main, "atmel,at91rm9200-pmc",
> + of_at91rm9200_pmc_setup);
> +
> +static void __init of_at91sam9260_pmc_setup(struct device_node *np)
> +{
> + of_at91_pmc_setup(np, &at91sam9260_caps);
> +}
> +CLK_OF_DECLARE(at91sam9260_clk_main, "atmel,at91sam9260-pmc",
> + of_at91sam9260_pmc_setup);
> +
> +static void __init of_at91sam9g45_pmc_setup(struct device_node *np)
> +{
> + of_at91_pmc_setup(np, &at91sam9g45_caps);
> +}
> +CLK_OF_DECLARE(at91sam9g45_clk_main, "atmel,at91sam9g45-pmc",
> + of_at91sam9g45_pmc_setup);
> +
> +static void __init of_at91sam9n12_pmc_setup(struct device_node *np)
> +{
> + of_at91_pmc_setup(np, &at91sam9n12_caps);
> +}
> +CLK_OF_DECLARE(at91sam9n12_clk_main, "atmel,at91sam9n12-pmc",
> + of_at91sam9n12_pmc_setup);
> +
> +static void __init of_at91sam9x5_pmc_setup(struct device_node *np)
> +{
> + of_at91_pmc_setup(np, &at91sam9x5_caps);
> +}
> +CLK_OF_DECLARE(at91sam9x5_clk_main, "atmel,at91sam9x5-pmc",
> + of_at91sam9x5_pmc_setup);
> +
> +static void __init of_sama5d3_pmc_setup(struct device_node *np)
> +{
> + of_at91_pmc_setup(np, &sama5d3_caps);
> +}
> +CLK_OF_DECLARE(sama5d3_clk_main, "atmel,sama5d3-pmc",
> + of_sama5d3_pmc_setup);
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> new file mode 100644
> index 0000000..b7e8397
> --- /dev/null
> +++ b/drivers/clk/at91/pmc.h
> @@ -0,0 +1,58 @@
> +/*
> + * drivers/clk/at91/pmc.h
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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.
> + */
> +
> +#ifndef __PMC_H_
> +#define __PMC_H_
> +
> +#include <linux/io.h>
> +#include <linux/irqdomain.h>
> +#include <linux/spinlock.h>
> +
> +struct clk_range {
> + unsigned long min;
> + unsigned long max;
> +};
> +
> +#define CLK_RANGE(MIN, MAX) {.min = MIN, .max = MAX,}
> +
> +struct at91_pmc_caps {
> + u32 available_irqs;
> +};
> +
> +struct at91_pmc {
> + void __iomem *regbase;
> + int virq;
> + spinlock_t lock;
> + const struct at91_pmc_caps *caps;
> + struct irq_domain *irqdomain;
> +};
> +
> +static inline void pmc_lock(struct at91_pmc *pmc)
> +{
> + spin_lock(&pmc->lock);
> +}
> +
> +static inline void pmc_unlock(struct at91_pmc *pmc)
> +{
> + spin_unlock(&pmc->lock);
> +}
> +
> +static inline u32 pmc_read(struct at91_pmc *pmc, int offset)
> +{
> + return readl_relaxed(pmc->regbase + offset);
> +}
> +
> +static inline void pmc_write(struct at91_pmc *pmc, int offset, u32 value)
> +{
> + return writel_relaxed(value, pmc->regbase + offset);
> +}
> +
> +#endif /* __PMC_H_ */
>


--
Nicolas Ferre

2013-10-17 15:47:52

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 04/17] clk: at91: add PMC macro file for dt definitions

On 11/10/2013 10:41, Boris BREZILLON :
> This patch adds a new macro file for PMC macros.
>
> This macro file includes the definitions of SR (status register) bit
> offsets and will be use to reference PMC irqs.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> include/dt-bindings/clk/at91.h | 22 ++++++++++++++++++++++
> 1 file changed, 22 insertions(+)
> create mode 100644 include/dt-bindings/clk/at91.h
>
> diff --git a/include/dt-bindings/clk/at91.h b/include/dt-bindings/clk/at91.h
> new file mode 100644
> index 0000000..0b4cb99
> --- /dev/null
> +++ b/include/dt-bindings/clk/at91.h
> @@ -0,0 +1,22 @@
> +/*
> + * This header provides constants for AT91 pmc status.
> + *
> + * The constants defined in this header are being used in dts.
> + *
> + * Licensed under GPLv2 or later.
> + */
> +
> +#ifndef _DT_BINDINGS_CLK_AT91_H
> +#define _DT_BINDINGS_CLK_AT91_H
> +
> +#define AT91_PMC_MOSCS 0 /* MOSCS Flag */
> +#define AT91_PMC_LOCKA 1 /* PLLA Lock */
> +#define AT91_PMC_LOCKB 2 /* PLLB Lock */
> +#define AT91_PMC_MCKRDY 3 /* Master Clock */
> +#define AT91_PMC_LOCKU 6 /* UPLL Lock */
> +#define AT91_PMC_PCKRDY(id) (8 + (id)) /* Programmable Clock */
> +#define AT91_PMC_MOSCSELS 16 /* Main Oscillator Selection */
> +#define AT91_PMC_MOSCRCS 17 /* Main On-Chip RC */
> +#define AT91_PMC_CFDEV 18 /* Clock Failure Detector Event */
> +
> +#endif
>


--
Nicolas Ferre

2013-10-17 15:59:02

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 05/17] clk: at91: add PMC main clock

On 11/10/2013 10:44, Boris BREZILLON :
> This patch adds new at91 main oscillator clock implementation using common
> clk framework.
>
> If rate is not provided during clock registration it is calculated using
> the slow clock (main clk parent in this case) rate and MCFR register.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> drivers/clk/at91/Makefile | 1 +
> drivers/clk/at91/clk-main.c | 189 +++++++++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/pmc.c | 5 ++
> drivers/clk/at91/pmc.h | 3 +
> 4 files changed, 198 insertions(+)
> create mode 100644 drivers/clk/at91/clk-main.c
>
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index 1d4fb21..44105bd 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -3,3 +3,4 @@
> #
>
> obj-y += pmc.o
> +obj-y += clk-main.o
> diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c
> new file mode 100644
> index 0000000..905ceff
> --- /dev/null
> +++ b/drivers/clk/at91/clk-main.c
> @@ -0,0 +1,189 @@
> +/*
> + * drivers/clk/at91/clk-main.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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 SLOW_CLOCK_FREQ 32768
> +#define MAINF_DIV 16
> +#define MAINFRDY_TIMEOUT (((MAINF_DIV + 1) * USEC_PER_SEC) / \
> + SLOW_CLOCK_FREQ)
> +#define MAINF_LOOP_MIN_WAIT (USEC_PER_SEC / SLOW_CLOCK_FREQ)
> +#define MAINF_LOOP_MAX_WAIT MAINFRDY_TIMEOUT
> +
> +struct clk_main {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + unsigned long rate;
> + unsigned int irq;
> + wait_queue_head_t wait;
> +};
> +
> +#define to_clk_main(hw) container_of(hw, struct clk_main, hw)
> +
> +static irqreturn_t clk_main_irq_handler(int irq, void *dev_id)
> +{
> + struct clk_main *clkmain = (struct clk_main *)dev_id;
> +
> + wake_up(&clkmain->wait);
> + disable_irq_nosync(clkmain->irq);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int clk_main_prepare(struct clk_hw *hw)
> +{
> + struct clk_main *clkmain = to_clk_main(hw);
> + struct at91_pmc *pmc = clkmain->pmc;
> + unsigned long halt_time, timeout;
> + u32 tmp;
> +
> + while (!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MOSCS)) {
> + enable_irq(clkmain->irq);
> + wait_event(clkmain->wait,
> + pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MOSCS);
> + }
> +
> + if (clkmain->rate)
> + return 0;
> +
> + timeout = jiffies + usecs_to_jiffies(MAINFRDY_TIMEOUT);
> + do {
> + halt_time = jiffies;
> + tmp = pmc_read(pmc, AT91_CKGR_MCFR);
> + if (tmp & AT91_PMC_MAINRDY)
> + return 0;
> + usleep_range(MAINF_LOOP_MIN_WAIT, MAINF_LOOP_MAX_WAIT);
> + } while (time_before(halt_time, timeout));
> +
> + return 0;
> +}
> +
> +static int clk_main_is_prepared(struct clk_hw *hw)
> +{
> + struct clk_main *clkmain = to_clk_main(hw);
> +
> + return !!(pmc_read(clkmain->pmc, AT91_PMC_SR) & AT91_PMC_MOSCS);
> +}
> +
> +static unsigned long clk_main_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + u32 tmp;
> + struct clk_main *clkmain = to_clk_main(hw);
> + struct at91_pmc *pmc = clkmain->pmc;
> +
> + if (clkmain->rate)
> + return clkmain->rate;
> +
> + tmp = pmc_read(pmc, AT91_CKGR_MCFR) & AT91_PMC_MAINF;
> + clkmain->rate = (tmp * parent_rate) / MAINF_DIV;
> +
> + return clkmain->rate;
> +}
> +
> +static const struct clk_ops main_ops = {
> + .prepare = clk_main_prepare,
> + .is_prepared = clk_main_is_prepared,
> + .recalc_rate = clk_main_recalc_rate,
> +};
> +
> +static struct clk * __init
> +at91_clk_register_main(struct at91_pmc *pmc,
> + unsigned int irq,
> + const char *name,
> + const char *parent_name,
> + unsigned long rate)
> +{
> + int ret;
> + struct clk_main *clkmain;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + if (!pmc || !irq || !name)
> + return ERR_PTR(-EINVAL);
> +
> + if (!rate && !parent_name)
> + return ERR_PTR(-EINVAL);
> +
> + clkmain = kzalloc(sizeof(*clkmain), GFP_KERNEL);
> + if (!clkmain)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &main_ops;
> + init.parent_names = parent_name ? &parent_name : NULL;
> + init.num_parents = parent_name ? 1 : 0;
> + init.flags = parent_name ? 0 : CLK_IS_ROOT;
> +
> + clkmain->hw.init = &init;
> + clkmain->rate = rate;
> + clkmain->pmc = pmc;
> + clkmain->irq = irq;
> + init_waitqueue_head(&clkmain->wait);
> + irq_set_status_flags(clkmain->irq, IRQ_NOAUTOEN);
> + ret = request_irq(clkmain->irq, clk_main_irq_handler,
> + IRQF_TRIGGER_HIGH, "clk-main", clkmain);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + clk = clk_register(NULL, &clkmain->hw);
> + if (IS_ERR(clk)) {
> + free_irq(clkmain->irq, clkmain);
> + kfree(clkmain);
> + }
> +
> + return clk;
> +}
> +
> +
> +
> +static void __init
> +of_at91_clk_main_setup(struct device_node *np, struct at91_pmc *pmc)
> +{
> + struct clk *clk;
> + unsigned int irq;
> + const char *parent_name;
> + const char *name = np->name;
> + u32 rate = 0;
> +
> + parent_name = of_clk_get_parent_name(np, 0);
> + of_property_read_string(np, "clock-output-names", &name);
> + of_property_read_u32(np, "clock-frequency", &rate);
> + irq = irq_of_parse_and_map(np, 0);
> + if (!irq)
> + return;
> +
> + clk = at91_clk_register_main(pmc, irq, name, parent_name, rate);
> + if (IS_ERR(clk))
> + return;
> +
> + of_clk_add_provider(np, of_clk_src_simple_get, clk);
> +}
> +
> +void __init of_at91rm9200_clk_main_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_main_setup(np, pmc);
> +}
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index d92e46c..ccfe506 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -208,6 +208,11 @@ out_free_pmc:
> }
>
> static const struct of_device_id pmc_clk_ids[] __initdata = {
> + /* Main clock */
> + {
> + .compatible = "atmel,at91rm9200-clk-main",
> + .data = of_at91rm9200_clk_main_setup
> + },
> { /*sentinel*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index b7e8397..294e230 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -55,4 +55,7 @@ static inline void pmc_write(struct at91_pmc *pmc, int offset, u32 value)
> return writel_relaxed(value, pmc->regbase + offset);
> }
>
> +extern void __init of_at91rm9200_clk_main_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +
> #endif /* __PMC_H_ */
>


--
Nicolas Ferre

2013-10-17 15:59:23

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 06/17] clk: at91: add PMC pll clocks

On 11/10/2013 10:48, Boris BREZILLON :
> This patch adds new at91 pll clock implementation using common clk framework.
>
> The pll clock layout describe the PLLX register layout.
> There are four pll clock layouts:
> - at91rm9200
> - at91sam9g20
> - at91sam9g45
> - sama5d3
>
> PLL clocks are given characteristics:
> - min/max clock source rate
> - ranges of valid clock output rates
> - values to set in out and icpll fields for each supported output range
>
> These characteristics are checked during rate change to avoid
> over/underclocking.
>
> These characteristics are described in atmel's SoC datasheet in
> "Electrical Characteristics" paragraph.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> drivers/clk/at91/Makefile | 2 +-
> drivers/clk/at91/clk-pll.c | 545 +++++++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/clk-plldiv.c | 137 +++++++++++
> drivers/clk/at91/pmc.c | 21 ++
> drivers/clk/at91/pmc.h | 11 +
> include/linux/clk/at91_pmc.h | 2 +
> 6 files changed, 717 insertions(+), 1 deletion(-)
> create mode 100644 drivers/clk/at91/clk-pll.c
> create mode 100644 drivers/clk/at91/clk-plldiv.c
>
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index 44105bd..902bbf1 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -3,4 +3,4 @@
> #
>
> obj-y += pmc.o
> -obj-y += clk-main.o
> +obj-y += clk-main.o clk-pll.o clk-plldiv.o
> diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c
> new file mode 100644
> index 0000000..44caddf
> --- /dev/null
> +++ b/drivers/clk/at91/clk-pll.c
> @@ -0,0 +1,545 @@
> +/*
> + * drivers/clk/at91/clk-pll.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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/of_irq.h>
> +#include <linux/io.h>
> +#include <linux/wait.h>
> +#include <linux/sched.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +
> +#include "pmc.h"
> +
> +#define PLL_STATUS_MASK(id) (1 << (1 + (id)))
> +#define PLL_REG(id) (AT91_CKGR_PLLAR + ((id) * 4))
> +#define PLL_DIV_MASK 0xff
> +#define PLL_DIV_MAX PLL_DIV_MASK
> +#define PLL_DIV(reg) ((reg) & PLL_DIV_MASK)
> +#define PLL_MUL(reg, layout) (((reg) >> (layout)->mul_shift) & \
> + (layout)->mul_mask)
> +#define PLL_ICPR_SHIFT(id) ((id) * 16)
> +#define PLL_ICPR_MASK(id) (0xffff << PLL_ICPR_SHIFT(id))
> +#define PLL_MAX_COUNT 0x3ff
> +#define PLL_COUNT_SHIFT 8
> +#define PLL_OUT_SHIFT 14
> +#define PLL_MAX_ID 1
> +
> +struct clk_pll_characteristics {
> + struct clk_range input;
> + int num_output;
> + struct clk_range *output;
> + u16 *icpll;
> + u8 *out;
> +};
> +
> +struct clk_pll_layout {
> + u32 pllr_mask;
> + u16 mul_mask;
> + u8 mul_shift;
> +};
> +
> +#define to_clk_pll(hw) container_of(hw, struct clk_pll, hw)
> +
> +struct clk_pll {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + unsigned int irq;
> + wait_queue_head_t wait;
> + u8 id;
> + u8 div;
> + u8 range;
> + u16 mul;
> + const struct clk_pll_layout *layout;
> + const struct clk_pll_characteristics *characteristics;
> +};
> +
> +static irqreturn_t clk_pll_irq_handler(int irq, void *dev_id)
> +{
> + struct clk_pll *pll = (struct clk_pll *)dev_id;
> +
> + wake_up(&pll->wait);
> + disable_irq_nosync(pll->irq);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int clk_pll_prepare(struct clk_hw *hw)
> +{
> + struct clk_pll *pll = to_clk_pll(hw);
> + struct at91_pmc *pmc = pll->pmc;
> + const struct clk_pll_layout *layout = pll->layout;
> + u8 id = pll->id;
> + u32 mask = PLL_STATUS_MASK(id);
> + int offset = PLL_REG(id);
> + u8 out = 0;
> + u32 pllr, icpr;
> + u8 div;
> + u16 mul;
> +
> + pllr = pmc_read(pmc, offset);
> + div = PLL_DIV(pllr);
> + mul = PLL_MUL(pllr, layout);
> +
> + if ((pmc_read(pmc, AT91_PMC_SR) & mask) &&
> + (div == pll->div && mul == pll->mul))
> + return 0;
> +
> + if (characteristics->out)
> + out = characteristics->out[pll->range];
> + if (characteristics->icpll) {
> + icpr = pmc_read(pmc, AT91_PMC_PLLICPR) & ~PLL_ICPR_MASK(id);
> + icpr |= (characteristics->icpll[pll->range] <<
> + PLL_ICPR_SHIFT(id));
> + pmc_write(pmc, AT91_PMC_PLLICPR, icpr);
> + }
> +
> + pllr &= ~layout->pllr_mask;
> + pllr |= layout->pllr_mask &
> + (pll->div | (PLL_MAX_COUNT << PLL_COUNT_SHIFT) |
> + (out << PLL_OUT_SHIFT) |
> + ((pll->mul & layout->mul_mask) << layout->mul_shift));
> + pmc_write(pmc, offset, pllr);
> +
> + while (!(pmc_read(pmc, AT91_PMC_SR) & mask)) {
> + enable_irq(pll->irq);
> + wait_event(pll->wait,
> + pmc_read(pmc, AT91_PMC_SR) & mask);
> + }
> +
> + return 0;
> +}
> +
> +static int clk_pll_is_ready(struct clk_hw *hw)
> +{
> + struct clk_pll *pll = to_clk_pll(hw);
> + struct at91_pmc *pmc = pll->pmc;
> +
> + return !!(pmc_read(pmc, AT91_PMC_SR) &
> + PLL_STATUS_MASK(pll->id));
> +}
> +
> +static void clk_pll_disable(struct clk_hw *hw)
> +{
> + struct clk_pll *pll = to_clk_pll(hw);
> + struct at91_pmc *pmc = pll->pmc;
> + const struct clk_pll_layout *layout = pll->layout;
> + int offset = PLL_REG(pll->id);
> + u32 tmp = pmc_read(pmc, offset) & ~(layout->pllr_mask);
> +
> + pmc_write(pmc, offset, tmp);
> +}
> +
> +static unsigned long clk_pll_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct clk_pll *pll = to_clk_pll(hw);
> + const struct clk_pll_layout *layout = pll->layout;
> + struct at91_pmc *pmc = pll->pmc;
> + int offset = PLL_REG(pll->id);
> + u32 tmp = pmc_read(pmc, offset) & layout->pllr_mask;
> + u8 div = PLL_DIV(tmp);
> + u16 mul = PLL_MUL(tmp, layout);
> + if (!div || !mul)
> + return 0;
> +
> + return (parent_rate * (mul + 1)) / div;
> +}
> +
> +static long clk_pll_get_best_div_mul(struct clk_pll *pll, unsigned long rate,
> + unsigned long parent_rate,
> + u32 *div, u32 *mul,
> + u32 *index) {
> + unsigned long maxrate;
> + unsigned long minrate;
> + unsigned long divrate;
> + unsigned long bestdiv = 1;
> + unsigned long bestmul;
> + unsigned long tmpdiv;
> + unsigned long roundup;
> + unsigned long rounddown;
> + unsigned long remainder;
> + unsigned long bestremainder;
> + unsigned long maxmul;
> + unsigned long maxdiv;
> + unsigned long mindiv;
> + int i = 0;
> + const struct clk_pll_layout *layout = pll->layout;
> + const struct clk_pll_characteristics *characteristics =
> + pll->characteristics;
> +
> + /* Minimum divider = 1 */
> + /* Maximum multiplier = max_mul */
> + maxmul = layout->mul_mask + 1;
> + maxrate = (parent_rate * maxmul) / 1;
> +
> + /* Maximum divider = max_div */
> + /* Minimum multiplier = 2 */
> + maxdiv = PLL_DIV_MAX;
> + minrate = (parent_rate * 2) / maxdiv;
> +
> + if (parent_rate < characteristics->input.min ||
> + parent_rate < characteristics->input.max)
> + return -ERANGE;
> +
> + if (parent_rate < minrate || parent_rate > maxrate)
> + return -ERANGE;
> +
> + for (i = 0; i < characteristics->num_output; i++) {
> + if (parent_rate >= characteristics->output[i].min &&
> + parent_rate <= characteristics->output[i].max)
> + break;
> + }
> +
> + if (i >= characteristics->num_output)
> + return -ERANGE;
> +
> + bestmul = rate / parent_rate;
> + rounddown = parent_rate % rate;
> + roundup = rate - rounddown;
> + bestremainder = roundup < rounddown ? roundup : rounddown;
> +
> + if (!bestremainder) {
> + if (div)
> + *div = bestdiv;
> + if (mul)
> + *mul = bestmul;
> + if (index)
> + *index = i;
> + return rate;
> + }
> +
> + maxdiv = 255 / (bestmul + 1);
> + if (parent_rate / maxdiv < characteristics->input.min)
> + maxdiv = parent_rate / characteristics->input.min;
> + mindiv = parent_rate / characteristics->input.max;
> + if (parent_rate % characteristics->input.max)
> + mindiv++;
> +
> + for (tmpdiv = mindiv; tmpdiv < maxdiv; tmpdiv++) {
> + divrate = parent_rate / tmpdiv;
> +
> + rounddown = rate % divrate;
> + roundup = divrate - rounddown;
> + remainder = roundup < rounddown ? roundup : rounddown;
> +
> + if (remainder < bestremainder) {
> + bestremainder = remainder;
> + bestmul = rate / divrate;
> + bestdiv = tmpdiv;
> + }
> +
> + if (!remainder)
> + break;
> + }
> +
> + rate = (parent_rate / bestdiv) * bestmul;
> +
> + if (div)
> + *div = bestdiv;
> + if (mul)
> + *mul = bestmul;
> + if (index)
> + *index = i;
> +
> + return rate;
> +}
> +
> +static long clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + struct clk_pll *pll = to_clk_pll(hw);
> +
> + return clk_pll_get_best_div_mul(pll, rate, *parent_rate,
> + NULL, NULL, NULL);
> +}
> +
> +static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct clk_pll *pll = to_clk_pll(hw);
> + struct at91_pmc *pmc = pll->pmc;
> + const struct clk_pll_layout *layout = pll->layout;
> + const struct clk_pll_characteristics *characteristics =
> + pll->characteristics;
> + u8 id = pll->id;
> + int offset = PLL_REG(id);
> + long ret;
> + u32 div;
> + u32 mul;
> + u32 index;
> + u32 tmp;
> + u8 out = 0;
> +
> + ret = clk_pll_get_best_div_mul(pll, rate, parent_rate,
> + &div, &mul, &index);
> + if (ret < 0)
> + return ret;
> +
> + pll->range = index;
> + pll->div = div;
> + pll->mul = mul;
> +
> + return 0;
> +}
> +
> +static const struct clk_ops pll_ops = {
> + .prepare = clk_pll_prepare,
> + .is_prepared = clk_pll_is_ready,
> + .disable = clk_pll_disable,
> + .is_enabled = clk_pll_is_ready,
> + .recalc_rate = clk_pll_recalc_rate,
> + .round_rate = clk_pll_round_rate,
> + .set_rate = clk_pll_set_rate,
> +};
> +
> +static struct clk * __init
> +at91_clk_register_pll(struct at91_pmc *pmc, unsigned int irq, const char *name,
> + const char *parent_name, u8 id,
> + const struct clk_pll_layout *layout,
> + const struct clk_pll_characteristics *characteristics)
> +{
> + struct clk_pll *pll;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> + int ret;
> + int offset = PLL_REG(id);
> + u32 tmp;
> +
> + if (id > PLL_MAX_ID)
> + return ERR_PTR(-EINVAL);
> +
> + pll = kzalloc(sizeof(*pll), GFP_KERNEL);
> + if (!pll)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &pll_ops;
> + init.parent_names = &parent_name;
> + init.num_parents = 1;
> + init.flags = CLK_SET_RATE_GATE;
> +
> + pll->id = id;
> + pll->hw.init = &init;
> + pll->layout = layout;
> + pll->characteristics = characteristics;
> + pll->pmc = pmc;
> + pll->irq = irq;
> + tmp = pmc_read(pmc, offset) & layout->pllr_mask;
> + pll->div = PLL_DIV(tmp);
> + pll->mul = PLL_MUL(tmp, layout);
> + init_waitqueue_head(&pll->wait);
> + irq_set_status_flags(pll->irq, IRQ_NOAUTOEN);
> + ret = request_irq(pll->irq, clk_pll_irq_handler, IRQF_TRIGGER_HIGH,
> + id ? "clk-pllb" : "clk-plla", pll);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + clk = clk_register(NULL, &pll->hw);
> + if (IS_ERR(clk))
> + kfree(pll);
> +
> + return clk;
> +}
> +
> +
> +static const struct clk_pll_layout at91rm9200_pll_layout = {
> + .pllr_mask = 0x7FFFFFF,
> + .mul_shift = 16,
> + .mul_mask = 0x7FF,
> +};
> +
> +static const struct clk_pll_layout at91sam9g45_pll_layout = {
> + .pllr_mask = 0xFFFFFF,
> + .mul_shift = 16,
> + .mul_mask = 0xFF,
> +};
> +
> +static const struct clk_pll_layout at91sam9g20_pllb_layout = {
> + .pllr_mask = 0x3FFFFF,
> + .mul_shift = 16,
> + .mul_mask = 0x3F,
> +};
> +
> +static const struct clk_pll_layout sama5d3_pll_layout = {
> + .pllr_mask = 0x1FFFFFF,
> + .mul_shift = 18,
> + .mul_mask = 0x7F,
> +};
> +
> +
> +static struct clk_pll_characteristics * __init
> +of_at91_clk_pll_get_characteristics(struct device_node *np)
> +{
> + int i;
> + int offset;
> + u32 tmp;
> + int num_output;
> + u32 num_cells;
> + struct clk_range input;
> + struct clk_range *output;
> + u8 *out = NULL;
> + u16 *icpll = NULL;
> + struct clk_pll_characteristics *characteristics;
> +
> + if (of_property_read_u32_index(np, "atmel,clk-input-range", 0, &tmp))
> + return NULL;
> + input.min = tmp;
> +
> + if (of_property_read_u32_index(np, "atmel,clk-input-range", 1, &tmp))
> + return NULL;
> + input.max = tmp;
> +
> + if (of_property_read_u32(np, "#atmel,pll-clk-output-range-cells",
> + &num_cells))
> + return NULL;
> +
> + if (num_cells < 2 || num_cells > 4)
> + return NULL;
> +
> + if (!of_get_property(np, "atmel,pll-clk-output-ranges", &tmp))
> + return NULL;
> + num_output = tmp / (sizeof(u32) * num_cells);
> +
> + characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL);
> + if (!characteristics)
> + return NULL;
> +
> + output = kzalloc(sizeof(*output) * num_output, GFP_KERNEL);
> + if (!output)
> + goto out_free_characteristics;
> +
> + if (num_cells > 2) {
> + out = kzalloc(sizeof(*out) * num_output, GFP_KERNEL);
> + if (!out)
> + goto out_free_output;
> + }
> +
> + if (num_cells > 3) {
> + icpll = kzalloc(sizeof(*icpll) * num_output, GFP_KERNEL);
> + if (!icpll)
> + goto out_free_output;
> + }
> +
> + for (i = 0; i < num_output; i++) {
> + offset = i * num_cells;
> + if (of_property_read_u32_index(np,
> + "atmel,pll-clk-output-ranges",
> + offset, &tmp))
> + goto out_free_output;
> + output[i].min = tmp;
> + if (of_property_read_u32_index(np,
> + "atmel,pll-clk-output-ranges",
> + offset + 1, &tmp))
> + goto out_free_output;
> + output[i].max = tmp;
> +
> + if (num_cells == 2)
> + continue;
> +
> + if (of_property_read_u32_index(np,
> + "atmel,pll-clk-output-ranges",
> + offset + 2, &tmp))
> + goto out_free_output;
> + out[i] = tmp;
> +
> + if (num_cells == 3)
> + continue;
> +
> + if (of_property_read_u32_index(np,
> + "atmel,pll-clk-output-ranges",
> + offset + 3, &tmp))
> + goto out_free_output;
> + icpll[i] = tmp;
> + }
> +
> + characteristics->input = input;
> + characteristics->num_output = num_output;
> + characteristics->output = output;
> + characteristics->out = out;
> + characteristics->icpll = icpll;
> + return characteristics;
> +
> +out_free_output:
> + kfree(icpll);
> + kfree(out);
> + kfree(output);
> +out_free_characteristics:
> + kfree(characteristics);
> + return NULL;
> +}
> +
> +static void __init
> +of_at91_clk_pll_setup(struct device_node *np, struct at91_pmc *pmc,
> + const struct clk_pll_layout *layout)
> +{
> + u32 id;
> + unsigned int irq;
> + struct clk *clk;
> + const char *parent_name;
> + const char *name = np->name;
> + struct clk_pll_characteristics *characteristics;
> +
> + if (of_property_read_u32(np, "atmel,clk-id", &id))
> + return;
> +
> + parent_name = of_clk_get_parent_name(np, 0);
> +
> + of_property_read_string(np, "clock-output-names", &name);
> +
> + characteristics = of_at91_clk_pll_get_characteristics(np);
> + if (!characteristics)
> + return;
> +
> + irq = irq_of_parse_and_map(np, 0);
> + if (!irq)
> + return;
> +
> + clk = at91_clk_register_pll(pmc, irq, name, parent_name, id, layout,
> + characteristics);
> + if (IS_ERR(clk))
> + goto out_free_characteristics;
> +
> + of_clk_add_provider(np, of_clk_src_simple_get, clk);
> + return;
> +
> +out_free_characteristics:
> + kfree(characteristics);
> +}
> +
> +void __init of_at91rm9200_clk_pll_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_pll_setup(np, pmc, &at91rm9200_pll_layout);
> +}
> +
> +void __init of_at91sam9g45_clk_pll_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_pll_setup(np, pmc, &at91sam9g45_pll_layout);
> +}
> +
> +void __init of_at91sam9g20_clk_pllb_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_pll_setup(np, pmc, &at91sam9g20_pllb_layout);
> +}
> +
> +void __init of_sama5d3_clk_pll_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_pll_setup(np, pmc, &sama5d3_pll_layout);
> +}
> diff --git a/drivers/clk/at91/clk-plldiv.c b/drivers/clk/at91/clk-plldiv.c
> new file mode 100644
> index 0000000..2bbb22b
> --- /dev/null
> +++ b/drivers/clk/at91/clk-plldiv.c
> @@ -0,0 +1,137 @@
> +/*
> + * drivers/clk/at91/clk-plldiv.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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 to_clk_plldiv(hw) container_of(hw, struct clk_plldiv, hw)
> +
> +struct clk_plldiv {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> +};
> +
> +static unsigned long clk_plldiv_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct clk_plldiv *plldiv = to_clk_plldiv(hw);
> + struct at91_pmc *pmc = plldiv->pmc;
> +
> + if (pmc_read(pmc, AT91_PMC_MCKR) & AT91_PMC_PLLADIV2)
> + return parent_rate / 2;
> +
> + return parent_rate;
> +}
> +
> +static long clk_plldiv_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_plldiv_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct clk_plldiv *plldiv = to_clk_plldiv(hw);
> + struct at91_pmc *pmc = plldiv->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_PLLADIV2;
> + if ((parent_rate / 2) == rate)
> + tmp |= AT91_PMC_PLLADIV2;
> + pmc_write(pmc, AT91_PMC_MCKR, tmp);
> + pmc_unlock(pmc);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops plldiv_ops = {
> + .recalc_rate = clk_plldiv_recalc_rate,
> + .round_rate = clk_plldiv_round_rate,
> + .set_rate = clk_plldiv_set_rate,
> +};
> +
> +static struct clk * __init
> +at91_clk_register_plldiv(struct at91_pmc *pmc, const char *name,
> + const char *parent_name)
> +{
> + struct clk_plldiv *plldiv;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + plldiv = kzalloc(sizeof(*plldiv), GFP_KERNEL);
> + if (!plldiv)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &plldiv_ops;
> + init.parent_names = parent_name ? &parent_name : NULL;
> + init.num_parents = parent_name ? 1 : 0;
> + init.flags = CLK_SET_RATE_GATE;
> +
> + plldiv->hw.init = &init;
> + plldiv->pmc = pmc;
> +
> + clk = clk_register(NULL, &plldiv->hw);
> +
> + if (IS_ERR(clk))
> + kfree(plldiv);
> +
> + return clk;
> +}
> +
> +static void __init
> +of_at91_clk_plldiv_setup(struct device_node *np, struct at91_pmc *pmc)
> +{
> + struct clk *clk;
> + const char *parent_name;
> + const char *name = np->name;
> +
> + parent_name = of_clk_get_parent_name(np, 0);
> +
> + of_property_read_string(np, "clock-output-names", &name);
> +
> + clk = at91_clk_register_plldiv(pmc, name, parent_name);
> +
> + if (IS_ERR(clk))
> + return;
> +
> + of_clk_add_provider(np, of_clk_src_simple_get, clk);
> + return;
> +}
> +
> +void __init of_at91sam9x5_clk_plldiv_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_plldiv_setup(np, pmc);
> +}
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index ccfe506..c882e97 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -213,6 +213,27 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
> .compatible = "atmel,at91rm9200-clk-main",
> .data = of_at91rm9200_clk_main_setup
> },
> + /* PLL clocks */
> + {
> + .compatible = "atmel,at91rm9200-clk-pll",
> + .data = of_at91rm9200_clk_pll_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9g45-clk-pll",
> + .data = of_at91sam9g45_clk_pll_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9g20-clk-pllb",
> + .data = of_at91sam9g20_clk_pllb_setup,
> + },
> + {
> + .compatible = "atmel,sama5d3-clk-pll",
> + .data = of_sama5d3_clk_pll_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9x5-clk-plldiv",
> + .data = of_at91sam9x5_clk_plldiv_setup,
> + },
> { /*sentinel*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 294e230..35ccbbc 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -58,4 +58,15 @@ static inline void pmc_write(struct at91_pmc *pmc, int offset, u32 value)
> extern void __init of_at91rm9200_clk_main_setup(struct device_node *np,
> struct at91_pmc *pmc);
>
> +extern void __init of_at91rm9200_clk_pll_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9g45_clk_pll_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9g20_clk_pllb_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_sama5d3_clk_pll_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9x5_clk_plldiv_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +
> #endif /* __PMC_H_ */
> diff --git a/include/linux/clk/at91_pmc.h b/include/linux/clk/at91_pmc.h
> index 00c45b3..a6911eb 100644
> --- a/include/linux/clk/at91_pmc.h
> +++ b/include/linux/clk/at91_pmc.h
> @@ -164,6 +164,8 @@ extern void __iomem *at91_pmc_base;
> #define AT91_PMC_CFDEV (1 << 18) /* Clock Failure Detector Event [some SAM9] */
> #define AT91_PMC_IMR 0x6c /* Interrupt Mask Register */
>
> +#define AT91_PMC_PLLICPR 0x80 /* PLL Charge Pump Current Register */
> +
> #define AT91_PMC_PROT 0xe4 /* Write Protect Mode Register [some SAM9] */
> #define AT91_PMC_WPEN (0x1 << 0) /* Write Protect Enable */
> #define AT91_PMC_WPKEY (0xffffff << 8) /* Write Protect Key */
>


--
Nicolas Ferre

2013-10-17 15:59:40

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 07/17] clk: at91: add PMC master clock

On 11/10/2013 10:51, Boris BREZILLON :
> This patch adds new at91 master clock implementation using common clk
> framework.
>
> The master clock layout describe the MCKR register layout.
> There are 2 master clock layouts:
> - at91rm9200
> - at91sam9x5
>
> Master clocks are given characteristics:
> - min/max clock output rate
>
> These characteristics are checked during rate change to avoid
> over/underclocking.
>
> These characteristics are described in atmel's SoC datasheet in
> "Electrical Characteristics" paragraph.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> drivers/clk/at91/Makefile | 2 +-
> drivers/clk/at91/clk-master.c | 278 +++++++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/clk-pll.c | 10 +-
> drivers/clk/at91/pmc.c | 9 ++
> drivers/clk/at91/pmc.h | 5 +
> 5 files changed, 295 insertions(+), 9 deletions(-)
> create mode 100644 drivers/clk/at91/clk-master.c
>
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index 902bbf1..e28fb2b 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -3,4 +3,4 @@
> #
>
> obj-y += pmc.o
> -obj-y += clk-main.o clk-pll.o clk-plldiv.o
> +obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
> diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
> new file mode 100644
> index 0000000..58af23d
> --- /dev/null
> +++ b/drivers/clk/at91/clk-master.c
> @@ -0,0 +1,278 @@
> +/*
> + * drivers/clk/at91/clk-master.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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/of_irq.h>
> +#include <linux/io.h>
> +#include <linux/wait.h>
> +#include <linux/sched.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +
> +#include "pmc.h"
> +
> +#define MASTER_SOURCE_MAX 4
> +
> +#define MASTER_PRES_MASK 0x7
> +#define MASTER_PRES_MAX MASTER_PRES_MASK
> +#define MASTER_DIV_SHIFT 8
> +#define MASTER_DIV_MASK 0x3
> +
> +struct clk_master_characteristics {
> + struct clk_range output;
> + u32 divisors[4];
> + u8 have_div3_pres;
> +};
> +
> +struct clk_master_layout {
> + u32 mask;
> + u8 pres_shift;
> +};
> +
> +#define to_clk_master(hw) container_of(hw, struct clk_master, hw)
> +
> +struct clk_master {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + unsigned int irq;
> + wait_queue_head_t wait;
> + const struct clk_master_layout *layout;
> + const struct clk_master_characteristics *characteristics;
> +};
> +
> +static irqreturn_t clk_master_irq_handler(int irq, void *dev_id)
> +{
> + struct clk_master *master = (struct clk_master *)dev_id;
> +
> + wake_up(&master->wait);
> + disable_irq_nosync(master->irq);
> +
> + return IRQ_HANDLED;
> +}
> +static int clk_master_prepare(struct clk_hw *hw)
> +{
> + struct clk_master *master = to_clk_master(hw);
> + struct at91_pmc *pmc = master->pmc;
> +
> + while (!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY)) {
> + enable_irq(master->irq);
> + wait_event(master->wait,
> + pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY);
> + }
> +
> + return 0;
> +}
> +
> +static int clk_master_is_prepared(struct clk_hw *hw)
> +{
> + struct clk_master *master = to_clk_master(hw);
> +
> + return !!(pmc_read(master->pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY);
> +}
> +
> +static unsigned long clk_master_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + u8 pres;
> + u8 div;
> + unsigned long rate = parent_rate;
> + struct clk_master *master = to_clk_master(hw);
> + struct at91_pmc *pmc = master->pmc;
> + const struct clk_master_layout *layout = master->layout;
> + const struct clk_master_characteristics *characteristics =
> + master->characteristics;
> + u32 tmp;
> +
> + pmc_lock(pmc);
> + tmp = pmc_read(pmc, AT91_PMC_MCKR) & layout->mask;
> + pmc_unlock(pmc);
> +
> + pres = (tmp >> layout->pres_shift) & MASTER_PRES_MASK;
> + div = (tmp >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
> +
> + if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
> + rate /= 3;
> + else
> + rate >>= pres;
> +
> + rate /= characteristics->divisors[div];
> +
> + if (rate < characteristics->output.min)
> + pr_warn("master clk is underclocked");
> + else if (rate > characteristics->output.max)
> + pr_warn("master clk is overclocked");
> +
> + return rate;
> +}
> +
> +static u8 clk_master_get_parent(struct clk_hw *hw)
> +{
> + struct clk_master *master = to_clk_master(hw);
> + struct at91_pmc *pmc = master->pmc;
> +
> + return pmc_read(pmc, AT91_PMC_MCKR) & AT91_PMC_CSS;
> +}
> +
> +static const struct clk_ops master_ops = {
> + .prepare = clk_master_prepare,
> + .is_prepared = clk_master_is_prepared,
> + .recalc_rate = clk_master_recalc_rate,
> + .get_parent = clk_master_get_parent,
> +};
> +
> +static struct clk * __init
> +at91_clk_register_master(struct at91_pmc *pmc, unsigned int irq,
> + const char *name, int num_parents,
> + const char **parent_names,
> + const struct clk_master_layout *layout,
> + const struct clk_master_characteristics *characteristics)
> +{
> + int ret;
> + struct clk_master *master;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + if (!pmc || !irq || !name || !num_parents || !parent_names)
> + return ERR_PTR(-EINVAL);
> +
> + master = kzalloc(sizeof(*master), GFP_KERNEL);
> + if (!master)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &master_ops;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + init.flags = 0;
> +
> + master->hw.init = &init;
> + master->layout = layout;
> + master->characteristics = characteristics;
> + master->pmc = pmc;
> + master->irq = irq;
> + init_waitqueue_head(&master->wait);
> + irq_set_status_flags(master->irq, IRQ_NOAUTOEN);
> + ret = request_irq(master->irq, clk_master_irq_handler,
> + IRQF_TRIGGER_HIGH, "clk-master", master);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + clk = clk_register(NULL, &master->hw);
> + if (IS_ERR(clk))
> + kfree(master);
> +
> + return clk;
> +}
> +
> +
> +static const struct clk_master_layout at91rm9200_master_layout = {
> + .mask = 0x31F,
> + .pres_shift = 2,
> +};
> +
> +static const struct clk_master_layout at91sam9x5_master_layout = {
> + .mask = 0x373,
> + .pres_shift = 4,
> +};
> +
> +
> +static struct clk_master_characteristics * __init
> +of_at91_clk_master_get_characteristics(struct device_node *np)
> +{
> + u32 tmp;
> + struct clk_master_characteristics *characteristics;
> +
> + characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL);
> + if (!characteristics)
> + return NULL;
> +
> + if (of_property_read_u32_index(np, "atmel,clk-output-range", 0, &tmp))
> + goto out_free_characteristics;
> + characteristics->output.min = tmp;
> +
> + if (of_property_read_u32_index(np, "atmel,clk-output-range", 1, &tmp))
> + goto out_free_characteristics;
> + characteristics->output.max = tmp;
> +
> + of_property_read_u32_array(np, "atmel,clk-divisors",
> + characteristics->divisors, 4);
> +
> + characteristics->have_div3_pres =
> + of_property_read_bool(np, "atmel,master-clk-have-div3-pres");
> +
> + return characteristics;
> +
> +out_free_characteristics:
> + kfree(characteristics);
> + return NULL;
> +}
> +
> +static void __init
> +of_at91_clk_master_setup(struct device_node *np, struct at91_pmc *pmc,
> + const struct clk_master_layout *layout)
> +{
> + struct clk *clk;
> + int num_parents;
> + int i;
> + unsigned int irq;
> + const char *parent_names[MASTER_SOURCE_MAX];
> + const char *name = np->name;
> + struct clk_master_characteristics *characteristics;
> +
> + num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells");
> + if (num_parents <= 0 || num_parents > MASTER_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);
> +
> + characteristics = of_at91_clk_master_get_characteristics(np);
> + if (!characteristics)
> + return;
> +
> + irq = irq_of_parse_and_map(np, 0);
> + if (!irq)
> + return;
> +
> + clk = at91_clk_register_master(pmc, irq, name, num_parents,
> + parent_names, layout,
> + characteristics);
> + if (IS_ERR(clk))
> + goto out_free_characteristics;
> +
> + of_clk_add_provider(np, of_clk_src_simple_get, clk);
> + return;
> +
> +out_free_characteristics:
> + kfree(characteristics);
> +}
> +
> +void __init of_at91rm9200_clk_master_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_master_setup(np, pmc, &at91rm9200_master_layout);
> +}
> +
> +void __init of_at91sam9x5_clk_master_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_master_setup(np, pmc, &at91sam9x5_master_layout);
> +}
> diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c
> index 44caddf..351adc5 100644
> --- a/drivers/clk/at91/clk-pll.c
> +++ b/drivers/clk/at91/clk-pll.c
> @@ -82,6 +82,8 @@ static int clk_pll_prepare(struct clk_hw *hw)
> struct clk_pll *pll = to_clk_pll(hw);
> struct at91_pmc *pmc = pll->pmc;
> const struct clk_pll_layout *layout = pll->layout;
> + const struct clk_pll_characteristics *characteristics =
> + pll->characteristics;
> u8 id = pll->id;
> u32 mask = PLL_STATUS_MASK(id);
> int offset = PLL_REG(id);
> @@ -271,18 +273,10 @@ static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> unsigned long parent_rate)
> {
> struct clk_pll *pll = to_clk_pll(hw);
> - struct at91_pmc *pmc = pll->pmc;
> - const struct clk_pll_layout *layout = pll->layout;
> - const struct clk_pll_characteristics *characteristics =
> - pll->characteristics;
> - u8 id = pll->id;
> - int offset = PLL_REG(id);
> long ret;
> u32 div;
> u32 mul;
> u32 index;
> - u32 tmp;
> - u8 out = 0;
>
> ret = clk_pll_get_best_div_mul(pll, rate, parent_rate,
> &div, &mul, &index);
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index c882e97..6f08879 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -234,6 +234,15 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
> .compatible = "atmel,at91sam9x5-clk-plldiv",
> .data = of_at91sam9x5_clk_plldiv_setup,
> },
> + /* Master clock */
> + {
> + .compatible = "atmel,at91rm9200-clk-master",
> + .data = of_at91rm9200_clk_master_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9x5-clk-master",
> + .data = of_at91sam9x5_clk_master_setup,
> + },
> { /*sentinel*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 35ccbbc..2e1dcb8 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -69,4 +69,9 @@ extern void __init of_sama5d3_clk_pll_setup(struct device_node *np,
> extern void __init of_at91sam9x5_clk_plldiv_setup(struct device_node *np,
> struct at91_pmc *pmc);
>
> +extern void __init of_at91rm9200_clk_master_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9x5_clk_master_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +
> #endif /* __PMC_H_ */
>


--
Nicolas Ferre

2013-10-17 15:59:59

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 08/17] clk: at91: add PMC system clocks

On 11/10/2013 11:41, Boris BREZILLON :
> This patch adds new at91 system clock implementation using common clk
> framework.
>
> Some peripherals need to enable a "system" clock in order to work properly.
> Each system clock is given an id based on the bit position in SCER/SCDR
> registers.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> drivers/clk/at91/Makefile | 1 +
> drivers/clk/at91/clk-system.c | 193 +++++++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/pmc.c | 5 ++
> drivers/clk/at91/pmc.h | 3 +
> 4 files changed, 202 insertions(+)
> create mode 100644 drivers/clk/at91/clk-system.c
>
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index e28fb2b..c2b7068 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -4,3 +4,4 @@
>
> obj-y += pmc.o
> obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
> +obj-y += clk-system.o
> diff --git a/drivers/clk/at91/clk-system.c b/drivers/clk/at91/clk-system.c
> new file mode 100644
> index 0000000..603b4b1
> --- /dev/null
> +++ b/drivers/clk/at91/clk-system.c
> @@ -0,0 +1,193 @@
> +/*
> + * drivers/clk/at91/clk-system.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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 SYSTEM_MAX_ID 31
> +
> +#define to_clk_system(hw) container_of(hw, struct clk_system, hw)
> +struct clk_system {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + u8 id;
> +};
> +
> +static int clk_system_enable(struct clk_hw *hw)
> +{
> + struct clk_system *sys = to_clk_system(hw);
> + struct at91_pmc *pmc = sys->pmc;
> +
> + pmc_write(pmc, AT91_PMC_SCER, 1 << sys->id);
> + return 0;
> +}
> +
> +static void clk_system_disable(struct clk_hw *hw)
> +{
> + struct clk_system *sys = to_clk_system(hw);
> + struct at91_pmc *pmc = sys->pmc;
> +
> + pmc_write(pmc, AT91_PMC_SCDR, 1 << sys->id);
> +}
> +
> +static int clk_system_is_enabled(struct clk_hw *hw)
> +{
> + struct clk_system *sys = to_clk_system(hw);
> + struct at91_pmc *pmc = sys->pmc;
> +
> + return !!(pmc_read(pmc, AT91_PMC_SCSR) & (1 << sys->id));
> +}
> +
> +static const struct clk_ops system_ops = {
> + .enable = clk_system_enable,
> + .disable = clk_system_disable,
> + .is_enabled = clk_system_is_enabled,
> +};
> +
> +static struct clk * __init
> +at91_clk_register_system(struct at91_pmc *pmc, const char *name,
> + const char *parent_name, u8 id)
> +{
> + struct clk_system *sys;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + if (!parent_name || id > SYSTEM_MAX_ID)
> + return ERR_PTR(-EINVAL);
> +
> + sys = kzalloc(sizeof(*sys), GFP_KERNEL);
> + if (!sys)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &system_ops;
> + init.parent_names = &parent_name;
> + init.num_parents = 1;
> + /*
> + * CLK_IGNORE_UNUSED is used to avoid ddrck switch off.
> + * TODO : we should implement a driver supporting at91 ddr controller
> + * (see drivers/memory) which would request and enable the ddrck clock.
> + * When this is done we will be able to remove CLK_IGNORE_UNUSED flag.
> + */
> + init.flags = CLK_IGNORE_UNUSED;
> +
> + sys->id = id;
> + sys->hw.init = &init;
> + sys->pmc = pmc;
> +
> + clk = clk_register(NULL, &sys->hw);
> + if (IS_ERR(clk))
> + kfree(sys);
> +
> + return clk;
> +}
> +
> +struct clk_system_data {
> + struct clk **clks;
> + u8 *ids;
> + unsigned int clk_num;
> +};
> +
> +static struct clk * __init
> +of_clk_src_system_get(struct of_phandle_args *clkspec, void *data)
> +{
> + struct clk_system_data *clk_data = data;
> + unsigned int id = clkspec->args[0];
> + int i;
> +
> + if (id > SYSTEM_MAX_ID)
> + goto err;
> +
> + for (i = 0; i < clk_data->clk_num; i++) {
> + if (clk_data->ids[i] == id)
> + return clk_data->clks[i];
> + }
> +
> +err:
> + pr_err("%s: invalid clock id %d\n", __func__, id);
> + return ERR_PTR(-EINVAL);
> +}
> +
> +static void __init
> +of_at91_clk_sys_setup(struct device_node *np, struct at91_pmc *pmc)
> +{
> + int i;
> + int num;
> + u32 id;
> + struct clk *clk;
> + u8 *ids;
> + struct clk **clks;
> + struct clk_system_data *clktab;
> + const char *name;
> + struct device_node *sysclknp;
> + const char *parent_name;
> +
> + num = of_get_child_count(np);
> + if (num > (SYSTEM_MAX_ID + 1))
> + return;
> +
> + clktab = kzalloc(sizeof(*clktab), GFP_KERNEL);
> + if (!clktab)
> + return;
> +
> + ids = kzalloc(num * sizeof(*ids), GFP_KERNEL);
> + if (!ids)
> + goto out_free_clktab;
> +
> + clks = kzalloc(num * sizeof(*clks), GFP_KERNEL);
> + if (!clks)
> + goto out_free_ids;
> +
> + i = 0;
> + for_each_child_of_node(np, sysclknp) {
> + name = sysclknp->name;
> +
> + if (of_property_read_u32(sysclknp, "atmel,clk-id", &id))
> + goto out_free_clks;
> +
> + parent_name = of_clk_get_parent_name(sysclknp, 0);
> +
> + clk = at91_clk_register_system(pmc, name, parent_name, id);
> + if (IS_ERR(clk))
> + goto out_free_clks;
> +
> + clks[i] = clk;
> + ids[i] = id;
> +
> + i++;
> + }
> +
> + clktab->clk_num = num;
> + clktab->clks = clks;
> + clktab->ids = ids;
> + of_clk_add_provider(np, of_clk_src_system_get, clktab);
> + return;
> +
> +out_free_clks:
> + kfree(clks);
> +out_free_ids:
> + kfree(ids);
> +out_free_clktab:
> + kfree(clktab);
> +}
> +
> +void __init of_at91rm9200_clk_sys_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_sys_setup(np, pmc);
> +}
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index 6f08879..ce76ba6 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -243,6 +243,11 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
> .compatible = "atmel,at91sam9x5-clk-master",
> .data = of_at91sam9x5_clk_master_setup,
> },
> + /* System clocks */
> + {
> + .compatible = "atmel,at91rm9200-clk-system",
> + .data = of_at91rm9200_clk_sys_setup,
> + },
> { /*sentinel*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 2e1dcb8..f88ef41 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -74,4 +74,7 @@ extern void __init of_at91rm9200_clk_master_setup(struct device_node *np,
> extern void __init of_at91sam9x5_clk_master_setup(struct device_node *np,
> struct at91_pmc *pmc);
>
> +extern void __init of_at91rm9200_clk_sys_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +
> #endif /* __PMC_H_ */
>


--
Nicolas Ferre

2013-10-17 16:02:55

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 09/17] clk: at91: add PMC peripheral clocks

On 11/10/2013 11:44, Boris BREZILLON :
> This patch adds new at91 peripheral clock implementation using common clk
> framework.
>
> Almost all peripherals provided by at91 SoCs need a clock to work properly.
> This clock is enabled/disabled using PCER/PCDR resgisters.
>
> Each peripheral is given an id (see atmel's datasheets) which is used to
> define and reference peripheral clocks.
>
> Some new SoCs (at91sam9x5 and sama5d3) provide a new register (PCR) where you
> can configure the peripheral clock as a division of the master clock.
> This will help reducing the peripherals power comsumption.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> drivers/clk/at91/Makefile | 2 +-
> drivers/clk/at91/clk-peripheral.c | 407 +++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/pmc.c | 9 +
> drivers/clk/at91/pmc.h | 5 +
> 4 files changed, 422 insertions(+), 1 deletion(-)
> create mode 100644 drivers/clk/at91/clk-peripheral.c
>
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index c2b7068..04deba3 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -4,4 +4,4 @@
>
> obj-y += pmc.o
> obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
> -obj-y += clk-system.o
> +obj-y += clk-system.o clk-peripheral.o
> diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c
> new file mode 100644
> index 0000000..2dce2e2
> --- /dev/null
> +++ b/drivers/clk/at91/clk-peripheral.c
> @@ -0,0 +1,407 @@
> +/*
> + * drivers/clk/at91/clk-peripheral.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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 PERIPHERAL_MAX 64
> +
> +#define PERIPHERAL_AT91RM9200 0
> +#define PERIPHERAL_AT91SAM9X5 1
> +
> +#define PERIPHERAL_ID_MIN 2
> +#define PERIPHERAL_ID_MAX 31
> +#define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX))
> +
> +#define PERIPHERAL_RSHIFT_MASK 0x3
> +#define PERIPHERAL_RSHIFT(val) (((val) >> 16) & PERIPHERAL_RSHIFT_MASK)
> +
> +struct clk_peripheral {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + u32 id;
> +};
> +
> +#define to_clk_peripheral(hw) container_of(hw, struct clk_peripheral, hw)
> +
> +struct clk_sam9x5_peripheral {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + u32 id;
> + u8 div;
> + u8 have_div_support;
> +};
> +
> +#define to_clk_sam9x5_peripheral(hw) \
> + container_of(hw, struct clk_sam9x5_peripheral, hw)
> +
> +static int clk_peripheral_enable(struct clk_hw *hw)
> +{
> + struct clk_peripheral *periph = to_clk_peripheral(hw);
> + struct at91_pmc *pmc = periph->pmc;
> + int offset = AT91_PMC_PCER;
> + u32 id = periph->id;
> +
> + if (id < PERIPHERAL_ID_MIN)
> + return 0;
> + if (id > PERIPHERAL_ID_MAX)
> + offset = AT91_PMC_PCER1;
> + pmc_write(pmc, offset, PERIPHERAL_MASK(id));
> + return 0;
> +}
> +
> +static void clk_peripheral_disable(struct clk_hw *hw)
> +{
> + struct clk_peripheral *periph = to_clk_peripheral(hw);
> + struct at91_pmc *pmc = periph->pmc;
> + int offset = AT91_PMC_PCDR;
> + u32 id = periph->id;
> +
> + if (id < PERIPHERAL_ID_MIN)
> + return;
> + if (id > PERIPHERAL_ID_MAX)
> + offset = AT91_PMC_PCDR1;
> + pmc_write(pmc, offset, PERIPHERAL_MASK(id));
> +}
> +
> +static int clk_peripheral_is_enabled(struct clk_hw *hw)
> +{
> + struct clk_peripheral *periph = to_clk_peripheral(hw);
> + struct at91_pmc *pmc = periph->pmc;
> + int offset = AT91_PMC_PCSR;
> + u32 id = periph->id;
> +
> + if (id < PERIPHERAL_ID_MIN)
> + return 1;
> + if (id > PERIPHERAL_ID_MAX)
> + offset = AT91_PMC_PCSR1;
> + return !!(pmc_read(pmc, offset) & PERIPHERAL_MASK(id));
> +}
> +
> +static const struct clk_ops peripheral_ops = {
> + .enable = clk_peripheral_enable,
> + .disable = clk_peripheral_disable,
> + .is_enabled = clk_peripheral_is_enabled,
> +};
> +
> +static struct clk * __init
> +at91_clk_register_peripheral(struct at91_pmc *pmc, const char *name,
> + const char *parent_name, u32 id)
> +{
> + struct clk_peripheral *periph;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + if (!pmc || !name || !parent_name || id > PERIPHERAL_ID_MAX)
> + return ERR_PTR(-EINVAL);
> +
> + periph = kzalloc(sizeof(*periph), GFP_KERNEL);
> + if (!periph)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &peripheral_ops;
> + init.parent_names = (parent_name ? &parent_name : NULL);
> + init.num_parents = (parent_name ? 1 : 0);
> + init.flags = 0;
> +
> + periph->id = id;
> + periph->hw.init = &init;
> + periph->pmc = pmc;
> +
> + clk = clk_register(NULL, &periph->hw);
> + if (IS_ERR(clk))
> + kfree(periph);
> +
> + return clk;
> +}
> +
> +static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
> +{
> + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> + struct at91_pmc *pmc = periph->pmc;
> +
> + if (periph->id < PERIPHERAL_ID_MIN)
> + return 0;
> + pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID) |
> + AT91_PMC_PCR_CMD |
> + AT91_PMC_PCR_DIV(periph->div) |
> + AT91_PMC_PCR_EN);
> + return 0;
> +}
> +
> +static void clk_sam9x5_peripheral_disable(struct clk_hw *hw)
> +{
> + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> + struct at91_pmc *pmc = periph->pmc;
> +
> + if (periph->id < PERIPHERAL_ID_MIN)
> + return;
> +
> + pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID) |
> + AT91_PMC_PCR_CMD);
> +}
> +
> +static int clk_sam9x5_peripheral_is_enabled(struct clk_hw *hw)
> +{
> + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> + struct at91_pmc *pmc = periph->pmc;
> + int ret;
> +
> + if (periph->id < PERIPHERAL_ID_MIN)
> + return 1;
> + pmc_lock(pmc);
> + pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID));
> + ret = !!(pmc_read(pmc, AT91_PMC_PCR) & AT91_PMC_PCR_EN);
> + pmc_unlock(pmc);
> +
> + return ret;
> +}
> +
> +static unsigned long
> +clk_sam9x5_peripheral_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> + struct at91_pmc *pmc = periph->pmc;
> + u32 shift;
> +
> + if (periph->id < PERIPHERAL_ID_MIN || !periph->have_div_support)
> + return parent_rate;
> + pmc_lock(pmc);
> + pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID));
> + shift = PERIPHERAL_RSHIFT(pmc_read(pmc, AT91_PMC_PCR));
> + pmc_unlock(pmc);
> + return parent_rate >> shift;
> +}
> +
> +static long clk_sam9x5_peripheral_round_rate(struct clk_hw *hw,
> + unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + int shift;
> + unsigned long best_rate;
> + unsigned long best_diff;
> + unsigned long cur_rate;
> + unsigned long cur_diff;
> + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> +
> + if (periph->id < 2 || !periph->have_div_support)
> + return *parent_rate;
> + if (rate >= *parent_rate)
> + return rate;
> + best_diff = *parent_rate - rate;
> + best_rate = *parent_rate;
> + for (shift = 1; shift < 4; shift++) {
> + cur_rate = *parent_rate >> shift;
> + if (cur_rate < rate)
> + cur_diff = rate - cur_rate;
> + else
> + cur_diff = cur_rate - rate;
> + if (cur_diff < best_diff) {
> + best_diff = cur_diff;
> + best_rate = cur_rate;
> + }
> + if (!best_diff || cur_rate < rate)
> + break;
> + }
> + return best_rate;
> +}
> +
> +static int clk_sam9x5_peripheral_set_rate(struct clk_hw *hw,
> + unsigned long rate,
> + unsigned long parent_rate)
> +{
> + int shift;
> + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> + if (periph->id < 2 || !periph->have_div_support) {
> + if (parent_rate == rate)
> + return 0;
> + else
> + return -EINVAL;
> + }
> +
> + for (shift = 0; shift < 4; shift++) {
> + if (parent_rate >> shift == rate) {
> + periph->div = shift;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +static const struct clk_ops sam9x5_peripheral_ops = {
> + .enable = clk_sam9x5_peripheral_enable,
> + .disable = clk_sam9x5_peripheral_disable,
> + .is_enabled = clk_sam9x5_peripheral_is_enabled,
> + .recalc_rate = clk_sam9x5_peripheral_recalc_rate,
> + .round_rate = clk_sam9x5_peripheral_round_rate,
> + .set_rate = clk_sam9x5_peripheral_set_rate,
> +};
> +
> +static struct clk * __init
> +at91_clk_register_sam9x5_peripheral(struct at91_pmc *pmc, const char *name,
> + const char *parent_name, u32 id,
> + u32 default_div)
> +{
> + struct clk_sam9x5_peripheral *periph;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + if (!pmc || !name || !parent_name)
> + return ERR_PTR(-EINVAL);
> +
> + periph = kzalloc(sizeof(*periph), GFP_KERNEL);
> + if (!periph)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &sam9x5_peripheral_ops;
> + init.parent_names = (parent_name ? &parent_name : NULL);
> + init.num_parents = (parent_name ? 1 : 0);
> + init.flags = CLK_SET_RATE_GATE;
> +
> + periph->id = id;
> + periph->hw.init = &init;
> + periph->div = default_div;
> + periph->pmc = pmc;
> +
> + clk = clk_register(NULL, &periph->hw);
> + if (IS_ERR(clk))
> + kfree(periph);
> +
> + return clk;
> +}
> +
> +struct clk_periph_data {
> + struct clk **clks;
> + u8 *ids;
> + unsigned int clk_num;
> +};
> +
> +static struct clk * __init
> +of_clk_src_periph_get(struct of_phandle_args *clkspec, void *data)
> +{
> + struct clk_periph_data *clk_data = data;
> + unsigned int id = clkspec->args[0];
> + int i;
> +
> + if (id >= PERIPHERAL_MAX)
> + goto err;
> +
> + for (i = 0; i < clk_data->clk_num; i++) {
> + if (clk_data->ids[i] == id)
> + return clk_data->clks[i];
> + }
> +
> +err:
> + pr_err("%s: invalid clock id %d\n", __func__, id);
> + return ERR_PTR(-EINVAL);
> +}
> +
> +static void __init
> +of_at91_clk_periph_setup(struct device_node *np, struct at91_pmc *pmc, u8 type)
> +{
> + int num;
> + int i;
> + u32 id;
> + struct clk *clk;
> + const char *parent_name;
> + const char *name;
> + u32 divisor;
> + struct clk **clks;
> + u8 *ids;
> + struct clk_periph_data *clktab;
> + struct device_node *periphclknp;
> +
> + parent_name = of_clk_get_parent_name(np, 0);
> + if (!parent_name)
> + return;
> +
> + num = of_get_child_count(np);
> + if (!num || num > PERIPHERAL_MAX)
> + return;
> +
> + clktab = kzalloc(sizeof(*clktab), GFP_KERNEL);
> + if (!clktab)
> + return;
> +
> + ids = kzalloc(num * sizeof(*ids), GFP_KERNEL);
> + if (!ids)
> + goto out_free_clktab;
> +
> + clks = kzalloc(num * sizeof(*clks), GFP_KERNEL);
> + if (!clks)
> + goto out_free_ids;
> +
> + i = 0;
> + for_each_child_of_node(np, periphclknp) {
> + name = periphclknp->name;
> +
> + if (of_property_read_u32(periphclknp, "atmel,clk-id", &id))
> + goto out_free_clks;
> + if (id >= PERIPHERAL_MAX)
> + goto out_free_clks;
> +
> + if (type == PERIPHERAL_AT91RM9200) {
> + clk = at91_clk_register_peripheral(pmc, name,
> + parent_name, id);
> + } else {
> + if (of_property_read_u32(periphclknp,
> + "atmel,clk-default-divisor",
> + &divisor))
> + divisor = 0;
> +
> + clk = at91_clk_register_sam9x5_peripheral(pmc, name,
> + parent_name,
> + id,
> + divisor);
> + }
> + if (IS_ERR(clk))
> + goto out_free_clks;
> +
> + clks[i] = clk;
> + ids[i++] = id;
> + }
> +
> + clktab->clk_num = num;
> + clktab->clks = clks;
> + clktab->ids = ids;
> + of_clk_add_provider(np, of_clk_src_periph_get, clktab);
> + return;
> +
> +out_free_clks:
> + kfree(clks);
> +out_free_ids:
> + kfree(ids);
> +out_free_clktab:
> + kfree(clktab);
> +}
> +
> +void __init of_at91rm9200_clk_periph_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_periph_setup(np, pmc, PERIPHERAL_AT91RM9200);
> +}
> +
> +void __init of_at91sam9x5_clk_periph_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_periph_setup(np, pmc, PERIPHERAL_AT91SAM9X5);
> +}
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index ce76ba6..8e27e92 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -248,6 +248,15 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
> .compatible = "atmel,at91rm9200-clk-system",
> .data = of_at91rm9200_clk_sys_setup,
> },
> + /* Peripheral clocks */
> + {
> + .compatible = "atmel,at91rm9200-clk-peripheral",
> + .data = of_at91rm9200_clk_periph_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9x5-clk-peripheral",
> + .data = of_at91sam9x5_clk_periph_setup,
> + },
> { /*sentinel*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index f88ef41..08c30fb 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -77,4 +77,9 @@ extern void __init of_at91sam9x5_clk_master_setup(struct device_node *np,
> extern void __init of_at91rm9200_clk_sys_setup(struct device_node *np,
> struct at91_pmc *pmc);
>
> +extern void __init of_at91rm9200_clk_periph_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9x5_clk_periph_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +
> #endif /* __PMC_H_ */
>


--
Nicolas Ferre

2013-10-17 16:03:24

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 10/17] clk: at91: add peripheral clk macros for peripheral clk dt bindings

On 11/10/2013 11:53, Boris BREZILLON :
> This patch adds the peripheral divisors macros (for sam9x5 compatible IPs)
> which will be used by peripheral clk dt definitions.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> include/dt-bindings/clk/at91.h | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/include/dt-bindings/clk/at91.h b/include/dt-bindings/clk/at91.h
> index 0b4cb99..a3b07ca 100644
> --- a/include/dt-bindings/clk/at91.h
> +++ b/include/dt-bindings/clk/at91.h
> @@ -19,4 +19,10 @@
> #define AT91_PMC_MOSCRCS 17 /* Main On-Chip RC */
> #define AT91_PMC_CFDEV 18 /* Clock Failure Detector Event */
>
> +/* sam9x5 peripheral divisors */
> +#define AT91SAM9X5_PERIPH_CLK_DIV1 0
> +#define AT91SAM9X5_PERIPH_CLK_DIV2 1
> +#define AT91SAM9X5_PERIPH_CLK_DIV4 2
> +#define AT91SAM9X5_PERIPH_CLK_DIV8 3
> +
> #endif
>


--
Nicolas Ferre

2013-10-17 16:09:01

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 11/17] clk: at91: add PMC programmable clocks

On 11/10/2013 11:57, Boris BREZILLON :
> This patch adds new at91 programmable clocks implementation using common clk
> framework.
> A programmable clock is a clock which can be exported on a given pin to clock
> external devices.
> Each programmable clock is given an id (from 0 to 8).
> The number of available programmable clocks depends on the SoC you're using.
> Programmable clock driver only implements the clock setting (clock rate and
> parent setting). It must be chained to a system clock in order to
> enable/disable the generated clock.
> The PCKX pins used to output the clock signals must be assigned to the
> appropriate peripheral (see atmel's datasheets).
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> drivers/clk/at91/Makefile | 2 +
> drivers/clk/at91/clk-programmable.c | 423 +++++++++++++++++++++++++++++++++++
> drivers/clk/at91/pmc.c | 15 ++
> drivers/clk/at91/pmc.h | 9 +
> 4 files changed, 449 insertions(+)
> create mode 100644 drivers/clk/at91/clk-programmable.c
>
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index 04deba3..3873b62 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -5,3 +5,5 @@
> obj-y += pmc.o
> obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
> obj-y += clk-system.o clk-peripheral.o
> +
> +obj-$(CONFIG_AT91_PROGRAMMABLE_CLOCKS) += clk-programmable.o
> diff --git a/drivers/clk/at91/clk-programmable.c b/drivers/clk/at91/clk-programmable.c
> new file mode 100644
> index 0000000..b34f80b
> --- /dev/null
> +++ b/drivers/clk/at91/clk-programmable.c
> @@ -0,0 +1,423 @@
> +/*
> + * drivers/clk/at91/clk-programmable.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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/of_irq.h>
> +#include <linux/io.h>
> +#include <linux/wait.h>
> +#include <linux/sched.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +
> +#include "pmc.h"
> +
> +#define PROG_SOURCE_MAX 5
> +#define PROG_ID_MAX 7
> +
> +#define PROG_STATUS_MASK(id) (1 << ((id) + 8))
> +#define PROG_PRES_MASK 0x7
> +#define PROG_MAX_RM9200_CSS 3
> +
> +struct clk_programmable_layout {
> + u8 pres_shift;
> + u8 css_mask;
> + u8 have_slck_mck;
> +};
> +
> +struct clk_programmable {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + unsigned int irq;
> + wait_queue_head_t wait;
> + u8 id;
> + u8 css;
> + u8 pres;
> + u8 slckmck;
> + const struct clk_programmable_layout *layout;
> +};
> +
> +#define to_clk_programmable(hw) container_of(hw, struct clk_programmable, hw)
> +
> +
> +static irqreturn_t clk_programmable_irq_handler(int irq, void *dev_id)
> +{
> + struct clk_programmable *prog = (struct clk_programmable *)dev_id;
> +
> + wake_up(&prog->wait);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int clk_programmable_prepare(struct clk_hw *hw)
> +{
> + u32 tmp;
> + struct clk_programmable *prog = to_clk_programmable(hw);
> + struct at91_pmc *pmc = prog->pmc;
> + const struct clk_programmable_layout *layout = prog->layout;
> + u8 id = prog->id;
> + u32 mask = PROG_STATUS_MASK(id);
> +
> + tmp = prog->css | (prog->pres << layout->pres_shift);
> + if (layout->have_slck_mck && prog->slckmck)
> + tmp |= AT91_PMC_CSSMCK_MCK;
> +
> + pmc_write(pmc, AT91_PMC_PCKR(id), tmp);
> +
> + while (!(pmc_read(pmc, AT91_PMC_SR) & mask))
> + wait_event(prog->wait, pmc_read(pmc, AT91_PMC_SR) & mask);
> +
> + return 0;
> +}
> +
> +static int clk_programmable_is_ready(struct clk_hw *hw)
> +{
> + struct clk_programmable *prog = to_clk_programmable(hw);
> + struct at91_pmc *pmc = prog->pmc;
> +
> + return !!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_PCKR(prog->id));
> +}
> +
> +static unsigned long clk_programmable_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + u32 tmp;
> + struct clk_programmable *prog = to_clk_programmable(hw);
> + struct at91_pmc *pmc = prog->pmc;
> + const struct clk_programmable_layout *layout = prog->layout;
> +
> + tmp = pmc_read(pmc, AT91_PMC_PCKR(prog->id));
> + prog->pres = (tmp >> layout->pres_shift) & PROG_PRES_MASK;
> +
> + return parent_rate >> prog->pres;
> +}
> +
> +static long clk_programmable_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + unsigned long best_rate = *parent_rate;
> + unsigned long best_diff;
> + unsigned long new_diff;
> + unsigned long cur_rate;
> + int shift = shift;
> +
> + if (rate > *parent_rate)
> + return *parent_rate;
> + else
> + best_diff = *parent_rate - rate;
> +
> + if (!best_diff)
> + return best_rate;
> +
> + for (shift = 1; shift < PROG_PRES_MASK; shift++) {
> + cur_rate = *parent_rate >> shift;
> +
> + if (cur_rate > rate)
> + new_diff = cur_rate - rate;
> + else
> + new_diff = rate - cur_rate;
> +
> + if (!new_diff)
> + return cur_rate;
> +
> + if (new_diff < best_diff) {
> + best_diff = new_diff;
> + best_rate = cur_rate;
> + }
> +
> + if (rate > cur_rate)
> + break;
> + }
> +
> + return best_rate;
> +}
> +
> +static int clk_programmable_set_parent(struct clk_hw *hw, u8 index)
> +{
> + struct clk_programmable *prog = to_clk_programmable(hw);
> + const struct clk_programmable_layout *layout = prog->layout;
> + if (index > layout->css_mask) {
> + if (index > PROG_MAX_RM9200_CSS && layout->have_slck_mck) {
> + prog->css = 0;
> + prog->slckmck = 1;
> + return 0;
> + } else {
> + return -EINVAL;
> + }
> + }
> +
> + prog->css = index;
> + return 0;
> +}
> +
> +static u8 clk_programmable_get_parent(struct clk_hw *hw)
> +{
> + u32 tmp;
> + u8 ret;
> + struct clk_programmable *prog = to_clk_programmable(hw);
> + struct at91_pmc *pmc = prog->pmc;
> + const struct clk_programmable_layout *layout = prog->layout;
> +
> + tmp = pmc_read(pmc, AT91_PMC_PCKR(prog->id));
> + prog->css = tmp & layout->css_mask;
> + ret = prog->css;
> + if (layout->have_slck_mck) {
> + prog->slckmck = !!(tmp & AT91_PMC_CSSMCK_MCK);
> + if (prog->slckmck && !ret)
> + ret = PROG_MAX_RM9200_CSS + 1;
> + }
> +
> + return ret;
> +}
> +
> +static int clk_programmable_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct clk_programmable *prog = to_clk_programmable(hw);
> + unsigned long best_rate = parent_rate;
> + unsigned long best_diff;
> + unsigned long new_diff;
> + unsigned long cur_rate;
> + int shift = 0;
> +
> + if (rate > parent_rate)
> + return parent_rate;
> + else
> + best_diff = parent_rate - rate;
> +
> + if (!best_diff) {
> + prog->pres = shift;
> + return 0;
> + }
> +
> + for (shift = 1; shift < PROG_PRES_MASK; shift++) {
> + cur_rate = parent_rate >> shift;
> +
> + if (cur_rate > rate)
> + new_diff = cur_rate - rate;
> + else
> + new_diff = rate - cur_rate;
> +
> + if (!new_diff)
> + break;
> +
> + if (new_diff < best_diff) {
> + best_diff = new_diff;
> + best_rate = cur_rate;
> + }
> +
> + if (rate > cur_rate)
> + break;
> + }
> +
> + prog->pres = shift;
> + return 0;
> +}
> +
> +static const struct clk_ops programmable_ops = {
> + .prepare = clk_programmable_prepare,
> + .is_prepared = clk_programmable_is_ready,
> + .recalc_rate = clk_programmable_recalc_rate,
> + .round_rate = clk_programmable_round_rate,
> + .get_parent = clk_programmable_get_parent,
> + .set_parent = clk_programmable_set_parent,
> + .set_rate = clk_programmable_set_rate,
> +};
> +
> +static struct clk * __init
> +at91_clk_register_programmable(struct at91_pmc *pmc, unsigned int irq,
> + const char *name, const char **parent_names,
> + u8 num_parents, u8 id,
> + const struct clk_programmable_layout *layout)
> +{
> + int ret;
> + struct clk_programmable *prog;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> + char irq_name[11];
> +
> + if (id > PROG_ID_MAX)
> + return ERR_PTR(-EINVAL);
> +
> + prog = kzalloc(sizeof(*prog), GFP_KERNEL);
> + if (!prog)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &programmable_ops;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
> +
> + prog->id = id;
> + prog->layout = layout;
> + prog->hw.init = &init;
> + prog->pmc = pmc;
> + prog->irq = irq;
> + init_waitqueue_head(&prog->wait);
> + irq_set_status_flags(prog->irq, IRQ_NOAUTOEN);
> + snprintf(irq_name, sizeof(irq_name), "clk-prog%d", id);
> + ret = request_irq(prog->irq, clk_programmable_irq_handler,
> + IRQF_TRIGGER_HIGH, irq_name, prog);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + clk = clk_register(NULL, &prog->hw);
> + if (IS_ERR(clk))
> + kfree(prog);
> +
> + return clk;
> +}
> +
> +static const struct clk_programmable_layout at91rm9200_programmable_layout = {
> + .pres_shift = 2,
> + .css_mask = 0x3,
> + .have_slck_mck = 0,
> +};
> +
> +static const struct clk_programmable_layout at91sam9g45_programmable_layout = {
> + .pres_shift = 2,
> + .css_mask = 0x3,
> + .have_slck_mck = 1,
> +};
> +
> +static const struct clk_programmable_layout at91sam9x5_programmable_layout = {
> + .pres_shift = 4,
> + .css_mask = 0x7,
> + .have_slck_mck = 0,
> +};
> +
> +struct clk_prog_data {
> + struct clk **clks;
> + u8 *ids;
> + unsigned int clk_num;
> +};
> +
> +static struct clk * __init
> +of_clk_src_periph_get(struct of_phandle_args *clkspec, void *data)
> +{
> + struct clk_prog_data *clk_data = data;
> + unsigned int id = clkspec->args[0];
> + int i;
> +
> + if (id > PROG_ID_MAX)
> + goto err;
> +
> + for (i = 0; i < clk_data->clk_num; i++) {
> + if (clk_data->ids[i] == id)
> + return clk_data->clks[i];
> + }
> +
> +err:
> + pr_err("%s: invalid clock id %d\n", __func__, id);
> + return ERR_PTR(-EINVAL);
> +}
> +
> +static void __init
> +of_at91_clk_prog_setup(struct device_node *np, struct at91_pmc *pmc,
> + const struct clk_programmable_layout *layout)
> +{
> + int num;
> + u32 id;
> + int i;
> + unsigned int irq;
> + struct clk *clk;
> + int num_parents;
> + u8 *ids;
> + struct clk **clks;
> + struct clk_prog_data *clktab;
> + const char *parent_names[PROG_SOURCE_MAX];
> + const char *name;
> + struct device_node *progclknp;
> +
> + num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells");
> + if (num_parents <= 0 || num_parents > PROG_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;
> + }
> +
> + num = of_get_child_count(np);
> + if (!num || num > (PROG_ID_MAX + 1))
> + return;
> +
> + clktab = kzalloc(sizeof(*clktab), GFP_KERNEL);
> + if (!clktab)
> + return;
> +
> + ids = kzalloc(num * sizeof(*ids), GFP_KERNEL);
> + if (!ids)
> + goto out_free_clktab;
> +
> + clks = kzalloc(num * sizeof(*clks), GFP_KERNEL);
> + if (!clks)
> + goto out_free_ids;
> +
> + i = 0;
> + for_each_child_of_node(np, progclknp) {
> + name = progclknp->name;
> +
> + if (of_property_read_u32(progclknp, "atmel,clk-id", &id))
> + goto out_free_clks;
> +
> + irq = irq_of_parse_and_map(np, 0);
> + if (!irq)
> + goto out_free_clks;
> +
> + clk = at91_clk_register_programmable(pmc, irq, name,
> + parent_names, num_parents,
> + id, layout);
> + if (IS_ERR(clk))
> + goto out_free_clks;
> +
> + clks[i] = clk;
> + ids[i++] = id;
> + }
> +
> + clktab->clk_num = num;
> + clktab->clks = clks;
> + clktab->ids = ids;
> + of_clk_add_provider(np, of_clk_src_periph_get, clktab);
> + return;
> +
> +out_free_clks:
> + kfree(clks);
> +out_free_ids:
> + kfree(ids);
> +out_free_clktab:
> + kfree(clktab);
> +}
> +
> +
> +void __init of_at91rm9200_clk_prog_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_prog_setup(np, pmc, &at91rm9200_programmable_layout);
> +}
> +
> +void __init of_at91sam9g45_clk_prog_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_prog_setup(np, pmc, &at91sam9g45_programmable_layout);
> +}
> +
> +void __init of_at91sam9x5_clk_prog_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_prog_setup(np, pmc, &at91sam9x5_programmable_layout);
> +}
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index 8e27e92..5b8a959e 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -257,6 +257,21 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
> .compatible = "atmel,at91sam9x5-clk-peripheral",
> .data = of_at91sam9x5_clk_periph_setup,
> },
> + /* Programmable clocks */
> +#if defined(CONFIG_AT91_PROGRAMMABLE_CLOCKS)
> + {
> + .compatible = "atmel,at91rm9200-clk-programmable",
> + .data = of_at91rm9200_clk_prog_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9g45-clk-programmable",
> + .data = of_at91sam9g45_clk_prog_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9x5-clk-programmable",
> + .data = of_at91sam9x5_clk_prog_setup,
> + },
> +#endif
> { /*sentinel*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 08c30fb..5f20dc1 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -82,4 +82,13 @@ extern void __init of_at91rm9200_clk_periph_setup(struct device_node *np,
> extern void __init of_at91sam9x5_clk_periph_setup(struct device_node *np,
> struct at91_pmc *pmc);
>
> +#if defined(CONFIG_AT91_PROGRAMMABLE_CLOCKS)
> +extern void __init of_at91rm9200_clk_prog_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9g45_clk_prog_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9x5_clk_prog_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +#endif
> +
> #endif /* __PMC_H_ */
>


--
Nicolas Ferre

2013-10-17 16:10:13

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 12/17] clk: at91: add PMC utmi clock

On 11/10/2013 12:22, Boris BREZILLON :
> This adds new at91 utmi clock implementation using common clk framework.
>
> This clock is a pll with a fixed factor (x40).
> It is used as a source for usb clock.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> arch/arm/mach-at91/Kconfig | 7 ++
> drivers/clk/at91/Makefile | 1 +
> drivers/clk/at91/clk-utmi.c | 162 +++++++++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/pmc.c | 7 ++
> drivers/clk/at91/pmc.h | 5 ++
> 5 files changed, 182 insertions(+)
> create mode 100644 drivers/clk/at91/clk-utmi.c
>
> diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
> index 85b53a4..6ad37da 100644
> --- a/arch/arm/mach-at91/Kconfig
> +++ b/arch/arm/mach-at91/Kconfig
> @@ -1,5 +1,8 @@
> if ARCH_AT91
>
> +config HAVE_AT91_UTMI
> + bool
> +
> config HAVE_AT91_DBGU0
> bool
>
> @@ -78,6 +81,7 @@ config SOC_SAMA5D3
> select HAVE_FB_ATMEL
> select HAVE_AT91_DBGU1
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_UTMI
> help
> Select this if you are using one of Atmel's SAMA5D3 family SoC.
> This support covers SAMA5D31, SAMA5D33, SAMA5D34, SAMA5D35.
> @@ -124,6 +128,7 @@ config SOC_AT91SAM9RL
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_UTMI
>
> config SOC_AT91SAM9G45
> bool "AT91SAM9G45 or AT91SAM9M10 families"
> @@ -131,6 +136,7 @@ config SOC_AT91SAM9G45
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_UTMI
> help
> Select this if you are using one of Atmel's AT91SAM9G45 family SoC.
> This support covers AT91SAM9G45, AT91SAM9G46, AT91SAM9M10 and AT91SAM9M11.
> @@ -141,6 +147,7 @@ config SOC_AT91SAM9X5
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_UTMI
> help
> Select this if you are using one of Atmel's AT91SAM9x5 family SoC.
> This means that your SAM9 name finishes with a '5' (except if it is
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index 3873b62..a824883 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -7,3 +7,4 @@ obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o
> obj-y += clk-system.o clk-peripheral.o
>
> obj-$(CONFIG_AT91_PROGRAMMABLE_CLOCKS) += clk-programmable.o
> +obj-$(CONFIG_HAVE_AT91_UTMI) += clk-utmi.o
> diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c
> new file mode 100644
> index 0000000..9a133df
> --- /dev/null
> +++ b/drivers/clk/at91/clk-utmi.c
> @@ -0,0 +1,162 @@
> +/*
> + * drivers/clk/at91/clk-utmi.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/io.h>
> +#include <linux/sched.h>
> +#include <linux/wait.h>
> +
> +#include "pmc.h"
> +
> +#define UTMI_FIXED_MUL 40
> +
> +struct clk_utmi {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + unsigned int irq;
> + wait_queue_head_t wait;
> +};
> +
> +#define to_clk_utmi(hw) container_of(hw, struct clk_utmi, hw)
> +
> +static irqreturn_t clk_utmi_irq_handler(int irq, void *dev_id)
> +{
> + struct clk_utmi *utmi = (struct clk_utmi *)dev_id;
> +
> + wake_up(&utmi->wait);
> + disable_irq_nosync(utmi->irq);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int clk_utmi_prepare(struct clk_hw *hw)
> +{
> + struct clk_utmi *utmi = to_clk_utmi(hw);
> + struct at91_pmc *pmc = utmi->pmc;
> + u32 tmp = at91_pmc_read(AT91_CKGR_UCKR) | AT91_PMC_UPLLEN |
> + AT91_PMC_UPLLCOUNT | AT91_PMC_BIASEN;
> +
> + pmc_write(pmc, AT91_CKGR_UCKR, tmp);
> +
> + while (!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_LOCKU)) {
> + enable_irq(utmi->irq);
> + wait_event(utmi->wait,
> + pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_LOCKU);
> + }
> +
> + return 0;
> +}
> +
> +static int clk_utmi_is_ready(struct clk_hw *hw)
> +{
> + struct clk_utmi *utmi = to_clk_utmi(hw);
> + struct at91_pmc *pmc = utmi->pmc;
> +
> + return !!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_LOCKU);
> +}
> +
> +static void clk_utmi_disable(struct clk_hw *hw)
> +{
> + struct clk_utmi *utmi = to_clk_utmi(hw);
> + struct at91_pmc *pmc = utmi->pmc;
> + u32 tmp = at91_pmc_read(AT91_CKGR_UCKR) & ~AT91_PMC_UPLLEN;
> +
> + pmc_write(pmc, AT91_CKGR_UCKR, tmp);
> +}
> +
> +static unsigned long clk_utmi_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + /* UTMI clk is a fixed clk multiplier */
> + return parent_rate * UTMI_FIXED_MUL;
> +}
> +
> +static const struct clk_ops utmi_ops = {
> + .prepare = clk_utmi_prepare,
> + .is_prepared = clk_utmi_is_ready,
> + .disable = clk_utmi_disable,
> + .is_enabled = clk_utmi_is_ready,
> + .recalc_rate = clk_utmi_recalc_rate,
> +};
> +
> +static struct clk * __init
> +at91_clk_register_utmi(struct at91_pmc *pmc, unsigned int irq,
> + const char *name, const char *parent_name)
> +{
> + int ret;
> + struct clk_utmi *utmi;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + utmi = kzalloc(sizeof(*utmi), GFP_KERNEL);
> + if (!utmi)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &utmi_ops;
> + init.parent_names = parent_name ? &parent_name : NULL;
> + init.num_parents = parent_name ? 1 : 0;
> + init.flags = CLK_SET_RATE_GATE;
> +
> + utmi->hw.init = &init;
> + utmi->pmc = pmc;
> + utmi->irq = irq;
> + init_waitqueue_head(&utmi->wait);
> + irq_set_status_flags(utmi->irq, IRQ_NOAUTOEN);
> + ret = request_irq(utmi->irq, clk_utmi_irq_handler,
> + IRQF_TRIGGER_HIGH, "clk-utmi", utmi);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + clk = clk_register(NULL, &utmi->hw);
> + if (IS_ERR(clk))
> + kfree(utmi);
> +
> + return clk;
> +}
> +
> +static void __init
> +of_at91_clk_utmi_setup(struct device_node *np, struct at91_pmc *pmc)
> +{
> + unsigned int irq;
> + struct clk *clk;
> + const char *parent_name;
> + const char *name = np->name;
> +
> + parent_name = of_clk_get_parent_name(np, 0);
> +
> + of_property_read_string(np, "clock-output-names", &name);
> +
> + irq = irq_of_parse_and_map(np, 0);
> + if (!irq)
> + return;
> +
> + clk = at91_clk_register_utmi(pmc, irq, name, parent_name);
> + if (IS_ERR(clk))
> + return;
> +
> + of_clk_add_provider(np, of_clk_src_simple_get, clk);
> + return;
> +}
> +
> +void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + of_at91_clk_utmi_setup(np, pmc);
> +}
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index 5b8a959e..8802999 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -272,6 +272,13 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
> .data = of_at91sam9x5_clk_prog_setup,
> },
> #endif
> + /* UTMI clock */
> +#if defined(CONFIG_HAVE_AT91_UTMI)
> + {
> + .compatible = "atmel,at91sam9x5-clk-utmi",
> + .data = of_at91sam9x5_clk_utmi_setup,
> + },
> +#endif
> { /*sentinel*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 5f20dc1..3ba9503 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -91,4 +91,9 @@ extern void __init of_at91sam9x5_clk_prog_setup(struct device_node *np,
> struct at91_pmc *pmc);
> #endif
>
> +#if defined(CONFIG_HAVE_AT91_UTMI)
> +extern void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +#endif
> +
> #endif /* __PMC_H_ */
>


--
Nicolas Ferre

2013-10-17 16:18:23

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 13/17] clk: at91: add PMC usb clock

On 11/10/2013 13:15, Boris BREZILLON :
> This patch adds new at91 usb clock implementation using common clk framework.
> This clock is used to clock usb ports (ohci, ehci and udc).
>
> Signed-off-by: Boris BREZILLON <[email protected]>

Didn't you missed some 9n12 initialization? See below...

> ---
> arch/arm/mach-at91/Kconfig | 11 ++
> drivers/clk/at91/Makefile | 1 +
> drivers/clk/at91/clk-usb.c | 400 ++++++++++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/pmc.c | 11 ++
> drivers/clk/at91/pmc.h | 9 +
> 5 files changed, 432 insertions(+)
> create mode 100644 drivers/clk/at91/clk-usb.c
>
> diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
> index 6ad37da..b76dc4c 100644
> --- a/arch/arm/mach-at91/Kconfig
> +++ b/arch/arm/mach-at91/Kconfig
> @@ -3,6 +3,9 @@ if ARCH_AT91
> config HAVE_AT91_UTMI
> bool
>
> +config HAVE_AT91_USB_CLK
> + bool
> +
> config HAVE_AT91_DBGU0
> bool
>
> @@ -82,6 +85,7 @@ config SOC_SAMA5D3
> select HAVE_AT91_DBGU1
> select AT91_USE_OLD_CLK
> select HAVE_AT91_UTMI
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's SAMA5D3 family SoC.
> This support covers SAMA5D31, SAMA5D33, SAMA5D34, SAMA5D35.
> @@ -96,12 +100,14 @@ config SOC_AT91RM9200
> select MULTI_IRQ_HANDLER
> select SPARSE_IRQ
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
>
> config SOC_AT91SAM9260
> bool "AT91SAM9260, AT91SAM9XE or AT91SAM9G20"
> select HAVE_AT91_DBGU0
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9260, AT91SAM9XE
> or AT91SAM9G20 SoC.
> @@ -112,6 +118,7 @@ config SOC_AT91SAM9261
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9261 or AT91SAM9G10 SoC.
>
> @@ -121,6 +128,7 @@ config SOC_AT91SAM9263
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
>
> config SOC_AT91SAM9RL
> bool "AT91SAM9RL"
> @@ -137,6 +145,7 @@ config SOC_AT91SAM9G45
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> select HAVE_AT91_UTMI
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9G45 family SoC.
> This support covers AT91SAM9G45, AT91SAM9G46, AT91SAM9M10 and AT91SAM9M11.
> @@ -148,6 +157,7 @@ config SOC_AT91SAM9X5
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> select HAVE_AT91_UTMI
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9x5 family SoC.
> This means that your SAM9 name finishes with a '5' (except if it is
> @@ -161,6 +171,7 @@ config SOC_AT91SAM9N12
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using Atmel's AT91SAM9N12 SoC.
>
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index a824883..61db058 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -8,3 +8,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
> diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c
> new file mode 100644
> index 0000000..0454555
> --- /dev/null
> +++ b/drivers/clk/at91/clk-usb.c
> @@ -0,0 +1,400 @@
> +/*
> + * drivers/clk/at91/clk-usb.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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 USB_SOURCE_MAX 2
> +
> +#define SAM9X5_USB_DIV_SHIFT 8
> +#define SAM9X5_USB_MAX_DIV 0xf
> +
> +#define RM9200_USB_DIV_SHIFT 28
> +#define RM9200_USB_DIV_TAB_SIZE 4
> +
> +struct at91sam9x5_clk_usb {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> +};
> +
> +#define to_at91sam9x5_clk_usb(hw) \
> + container_of(hw, struct at91sam9x5_clk_usb, hw)
> +
> +struct at91rm9200_clk_usb {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + u32 divisors[4];
> +};
> +
> +#define to_at91rm9200_clk_usb(hw) \
> + container_of(hw, struct at91rm9200_clk_usb, hw)
> +
> +static unsigned long at91sam9x5_clk_usb_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + u32 tmp;
> + u8 usbdiv;
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + tmp = pmc_read(pmc, AT91_PMC_USB);
> + usbdiv = (tmp & AT91_PMC_OHCIUSBDIV) >> SAM9X5_USB_DIV_SHIFT;
> + return parent_rate / (usbdiv + 1);
> +}
> +
> +static long at91sam9x5_clk_usb_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 >= SAM9X5_USB_MAX_DIV)
> + return *parent_rate / (SAM9X5_USB_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_usb_set_parent(struct clk_hw *hw, u8 index)
> +{
> + u32 tmp;
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + if (index > 1)
> + return -EINVAL;
> + tmp = pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_USBS;
> + if (index)
> + tmp |= AT91_PMC_USBS;
> + pmc_write(pmc, AT91_PMC_USB, tmp);
> + return 0;
> +}
> +
> +static u8 at91sam9x5_clk_usb_get_parent(struct clk_hw *hw)
> +{
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + return pmc_read(pmc, AT91_PMC_USB) & AT91_PMC_USBS;
> +}
> +
> +static int at91sam9x5_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + u32 tmp;
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> + unsigned long div = parent_rate / rate;
> +
> + if (parent_rate % rate || div < 1 || div >= SAM9X5_USB_MAX_DIV)
> + return -EINVAL;
> +
> + tmp = pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_OHCIUSBDIV;
> + tmp |= (div - 1) << SAM9X5_USB_DIV_SHIFT;
> + pmc_write(pmc, AT91_PMC_USB, tmp);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops at91sam9x5_usb_ops = {
> + .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
> + .round_rate = at91sam9x5_clk_usb_round_rate,
> + .get_parent = at91sam9x5_clk_usb_get_parent,
> + .set_parent = at91sam9x5_clk_usb_set_parent,
> + .set_rate = at91sam9x5_clk_usb_set_rate,
> +};
> +
> +static int at91sam9n12_clk_usb_enable(struct clk_hw *hw)
> +{
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + pmc_write(pmc, AT91_PMC_USB,
> + pmc_read(pmc, AT91_PMC_USB) | AT91_PMC_USBS);
> + return 0;
> +}
> +
> +static void at91sam9n12_clk_usb_disable(struct clk_hw *hw)
> +{
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + pmc_write(pmc, AT91_PMC_USB,
> + pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_USBS);
> +}
> +
> +static int at91sam9n12_clk_usb_is_enabled(struct clk_hw *hw)
> +{
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + return !!(pmc_read(pmc, AT91_PMC_USB) & AT91_PMC_USBS);
> +}
> +
> +static const struct clk_ops at91sam9n12_usb_ops = {
> + .enable = at91sam9n12_clk_usb_enable,
> + .disable = at91sam9n12_clk_usb_disable,
> + .is_enabled = at91sam9n12_clk_usb_is_enabled,
> + .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
> + .round_rate = at91sam9x5_clk_usb_round_rate,
> + .set_rate = at91sam9x5_clk_usb_set_rate,
> +};
> +
> +static struct clk * __init
> +at91sam9x5_clk_register_usb(struct at91_pmc *pmc, const char *name,
> + const char **parent_names, u8 num_parents)
> +{
> + struct at91sam9x5_clk_usb *usb;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + usb = kzalloc(sizeof(*usb), GFP_KERNEL);
> + if (!usb)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &at91sam9x5_usb_ops;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
> +
> + usb->hw.init = &init;
> + usb->pmc = pmc;
> +
> + clk = clk_register(NULL, &usb->hw);
> + if (IS_ERR(clk))
> + kfree(usb);
> +
> + return clk;
> +}
> +
> +static struct clk * __init
> +at91sam9n12_clk_register_usb(struct at91_pmc *pmc, const char *name,
> + const char *parent_name)
> +{
> + struct at91sam9x5_clk_usb *usb;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + usb = kzalloc(sizeof(*usb), GFP_KERNEL);
> + if (!usb)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &at91sam9n12_usb_ops;
> + init.parent_names = &parent_name;
> + init.num_parents = 1;
> + init.flags = CLK_SET_RATE_GATE;
> +
> + usb->hw.init = &init;
> + usb->pmc = pmc;
> +
> + clk = clk_register(NULL, &usb->hw);
> + if (IS_ERR(clk))
> + kfree(usb);
> +
> + return clk;
> +}
> +
> +static unsigned long at91rm9200_clk_usb_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> + u32 tmp;
> + u8 usbdiv;
> +
> + tmp = pmc_read(pmc, AT91_CKGR_PLLBR);
> + usbdiv = (tmp & AT91_PMC_USBDIV) >> RM9200_USB_DIV_SHIFT;
> + if (usb->divisors[usbdiv])
> + return parent_rate / usb->divisors[usbdiv];
> +
> + return 0;
> +}
> +
> +static long at91rm9200_clk_usb_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
> + unsigned long bestrate = 0;
> + int bestdiff = -1;
> + unsigned long tmprate;
> + int tmpdiff;
> + int i = 0;
> +
> + for (i = 0; i < 4; i++) {
> + if (!usb->divisors[i])
> + continue;
> + tmprate = *parent_rate / usb->divisors[i];
> + if (tmprate < rate)
> + tmpdiff = rate - tmprate;
> + else
> + tmpdiff = tmprate - rate;
> +
> + if (bestdiff < 0 || bestdiff > tmpdiff) {
> + bestrate = tmprate;
> + bestdiff = tmpdiff;
> + }
> +
> + if (!bestdiff)
> + break;
> + }
> +
> + return bestrate;
> +}
> +
> +static int at91rm9200_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + u32 tmp;
> + int i;
> + struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> + unsigned long div = parent_rate / rate;
> +
> + if (parent_rate % rate)
> + return -EINVAL;
> + for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) {
> + if (usb->divisors[i] == div) {
> + tmp = pmc_read(pmc, AT91_CKGR_PLLBR) &
> + ~AT91_PMC_USBDIV;
> + tmp |= i << RM9200_USB_DIV_SHIFT;
> + pmc_write(pmc, AT91_CKGR_PLLBR, tmp);
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +static const struct clk_ops at91rm9200_usb_ops = {
> + .recalc_rate = at91rm9200_clk_usb_recalc_rate,
> + .round_rate = at91rm9200_clk_usb_round_rate,
> + .set_rate = at91rm9200_clk_usb_set_rate,
> +};
> +
> +static struct clk * __init
> +at91rm9200_clk_register_usb(struct at91_pmc *pmc, const char *name,
> + const char *parent_name, const u32 *divisors)
> +{
> + struct at91rm9200_clk_usb *usb;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + usb = kzalloc(sizeof(*usb), GFP_KERNEL);
> + if (!usb)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &at91rm9200_usb_ops;
> + init.parent_names = &parent_name;
> + init.num_parents = 1;
> + init.flags = 0;
> +
> + usb->hw.init = &init;
> + usb->pmc = pmc;
> + memcpy(usb->divisors, divisors, sizeof(usb->divisors));
> +
> + clk = clk_register(NULL, &usb->hw);
> + if (IS_ERR(clk))
> + kfree(usb);
> +
> + return clk;
> +}
> +
> +void __init of_at91sam9x5_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + struct clk *clk;
> + int i;
> + int num_parents;
> + const char *parent_names[USB_SOURCE_MAX];
> + const char *name = np->name;
> +
> + num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells");
> + if (num_parents <= 0 || num_parents > USB_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_usb(pmc, name, parent_names, num_parents);
> + if (IS_ERR(clk))
> + return;
> +
> + of_clk_add_provider(np, of_clk_src_simple_get, clk);
> +}
> +
> +void __init of_at91sam9n12_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + struct clk *clk;
> + const char *parent_name;
> + const char *name = np->name;
> +
> + parent_name = of_clk_get_parent_name(np, 0);
> + if (!parent_name)
> + return;
> +
> + of_property_read_string(np, "clock-output-names", &name);
> +
> + clk = at91sam9n12_clk_register_usb(pmc, name, parent_name);
> + if (IS_ERR(clk))
> + return;
> +
> + of_clk_add_provider(np, of_clk_src_simple_get, clk);
> +}
> +
> +void __init of_at91rm9200_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + struct clk *clk;
> + const char *parent_name;
> + const char *name = np->name;
> + u32 divisors[4] = {0, 0, 0, 0};
> +
> + parent_name = of_clk_get_parent_name(np, 0);
> + if (!parent_name)
> + return;
> +
> + of_property_read_u32_array(np, "atmel,clk-divisors", divisors, 4);
> + if (!divisors[0])
> + return;
> +
> + of_property_read_string(np, "clock-output-names", &name);
> +
> + clk = at91rm9200_clk_register_usb(pmc, name, parent_name, divisors);
> + if (IS_ERR(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 8802999..dc0b1b6 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -279,6 +279,17 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
> .data = of_at91sam9x5_clk_utmi_setup,
> },
> #endif
> + /* USB clock */
> +#if defined(CONFIG_HAVE_AT91_USB_CLK)
> + {
> + .compatible = "atmel,at91rm9200-clk-usb",
> + .data = of_at91rm9200_clk_usb_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9x5-clk-usb",
> + .data = of_at91sam9x5_clk_usb_setup,
> + },

What about of_at91sam9n12_clk_usb_setup() ??

Otherwise, when added, you can add my:

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


> +#endif
> { /*sentinel*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 3ba9503..eba7334 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -96,4 +96,13 @@ extern void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np,
> struct at91_pmc *pmc);
> #endif
>
> +#if defined(CONFIG_HAVE_AT91_USB_CLK)
> +extern void __init of_at91rm9200_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9x5_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9n12_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +#endif
> +
> #endif /* __PMC_H_ */
>


--
Nicolas Ferre

2013-10-17 16:27:23

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 14/17] clk: at91: add PMC smd clock

On 11/10/2013 13:27, Boris BREZILLON :
> This patch adds at91 smd (Soft Modem) clock implementation using common clk
> framework.
>
> Not used by any driver right now.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

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

> ---
> arch/arm/mach-at91/Kconfig | 5 ++
> drivers/clk/at91/Makefile | 1 +
> drivers/clk/at91/clk-smd.c | 173 ++++++++++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/pmc.c | 7 ++
> drivers/clk/at91/pmc.h | 5 ++
> 5 files changed, 191 insertions(+)
> create mode 100644 drivers/clk/at91/clk-smd.c
>
> diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
> index b76dc4c..97033f7 100644
> --- a/arch/arm/mach-at91/Kconfig
> +++ b/arch/arm/mach-at91/Kconfig
> @@ -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.
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index 61db058..0e92b71 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -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
> diff --git a/drivers/clk/at91/clk-smd.c b/drivers/clk/at91/clk-smd.c
> new file mode 100644
> index 0000000..9f3fa39
> --- /dev/null
> +++ b/drivers/clk/at91/clk-smd.c
> @@ -0,0 +1,173 @@
> +/*
> + * drivers/clk/at91/clk-smd.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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);
> +}
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index dc0b1b6..00a4a51 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -290,6 +290,13 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
> .data = of_at91sam9x5_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*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index eba7334..d0b8b38 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -105,4 +105,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_ */
>


--
Nicolas Ferre

2013-10-17 16:37:53

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 15/17] clk: at91: add PMC clk device tree binding doc.

On 11/10/2013 13:38, Boris BREZILLON :
> This patch adds new at91 clks dt bindings documentation.
>
> Signed-off-by: Boris BREZILLON <[email protected]>

It seems good

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

> ---
> .../devicetree/bindings/clock/at91-clock.txt | 328 ++++++++++++++++++++
> 1 file changed, 328 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/clock/at91-clock.txt
>
> diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt
> new file mode 100644
> index 0000000..360d8fe
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt
> @@ -0,0 +1,328 @@
> +Device Tree Clock bindings for arch-at91
> +
> +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:
> + "atmel,at91rm9200-pmc" or
> + "atmel,at91sam9g45-pmc" or
> + "atmel,at91sam9n12-pmc" or
> + "atmel,at91sam9x5-pmc" or
> + "atmel,sama5d3-pmc":
> + at91 PMC (Power Management Controller)
> + All at91 specific clocks (clocks defined below) must be child
> + node of the PMC node.
> +
> + "atmel,at91rm9200-clk-main":
> + at91 main oscillator
> +
> + "atmel,at91rm9200-clk-master" or
> + "atmel,at91sam9x5-clk-master":
> + at91 master clock
> +
> + "atmel,at91sam9x5-clk-peripheral" or
> + "atmel,at91rm9200-clk-peripheral":
> + at91 peripheral clocks
> +
> + "atmel,at91rm9200-clk-pll" or
> + "atmel,at91sam9g45-clk-pll" or
> + "atmel,at91sam9g20-clk-pllb" or
> + "atmel,sama5d3-clk-pll":
> + at91 pll clocks
> +
> + "atmel,at91sam9x5-clk-plldiv":
> + at91 plla divisor
> +
> + "atmel,at91rm9200-clk-programmable" or
> + "atmel,at91sam9g45-clk-programmable" or
> + "atmel,at91sam9x5-clk-programmable":
> + at91 programmable clocks
> +
> + "atmel,at91sam9x5-clk-smd":
> + at91 SMD (Soft Modem) clock
> +
> + "atmel,at91rm9200-clk-system":
> + at91 system clocks
> +
> + "atmel,at91rm9200-clk-usb" or
> + "atmel,at91sam9x5-clk-usb" or
> + "atmel,at91sam9n12-clk-usb":
> + at91 usb clock
> +
> + "atmel,at91sam9x5-clk-utmi":
> + at91 utmi clock
> +
> +Required properties for PMC node:
> +- reg : defines the IO memory reserved for the PMC.
> +- interrupts : shall be set to PMC interrupt line.
> +- interrupt-controller : tell that the PMC is an interrupt controller.
> +- #interrupt-cells : must be set to 1. The first cell encodes the interrupt id,
> + and reflect the bit position in the PMC_ER/DR/SR registers.
> + You can use the dt macros defined in dt-bindings/clk/at91.h.
> + 0 (AT91_PMC_MOSCS) -> main oscillator ready
> + 1 (AT91_PMC_LOCKA) -> PLL A ready
> + 2 (AT91_PMC_LOCKB) -> PLL B ready
> + 3 (AT91_PMC_MCKRDY) -> master clock ready
> + 6 (AT91_PMC_LOCKU) -> UTMI PLL clock ready
> + 8 .. 15 (AT91_PMC_PCKRDY(id)) -> programmable clock ready
> + 16 (AT91_PMC_MOSCSELS) -> main oscillator selected
> + 17 (AT91_PMC_MOSCRCS) -> RC main oscillator stabilized
> + 18 (AT91_PMC_CFDEV) -> clock failure detected
> +
> +For example:
> + pmc: pmc@fffffc00 {
> + compatible = "atmel,sama5d3-pmc";
> + interrupts = <1 4 7>;
> + interrupt-controller;
> + #interrupt-cells = <2>;
> +
> + /* put at91 clocks here */
> + };
> +
> +Required properties for main clock:
> +- interrupt-parent : must reference the PMC node.
> +- interrupts : shall be set to "<0>".
> +- #clock-cells : from common clock binding; shall be set to 0.
> +- clocks (optional if clock-frequency is provided) : shall be the slow clock
> + phandle. This clock is used to calculate the main clock rate if
> + "clock-frequency" is not provided.
> +- clock-frequency : the main oscillator frequency.Prefer the use of
> + "clock-frequency" over automatic clock rate calculation.
> +
> +For example:
> + main: mainck {
> + compatible = "atmel,at91rm9200-clk-main";
> + interrupt-parent = <&pmc>;
> + interrupts = <0>;
> + #clock-cells = <0>;
> + clocks = <&ck32k>;
> + clock-frequency = <18432000>;
> + };
> +
> +Required properties for master clock:
> +- interrupt-parent : must reference the PMC node.
> +- interrupts : shall be set to "<3>".
> +- #clock-cells : from common clock binding; shall be set to 0.
> +- clocks : shall be the master clock sources (see atmel datasheet) phandles.
> + e.g. "<&ck32k>, <&main>, <&plla>, <&pllb>".
> +- atmel,clk-output-range : minimum and maximum clock frequency (two u32
> + fields).
> + e.g. output = <0 133000000>; <=> 0 to 133MHz.
> +- atmel,clk-divisors : master clock divisors table (four u32 fields).
> + 0 <=> reserved value.
> + e.g. divisors = <1 2 4 6>;
> +- atmel,master-clk-have-div3-pres : some SoC use the reserved value 7 in the
> + PRES field as CLOCK_DIV3 (e.g sam9x5).
> +
> +For example:
> + mck: mck {
> + compatible = "atmel,at91rm9200-clk-master";
> + interrupt-parent = <&pmc>;
> + interrupts = <3>;
> + #clock-cells = <0>;
> + atmel,clk-output-range = <0 133000000>;
> + atmel,clk-divisors = <1 2 4 0>;
> + };
> +
> +Required properties for peripheral clocks:
> +- #clock-cells : from common clock binding; shall be set to 1. The second cell
> + is used to encode the peripheral id. Peripheral ids are defined in
> + atmel's SoC datasheets.
> +- clocks : shall be the master clock phandle.
> + e.g. clocks = <&mck>;
> +- name: device tree node describing a specific system clock.
> + * atmel,clk-id: peripheral id. See Atmel's datasheets to get a full
> + list of peripheral ids.
> + * atmel,clk-default-divisor (optional, only available for
> + "atmel,at91sam9x5-clk-peripheral"): sam9x5 and sama5d3 SoC provides
> + configurable peripheral clock divisor. If you define this property
> + (u32), the default divisor will be applied when enabling
> + peripheral clock. If not provided the peripheral clock is not
> + divided.
> +
> +For example:
> + periph: periphck {
> + compatible = "atmel,at91sam9x5-clk-peripheral";
> + #clock-cells = <1>;
> + clocks = <&mck>;
> +
> + pioAB_clk {
> + atmel,clk-id = <2>;
> + atmel,clk-default-divisor = <1>;
> + };
> +
> + pioCD_clk {
> + atmel,clk-id = <3>;
> + atmel,clk-default-divisor = <2>;
> + };
> + };
> +
> +
> +Required properties for pll clocks:
> +- interrupt-parent : must reference the PMC node.
> +- interrupts : shall be set to "<1>".
> +- #clock-cells : from common clock binding; shall be set to 0.
> +- clocks : shall be the main clock phandle.
> +- atmel,clk-id : pll id.
> + 0 -> PLL A
> + 1 -> PLL B
> +- atmel,clk-input-range : minimum and maximum source clock frequency (two u32
> + fields).
> + e.g. input = <1 32000000>; <=> 1 to 32MHz.
> +- #atmel,pll-clk-output-range-cells : number of cells reserved for pll output
> + range description. Sould be set to 2, 3
> + or 4.
> + * 1st and 2nd cells represent the frequency range (min-max).
> + * 3rd cell is optional and represents the OUT field value for the given
> + range.
> + * 4th cell is optional and represents the ICPLL field (PLLICPR
> + register)
> +- atmel,pll-clk-output-ranges : pll output frequency ranges + optional parameter
> + depending on #atmel,pll-output-range-cells
> + property value.
> +
> +For example:
> + plla: pllack {
> + compatible = "atmel,at91sam9g45-clk-pll";
> + interrupt-parent = <&pmc>;
> + interrupts = <1>;
> + #clock-cells = <0>;
> + clocks = <&main>;
> + atmel,clk-id = <0>;
> + atmel,clk-input-range = <2000000 32000000>;
> + #atmel,pll-clk-output-range-cells = <4>;
> + atmel,pll-clk-output-ranges = <74500000 800000000 0 0
> + 69500000 750000000 1 0
> + 64500000 700000000 2 0
> + 59500000 650000000 3 0
> + 54500000 600000000 0 1
> + 49500000 550000000 1 1
> + 44500000 500000000 2 1
> + 40000000 450000000 3 1>;
> + };
> +
> +Required properties for plldiv clocks (plldiv = pll / 2):
> +- #clock-cells : from common clock binding; shall be set to 0.
> +- clocks : shall be the plla clock phandle.
> +
> +The pll divisor is equal to 2 and cannot be changed.
> +
> +For example:
> + plladiv: plladivck {
> + compatible = "atmel,at91sam9x5-clk-plldiv";
> + #clock-cells = <0>;
> + clocks = <&plla>;
> + };
> +
> +Required properties for programmable clocks:
> +- interrupt-parent : must reference the PMC node.
> +- #clock-cells : from common clock binding; shall be set to 1. The second cell
> + is used to encode the programmable clock id.
> + Peripheral ids are in atmel's SoC
> + datasheets.
> +- clocks : shall be the programmable clock source phandles.
> + e.g. clocks = <&clk32k>, <&main>, <&plla>, <&pllb>;
> +- name: device tree node describing a specific prog clock.
> + * atmel,clk-id : programmable clock id (register offset from PCKx
> + register).
> + * interrupts : shall be set to "<(8 + id)>".
> +
> +For example:
> + prog: progck {
> + compatible = "atmel,at91sam9g45-clk-programmable";
> + interrupt-parent = <&pmc>;
> + #clock-cells = <1>;
> + clocks = <&clk32k>, <&main>, <&plladiv>, <&utmi>, <&mck>;
> +
> + prog0 {
> + atmel,clk-id = <0>;
> + interrupts = <8>;
> + };
> +
> + prog1 {
> + atmel,clk-id = <1>;
> + interrupts = <9>;
> + };
> + };
> +
> +
> +Required properties for smd clock:
> +- #clock-cells : from common clock binding; shall be set to 0.
> +- clocks : shall be the smd clock source phandles.
> + e.g. clocks = <&plladiv>, <&utmi>;
> +
> +For example:
> + smd: smdck {
> + compatible = "atmel,at91sam9x5-clk-smd";
> + #clock-cells = <0>;
> + clocks = <&plladiv>, <&utmi>;
> + };
> +
> +Required properties for system clocks:
> +- #clock-cells : from common clock binding; shall be set to 1. The second cell
> + is used to encode the system clock id (bit used in SCER/SCDR register).
> +- name: device tree node describing a specific system clock.
> + * id: system clock id (bit position in SCER/SCDR/SCSR registers).
> + See Atmel's datasheet to get a full list of system clock ids.
> +
> +For example:
> + system: systemck {
> + compatible = "atmel,at91rm9200-clk-system";
> + #clock-cells = <1>;
> +
> + ddrck {
> + atmel,clk-id = <2>;
> + clocks = <&mck>;
> + };
> +
> + uhpck {
> + atmel,clk-id = <6>;
> + clocks = <&usb>;
> + };
> +
> + udpck {
> + atmel,clk-id = <7>;
> + clocks = <&usb>;
> + };
> + };
> +
> +
> +Required properties for usb clock:
> +- #clock-cells : from common clock binding; shall be set to 0.
> +- clocks : shall be the smd clock source phandles.
> + e.g. clocks = <&pllb>;
> +- atmel,clk-divisors (only available for "atmel,at91rm9200-clk-usb"):
> + usb clock divisor table.
> + e.g. divisors = <1 2 4 0>;
> +
> +For example:
> + usb: usbck {
> + compatible = "atmel,at91sam9x5-clk-usb";
> + #clock-cells = <0>;
> + clocks = <&plladiv>, <&utmi>;
> + };
> +
> + usb: usbck {
> + compatible = "atmel,at91rm9200-clk-usb";
> + #clock-cells = <0>;
> + clocks = <&pllb>;
> + atmel,clk-divisors = <1 2 4 0>;
> + };
> +
> +
> +Required properties for utmi clock:
> +- interrupt-parent : must reference the PMC node.
> +- interrupts : shall be set to "<AT91_PMC_LOCKU IRQ_TYPE_LEVEL_HIGH>".
> +- #clock-cells : from common clock binding; shall be set to 0.
> +- clocks : shall be the main clock source phandle.
> +
> +For example:
> + utmi: utmick {
> + compatible = "atmel,at91sam9x5-clk-utmi";
> + interrupt-parent = <&pmc>;
> + interrupts = <AT91_PMC_LOCKU IRQ_TYPE_LEVEL_HIGH>;
> + #clock-cells = <0>;
> + clocks = <&main>;
> + };
>


--
Nicolas Ferre

2013-10-17 20:03:14

by Boris BREZILLON

[permalink] [raw]
Subject: [PATCH v5 13/17] clk: at91: add PMC usb clock

This patch adds new at91 usb clock implementation using common clk framework.
This clock is used to clock usb ports (ohci, ehci and udc).

Signed-off-by: Boris BREZILLON <[email protected]>
Acked-by: Nicolas Ferre <[email protected]>
---
arch/arm/mach-at91/Kconfig | 11 ++
drivers/clk/at91/Makefile | 1 +
drivers/clk/at91/clk-usb.c | 400 ++++++++++++++++++++++++++++++++++++++++++++
drivers/clk/at91/pmc.c | 15 ++
drivers/clk/at91/pmc.h | 9 +
5 files changed, 436 insertions(+)
create mode 100644 drivers/clk/at91/clk-usb.c

diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
index 6ad37da..b76dc4c 100644
--- a/arch/arm/mach-at91/Kconfig
+++ b/arch/arm/mach-at91/Kconfig
@@ -3,6 +3,9 @@ if ARCH_AT91
config HAVE_AT91_UTMI
bool

+config HAVE_AT91_USB_CLK
+ bool
+
config HAVE_AT91_DBGU0
bool

@@ -82,6 +85,7 @@ config SOC_SAMA5D3
select HAVE_AT91_DBGU1
select AT91_USE_OLD_CLK
select HAVE_AT91_UTMI
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's SAMA5D3 family SoC.
This support covers SAMA5D31, SAMA5D33, SAMA5D34, SAMA5D35.
@@ -96,12 +100,14 @@ config SOC_AT91RM9200
select MULTI_IRQ_HANDLER
select SPARSE_IRQ
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK

config SOC_AT91SAM9260
bool "AT91SAM9260, AT91SAM9XE or AT91SAM9G20"
select HAVE_AT91_DBGU0
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's AT91SAM9260, AT91SAM9XE
or AT91SAM9G20 SoC.
@@ -112,6 +118,7 @@ config SOC_AT91SAM9261
select HAVE_FB_ATMEL
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's AT91SAM9261 or AT91SAM9G10 SoC.

@@ -121,6 +128,7 @@ config SOC_AT91SAM9263
select HAVE_FB_ATMEL
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK

config SOC_AT91SAM9RL
bool "AT91SAM9RL"
@@ -137,6 +145,7 @@ config SOC_AT91SAM9G45
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
select HAVE_AT91_UTMI
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's AT91SAM9G45 family SoC.
This support covers AT91SAM9G45, AT91SAM9G46, AT91SAM9M10 and AT91SAM9M11.
@@ -148,6 +157,7 @@ config SOC_AT91SAM9X5
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
select HAVE_AT91_UTMI
+ select HAVE_AT91_USB_CLK
help
Select this if you are using one of Atmel's AT91SAM9x5 family SoC.
This means that your SAM9 name finishes with a '5' (except if it is
@@ -161,6 +171,7 @@ config SOC_AT91SAM9N12
select HAVE_FB_ATMEL
select SOC_AT91SAM9
select AT91_USE_OLD_CLK
+ select HAVE_AT91_USB_CLK
help
Select this if you are using Atmel's AT91SAM9N12 SoC.

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index a824883..61db058 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -8,3 +8,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
diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c
new file mode 100644
index 0000000..0454555
--- /dev/null
+++ b/drivers/clk/at91/clk-usb.c
@@ -0,0 +1,400 @@
+/*
+ * drivers/clk/at91/clk-usb.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * 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 USB_SOURCE_MAX 2
+
+#define SAM9X5_USB_DIV_SHIFT 8
+#define SAM9X5_USB_MAX_DIV 0xf
+
+#define RM9200_USB_DIV_SHIFT 28
+#define RM9200_USB_DIV_TAB_SIZE 4
+
+struct at91sam9x5_clk_usb {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+};
+
+#define to_at91sam9x5_clk_usb(hw) \
+ container_of(hw, struct at91sam9x5_clk_usb, hw)
+
+struct at91rm9200_clk_usb {
+ struct clk_hw hw;
+ struct at91_pmc *pmc;
+ u32 divisors[4];
+};
+
+#define to_at91rm9200_clk_usb(hw) \
+ container_of(hw, struct at91rm9200_clk_usb, hw)
+
+static unsigned long at91sam9x5_clk_usb_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ u32 tmp;
+ u8 usbdiv;
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ tmp = pmc_read(pmc, AT91_PMC_USB);
+ usbdiv = (tmp & AT91_PMC_OHCIUSBDIV) >> SAM9X5_USB_DIV_SHIFT;
+ return parent_rate / (usbdiv + 1);
+}
+
+static long at91sam9x5_clk_usb_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 >= SAM9X5_USB_MAX_DIV)
+ return *parent_rate / (SAM9X5_USB_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_usb_set_parent(struct clk_hw *hw, u8 index)
+{
+ u32 tmp;
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ if (index > 1)
+ return -EINVAL;
+ tmp = pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_USBS;
+ if (index)
+ tmp |= AT91_PMC_USBS;
+ pmc_write(pmc, AT91_PMC_USB, tmp);
+ return 0;
+}
+
+static u8 at91sam9x5_clk_usb_get_parent(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ return pmc_read(pmc, AT91_PMC_USB) & AT91_PMC_USBS;
+}
+
+static int at91sam9x5_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ u32 tmp;
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+ unsigned long div = parent_rate / rate;
+
+ if (parent_rate % rate || div < 1 || div >= SAM9X5_USB_MAX_DIV)
+ return -EINVAL;
+
+ tmp = pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_OHCIUSBDIV;
+ tmp |= (div - 1) << SAM9X5_USB_DIV_SHIFT;
+ pmc_write(pmc, AT91_PMC_USB, tmp);
+
+ return 0;
+}
+
+static const struct clk_ops at91sam9x5_usb_ops = {
+ .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
+ .round_rate = at91sam9x5_clk_usb_round_rate,
+ .get_parent = at91sam9x5_clk_usb_get_parent,
+ .set_parent = at91sam9x5_clk_usb_set_parent,
+ .set_rate = at91sam9x5_clk_usb_set_rate,
+};
+
+static int at91sam9n12_clk_usb_enable(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ pmc_write(pmc, AT91_PMC_USB,
+ pmc_read(pmc, AT91_PMC_USB) | AT91_PMC_USBS);
+ return 0;
+}
+
+static void at91sam9n12_clk_usb_disable(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ pmc_write(pmc, AT91_PMC_USB,
+ pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_USBS);
+}
+
+static int at91sam9n12_clk_usb_is_enabled(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+
+ return !!(pmc_read(pmc, AT91_PMC_USB) & AT91_PMC_USBS);
+}
+
+static const struct clk_ops at91sam9n12_usb_ops = {
+ .enable = at91sam9n12_clk_usb_enable,
+ .disable = at91sam9n12_clk_usb_disable,
+ .is_enabled = at91sam9n12_clk_usb_is_enabled,
+ .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
+ .round_rate = at91sam9x5_clk_usb_round_rate,
+ .set_rate = at91sam9x5_clk_usb_set_rate,
+};
+
+static struct clk * __init
+at91sam9x5_clk_register_usb(struct at91_pmc *pmc, const char *name,
+ const char **parent_names, u8 num_parents)
+{
+ struct at91sam9x5_clk_usb *usb;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91sam9x5_usb_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+
+ usb->hw.init = &init;
+ usb->pmc = pmc;
+
+ clk = clk_register(NULL, &usb->hw);
+ if (IS_ERR(clk))
+ kfree(usb);
+
+ return clk;
+}
+
+static struct clk * __init
+at91sam9n12_clk_register_usb(struct at91_pmc *pmc, const char *name,
+ const char *parent_name)
+{
+ struct at91sam9x5_clk_usb *usb;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91sam9n12_usb_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE;
+
+ usb->hw.init = &init;
+ usb->pmc = pmc;
+
+ clk = clk_register(NULL, &usb->hw);
+ if (IS_ERR(clk))
+ kfree(usb);
+
+ return clk;
+}
+
+static unsigned long at91rm9200_clk_usb_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+ u32 tmp;
+ u8 usbdiv;
+
+ tmp = pmc_read(pmc, AT91_CKGR_PLLBR);
+ usbdiv = (tmp & AT91_PMC_USBDIV) >> RM9200_USB_DIV_SHIFT;
+ if (usb->divisors[usbdiv])
+ return parent_rate / usb->divisors[usbdiv];
+
+ return 0;
+}
+
+static long at91rm9200_clk_usb_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
+ unsigned long bestrate = 0;
+ int bestdiff = -1;
+ unsigned long tmprate;
+ int tmpdiff;
+ int i = 0;
+
+ for (i = 0; i < 4; i++) {
+ if (!usb->divisors[i])
+ continue;
+ tmprate = *parent_rate / usb->divisors[i];
+ if (tmprate < rate)
+ tmpdiff = rate - tmprate;
+ else
+ tmpdiff = tmprate - rate;
+
+ if (bestdiff < 0 || bestdiff > tmpdiff) {
+ bestrate = tmprate;
+ bestdiff = tmpdiff;
+ }
+
+ if (!bestdiff)
+ break;
+ }
+
+ return bestrate;
+}
+
+static int at91rm9200_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ u32 tmp;
+ int i;
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
+ struct at91_pmc *pmc = usb->pmc;
+ unsigned long div = parent_rate / rate;
+
+ if (parent_rate % rate)
+ return -EINVAL;
+ for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) {
+ if (usb->divisors[i] == div) {
+ tmp = pmc_read(pmc, AT91_CKGR_PLLBR) &
+ ~AT91_PMC_USBDIV;
+ tmp |= i << RM9200_USB_DIV_SHIFT;
+ pmc_write(pmc, AT91_CKGR_PLLBR, tmp);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const struct clk_ops at91rm9200_usb_ops = {
+ .recalc_rate = at91rm9200_clk_usb_recalc_rate,
+ .round_rate = at91rm9200_clk_usb_round_rate,
+ .set_rate = at91rm9200_clk_usb_set_rate,
+};
+
+static struct clk * __init
+at91rm9200_clk_register_usb(struct at91_pmc *pmc, const char *name,
+ const char *parent_name, const u32 *divisors)
+{
+ struct at91rm9200_clk_usb *usb;
+ struct clk *clk = NULL;
+ struct clk_init_data init;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91rm9200_usb_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = 0;
+
+ usb->hw.init = &init;
+ usb->pmc = pmc;
+ memcpy(usb->divisors, divisors, sizeof(usb->divisors));
+
+ clk = clk_register(NULL, &usb->hw);
+ if (IS_ERR(clk))
+ kfree(usb);
+
+ return clk;
+}
+
+void __init of_at91sam9x5_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ struct clk *clk;
+ int i;
+ int num_parents;
+ const char *parent_names[USB_SOURCE_MAX];
+ const char *name = np->name;
+
+ num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells");
+ if (num_parents <= 0 || num_parents > USB_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_usb(pmc, name, parent_names, num_parents);
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+void __init of_at91sam9n12_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ clk = at91sam9n12_clk_register_usb(pmc, name, parent_name);
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+void __init of_at91rm9200_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+ u32 divisors[4] = {0, 0, 0, 0};
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return;
+
+ of_property_read_u32_array(np, "atmel,clk-divisors", divisors, 4);
+ if (!divisors[0])
+ return;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ clk = at91rm9200_clk_register_usb(pmc, name, parent_name, divisors);
+ if (IS_ERR(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 8802999..1998aadab 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -279,6 +279,21 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
.data = of_at91sam9x5_clk_utmi_setup,
},
#endif
+ /* USB clock */
+#if defined(CONFIG_HAVE_AT91_USB_CLK)
+ {
+ .compatible = "atmel,at91rm9200-clk-usb",
+ .data = of_at91rm9200_clk_usb_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-clk-usb",
+ .data = of_at91sam9x5_clk_usb_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9n12-clk-usb",
+ .data = of_at91sam9n12_clk_usb_setup,
+ },
+#endif
{ /*sentinel*/ }
};

diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 3ba9503..eba7334 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -96,4 +96,13 @@ extern void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np,
struct at91_pmc *pmc);
#endif

+#if defined(CONFIG_HAVE_AT91_USB_CLK)
+extern void __init of_at91rm9200_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9x5_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+extern void __init of_at91sam9n12_clk_usb_setup(struct device_node *np,
+ struct at91_pmc *pmc);
+#endif
+
#endif /* __PMC_H_ */
--
1.7.9.5

2013-10-18 07:53:16

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v5 13/17] clk: at91: add PMC usb clock

On 17/10/2013 18:55, Boris BREZILLON :
> This patch adds new at91 usb clock implementation using common clk framework.
> This clock is used to clock usb ports (ohci, ehci and udc).
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> Acked-by: Nicolas Ferre <[email protected]>

Ok, this one is now replacing the older 13/17.

Thanks Boris.

Bye,

> ---
> arch/arm/mach-at91/Kconfig | 11 ++
> drivers/clk/at91/Makefile | 1 +
> drivers/clk/at91/clk-usb.c | 400 ++++++++++++++++++++++++++++++++++++++++++++
> drivers/clk/at91/pmc.c | 15 ++
> drivers/clk/at91/pmc.h | 9 +
> 5 files changed, 436 insertions(+)
> create mode 100644 drivers/clk/at91/clk-usb.c
>
> diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
> index 6ad37da..b76dc4c 100644
> --- a/arch/arm/mach-at91/Kconfig
> +++ b/arch/arm/mach-at91/Kconfig
> @@ -3,6 +3,9 @@ if ARCH_AT91
> config HAVE_AT91_UTMI
> bool
>
> +config HAVE_AT91_USB_CLK
> + bool
> +
> config HAVE_AT91_DBGU0
> bool
>
> @@ -82,6 +85,7 @@ config SOC_SAMA5D3
> select HAVE_AT91_DBGU1
> select AT91_USE_OLD_CLK
> select HAVE_AT91_UTMI
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's SAMA5D3 family SoC.
> This support covers SAMA5D31, SAMA5D33, SAMA5D34, SAMA5D35.
> @@ -96,12 +100,14 @@ config SOC_AT91RM9200
> select MULTI_IRQ_HANDLER
> select SPARSE_IRQ
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
>
> config SOC_AT91SAM9260
> bool "AT91SAM9260, AT91SAM9XE or AT91SAM9G20"
> select HAVE_AT91_DBGU0
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9260, AT91SAM9XE
> or AT91SAM9G20 SoC.
> @@ -112,6 +118,7 @@ config SOC_AT91SAM9261
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9261 or AT91SAM9G10 SoC.
>
> @@ -121,6 +128,7 @@ config SOC_AT91SAM9263
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
>
> config SOC_AT91SAM9RL
> bool "AT91SAM9RL"
> @@ -137,6 +145,7 @@ config SOC_AT91SAM9G45
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> select HAVE_AT91_UTMI
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9G45 family SoC.
> This support covers AT91SAM9G45, AT91SAM9G46, AT91SAM9M10 and AT91SAM9M11.
> @@ -148,6 +157,7 @@ config SOC_AT91SAM9X5
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> select HAVE_AT91_UTMI
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using one of Atmel's AT91SAM9x5 family SoC.
> This means that your SAM9 name finishes with a '5' (except if it is
> @@ -161,6 +171,7 @@ config SOC_AT91SAM9N12
> select HAVE_FB_ATMEL
> select SOC_AT91SAM9
> select AT91_USE_OLD_CLK
> + select HAVE_AT91_USB_CLK
> help
> Select this if you are using Atmel's AT91SAM9N12 SoC.
>
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index a824883..61db058 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -8,3 +8,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
> diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c
> new file mode 100644
> index 0000000..0454555
> --- /dev/null
> +++ b/drivers/clk/at91/clk-usb.c
> @@ -0,0 +1,400 @@
> +/*
> + * drivers/clk/at91/clk-usb.c
> + *
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * 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 USB_SOURCE_MAX 2
> +
> +#define SAM9X5_USB_DIV_SHIFT 8
> +#define SAM9X5_USB_MAX_DIV 0xf
> +
> +#define RM9200_USB_DIV_SHIFT 28
> +#define RM9200_USB_DIV_TAB_SIZE 4
> +
> +struct at91sam9x5_clk_usb {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> +};
> +
> +#define to_at91sam9x5_clk_usb(hw) \
> + container_of(hw, struct at91sam9x5_clk_usb, hw)
> +
> +struct at91rm9200_clk_usb {
> + struct clk_hw hw;
> + struct at91_pmc *pmc;
> + u32 divisors[4];
> +};
> +
> +#define to_at91rm9200_clk_usb(hw) \
> + container_of(hw, struct at91rm9200_clk_usb, hw)
> +
> +static unsigned long at91sam9x5_clk_usb_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + u32 tmp;
> + u8 usbdiv;
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + tmp = pmc_read(pmc, AT91_PMC_USB);
> + usbdiv = (tmp & AT91_PMC_OHCIUSBDIV) >> SAM9X5_USB_DIV_SHIFT;
> + return parent_rate / (usbdiv + 1);
> +}
> +
> +static long at91sam9x5_clk_usb_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 >= SAM9X5_USB_MAX_DIV)
> + return *parent_rate / (SAM9X5_USB_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_usb_set_parent(struct clk_hw *hw, u8 index)
> +{
> + u32 tmp;
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + if (index > 1)
> + return -EINVAL;
> + tmp = pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_USBS;
> + if (index)
> + tmp |= AT91_PMC_USBS;
> + pmc_write(pmc, AT91_PMC_USB, tmp);
> + return 0;
> +}
> +
> +static u8 at91sam9x5_clk_usb_get_parent(struct clk_hw *hw)
> +{
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + return pmc_read(pmc, AT91_PMC_USB) & AT91_PMC_USBS;
> +}
> +
> +static int at91sam9x5_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + u32 tmp;
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> + unsigned long div = parent_rate / rate;
> +
> + if (parent_rate % rate || div < 1 || div >= SAM9X5_USB_MAX_DIV)
> + return -EINVAL;
> +
> + tmp = pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_OHCIUSBDIV;
> + tmp |= (div - 1) << SAM9X5_USB_DIV_SHIFT;
> + pmc_write(pmc, AT91_PMC_USB, tmp);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops at91sam9x5_usb_ops = {
> + .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
> + .round_rate = at91sam9x5_clk_usb_round_rate,
> + .get_parent = at91sam9x5_clk_usb_get_parent,
> + .set_parent = at91sam9x5_clk_usb_set_parent,
> + .set_rate = at91sam9x5_clk_usb_set_rate,
> +};
> +
> +static int at91sam9n12_clk_usb_enable(struct clk_hw *hw)
> +{
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + pmc_write(pmc, AT91_PMC_USB,
> + pmc_read(pmc, AT91_PMC_USB) | AT91_PMC_USBS);
> + return 0;
> +}
> +
> +static void at91sam9n12_clk_usb_disable(struct clk_hw *hw)
> +{
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + pmc_write(pmc, AT91_PMC_USB,
> + pmc_read(pmc, AT91_PMC_USB) & ~AT91_PMC_USBS);
> +}
> +
> +static int at91sam9n12_clk_usb_is_enabled(struct clk_hw *hw)
> +{
> + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> +
> + return !!(pmc_read(pmc, AT91_PMC_USB) & AT91_PMC_USBS);
> +}
> +
> +static const struct clk_ops at91sam9n12_usb_ops = {
> + .enable = at91sam9n12_clk_usb_enable,
> + .disable = at91sam9n12_clk_usb_disable,
> + .is_enabled = at91sam9n12_clk_usb_is_enabled,
> + .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
> + .round_rate = at91sam9x5_clk_usb_round_rate,
> + .set_rate = at91sam9x5_clk_usb_set_rate,
> +};
> +
> +static struct clk * __init
> +at91sam9x5_clk_register_usb(struct at91_pmc *pmc, const char *name,
> + const char **parent_names, u8 num_parents)
> +{
> + struct at91sam9x5_clk_usb *usb;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + usb = kzalloc(sizeof(*usb), GFP_KERNEL);
> + if (!usb)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &at91sam9x5_usb_ops;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
> +
> + usb->hw.init = &init;
> + usb->pmc = pmc;
> +
> + clk = clk_register(NULL, &usb->hw);
> + if (IS_ERR(clk))
> + kfree(usb);
> +
> + return clk;
> +}
> +
> +static struct clk * __init
> +at91sam9n12_clk_register_usb(struct at91_pmc *pmc, const char *name,
> + const char *parent_name)
> +{
> + struct at91sam9x5_clk_usb *usb;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + usb = kzalloc(sizeof(*usb), GFP_KERNEL);
> + if (!usb)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &at91sam9n12_usb_ops;
> + init.parent_names = &parent_name;
> + init.num_parents = 1;
> + init.flags = CLK_SET_RATE_GATE;
> +
> + usb->hw.init = &init;
> + usb->pmc = pmc;
> +
> + clk = clk_register(NULL, &usb->hw);
> + if (IS_ERR(clk))
> + kfree(usb);
> +
> + return clk;
> +}
> +
> +static unsigned long at91rm9200_clk_usb_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> + u32 tmp;
> + u8 usbdiv;
> +
> + tmp = pmc_read(pmc, AT91_CKGR_PLLBR);
> + usbdiv = (tmp & AT91_PMC_USBDIV) >> RM9200_USB_DIV_SHIFT;
> + if (usb->divisors[usbdiv])
> + return parent_rate / usb->divisors[usbdiv];
> +
> + return 0;
> +}
> +
> +static long at91rm9200_clk_usb_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
> + unsigned long bestrate = 0;
> + int bestdiff = -1;
> + unsigned long tmprate;
> + int tmpdiff;
> + int i = 0;
> +
> + for (i = 0; i < 4; i++) {
> + if (!usb->divisors[i])
> + continue;
> + tmprate = *parent_rate / usb->divisors[i];
> + if (tmprate < rate)
> + tmpdiff = rate - tmprate;
> + else
> + tmpdiff = tmprate - rate;
> +
> + if (bestdiff < 0 || bestdiff > tmpdiff) {
> + bestrate = tmprate;
> + bestdiff = tmpdiff;
> + }
> +
> + if (!bestdiff)
> + break;
> + }
> +
> + return bestrate;
> +}
> +
> +static int at91rm9200_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + u32 tmp;
> + int i;
> + struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
> + struct at91_pmc *pmc = usb->pmc;
> + unsigned long div = parent_rate / rate;
> +
> + if (parent_rate % rate)
> + return -EINVAL;
> + for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) {
> + if (usb->divisors[i] == div) {
> + tmp = pmc_read(pmc, AT91_CKGR_PLLBR) &
> + ~AT91_PMC_USBDIV;
> + tmp |= i << RM9200_USB_DIV_SHIFT;
> + pmc_write(pmc, AT91_CKGR_PLLBR, tmp);
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +static const struct clk_ops at91rm9200_usb_ops = {
> + .recalc_rate = at91rm9200_clk_usb_recalc_rate,
> + .round_rate = at91rm9200_clk_usb_round_rate,
> + .set_rate = at91rm9200_clk_usb_set_rate,
> +};
> +
> +static struct clk * __init
> +at91rm9200_clk_register_usb(struct at91_pmc *pmc, const char *name,
> + const char *parent_name, const u32 *divisors)
> +{
> + struct at91rm9200_clk_usb *usb;
> + struct clk *clk = NULL;
> + struct clk_init_data init;
> +
> + usb = kzalloc(sizeof(*usb), GFP_KERNEL);
> + if (!usb)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &at91rm9200_usb_ops;
> + init.parent_names = &parent_name;
> + init.num_parents = 1;
> + init.flags = 0;
> +
> + usb->hw.init = &init;
> + usb->pmc = pmc;
> + memcpy(usb->divisors, divisors, sizeof(usb->divisors));
> +
> + clk = clk_register(NULL, &usb->hw);
> + if (IS_ERR(clk))
> + kfree(usb);
> +
> + return clk;
> +}
> +
> +void __init of_at91sam9x5_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + struct clk *clk;
> + int i;
> + int num_parents;
> + const char *parent_names[USB_SOURCE_MAX];
> + const char *name = np->name;
> +
> + num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells");
> + if (num_parents <= 0 || num_parents > USB_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_usb(pmc, name, parent_names, num_parents);
> + if (IS_ERR(clk))
> + return;
> +
> + of_clk_add_provider(np, of_clk_src_simple_get, clk);
> +}
> +
> +void __init of_at91sam9n12_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + struct clk *clk;
> + const char *parent_name;
> + const char *name = np->name;
> +
> + parent_name = of_clk_get_parent_name(np, 0);
> + if (!parent_name)
> + return;
> +
> + of_property_read_string(np, "clock-output-names", &name);
> +
> + clk = at91sam9n12_clk_register_usb(pmc, name, parent_name);
> + if (IS_ERR(clk))
> + return;
> +
> + of_clk_add_provider(np, of_clk_src_simple_get, clk);
> +}
> +
> +void __init of_at91rm9200_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc)
> +{
> + struct clk *clk;
> + const char *parent_name;
> + const char *name = np->name;
> + u32 divisors[4] = {0, 0, 0, 0};
> +
> + parent_name = of_clk_get_parent_name(np, 0);
> + if (!parent_name)
> + return;
> +
> + of_property_read_u32_array(np, "atmel,clk-divisors", divisors, 4);
> + if (!divisors[0])
> + return;
> +
> + of_property_read_string(np, "clock-output-names", &name);
> +
> + clk = at91rm9200_clk_register_usb(pmc, name, parent_name, divisors);
> + if (IS_ERR(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 8802999..1998aadab 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -279,6 +279,21 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
> .data = of_at91sam9x5_clk_utmi_setup,
> },
> #endif
> + /* USB clock */
> +#if defined(CONFIG_HAVE_AT91_USB_CLK)
> + {
> + .compatible = "atmel,at91rm9200-clk-usb",
> + .data = of_at91rm9200_clk_usb_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9x5-clk-usb",
> + .data = of_at91sam9x5_clk_usb_setup,
> + },
> + {
> + .compatible = "atmel,at91sam9n12-clk-usb",
> + .data = of_at91sam9n12_clk_usb_setup,
> + },
> +#endif
> { /*sentinel*/ }
> };
>
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 3ba9503..eba7334 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -96,4 +96,13 @@ extern void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np,
> struct at91_pmc *pmc);
> #endif
>
> +#if defined(CONFIG_HAVE_AT91_USB_CLK)
> +extern void __init of_at91rm9200_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9x5_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +extern void __init of_at91sam9n12_clk_usb_setup(struct device_node *np,
> + struct at91_pmc *pmc);
> +#endif
> +
> #endif /* __PMC_H_ */
>


--
Nicolas Ferre

2013-10-18 08:18:15

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v4 00/17] ARM: at91: move to common clk framework

On 11/10/2013 09:37, Boris BREZILLON :
> Hello,
>
> This patch series is the 4th version of the new at91 clock implementation
> (using common clk framework).

Mike, DT maintainers,

Can you have a look at this series converting the AT91 family to common
clock framework and associated Device Tree description?

Here is the thread on gmane:
http://thread.gmane.org/gmane.linux.ports.arm.kernel/272550

Mike, I think that you already reviewed a previous version of this
series and I guess that we have finalized it.

So gentlemen, It can be good for us if you can give us your "review" or
"acknowledgement" tags...

Thanks, best regards,


> Most of the clock provided by the PMC (Power Management Controller) are
> implemented :
> - main clock (main oscillator)
> - pll clocks
> - master clock
> - programmable clocks
> - utmi clock
> - peripheral clocks
> - system clocks
>
> This implementation is only compatible with device tree definition.
> The goal is to define the whole clock tree in the device tree.
>
> Could a dt maintainer take a look at these dt bindinds.
>
> This patch series is based on linus tree (but should apply correctly on
> linux-next) and has been tested on sama5d31ek.
>
> Best Regards,
> Boris
>
> Changes since v3:
> - simplify master clk implementation (drop set_rate/parent support)
> - fix bug in set_rate function of pll driver
> - fix coding style issues
> - define macros and constants where needed
> - remove peripheral id macro references
> - remove sam9g35 specific handling (sam9g35 = sam9x5)
> - rework main clk prepare function to handle automatic rate calculation
>
> Changes since v2:
> - fix several bugs in clk implementations
> - drop non-dt boards support
> - split the series to ease review and tests:
> * 1 patch series for new clk implementations (this series)
> * 1 patch series to move each at91 SoC to common clk framework (coming soon)
> - modify dt-bindings (add atmel,clk- prefix to atmel specific properties)
> - add clk macros for dt-bindings
> - add pmc framework (helper function to access pmc registers)
> - add interrupt support to enable passive wait in clk_prepare functions
>
> Changes since v1:
> - fix bugs in pll, programmable and system clock implementations
> (wrong bit position).
> - add usb clock configuration support (ohci and udc drivers +
> clk_lookup for non dt boards)
> - rework of the system clock interfaces (no need to define a parent clock,
> system clock is a gate with no rate info)
> - change system, peripheral and programmable clk dt bindings (1 master node
> and multiple child nodes each defining a system/peripheral or prog clock)
> - fix bugs in sama5 dt definition
>
> Boris BREZILLON (17):
> ARM: at91: move at91_pmc.h to include/linux/clk/at91_pmc.h
> ARM: at91: add Kconfig options for common clk support
> clk: at91: add PMC base support
> clk: at91: add PMC macro file for dt definitions
> clk: at91: add PMC main clock
> clk: at91: add PMC pll clocks
> clk: at91: add PMC master clock
> clk: at91: add PMC system clocks
> clk: at91: add PMC peripheral clocks
> clk: at91: add peripheral clk macros for peripheral clk dt bindings
> clk: at91: add PMC programmable clocks
> clk: at91: add PMC utmi clock
> clk: at91: add PMC usb clock
> clk: at91: add PMC smd clock
> clk: at91: add PMC clk device tree binding doc.
> ARM: at91: move pit timer to common clk framework
> ARM: at91: add new compatible strings for pmc driver
>
> .../devicetree/bindings/clock/at91-clock.txt | 328 ++++++++++++
> arch/arm/mach-at91/Kconfig | 44 ++
> arch/arm/mach-at91/Kconfig.non_dt | 6 +
> arch/arm/mach-at91/Makefile | 2 +-
> arch/arm/mach-at91/at91rm9200.c | 2 +-
> arch/arm/mach-at91/at91sam9260.c | 2 +-
> arch/arm/mach-at91/at91sam9261.c | 2 +-
> arch/arm/mach-at91/at91sam9263.c | 2 +-
> arch/arm/mach-at91/at91sam926x_time.c | 14 +-
> arch/arm/mach-at91/at91sam9g45.c | 2 +-
> arch/arm/mach-at91/at91sam9n12.c | 2 +-
> arch/arm/mach-at91/at91sam9rl.c | 2 +-
> arch/arm/mach-at91/at91sam9x5.c | 2 +-
> arch/arm/mach-at91/clock.c | 7 +-
> arch/arm/mach-at91/generic.h | 3 +-
> arch/arm/mach-at91/pm.c | 2 +-
> arch/arm/mach-at91/pm_slowclock.S | 2 +-
> arch/arm/mach-at91/sama5d3.c | 2 +-
> arch/arm/mach-at91/setup.c | 8 +-
> drivers/clk/Makefile | 1 +
> drivers/clk/at91/Makefile | 12 +
> drivers/clk/at91/clk-main.c | 189 +++++++
> drivers/clk/at91/clk-master.c | 278 ++++++++++
> drivers/clk/at91/clk-peripheral.c | 407 +++++++++++++++
> drivers/clk/at91/clk-pll.c | 539 ++++++++++++++++++++
> drivers/clk/at91/clk-plldiv.c | 137 +++++
> drivers/clk/at91/clk-programmable.c | 423 +++++++++++++++
> drivers/clk/at91/clk-smd.c | 173 +++++++
> drivers/clk/at91/clk-system.c | 193 +++++++
> drivers/clk/at91/clk-usb.c | 400 +++++++++++++++
> drivers/clk/at91/clk-utmi.c | 162 ++++++
> drivers/clk/at91/pmc.c | 372 ++++++++++++++
> drivers/clk/at91/pmc.h | 113 ++++
> drivers/usb/gadget/atmel_usba_udc.c | 2 +-
> include/dt-bindings/clk/at91.h | 28 +
> .../include/mach => include/linux/clk}/at91_pmc.h | 4 +-
> 36 files changed, 3847 insertions(+), 20 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/clock/at91-clock.txt
> create mode 100644 drivers/clk/at91/Makefile
> create mode 100644 drivers/clk/at91/clk-main.c
> create mode 100644 drivers/clk/at91/clk-master.c
> create mode 100644 drivers/clk/at91/clk-peripheral.c
> create mode 100644 drivers/clk/at91/clk-pll.c
> create mode 100644 drivers/clk/at91/clk-plldiv.c
> create mode 100644 drivers/clk/at91/clk-programmable.c
> create mode 100644 drivers/clk/at91/clk-smd.c
> create mode 100644 drivers/clk/at91/clk-system.c
> create mode 100644 drivers/clk/at91/clk-usb.c
> create mode 100644 drivers/clk/at91/clk-utmi.c
> create mode 100644 drivers/clk/at91/pmc.c
> create mode 100644 drivers/clk/at91/pmc.h
> create mode 100644 include/dt-bindings/clk/at91.h
> rename {arch/arm/mach-at91/include/mach => include/linux/clk}/at91_pmc.h (98%)
>


--
Nicolas Ferre