aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArnd Bergmann <arnd@arndb.de>2013-05-06 17:49:09 -0400
committerArnd Bergmann <arnd@arndb.de>2013-05-06 17:49:09 -0400
commit241a9871263f3114717c0ed416a1bd1d2415d1fb (patch)
treec41f73f7c38ae2120c3507cd2d487eeed160472a
parent2254c36ddeb87d4975d2e4709413dd275a913e83 (diff)
parent0682edaaa32c778ad2efac73fe3c8d9309e35991 (diff)
Merge branch 'exynos/pwm-clocksource' into late/multiplatform
This series from Tomasz Figa restores support for the pwm clocksource in Exynos, which was broken during the conversion of the platform to the common clk framework. The clocksource is only used in one board in the mainline kernel (universal_c210), and this makes it work for DT based probing as well as restoring the non-DT based case. * exynos/pwm-clocksource: ARM: dts: exynops4210: really add universal_c210 dts ARM: dts: exynos4210: Add basic dts file for universal_c210 board ARM: dts: exynos4: Add node for PWM device ARM: SAMSUNG: Do not register legacy timer interrupts on Exynos clocksource: samsung_pwm_timer: Work around rounding errors in clockevents core clocksource: samsung_pwm_timer: Correct programming of clock events clocksource: samsung_pwm_timer: Use proper clockevents max_delta clocksource: samsung_pwm_timer: Add support for non-DT platforms clocksource: samsung_pwm_timer: Drop unused samsung_pwm struct clocksource: samsung_pwm_timer: Keep all driver data in a structure clocksource: samsung_pwm_timer: Make PWM spinlock global clocksource: samsung_pwm_timer: Let platforms select the driver Documentation: Add device tree bindings for Samsung PWM timers clocksource: add samsung pwm timer driver Conflicts: arch/arm/boot/dts/Makefile arch/arm/mach-exynos/common.c drivers/clocksource/Kconfig drivers/clocksource/Makefile Signed-off-by: Arnd Bergmann <arnd@arndb.de>
-rw-r--r--Documentation/devicetree/bindings/pwm/pwm-samsung.txt43
-rw-r--r--arch/arm/boot/dts/Makefile1
-rw-r--r--arch/arm/boot/dts/exynos4.dtsi8
-rw-r--r--arch/arm/boot/dts/exynos4210-universal_c210.dts352
-rw-r--r--arch/arm/mach-exynos/common.c15
-rw-r--r--arch/arm/plat-samsung/Kconfig4
-rw-r--r--drivers/clocksource/Kconfig9
-rw-r--r--drivers/clocksource/Makefile1
-rw-r--r--drivers/clocksource/samsung_pwm_timer.c494
-rw-r--r--include/clocksource/samsung_pwm.h36
10 files changed, 946 insertions, 17 deletions
diff --git a/Documentation/devicetree/bindings/pwm/pwm-samsung.txt b/Documentation/devicetree/bindings/pwm/pwm-samsung.txt
new file mode 100644
index 000000000000..ac67c687a327
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/pwm-samsung.txt
@@ -0,0 +1,43 @@
1* Samsung PWM timers
2
3Samsung SoCs contain PWM timer blocks which can be used for system clock source
4and clock event timers, as well as to drive SoC outputs with PWM signal. Each
5PWM timer block provides 5 PWM channels (not all of them can drive physical
6outputs - see SoC and board manual).
7
8Be aware that the clocksource driver supports only uniprocessor systems.
9
10Required properties:
11- compatible : should be one of following:
12 samsung,s3c2410-pwm - for 16-bit timers present on S3C24xx SoCs
13 samsung,s3c6400-pwm - for 32-bit timers present on S3C64xx SoCs
14 samsung,s5p6440-pwm - for 32-bit timers present on S5P64x0 SoCs
15 samsung,s5pc100-pwm - for 32-bit timers present on S5PC100, S5PV210,
16 Exynos4210 rev0 SoCs
17 samsung,exynos4210-pwm - for 32-bit timers present on Exynos4210,
18 Exynos4x12 and Exynos5250 SoCs
19- reg: base address and size of register area
20- interrupts: list of timer interrupts (one interrupt per timer, starting at
21 timer 0)
22- #pwm-cells: number of cells used for PWM specifier - must be 3
23 the specifier format is as follows:
24 - phandle to PWM controller node
25 - index of PWM channel (from 0 to 4)
26 - PWM signal period in nanoseconds
27 - bitmask of optional PWM flags:
28 0x1 - invert PWM signal
29
30Optional properties:
31- samsung,pwm-outputs: list of PWM channels used as PWM outputs on particular
32 platform - an array of up to 5 elements being indices of PWM channels
33 (from 0 to 4), the order does not matter.
34
35Example:
36 pwm@7f006000 {
37 compatible = "samsung,s3c6400-pwm";
38 reg = <0x7f006000 0x1000>;
39 interrupt-parent = <&vic0>;
40 interrupts = <23>, <24>, <25>, <27>, <28>;
41 samsung,pwm-outputs = <0>, <1>;
42 #pwm-cells = <3>;
43 }
diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index d3cd880d70b3..2d870f4e4e2b 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -42,6 +42,7 @@ dtb-$(CONFIG_ARCH_DOVE) += dove-cm-a510.dtb \
42dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \ 42dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \
43 exynos4210-smdkv310.dtb \ 43 exynos4210-smdkv310.dtb \
44 exynos4210-trats.dtb \ 44 exynos4210-trats.dtb \
45 exynos4210-universal_c210.dtb \
45 exynos4412-odroidx.dtb \ 46 exynos4412-odroidx.dtb \
46 exynos4412-smdk4412.dtb \ 47 exynos4412-smdk4412.dtb \
47 exynos4412-origen.dtb \ 48 exynos4412-origen.dtb \
diff --git a/arch/arm/boot/dts/exynos4.dtsi b/arch/arm/boot/dts/exynos4.dtsi
index 9ac47d51c407..b620e34fbd92 100644
--- a/arch/arm/boot/dts/exynos4.dtsi
+++ b/arch/arm/boot/dts/exynos4.dtsi
@@ -316,6 +316,14 @@
316 status = "disabled"; 316 status = "disabled";
317 }; 317 };
318 318
319 pwm@139D0000 {
320 compatible = "samsung,exynos4210-pwm";
321 reg = <0x139D0000 0x1000>;
322 interrupts = <0 37 0>, <0 38 0>, <0 39 0>, <0 40 0>, <0 41 0>;
323 #pwm-cells = <2>;
324 status = "disabled";
325 };
326
319 amba { 327 amba {
320 #address-cells = <1>; 328 #address-cells = <1>;
321 #size-cells = <1>; 329 #size-cells = <1>;
diff --git a/arch/arm/boot/dts/exynos4210-universal_c210.dts b/arch/arm/boot/dts/exynos4210-universal_c210.dts
new file mode 100644
index 000000000000..345cdb51dcb7
--- /dev/null
+++ b/arch/arm/boot/dts/exynos4210-universal_c210.dts
@@ -0,0 +1,352 @@
1/*
2 * Samsung's Exynos4210 based Universal C210 board device tree source
3 *
4 * Copyright (c) 2012-2013 Samsung Electronics Co., Ltd.
5 * http://www.samsung.com
6 *
7 * Device tree source file for Samsung's Universal C210 board which is based on
8 * Samsung's Exynos4210 rev0 SoC.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13*/
14
15/dts-v1/;
16/include/ "exynos4210.dtsi"
17
18/ {
19 model = "Samsung Universal C210 based on Exynos4210 rev0";
20 compatible = "samsung,universal_c210", "samsung,exynos4210";
21
22 memory {
23 reg = <0x40000000 0x10000000
24 0x50000000 0x10000000>;
25 };
26
27 chosen {
28 bootargs = "console=ttySAC2,115200N8 root=/dev/mmcblk0p5 rw rootwait earlyprintk panic=5 maxcpus=1";
29 };
30
31 mct@10050000 {
32 compatible = "none";
33 };
34
35 fixed-rate-clocks {
36 xxti {
37 compatible = "samsung,clock-xxti";
38 clock-frequency = <0>;
39 };
40
41 xusbxti {
42 compatible = "samsung,clock-xusbxti";
43 clock-frequency = <24000000>;
44 };
45 };
46
47 vemmc_reg: voltage-regulator {
48 compatible = "regulator-fixed";
49 regulator-name = "VMEM_VDD_2_8V";
50 regulator-min-microvolt = <2800000>;
51 regulator-max-microvolt = <2800000>;
52 gpio = <&gpe1 3 0>;
53 enable-active-high;
54 };
55
56 sdhci_emmc: sdhci@12510000 {
57 bus-width = <8>;
58 non-removable;
59 pinctrl-0 = <&sd0_clk &sd0_cmd &sd0_bus8>;
60 pinctrl-names = "default";
61 vmmc-supply = <&vemmc_reg>;
62 status = "okay";
63 };
64
65 serial@13800000 {
66 status = "okay";
67 };
68
69 serial@13810000 {
70 status = "okay";
71 };
72
73 serial@13820000 {
74 status = "okay";
75 };
76
77 serial@13830000 {
78 status = "okay";
79 };
80
81 gpio-keys {
82 compatible = "gpio-keys";
83
84 vol-up-key {
85 gpios = <&gpx2 0 1>;
86 linux,code = <115>;
87 label = "volume up";
88 debounce-interval = <1>;
89 };
90
91 vol-down-key {
92 gpios = <&gpx2 1 1>;
93 linux,code = <114>;
94 label = "volume down";
95 debounce-interval = <1>;
96 };
97
98 config-key {
99 gpios = <&gpx2 2 1>;
100 linux,code = <171>;
101 label = "config";
102 debounce-interval = <1>;
103 gpio-key,wakeup;
104 };
105
106 camera-key {
107 gpios = <&gpx2 3 1>;
108 linux,code = <212>;
109 label = "camera";
110 debounce-interval = <1>;
111 };
112
113 power-key {
114 gpios = <&gpx2 7 1>;
115 linux,code = <116>;
116 label = "power";
117 debounce-interval = <1>;
118 gpio-key,wakeup;
119 };
120
121 ok-key {
122 gpios = <&gpx3 5 1>;
123 linux,code = <352>;
124 label = "ok";
125 debounce-interval = <1>;
126 };
127 };
128
129 tsp_reg: voltage-regulator {
130 compatible = "regulator-fixed";
131 regulator-name = "TSP_2_8V";
132 regulator-min-microvolt = <2800000>;
133 regulator-max-microvolt = <2800000>;
134 gpio = <&gpe2 3 0>;
135 enable-active-high;
136 };
137
138 i2c@13890000 {
139 samsung,i2c-sda-delay = <100>;
140 samsung,i2c-slave-addr = <0x10>;
141 samsung,i2c-max-bus-freq = <100000>;
142 pinctrl-0 = <&i2c3_bus>;
143 pinctrl-names = "default";
144 status = "okay";
145
146 tsp@4a {
147 /* TBD: Atmel maXtouch touchscreen */
148 reg = <0x4a>;
149 };
150 };
151
152 i2c@138B0000 {
153 samsung,i2c-sda-delay = <100>;
154 samsung,i2c-slave-addr = <0x10>;
155 samsung,i2c-max-bus-freq = <100000>;
156 pinctrl-0 = <&i2c5_bus>;
157 pinctrl-names = "default";
158 status = "okay";
159
160 vdd_arm_reg: pmic@60 {
161 compatible = "maxim,max8952";
162 reg = <0x60>;
163
164 max8952,vid-gpios = <&gpx0 3 0>, <&gpx0 4 0>;
165 max8952,default-mode = <0>;
166 max8952,dvs-mode-microvolt = <1250000>, <1200000>,
167 <1050000>, <950000>;
168 max8952,sync-freq = <0>;
169 max8952,ramp-speed = <0>;
170
171 regulator-name = "vdd_arm";
172 regulator-min-microvolt = <770000>;
173 regulator-max-microvolt = <1400000>;
174 regulator-always-on;
175 regulator-boot-on;
176 };
177
178 pmic@66 {
179 compatible = "national,lp3974";
180 reg = <0x66>;
181
182 max8998,pmic-buck1-default-dvs-idx = <0>;
183 max8998,pmic-buck1-dvs-gpios = <&gpx0 5 0>,
184 <&gpx0 6 0>;
185 max8998,pmic-buck1-dvs-voltage = <1100000>, <1000000>,
186 <1100000>, <1000000>;
187
188 max8998,pmic-buck2-default-dvs-idx = <0>;
189 max8998,pmic-buck2-dvs-gpio = <&gpe2 0 0>;
190 max8998,pmic-buck2-dvs-voltage = <1200000>, <1100000>;
191
192 regulators {
193 ldo2_reg: LDO2 {
194 regulator-name = "VALIVE_1.2V";
195 regulator-min-microvolt = <1200000>;
196 regulator-max-microvolt = <1200000>;
197 regulator-always-on;
198 };
199
200 ldo3_reg: LDO3 {
201 regulator-name = "VUSB+MIPI_1.1V";
202 regulator-min-microvolt = <1100000>;
203 regulator-max-microvolt = <1100000>;
204 };
205
206 ldo4_reg: LDO4 {
207 regulator-name = "VADC_3.3V";
208 regulator-min-microvolt = <3300000>;
209 regulator-max-microvolt = <3300000>;
210 };
211
212 ldo5_reg: LDO5 {
213 regulator-name = "VTF_2.8V";
214 regulator-min-microvolt = <2800000>;
215 regulator-max-microvolt = <2800000>;
216 };
217
218 ldo6_reg: LDO6 {
219 regulator-name = "LDO6";
220 regulator-min-microvolt = <2000000>;
221 regulator-max-microvolt = <2000000>;
222 };
223
224 ldo7_reg: LDO7 {
225 regulator-name = "VLCD+VMIPI_1.8V";
226 regulator-min-microvolt = <1800000>;
227 regulator-max-microvolt = <1800000>;
228 };
229
230 ldo8_reg: LDO8 {
231 regulator-name = "VUSB+VDAC_3.3V";
232 regulator-min-microvolt = <3300000>;
233 regulator-max-microvolt = <3300000>;
234 };
235
236 ldo9_reg: LDO9 {
237 regulator-name = "VCC_2.8V";
238 regulator-min-microvolt = <2800000>;
239 regulator-max-microvolt = <2800000>;
240 regulator-always-on;
241 };
242
243 ldo10_reg: LDO10 {
244 regulator-name = "VPLL_1.1V";
245 regulator-min-microvolt = <1100000>;
246 regulator-max-microvolt = <1100000>;
247 regulator-boot-on;
248 regulator-always-on;
249 };
250
251 ldo11_reg: LDO11 {
252 regulator-name = "CAM_AF_3.3V";
253 regulator-min-microvolt = <3300000>;
254 regulator-max-microvolt = <3300000>;
255 };
256
257 ldo12_reg: LDO12 {
258 regulator-name = "PS_2.8V";
259 regulator-min-microvolt = <2800000>;
260 regulator-max-microvolt = <2800000>;
261 };
262
263 ldo13_reg: LDO13 {
264 regulator-name = "VHIC_1.2V";
265 regulator-min-microvolt = <1200000>;
266 regulator-max-microvolt = <1200000>;
267 };
268
269 ldo14_reg: LDO14 {
270 regulator-name = "CAM_I_HOST_1.8V";
271 regulator-min-microvolt = <1800000>;
272 regulator-max-microvolt = <1800000>;
273 };
274
275 ldo15_reg: LDO15 {
276 regulator-name = "CAM_S_DIG+FM33_CORE_1.2V";
277 regulator-min-microvolt = <1200000>;
278 regulator-max-microvolt = <1200000>;
279 };
280
281 ldo16_reg: LDO16 {
282 regulator-name = "CAM_S_ANA_2.8V";
283 regulator-min-microvolt = <2800000>;
284 regulator-max-microvolt = <2800000>;
285 };
286
287 ldo17_reg: LDO17 {
288 regulator-name = "VCC_3.0V_LCD";
289 regulator-min-microvolt = <3000000>;
290 regulator-max-microvolt = <3000000>;
291 };
292
293 buck1_reg: BUCK1 {
294 regulator-name = "VINT_1.1V";
295 regulator-min-microvolt = <750000>;
296 regulator-max-microvolt = <1500000>;
297 regulator-boot-on;
298 regulator-always-on;
299 };
300
301 buck2_reg: BUCK2 {
302 regulator-name = "VG3D_1.1V";
303 regulator-min-microvolt = <750000>;
304 regulator-max-microvolt = <1500000>;
305 regulator-boot-on;
306 };
307
308 buck3_reg: BUCK3 {
309 regulator-name = "VCC_1.8V";
310 regulator-min-microvolt = <1800000>;
311 regulator-max-microvolt = <1800000>;
312 regulator-always-on;
313 };
314
315 buck4_reg: BUCK4 {
316 regulator-name = "VMEM_1.2V";
317 regulator-min-microvolt = <1200000>;
318 regulator-max-microvolt = <1200000>;
319 regulator-always-on;
320 };
321
322 ap32khz_reg: EN32KHz-AP {
323 regulator-name = "32KHz AP";
324 regulator-always-on;
325 };
326
327 cp32khz_reg: EN32KHz-CP {
328 regulator-name = "32KHz CP";
329 };
330
331 vichg_reg: ENVICHG {
332 regulator-name = "VICHG";
333 };
334
335 safeout1_reg: ESAFEOUT1 {
336 regulator-name = "SAFEOUT1";
337 regulator-always-on;
338 };
339
340 safeout2_reg: ESAFEOUT2 {
341 regulator-name = "SAFEOUT2";
342 regulator-boot-on;
343 };
344 };
345 };
346 };
347
348 pwm@139D0000 {
349 compatible = "samsung,s5p6440-pwm";
350 status = "okay";
351 };
352};
diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c
index 9208079d5d52..4bc1c49c69f1 100644
--- a/arch/arm/mach-exynos/common.c
+++ b/arch/arm/mach-exynos/common.c
@@ -449,13 +449,6 @@ void __init exynos4_init_irq(void)
449 if (!of_have_populated_dt()) 449 if (!of_have_populated_dt())
450 combiner_init(S5P_VA_COMBINER_BASE, NULL, 450 combiner_init(S5P_VA_COMBINER_BASE, NULL,
451 max_combiner_nr(), COMBINER_IRQ(0, 0)); 451 max_combiner_nr(), COMBINER_IRQ(0, 0));
452
453 /*
454 * The parameters of s5p_init_irq() are for VIC init.
455 * Theses parameters should be NULL and 0 because EXYNOS4
456 * uses GIC instead of VIC.
457 */
458 s5p_init_irq(NULL, 0);
459} 452}
460 453
461void __init exynos5_init_irq(void) 454void __init exynos5_init_irq(void)
@@ -463,14 +456,6 @@ void __init exynos5_init_irq(void)
463#ifdef CONFIG_OF 456#ifdef CONFIG_OF
464 irqchip_init(); 457 irqchip_init();
465#endif 458#endif
466 /*
467 * The parameters of s5p_init_irq() are for VIC init.
468 * Theses parameters should be NULL and 0 because EXYNOS4
469 * uses GIC instead of VIC.
470 */
471 if (!of_machine_is_compatible("samsung,exynos5440"))
472 s5p_init_irq(NULL, 0);
473
474 gic_arch_extn.irq_set_wake = s3c_irq_wake; 459 gic_arch_extn.irq_set_wake = s3c_irq_wake;
475} 460}
476 461
diff --git a/arch/arm/plat-samsung/Kconfig b/arch/arm/plat-samsung/Kconfig
index 77dd30af32f5..53b2d59ca252 100644
--- a/arch/arm/plat-samsung/Kconfig
+++ b/arch/arm/plat-samsung/Kconfig
@@ -105,9 +105,9 @@ config SAMSUNG_IRQ_VIC_TIMER
105 Internal configuration to build the VIC timer interrupt code. 105 Internal configuration to build the VIC timer interrupt code.
106 106
107config S5P_IRQ 107config S5P_IRQ
108 def_bool (ARCH_S5P64X0 || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_EXYNOS) 108 def_bool (ARCH_S5P64X0 || ARCH_S5PC100 || ARCH_S5PV210)
109 help 109 help
110 Support common interrup part for ARCH_S5P and ARCH_EXYNOS SoCs 110 Support common interrupt part for ARCH_S5P SoCs
111 111
112config S5P_EXT_INT 112config S5P_EXT_INT
113 bool 113 bool
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 73fcddb8314d..5d9bab2b5012 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -75,3 +75,12 @@ config CLKSRC_EXYNOS_MCT
75 def_bool y if ARCH_EXYNOS 75 def_bool y if ARCH_EXYNOS
76 help 76 help
77 Support for Multi Core Timer controller on Exynos SoCs. 77 Support for Multi Core Timer controller on Exynos SoCs.
78
79config CLKSRC_SAMSUNG_PWM
80 bool
81 select CLKSRC_MMIO
82 help
83 This is a new clocksource driver for the PWM timer found in
84 Samsung S3C, S5P and Exynos SoCs, replacing an earlier driver
85 for all devicetree enabled platforms. This driver will be
86 needed only on systems that do not have the Exynos MCT available.
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index cd1f09cbd61a..2289f0cb4c4c 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra20_timer.o
21obj-$(CONFIG_VT8500_TIMER) += vt8500_timer.o 21obj-$(CONFIG_VT8500_TIMER) += vt8500_timer.o
22obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o 22obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o
23obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o 23obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o
24obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o
24 25
25obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o 26obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o
26obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o 27obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o
diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c
new file mode 100644
index 000000000000..0234c8d2c8f2
--- /dev/null
+++ b/drivers/clocksource/samsung_pwm_timer.c
@@ -0,0 +1,494 @@
1/*
2 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
3 * http://www.samsung.com/
4 *
5 * samsung - Common hr-timer support (s3c and s5p)
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10*/
11
12#include <linux/interrupt.h>
13#include <linux/irq.h>
14#include <linux/err.h>
15#include <linux/clk.h>
16#include <linux/clockchips.h>
17#include <linux/list.h>
18#include <linux/module.h>
19#include <linux/of.h>
20#include <linux/of_address.h>
21#include <linux/of_irq.h>
22#include <linux/platform_device.h>
23#include <linux/slab.h>
24
25#include <clocksource/samsung_pwm.h>
26
27#include <asm/sched_clock.h>
28
29/*
30 * Clocksource driver
31 */
32
33#define REG_TCFG0 0x00
34#define REG_TCFG1 0x04
35#define REG_TCON 0x08
36#define REG_TINT_CSTAT 0x44
37
38#define REG_TCNTB(chan) (0x0c + 12 * (chan))
39#define REG_TCMPB(chan) (0x10 + 12 * (chan))
40
41#define TCFG0_PRESCALER_MASK 0xff
42#define TCFG0_PRESCALER1_SHIFT 8
43
44#define TCFG1_SHIFT(x) ((x) * 4)
45#define TCFG1_MUX_MASK 0xf
46
47#define TCON_START(chan) (1 << (4 * (chan) + 0))
48#define TCON_MANUALUPDATE(chan) (1 << (4 * (chan) + 1))
49#define TCON_INVERT(chan) (1 << (4 * (chan) + 2))
50#define TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3))
51
52DEFINE_SPINLOCK(samsung_pwm_lock);
53EXPORT_SYMBOL(samsung_pwm_lock);
54
55struct samsung_pwm_clocksource {
56 void __iomem *base;
57 unsigned int irq[SAMSUNG_PWM_NUM];
58 struct samsung_pwm_variant variant;
59
60 struct clk *timerclk;
61
62 unsigned int event_id;
63 unsigned int source_id;
64 unsigned int tcnt_max;
65 unsigned int tscaler_div;
66 unsigned int tdiv;
67
68 unsigned long clock_count_per_tick;
69};
70
71static struct samsung_pwm_clocksource pwm;
72
73static void samsung_timer_set_prescale(unsigned int channel, u16 prescale)
74{
75 unsigned long flags;
76 u8 shift = 0;
77 u32 reg;
78
79 if (channel >= 2)
80 shift = TCFG0_PRESCALER1_SHIFT;
81
82 spin_lock_irqsave(&samsung_pwm_lock, flags);
83
84 reg = readl(pwm.base + REG_TCFG0);
85 reg &= ~(TCFG0_PRESCALER_MASK << shift);
86 reg |= (prescale - 1) << shift;
87 writel(reg, pwm.base + REG_TCFG0);
88
89 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
90}
91
92static void samsung_timer_set_divisor(unsigned int channel, u8 divisor)
93{
94 u8 shift = TCFG1_SHIFT(channel);
95 unsigned long flags;
96 u32 reg;
97 u8 bits;
98
99 bits = (fls(divisor) - 1) - pwm.variant.div_base;
100
101 spin_lock_irqsave(&samsung_pwm_lock, flags);
102
103 reg = readl(pwm.base + REG_TCFG1);
104 reg &= ~(TCFG1_MUX_MASK << shift);
105 reg |= bits << shift;
106 writel(reg, pwm.base + REG_TCFG1);
107
108 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
109}
110
111static void samsung_time_stop(unsigned int channel)
112{
113 unsigned long tcon;
114 unsigned long flags;
115
116 if (channel > 0)
117 ++channel;
118
119 spin_lock_irqsave(&samsung_pwm_lock, flags);
120
121 tcon = __raw_readl(pwm.base + REG_TCON);
122 tcon &= ~TCON_START(channel);
123 __raw_writel(tcon, pwm.base + REG_TCON);
124
125 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
126}
127
128static void samsung_time_setup(unsigned int channel, unsigned long tcnt)
129{
130 unsigned long tcon;
131 unsigned long flags;
132 unsigned int tcon_chan = channel;
133
134 if (tcon_chan > 0)
135 ++tcon_chan;
136
137 spin_lock_irqsave(&samsung_pwm_lock, flags);
138
139 tcon = __raw_readl(pwm.base + REG_TCON);
140
141 tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan));
142 tcon |= TCON_MANUALUPDATE(tcon_chan);
143
144 __raw_writel(tcnt, pwm.base + REG_TCNTB(channel));
145 __raw_writel(tcnt, pwm.base + REG_TCMPB(channel));
146 __raw_writel(tcon, pwm.base + REG_TCON);
147
148 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
149}
150
151static void samsung_time_start(unsigned int channel, bool periodic)
152{
153 unsigned long tcon;
154 unsigned long flags;
155
156 if (channel > 0)
157 ++channel;
158
159 spin_lock_irqsave(&samsung_pwm_lock, flags);
160
161 tcon = __raw_readl(pwm.base + REG_TCON);
162
163 tcon &= ~TCON_MANUALUPDATE(channel);
164 tcon |= TCON_START(channel);
165
166 if (periodic)
167 tcon |= TCON_AUTORELOAD(channel);
168 else
169 tcon &= ~TCON_AUTORELOAD(channel);
170
171 __raw_writel(tcon, pwm.base + REG_TCON);
172
173 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
174}
175
176static int samsung_set_next_event(unsigned long cycles,
177 struct clock_event_device *evt)
178{
179 /*
180 * This check is needed to account for internal rounding
181 * errors inside clockevents core, which might result in
182 * passing cycles = 0, which in turn would not generate any
183 * timer interrupt and hang the system.
184 *
185 * Another solution would be to set up the clockevent device
186 * with min_delta = 2, but this would unnecessarily increase
187 * the minimum sleep period.
188 */
189 if (!cycles)
190 cycles = 1;
191
192 samsung_time_setup(pwm.event_id, cycles);
193 samsung_time_start(pwm.event_id, false);
194
195 return 0;
196}
197
198static void samsung_timer_resume(void)
199{
200 /* event timer restart */
201 samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1);
202 samsung_time_start(pwm.event_id, true);
203
204 /* source timer restart */
205 samsung_time_setup(pwm.source_id, pwm.tcnt_max);
206 samsung_time_start(pwm.source_id, true);
207}
208
209static void samsung_set_mode(enum clock_event_mode mode,
210 struct clock_event_device *evt)
211{
212 samsung_time_stop(pwm.event_id);
213
214 switch (mode) {
215 case CLOCK_EVT_MODE_PERIODIC:
216 samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1);
217 samsung_time_start(pwm.event_id, true);
218 break;
219
220 case CLOCK_EVT_MODE_ONESHOT:
221 break;
222
223 case CLOCK_EVT_MODE_UNUSED:
224 case CLOCK_EVT_MODE_SHUTDOWN:
225 break;
226
227 case CLOCK_EVT_MODE_RESUME:
228 samsung_timer_resume();
229 break;
230 }
231}
232
233static struct clock_event_device time_event_device = {
234 .name = "samsung_event_timer",
235 .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
236 .rating = 200,
237 .set_next_event = samsung_set_next_event,
238 .set_mode = samsung_set_mode,
239};
240
241static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id)
242{
243 struct clock_event_device *evt = dev_id;
244
245 if (pwm.variant.has_tint_cstat) {
246 u32 mask = (1 << pwm.event_id);
247 writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);
248 }
249
250 evt->event_handler(evt);
251
252 return IRQ_HANDLED;
253}
254
255static struct irqaction samsung_clock_event_irq = {
256 .name = "samsung_time_irq",
257 .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
258 .handler = samsung_clock_event_isr,
259 .dev_id = &time_event_device,
260};
261
262static void __init samsung_clockevent_init(void)
263{
264 unsigned long pclk;
265 unsigned long clock_rate;
266 unsigned int irq_number;
267
268 pclk = clk_get_rate(pwm.timerclk);
269
270 samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div);
271 samsung_timer_set_divisor(pwm.event_id, pwm.tdiv);
272
273 clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);
274 pwm.clock_count_per_tick = clock_rate / HZ;
275
276 time_event_device.cpumask = cpumask_of(0);
277 clockevents_config_and_register(&time_event_device,
278 clock_rate, 1, pwm.tcnt_max);
279
280 irq_number = pwm.irq[pwm.event_id];
281 setup_irq(irq_number, &samsung_clock_event_irq);
282
283 if (pwm.variant.has_tint_cstat) {
284 u32 mask = (1 << pwm.event_id);
285 writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);
286 }
287}
288
289static void __iomem *samsung_timer_reg(void)
290{
291 switch (pwm.source_id) {
292 case 0:
293 case 1:
294 case 2:
295 case 3:
296 return pwm.base + pwm.source_id * 0x0c + 0x14;
297
298 case 4:
299 return pwm.base + 0x40;
300
301 default:
302 BUG();
303 }
304}
305
306/*
307 * Override the global weak sched_clock symbol with this
308 * local implementation which uses the clocksource to get some
309 * better resolution when scheduling the kernel. We accept that
310 * this wraps around for now, since it is just a relative time
311 * stamp. (Inspired by U300 implementation.)
312 */
313static u32 notrace samsung_read_sched_clock(void)
314{
315 void __iomem *reg = samsung_timer_reg();
316
317 if (!reg)
318 return 0;
319
320 return ~__raw_readl(reg);
321}
322
323static void __init samsung_clocksource_init(void)
324{
325 void __iomem *reg = samsung_timer_reg();
326 unsigned long pclk;
327 unsigned long clock_rate;
328 int ret;
329
330 pclk = clk_get_rate(pwm.timerclk);
331
332 samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div);
333 samsung_timer_set_divisor(pwm.source_id, pwm.tdiv);
334
335 clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);
336
337 samsung_time_setup(pwm.source_id, pwm.tcnt_max);
338 samsung_time_start(pwm.source_id, true);
339
340 setup_sched_clock(samsung_read_sched_clock,
341 pwm.variant.bits, clock_rate);
342
343 ret = clocksource_mmio_init(reg, "samsung_clocksource_timer",
344 clock_rate, 250, pwm.variant.bits,
345 clocksource_mmio_readl_down);
346 if (ret)
347 panic("samsung_clocksource_timer: can't register clocksource\n");
348}
349
350static void __init samsung_timer_resources(void)
351{
352 pwm.timerclk = clk_get(NULL, "timers");
353 if (IS_ERR(pwm.timerclk))
354 panic("failed to get timers clock for timer");
355
356 clk_prepare_enable(pwm.timerclk);
357
358 pwm.tcnt_max = (1UL << pwm.variant.bits) - 1;
359 if (pwm.variant.bits == 16) {
360 pwm.tscaler_div = 25;
361 pwm.tdiv = 2;
362 } else {
363 pwm.tscaler_div = 2;
364 pwm.tdiv = 1;
365 }
366}
367
368/*
369 * PWM master driver
370 */
371static void __init _samsung_pwm_clocksource_init(void)
372{
373 u8 mask;
374 int channel;
375
376 mask = ~pwm.variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1);
377 channel = fls(mask) - 1;
378 if (channel < 0)
379 panic("failed to find PWM channel for clocksource");
380 pwm.source_id = channel;
381
382 mask &= ~(1 << channel);
383 channel = fls(mask) - 1;
384 if (channel < 0)
385 panic("failed to find PWM channel for clock event");
386 pwm.event_id = channel;
387
388 samsung_timer_resources();
389 samsung_clockevent_init();
390 samsung_clocksource_init();
391}
392
393void __init samsung_pwm_clocksource_init(void __iomem *base,
394 unsigned int *irqs, struct samsung_pwm_variant *variant)
395{
396 pwm.base = base;
397 memcpy(&pwm.variant, variant, sizeof(pwm.variant));
398 memcpy(pwm.irq, irqs, SAMSUNG_PWM_NUM * sizeof(*irqs));
399
400 _samsung_pwm_clocksource_init();
401}
402
403#ifdef CONFIG_CLKSRC_OF
404static void __init samsung_pwm_alloc(struct device_node *np,
405 const struct samsung_pwm_variant *variant)
406{
407 struct resource res;
408 struct property *prop;
409 const __be32 *cur;
410 u32 val;
411 int i;
412
413 memcpy(&pwm.variant, variant, sizeof(pwm.variant));
414 for (i = 0; i < SAMSUNG_PWM_NUM; ++i)
415 pwm.irq[i] = irq_of_parse_and_map(np, i);
416
417 of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) {
418 if (val >= SAMSUNG_PWM_NUM) {
419 pr_warning("%s: invalid channel index in samsung,pwm-outputs property\n",
420 __func__);
421 continue;
422 }
423 pwm.variant.output_mask |= 1 << val;
424 }
425
426 of_address_to_resource(np, 0, &res);
427 if (!request_mem_region(res.start,
428 resource_size(&res), "samsung-pwm")) {
429 pr_err("%s: failed to request IO mem region\n", __func__);
430 return;
431 }
432
433 pwm.base = ioremap(res.start, resource_size(&res));
434 if (!pwm.base) {
435 pr_err("%s: failed to map PWM registers\n", __func__);
436 release_mem_region(res.start, resource_size(&res));
437 return;
438 }
439
440 _samsung_pwm_clocksource_init();
441}
442
443static const struct samsung_pwm_variant s3c24xx_variant = {
444 .bits = 16,
445 .div_base = 1,
446 .has_tint_cstat = false,
447 .tclk_mask = (1 << 4),
448};
449
450static void __init s3c2410_pwm_clocksource_init(struct device_node *np)
451{
452 samsung_pwm_alloc(np, &s3c24xx_variant);
453}
454CLOCKSOURCE_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm", s3c2410_pwm_clocksource_init);
455
456static const struct samsung_pwm_variant s3c64xx_variant = {
457 .bits = 32,
458 .div_base = 0,
459 .has_tint_cstat = true,
460 .tclk_mask = (1 << 7) | (1 << 6) | (1 << 5),
461};
462
463static void __init s3c64xx_pwm_clocksource_init(struct device_node *np)
464{
465 samsung_pwm_alloc(np, &s3c64xx_variant);
466}
467CLOCKSOURCE_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm", s3c64xx_pwm_clocksource_init);
468
469static const struct samsung_pwm_variant s5p64x0_variant = {
470 .bits = 32,
471 .div_base = 0,
472 .has_tint_cstat = true,
473 .tclk_mask = 0,
474};
475
476static void __init s5p64x0_pwm_clocksource_init(struct device_node *np)
477{
478 samsung_pwm_alloc(np, &s5p64x0_variant);
479}
480CLOCKSOURCE_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm", s5p64x0_pwm_clocksource_init);
481
482static const struct samsung_pwm_variant s5p_variant = {
483 .bits = 32,
484 .div_base = 0,
485 .has_tint_cstat = true,
486 .tclk_mask = (1 << 5),
487};
488
489static void __init s5p_pwm_clocksource_init(struct device_node *np)
490{
491 samsung_pwm_alloc(np, &s5p_variant);
492}
493CLOCKSOURCE_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init);
494#endif
diff --git a/include/clocksource/samsung_pwm.h b/include/clocksource/samsung_pwm.h
new file mode 100644
index 000000000000..5c449c8199e9
--- /dev/null
+++ b/include/clocksource/samsung_pwm.h
@@ -0,0 +1,36 @@
1/*
2 * Copyright (C) 2013 Samsung Electronics Co., Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#ifndef __CLOCKSOURCE_SAMSUNG_PWM_H
17#define __CLOCKSOURCE_SAMSUNG_PWM_H
18
19#include <linux/spinlock.h>
20
21#define SAMSUNG_PWM_NUM 5
22
23extern spinlock_t samsung_pwm_lock;
24
25struct samsung_pwm_variant {
26 u8 bits;
27 u8 div_base;
28 u8 tclk_mask;
29 u8 output_mask;
30 bool has_tint_cstat;
31};
32
33void samsung_pwm_clocksource_init(void __iomem *base,
34 unsigned int *irqs, struct samsung_pwm_variant *variant);
35
36#endif /* __CLOCKSOURCE_SAMSUNG_PWM_H */