diff options
-rw-r--r-- | Documentation/devicetree/bindings/pwm/pwm-samsung.txt | 43 | ||||
-rw-r--r-- | arch/arm/boot/dts/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/boot/dts/exynos4.dtsi | 8 | ||||
-rw-r--r-- | arch/arm/boot/dts/exynos4210-universal_c210.dts | 352 | ||||
-rw-r--r-- | arch/arm/boot/dts/exynos4210.dtsi | 1 | ||||
-rw-r--r-- | arch/arm/boot/dts/exynos4212.dtsi | 9 | ||||
-rw-r--r-- | arch/arm/boot/dts/exynos4412.dtsi | 9 | ||||
-rw-r--r-- | arch/arm/mach-exynos/common.c | 35 | ||||
-rw-r--r-- | arch/arm/mach-exynos/common.h | 7 | ||||
-rw-r--r-- | arch/arm/plat-samsung/Kconfig | 4 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-exynos4.c | 93 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-exynos5250.c | 1 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-exynos5440.c | 1 | ||||
-rw-r--r-- | drivers/clk/samsung/clk.h | 2 | ||||
-rw-r--r-- | drivers/clocksource/Kconfig | 9 | ||||
-rw-r--r-- | drivers/clocksource/Makefile | 1 | ||||
-rw-r--r-- | drivers/clocksource/exynos_mct.c | 21 | ||||
-rw-r--r-- | drivers/clocksource/samsung_pwm_timer.c | 494 | ||||
-rw-r--r-- | drivers/irqchip/exynos-combiner.c | 125 | ||||
-rw-r--r-- | include/clocksource/samsung_pwm.h | 36 |
20 files changed, 1098 insertions, 154 deletions
diff --git a/Documentation/devicetree/bindings/pwm/pwm-samsung.txt b/Documentation/devicetree/bindings/pwm/pwm-samsung.txt new file mode 100644 index 00000000000..ac67c687a32 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm-samsung.txt @@ -0,0 +1,43 @@ +* Samsung PWM timers + +Samsung SoCs contain PWM timer blocks which can be used for system clock source +and clock event timers, as well as to drive SoC outputs with PWM signal. Each +PWM timer block provides 5 PWM channels (not all of them can drive physical +outputs - see SoC and board manual). + +Be aware that the clocksource driver supports only uniprocessor systems. + +Required properties: +- compatible : should be one of following: + samsung,s3c2410-pwm - for 16-bit timers present on S3C24xx SoCs + samsung,s3c6400-pwm - for 32-bit timers present on S3C64xx SoCs + samsung,s5p6440-pwm - for 32-bit timers present on S5P64x0 SoCs + samsung,s5pc100-pwm - for 32-bit timers present on S5PC100, S5PV210, + Exynos4210 rev0 SoCs + samsung,exynos4210-pwm - for 32-bit timers present on Exynos4210, + Exynos4x12 and Exynos5250 SoCs +- reg: base address and size of register area +- interrupts: list of timer interrupts (one interrupt per timer, starting at + timer 0) +- #pwm-cells: number of cells used for PWM specifier - must be 3 + the specifier format is as follows: + - phandle to PWM controller node + - index of PWM channel (from 0 to 4) + - PWM signal period in nanoseconds + - bitmask of optional PWM flags: + 0x1 - invert PWM signal + +Optional properties: +- samsung,pwm-outputs: list of PWM channels used as PWM outputs on particular + platform - an array of up to 5 elements being indices of PWM channels + (from 0 to 4), the order does not matter. + +Example: + pwm@7f006000 { + compatible = "samsung,s3c6400-pwm"; + reg = <0x7f006000 0x1000>; + interrupt-parent = <&vic0>; + interrupts = <23>, <24>, <25>, <27>, <28>; + samsung,pwm-outputs = <0>, <1>; + #pwm-cells = <3>; + } diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index 5f1f4dfd706..8562af4fe8f 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -49,6 +49,7 @@ dtb-$(CONFIG_ARCH_DOVE) += dove-cm-a510.dtb \ dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \ exynos4210-smdkv310.dtb \ exynos4210-trats.dtb \ + exynos4210-universal_c210.dtb \ exynos4412-odroidx.dtb \ exynos4412-smdk4412.dtb \ exynos4412-origen.dtb \ diff --git a/arch/arm/boot/dts/exynos4.dtsi b/arch/arm/boot/dts/exynos4.dtsi index 7cfbbd3b773..359694c7891 100644 --- a/arch/arm/boot/dts/exynos4.dtsi +++ b/arch/arm/boot/dts/exynos4.dtsi @@ -336,6 +336,14 @@ status = "disabled"; }; + pwm@139D0000 { + compatible = "samsung,exynos4210-pwm"; + reg = <0x139D0000 0x1000>; + interrupts = <0 37 0>, <0 38 0>, <0 39 0>, <0 40 0>, <0 41 0>; + #pwm-cells = <2>; + status = "disabled"; + }; + amba { #address-cells = <1>; #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 00000000000..345cdb51dcb --- /dev/null +++ b/arch/arm/boot/dts/exynos4210-universal_c210.dts @@ -0,0 +1,352 @@ +/* + * Samsung's Exynos4210 based Universal C210 board device tree source + * + * Copyright (c) 2012-2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Device tree source file for Samsung's Universal C210 board which is based on + * Samsung's Exynos4210 rev0 SoC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +/dts-v1/; +/include/ "exynos4210.dtsi" + +/ { + model = "Samsung Universal C210 based on Exynos4210 rev0"; + compatible = "samsung,universal_c210", "samsung,exynos4210"; + + memory { + reg = <0x40000000 0x10000000 + 0x50000000 0x10000000>; + }; + + chosen { + bootargs = "console=ttySAC2,115200N8 root=/dev/mmcblk0p5 rw rootwait earlyprintk panic=5 maxcpus=1"; + }; + + mct@10050000 { + compatible = "none"; + }; + + fixed-rate-clocks { + xxti { + compatible = "samsung,clock-xxti"; + clock-frequency = <0>; + }; + + xusbxti { + compatible = "samsung,clock-xusbxti"; + clock-frequency = <24000000>; + }; + }; + + vemmc_reg: voltage-regulator { + compatible = "regulator-fixed"; + regulator-name = "VMEM_VDD_2_8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + gpio = <&gpe1 3 0>; + enable-active-high; + }; + + sdhci_emmc: sdhci@12510000 { + bus-width = <8>; + non-removable; + pinctrl-0 = <&sd0_clk &sd0_cmd &sd0_bus8>; + pinctrl-names = "default"; + vmmc-supply = <&vemmc_reg>; + status = "okay"; + }; + + serial@13800000 { + status = "okay"; + }; + + serial@13810000 { + status = "okay"; + }; + + serial@13820000 { + status = "okay"; + }; + + serial@13830000 { + status = "okay"; + }; + + gpio-keys { + compatible = "gpio-keys"; + + vol-up-key { + gpios = <&gpx2 0 1>; + linux,code = <115>; + label = "volume up"; + debounce-interval = <1>; + }; + + vol-down-key { + gpios = <&gpx2 1 1>; + linux,code = <114>; + label = "volume down"; + debounce-interval = <1>; + }; + + config-key { + gpios = <&gpx2 2 1>; + linux,code = <171>; + label = "config"; + debounce-interval = <1>; + gpio-key,wakeup; + }; + + camera-key { + gpios = <&gpx2 3 1>; + linux,code = <212>; + label = "camera"; + debounce-interval = <1>; + }; + + power-key { + gpios = <&gpx2 7 1>; + linux,code = <116>; + label = "power"; + debounce-interval = <1>; + gpio-key,wakeup; + }; + + ok-key { + gpios = <&gpx3 5 1>; + linux,code = <352>; + label = "ok"; + debounce-interval = <1>; + }; + }; + + tsp_reg: voltage-regulator { + compatible = "regulator-fixed"; + regulator-name = "TSP_2_8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + gpio = <&gpe2 3 0>; + enable-active-high; + }; + + i2c@13890000 { + samsung,i2c-sda-delay = <100>; + samsung,i2c-slave-addr = <0x10>; + samsung,i2c-max-bus-freq = <100000>; + pinctrl-0 = <&i2c3_bus>; + pinctrl-names = "default"; + status = "okay"; + + tsp@4a { + /* TBD: Atmel maXtouch touchscreen */ + reg = <0x4a>; + }; + }; + + i2c@138B0000 { + samsung,i2c-sda-delay = <100>; + samsung,i2c-slave-addr = <0x10>; + samsung,i2c-max-bus-freq = <100000>; + pinctrl-0 = <&i2c5_bus>; + pinctrl-names = "default"; + status = "okay"; + + vdd_arm_reg: pmic@60 { + compatible = "maxim,max8952"; + reg = <0x60>; + + max8952,vid-gpios = <&gpx0 3 0>, <&gpx0 4 0>; + max8952,default-mode = <0>; + max8952,dvs-mode-microvolt = <1250000>, <1200000>, + <1050000>, <950000>; + max8952,sync-freq = <0>; + max8952,ramp-speed = <0>; + + regulator-name = "vdd_arm"; + regulator-min-microvolt = <770000>; + regulator-max-microvolt = <1400000>; + regulator-always-on; + regulator-boot-on; + }; + + pmic@66 { + compatible = "national,lp3974"; + reg = <0x66>; + + max8998,pmic-buck1-default-dvs-idx = <0>; + max8998,pmic-buck1-dvs-gpios = <&gpx0 5 0>, + <&gpx0 6 0>; + max8998,pmic-buck1-dvs-voltage = <1100000>, <1000000>, + <1100000>, <1000000>; + + max8998,pmic-buck2-default-dvs-idx = <0>; + max8998,pmic-buck2-dvs-gpio = <&gpe2 0 0>; + max8998,pmic-buck2-dvs-voltage = <1200000>, <1100000>; + + regulators { + ldo2_reg: LDO2 { + regulator-name = "VALIVE_1.2V"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + }; + + ldo3_reg: LDO3 { + regulator-name = "VUSB+MIPI_1.1V"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + }; + + ldo4_reg: LDO4 { + regulator-name = "VADC_3.3V"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + ldo5_reg: LDO5 { + regulator-name = "VTF_2.8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + ldo6_reg: LDO6 { + regulator-name = "LDO6"; + regulator-min-microvolt = <2000000>; + regulator-max-microvolt = <2000000>; + }; + + ldo7_reg: LDO7 { + regulator-name = "VLCD+VMIPI_1.8V"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + ldo8_reg: LDO8 { + regulator-name = "VUSB+VDAC_3.3V"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + ldo9_reg: LDO9 { + regulator-name = "VCC_2.8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + regulator-always-on; + }; + + ldo10_reg: LDO10 { + regulator-name = "VPLL_1.1V"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + regulator-boot-on; + regulator-always-on; + }; + + ldo11_reg: LDO11 { + regulator-name = "CAM_AF_3.3V"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + ldo12_reg: LDO12 { + regulator-name = "PS_2.8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + ldo13_reg: LDO13 { + regulator-name = "VHIC_1.2V"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + ldo14_reg: LDO14 { + regulator-name = "CAM_I_HOST_1.8V"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + ldo15_reg: LDO15 { + regulator-name = "CAM_S_DIG+FM33_CORE_1.2V"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + ldo16_reg: LDO16 { + regulator-name = "CAM_S_ANA_2.8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + ldo17_reg: LDO17 { + regulator-name = "VCC_3.0V_LCD"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + }; + + buck1_reg: BUCK1 { + regulator-name = "VINT_1.1V"; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <1500000>; + regulator-boot-on; + regulator-always-on; + }; + + buck2_reg: BUCK2 { + regulator-name = "VG3D_1.1V"; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <1500000>; + regulator-boot-on; + }; + + buck3_reg: BUCK3 { + regulator-name = "VCC_1.8V"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + buck4_reg: BUCK4 { + regulator-name = "VMEM_1.2V"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + }; + + ap32khz_reg: EN32KHz-AP { + regulator-name = "32KHz AP"; + regulator-always-on; + }; + + cp32khz_reg: EN32KHz-CP { + regulator-name = "32KHz CP"; + }; + + vichg_reg: ENVICHG { + regulator-name = "VICHG"; + }; + + safeout1_reg: ESAFEOUT1 { + regulator-name = "SAFEOUT1"; + regulator-always-on; + }; + + safeout2_reg: ESAFEOUT2 { + regulator-name = "SAFEOUT2"; + regulator-boot-on; + }; + }; + }; + }; + + pwm@139D0000 { + compatible = "samsung,s5p6440-pwm"; + status = "okay"; + }; +}; diff --git a/arch/arm/boot/dts/exynos4210.dtsi b/arch/arm/boot/dts/exynos4210.dtsi index 66e6b03bf35..54710de8290 100644 --- a/arch/arm/boot/dts/exynos4210.dtsi +++ b/arch/arm/boot/dts/exynos4210.dtsi @@ -41,6 +41,7 @@ }; combiner:interrupt-controller@10440000 { + samsung,combiner-nr = <16>; interrupts = <0 0 0>, <0 1 0>, <0 2 0>, <0 3 0>, <0 4 0>, <0 5 0>, <0 6 0>, <0 7 0>, <0 8 0>, <0 9 0>, <0 10 0>, <0 11 0>, diff --git a/arch/arm/boot/dts/exynos4212.dtsi b/arch/arm/boot/dts/exynos4212.dtsi index 36d4299789e..c0f60f49cea 100644 --- a/arch/arm/boot/dts/exynos4212.dtsi +++ b/arch/arm/boot/dts/exynos4212.dtsi @@ -26,6 +26,15 @@ cpu-offset = <0x8000>; }; + interrupt-controller@10440000 { + samsung,combiner-nr = <18>; + interrupts = <0 0 0>, <0 1 0>, <0 2 0>, <0 3 0>, + <0 4 0>, <0 5 0>, <0 6 0>, <0 7 0>, + <0 8 0>, <0 9 0>, <0 10 0>, <0 11 0>, + <0 12 0>, <0 13 0>, <0 14 0>, <0 15 0>, + <0 107 0>, <0 108 0>; + }; + mct@10050000 { compatible = "samsung,exynos4412-mct"; reg = <0x10050000 0x800>; diff --git a/arch/arm/boot/dts/exynos4412.dtsi b/arch/arm/boot/dts/exynos4412.dtsi index 7f428272fee..270b389e0a1 100644 --- a/arch/arm/boot/dts/exynos4412.dtsi +++ b/arch/arm/boot/dts/exynos4412.dtsi @@ -26,6 +26,15 @@ cpu-offset = <0x4000>; }; + interrupt-controller@10440000 { + samsung,combiner-nr = <20>; + interrupts = <0 0 0>, <0 1 0>, <0 2 0>, <0 3 0>, + <0 4 0>, <0 5 0>, <0 6 0>, <0 7 0>, + <0 8 0>, <0 9 0>, <0 10 0>, <0 11 0>, + <0 12 0>, <0 13 0>, <0 14 0>, <0 15 0>, + <0 107 0>, <0 108 0>, <0 48 0>, <0 42 0>; + }; + mct@10050000 { compatible = "samsung,exynos4412-mct"; reg = <0x10050000 0x800>; diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index d126f26dbbf..745e304ad0d 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -452,13 +452,26 @@ void __init exynos_init_time(void) } else { /* todo: remove after migrating legacy E4 platforms to dt */ #ifdef CONFIG_ARCH_EXYNOS4 - exynos4_clk_init(NULL); + exynos4_clk_init(NULL, !soc_is_exynos4210(), S5P_VA_CMU, readl(S5P_VA_CHIPID + 8) & 1); exynos4_clk_register_fixed_ext(xxti_f, xusbxti_f); #endif - mct_init(); + mct_init(S5P_VA_SYSTIMER, EXYNOS4_IRQ_MCT_G0, EXYNOS4_IRQ_MCT_L0, EXYNOS4_IRQ_MCT_L1); } } +static unsigned int max_combiner_nr(void) +{ + if (soc_is_exynos5250()) + return EXYNOS5_MAX_COMBINER_NR; + else if (soc_is_exynos4412()) + return EXYNOS4412_MAX_COMBINER_NR; + else if (soc_is_exynos4212()) + return EXYNOS4212_MAX_COMBINER_NR; + else + return EXYNOS4210_MAX_COMBINER_NR; +} + + void __init exynos4_init_irq(void) { unsigned int gic_bank_offset; @@ -473,14 +486,8 @@ void __init exynos4_init_irq(void) #endif if (!of_have_populated_dt()) - combiner_init(S5P_VA_COMBINER_BASE, NULL); - - /* - * The parameters of s5p_init_irq() are for VIC init. - * Theses parameters should be NULL and 0 because EXYNOS4 - * uses GIC instead of VIC. - */ - s5p_init_irq(NULL, 0); + combiner_init(S5P_VA_COMBINER_BASE, NULL, + max_combiner_nr(), COMBINER_IRQ(0, 0)); gic_arch_extn.irq_set_wake = s3c_irq_wake; } @@ -490,14 +497,6 @@ void __init exynos5_init_irq(void) #ifdef CONFIG_OF irqchip_init(); #endif - /* - * The parameters of s5p_init_irq() are for VIC init. - * Theses parameters should be NULL and 0 because EXYNOS4 - * uses GIC instead of VIC. - */ - if (!of_machine_is_compatible("samsung,exynos5440")) - s5p_init_irq(NULL, 0); - gic_arch_extn.irq_set_wake = s3c_irq_wake; } diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h index b17448c1a16..60dd35cc01a 100644 --- a/arch/arm/mach-exynos/common.h +++ b/arch/arm/mach-exynos/common.h @@ -14,7 +14,7 @@ #include <linux/of.h> -extern void mct_init(void); +void mct_init(void __iomem *base, int irq_g0, int irq_l0, int irq_l1); void exynos_init_time(void); extern unsigned long xxti_f, xusbxti_f; @@ -27,7 +27,7 @@ void exynos5_restart(char mode, const char *cmd); void exynos_init_late(void); /* ToDo: remove these after migrating legacy exynos4 platforms to dt */ -void exynos4_clk_init(struct device_node *np); +void exynos4_clk_init(struct device_node *np, int is_exynos4210, void __iomem *reg_base, unsigned long xom); void exynos4_clk_register_fixed_ext(unsigned long, unsigned long); void exynos_firmware_init(void); @@ -71,7 +71,8 @@ void exynos4212_register_clocks(void); #endif struct device_node; -void combiner_init(void __iomem *combiner_base, struct device_node *np); +void combiner_init(void __iomem *combiner_base, struct device_node *np, + unsigned int max_nr, int irq_base); extern struct smp_operations exynos_smp_ops; diff --git a/arch/arm/plat-samsung/Kconfig b/arch/arm/plat-samsung/Kconfig index 54d186106f9..f8ed2de0a67 100644 --- a/arch/arm/plat-samsung/Kconfig +++ b/arch/arm/plat-samsung/Kconfig @@ -93,9 +93,9 @@ config SAMSUNG_IRQ_VIC_TIMER Internal configuration to build the VIC timer interrupt code. config S5P_IRQ - def_bool (ARCH_S5P64X0 || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_EXYNOS) + def_bool (ARCH_S5P64X0 || ARCH_S5PC100 || ARCH_S5PV210) help - Support common interrup part for ARCH_S5P and ARCH_EXYNOS SoCs + Support common interrupt part for ARCH_S5P SoCs config S5P_EXT_INT bool diff --git a/drivers/clk/samsung/clk-exynos4.c b/drivers/clk/samsung/clk-exynos4.c index 71046694d9d..d0940e69d03 100644 --- a/drivers/clk/samsung/clk-exynos4.c +++ b/drivers/clk/samsung/clk-exynos4.c @@ -16,7 +16,6 @@ #include <linux/of.h> #include <linux/of_address.h> -#include <plat/cpu.h> #include "clk.h" #include "clk-pll.h" @@ -910,16 +909,6 @@ struct samsung_gate_clock exynos4x12_gate_clks[] __initdata = { CLK_IGNORE_UNUSED, 0), }; -#ifdef CONFIG_OF -static struct of_device_id exynos4_clk_ids[] __initdata = { - { .compatible = "samsung,exynos4210-clock", - .data = (void *)EXYNOS4210, }, - { .compatible = "samsung,exynos4412-clock", - .data = (void *)EXYNOS4X12, }, - { }, -}; -#endif - /* * The parent of the fin_pll clock is selected by the XOM[0] bit. This bit * resides in chipid register space, outside of the clock controller memory @@ -927,33 +916,40 @@ static struct of_device_id exynos4_clk_ids[] __initdata = { * controller is first remapped and the value of XOM[0] bit is read to * determine the parent clock. */ -static void __init exynos4_clk_register_finpll(void) +static unsigned long exynos4_get_xom(void) { - struct samsung_fixed_rate_clock fclk; + unsigned long xom = 0; + void __iomem *chipid_base; struct device_node *np; - struct clk *clk; - void __iomem *chipid_base = S5P_VA_CHIPID; - unsigned long xom, finpll_f = 24000000; - char *parent_name; np = of_find_compatible_node(NULL, NULL, "samsung,exynos4210-chipid"); - if (np) + if (np) { chipid_base = of_iomap(np, 0); - if (chipid_base) { - xom = readl(chipid_base + 8); - parent_name = xom & 1 ? "xusbxti" : "xxti"; - clk = clk_get(NULL, parent_name); - if (IS_ERR(clk)) { - pr_err("%s: failed to lookup parent clock %s, assuming " - "fin_pll clock frequency is 24MHz\n", __func__, - parent_name); - } else { - finpll_f = clk_get_rate(clk); - } + if (chipid_base) + xom = readl(chipid_base + 8); + + iounmap(chipid_base); + } + + return xom; +} + +static void __init exynos4_clk_register_finpll(unsigned long xom) +{ + struct samsung_fixed_rate_clock fclk; + struct clk *clk; + unsigned long finpll_f = 24000000; + char *parent_name; + + parent_name = xom & 1 ? "xusbxti" : "xxti"; + clk = clk_get(NULL, parent_name); + if (IS_ERR(clk)) { + pr_err("%s: failed to lookup parent clock %s, assuming " + "fin_pll clock frequency is 24MHz\n", __func__, + parent_name); } else { - pr_err("%s: failed to map chipid registers, assuming " - "fin_pll clock frequency is 24MHz\n", __func__); + finpll_f = clk_get_rate(clk); } fclk.id = fin_pll; @@ -963,8 +959,6 @@ static void __init exynos4_clk_register_finpll(void) fclk.fixed_rate = finpll_f; samsung_clk_register_fixed_rate(&fclk, 1); - if (np) - iounmap(chipid_base); } /* @@ -988,28 +982,14 @@ static __initdata struct of_device_id ext_clk_match[] = { }; /* register exynos4 clocks */ -void __init exynos4_clk_init(struct device_node *np) +void __init exynos4_clk_init(struct device_node *np, enum exynos4_soc exynos4_soc, void __iomem *reg_base, unsigned long xom) { - void __iomem *reg_base; struct clk *apll, *mpll, *epll, *vpll; - u32 exynos4_soc; if (np) { - const struct of_device_id *match; - match = of_match_node(exynos4_clk_ids, np); - exynos4_soc = (u32)match->data; - reg_base = of_iomap(np, 0); if (!reg_base) panic("%s: failed to map registers\n", __func__); - } else { - reg_base = S5P_VA_CMU; - if (soc_is_exynos4210()) - exynos4_soc = EXYNOS4210; - else if (soc_is_exynos4212() || soc_is_exynos4412()) - exynos4_soc = EXYNOS4X12; - else - panic("%s: unable to determine soc\n", __func__); } if (exynos4_soc == EXYNOS4210) @@ -1026,7 +1006,7 @@ void __init exynos4_clk_init(struct device_node *np) ARRAY_SIZE(exynos4_fixed_rate_ext_clks), ext_clk_match); - exynos4_clk_register_finpll(); + exynos4_clk_register_finpll(xom); if (exynos4_soc == EXYNOS4210) { apll = samsung_clk_register_pll45xx("fout_apll", "fin_pll", @@ -1087,5 +1067,16 @@ void __init exynos4_clk_init(struct device_node *np) _get_rate("sclk_epll"), _get_rate("sclk_vpll"), _get_rate("arm_clk")); } -CLK_OF_DECLARE(exynos4210_clk, "samsung,exynos4210-clock", exynos4_clk_init); -CLK_OF_DECLARE(exynos4412_clk, "samsung,exynos4412-clock", exynos4_clk_init); + + +static void __init exynos4210_clk_init(struct device_node *np) +{ + exynos4_clk_init(np, EXYNOS4210, NULL, exynos4_get_xom()); +} +CLK_OF_DECLARE(exynos4210_clk, "samsung,exynos4210-clock", exynos4210_clk_init); + +static void __init exynos4412_clk_init(struct device_node *np) +{ + exynos4_clk_init(np, EXYNOS4X12, NULL, exynos4_get_xom()); +} +CLK_OF_DECLARE(exynos4412_clk, "samsung,exynos4412-clock", exynos4412_clk_init); diff --git a/drivers/clk/samsung/clk-exynos5250.c b/drivers/clk/samsung/clk-exynos5250.c index bb54606ff03..5c97e75924a 100644 --- a/drivers/clk/samsung/clk-exynos5250.c +++ b/drivers/clk/samsung/clk-exynos5250.c @@ -16,7 +16,6 @@ #include <linux/of.h> #include <linux/of_address.h> -#include <plat/cpu.h> #include "clk.h" #include "clk-pll.h" diff --git a/drivers/clk/samsung/clk-exynos5440.c b/drivers/clk/samsung/clk-exynos5440.c index a0a094c06f1..7d5434167a9 100644 --- a/drivers/clk/samsung/clk-exynos5440.c +++ b/drivers/clk/samsung/clk-exynos5440.c @@ -15,7 +15,6 @@ #include <linux/of.h> #include <linux/of_address.h> -#include <plat/cpu.h> #include "clk.h" #include "clk-pll.h" diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h index 10b2111f0c0..e4ad6ea9aa7 100644 --- a/drivers/clk/samsung/clk.h +++ b/drivers/clk/samsung/clk.h @@ -20,8 +20,6 @@ #include <linux/of.h> #include <linux/of_address.h> -#include <mach/map.h> - /** * struct samsung_clock_alias: information about mux clock * @id: platform specific id of the clock. diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index c20de4a85cb..f151c6cf27c 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -76,3 +76,12 @@ config CLKSRC_EXYNOS_MCT def_bool y if ARCH_EXYNOS help Support for Multi Core Timer controller on Exynos SoCs. + +config CLKSRC_SAMSUNG_PWM + bool + select CLKSRC_MMIO + help + This is a new clocksource driver for the PWM timer found in + Samsung S3C, S5P and Exynos SoCs, replacing an earlier driver + for all devicetree enabled platforms. This driver will be + needed only on systems that do not have the Exynos MCT available. diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index caacdb63aff..8d979c72aa9 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_VT8500_TIMER) += vt8500_timer.o obj-$(CONFIG_ARCH_BCM) += bcm_kona_timer.o obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o +obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o diff --git a/drivers/clocksource/exynos_mct.c b/drivers/clocksource/exynos_mct.c index 13a9e4923a0..662fcc06582 100644 --- a/drivers/clocksource/exynos_mct.c +++ b/drivers/clocksource/exynos_mct.c @@ -25,11 +25,6 @@ #include <linux/clocksource.h> #include <asm/localtimer.h> - -#include <plat/cpu.h> - -#include <mach/map.h> -#include <mach/irqs.h> #include <asm/mach/time.h> #define EXYNOS4_MCTREG(x) (x) @@ -510,18 +505,14 @@ static void __init exynos4_timer_resources(struct device_node *np, void __iomem #endif /* CONFIG_LOCAL_TIMERS */ } -void __init mct_init(void) +void __init mct_init(void __iomem *base, int irq_g0, int irq_l0, int irq_l1) { - if (soc_is_exynos4210()) { - mct_irqs[MCT_G0_IRQ] = EXYNOS4_IRQ_MCT_G0; - mct_irqs[MCT_L0_IRQ] = EXYNOS4_IRQ_MCT_L0; - mct_irqs[MCT_L1_IRQ] = EXYNOS4_IRQ_MCT_L1; - mct_int_type = MCT_INT_SPI; - } else { - panic("unable to determine mct controller type\n"); - } + mct_irqs[MCT_G0_IRQ] = irq_g0; + mct_irqs[MCT_L0_IRQ] = irq_l0; + mct_irqs[MCT_L1_IRQ] = irq_l1; + mct_int_type = MCT_INT_SPI; - exynos4_timer_resources(NULL, S5P_VA_SYSTIMER); + exynos4_timer_resources(NULL, base); exynos4_clocksource_init(); exynos4_clockevent_init(); } diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c new file mode 100644 index 00000000000..0234c8d2c8f --- /dev/null +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * samsung - Common hr-timer support (s3c and s5p) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <clocksource/samsung_pwm.h> + +#include <asm/sched_clock.h> + +/* + * Clocksource driver + */ + +#define REG_TCFG0 0x00 +#define REG_TCFG1 0x04 +#define REG_TCON 0x08 +#define REG_TINT_CSTAT 0x44 + +#define REG_TCNTB(chan) (0x0c + 12 * (chan)) +#define REG_TCMPB(chan) (0x10 + 12 * (chan)) + +#define TCFG0_PRESCALER_MASK 0xff +#define TCFG0_PRESCALER1_SHIFT 8 + +#define TCFG1_SHIFT(x) ((x) * 4) +#define TCFG1_MUX_MASK 0xf + +#define TCON_START(chan) (1 << (4 * (chan) + 0)) +#define TCON_MANUALUPDATE(chan) (1 << (4 * (chan) + 1)) +#define TCON_INVERT(chan) (1 << (4 * (chan) + 2)) +#define TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3)) + +DEFINE_SPINLOCK(samsung_pwm_lock); +EXPORT_SYMBOL(samsung_pwm_lock); + +struct samsung_pwm_clocksource { + void __iomem *base; + unsigned int irq[SAMSUNG_PWM_NUM]; + struct samsung_pwm_variant variant; + + struct clk *timerclk; + + unsigned int event_id; + unsigned int source_id; + unsigned int tcnt_max; + unsigned int tscaler_div; + unsigned int tdiv; + + unsigned long clock_count_per_tick; +}; + +static struct samsung_pwm_clocksource pwm; + +static void samsung_timer_set_prescale(unsigned int channel, u16 prescale) +{ + unsigned long flags; + u8 shift = 0; + u32 reg; + + if (channel >= 2) + shift = TCFG0_PRESCALER1_SHIFT; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + reg = readl(pwm.base + REG_TCFG0); + reg &= ~(TCFG0_PRESCALER_MASK << shift); + reg |= (prescale - 1) << shift; + writel(reg, pwm.base + REG_TCFG0); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static void samsung_timer_set_divisor(unsigned int channel, u8 divisor) +{ + u8 shift = TCFG1_SHIFT(channel); + unsigned long flags; + u32 reg; + u8 bits; + + bits = (fls(divisor) - 1) - pwm.variant.div_base; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + reg = readl(pwm.base + REG_TCFG1); + reg &= ~(TCFG1_MUX_MASK << shift); + reg |= bits << shift; + writel(reg, pwm.base + REG_TCFG1); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static void samsung_time_stop(unsigned int channel) +{ + unsigned long tcon; + unsigned long flags; + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = __raw_readl(pwm.base + REG_TCON); + tcon &= ~TCON_START(channel); + __raw_writel(tcon, pwm.base + REG_TCON); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static void samsung_time_setup(unsigned int channel, unsigned long tcnt) +{ + unsigned long tcon; + unsigned long flags; + unsigned int tcon_chan = channel; + + if (tcon_chan > 0) + ++tcon_chan; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = __raw_readl(pwm.base + REG_TCON); + + tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan)); + tcon |= TCON_MANUALUPDATE(tcon_chan); + + __raw_writel(tcnt, pwm.base + REG_TCNTB(channel)); + __raw_writel(tcnt, pwm.base + REG_TCMPB(channel)); + __raw_writel(tcon, pwm.base + REG_TCON); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static void samsung_time_start(unsigned int channel, bool periodic) +{ + unsigned long tcon; + unsigned long flags; + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = __raw_readl(pwm.base + REG_TCON); + + tcon &= ~TCON_MANUALUPDATE(channel); + tcon |= TCON_START(channel); + + if (periodic) + tcon |= TCON_AUTORELOAD(channel); + else + tcon &= ~TCON_AUTORELOAD(channel); + + __raw_writel(tcon, pwm.base + REG_TCON); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static int samsung_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + /* + * This check is needed to account for internal rounding + * errors inside clockevents core, which might result in + * passing cycles = 0, which in turn would not generate any + * timer interrupt and hang the system. + * + * Another solution would be to set up the clockevent device + * with min_delta = 2, but this would unnecessarily increase + * the minimum sleep period. + */ + if (!cycles) + cycles = 1; + + samsung_time_setup(pwm.event_id, cycles); + samsung_time_start(pwm.event_id, false); + + return 0; +} + +static void samsung_timer_resume(void) +{ + /* event timer restart */ + samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1); + samsung_time_start(pwm.event_id, true); + + /* source timer restart */ + samsung_time_setup(pwm.source_id, pwm.tcnt_max); + samsung_time_start(pwm.source_id, true); +} + +static void samsung_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + samsung_time_stop(pwm.event_id); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1); + samsung_time_start(pwm.event_id, true); + break; + + case CLOCK_EVT_MODE_ONESHOT: + break; + + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + break; + + case CLOCK_EVT_MODE_RESUME: + samsung_timer_resume(); + break; + } +} + +static struct clock_event_device time_event_device = { + .name = "samsung_event_timer", + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, + .rating = 200, + .set_next_event = samsung_set_next_event, + .set_mode = samsung_set_mode, +}; + +static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + + if (pwm.variant.has_tint_cstat) { + u32 mask = (1 << pwm.event_id); + writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT); + } + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction samsung_clock_event_irq = { + .name = "samsung_time_irq", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, + .handler = samsung_clock_event_isr, + .dev_id = &time_event_device, +}; + +static void __init samsung_clockevent_init(void) +{ + unsigned long pclk; + unsigned long clock_rate; + unsigned int irq_number; + + pclk = clk_get_rate(pwm.timerclk); + + samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div); + samsung_timer_set_divisor(pwm.event_id, pwm.tdiv); + + clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv); + pwm.clock_count_per_tick = clock_rate / HZ; + + time_event_device.cpumask = cpumask_of(0); + clockevents_config_and_register(&time_event_device, + clock_rate, 1, pwm.tcnt_max); + + irq_number = pwm.irq[pwm.event_id]; + setup_irq(irq_number, &samsung_clock_event_irq); + + if (pwm.variant.has_tint_cstat) { + u32 mask = (1 << pwm.event_id); + writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT); + } +} + +static void __iomem *samsung_timer_reg(void) +{ + switch (pwm.source_id) { + case 0: + case 1: + case 2: + case 3: + return pwm.base + pwm.source_id * 0x0c + 0x14; + + case 4: + return pwm.base + 0x40; + + default: + BUG(); + } +} + +/* + * Override the global weak sched_clock symbol with this + * local implementation which uses the clocksource to get some + * better resolution when scheduling the kernel. We accept that + * this wraps around for now, since it is just a relative time + * stamp. (Inspired by U300 implementation.) + */ +static u32 notrace samsung_read_sched_clock(void) +{ + void __iomem *reg = samsung_timer_reg(); + + if (!reg) + return 0; + + return ~__raw_readl(reg); +} + +static void __init samsung_clocksource_init(void) +{ + void __iomem *reg = samsung_timer_reg(); + unsigned long pclk; + unsigned long clock_rate; + int ret; + + pclk = clk_get_rate(pwm.timerclk); + + samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div); + samsung_timer_set_divisor(pwm.source_id, pwm.tdiv); + + clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv); + + samsung_time_setup(pwm.source_id, pwm.tcnt_max); + samsung_time_start(pwm.source_id, true); + + setup_sched_clock(samsung_read_sched_clock, + pwm.variant.bits, clock_rate); + + ret = clocksource_mmio_init(reg, "samsung_clocksource_timer", + clock_rate, 250, pwm.variant.bits, + clocksource_mmio_readl_down); + if (ret) + panic("samsung_clocksource_timer: can't register clocksource\n"); +} + +static void __init samsung_timer_resources(void) +{ + pwm.timerclk = clk_get(NULL, "timers"); + if (IS_ERR(pwm.timerclk)) + panic("failed to get timers clock for timer"); + + clk_prepare_enable(pwm.timerclk); + + pwm.tcnt_max = (1UL << pwm.variant.bits) - 1; + if (pwm.variant.bits == 16) { + pwm.tscaler_div = 25; + pwm.tdiv = 2; + } else { + pwm.tscaler_div = 2; + pwm.tdiv = 1; + } +} + +/* + * PWM master driver + */ +static void __init _samsung_pwm_clocksource_init(void) +{ + u8 mask; + int channel; + + mask = ~pwm.variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1); + channel = fls(mask) - 1; + if (channel < 0) + panic("failed to find PWM channel for clocksource"); + pwm.source_id = channel; + + mask &= ~(1 << channel); + channel = fls(mask) - 1; + if (channel < 0) + panic("failed to find PWM channel for clock event"); + pwm.event_id = channel; + + samsung_timer_resources(); + samsung_clockevent_init(); + samsung_clocksource_init(); +} + +void __init samsung_pwm_clocksource_init(void __iomem *base, + unsigned int *irqs, struct samsung_pwm_variant *variant) +{ + pwm.base = base; + memcpy(&pwm.variant, variant, sizeof(pwm.variant)); + memcpy(pwm.irq, irqs, SAMSUNG_PWM_NUM * sizeof(*irqs)); + + _samsung_pwm_clocksource_init(); +} + +#ifdef CONFIG_CLKSRC_OF +static void __init samsung_pwm_alloc(struct device_node *np, + const struct samsung_pwm_variant *variant) +{ + struct resource res; + struct property *prop; + const __be32 *cur; + u32 val; + int i; + + memcpy(&pwm.variant, variant, sizeof(pwm.variant)); + for (i = 0; i < SAMSUNG_PWM_NUM; ++i) + pwm.irq[i] = irq_of_parse_and_map(np, i); + + of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { + if (val >= SAMSUNG_PWM_NUM) { + pr_warning("%s: invalid channel index in samsung,pwm-outputs property\n", + __func__); + continue; + } + pwm.variant.output_mask |= 1 << val; + } + + of_address_to_resource(np, 0, &res); + if (!request_mem_region(res.start, + resource_size(&res), "samsung-pwm")) { + pr_err("%s: failed to request IO mem region\n", __func__); + return; + } + + pwm.base = ioremap(res.start, resource_size(&res)); + if (!pwm.base) { + pr_err("%s: failed to map PWM registers\n", __func__); + release_mem_region(res.start, resource_size(&res)); + return; + } + + _samsung_pwm_clocksource_init(); +} + +static const struct samsung_pwm_variant s3c24xx_variant = { + .bits = 16, + .div_base = 1, + .has_tint_cstat = false, + .tclk_mask = (1 << 4), +}; + +static void __init s3c2410_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s3c24xx_variant); +} +CLOCKSOURCE_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm", s3c2410_pwm_clocksource_init); + +static const struct samsung_pwm_variant s3c64xx_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = (1 << 7) | (1 << 6) | (1 << 5), +}; + +static void __init s3c64xx_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s3c64xx_variant); +} +CLOCKSOURCE_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm", s3c64xx_pwm_clocksource_init); + +static const struct samsung_pwm_variant s5p64x0_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = 0, +}; + +static void __init s5p64x0_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s5p64x0_variant); +} +CLOCKSOURCE_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm", s5p64x0_pwm_clocksource_init); + +static const struct samsung_pwm_variant s5p_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = (1 << 5), +}; + +static void __init s5p_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s5p_variant); +} +CLOCKSOURCE_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init); +#endif diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index 02492ab20d2..a9d2b2fa4af 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -12,13 +12,16 @@ #include <linux/export.h> #include <linux/init.h> #include <linux/io.h> +#include <linux/slab.h> #include <linux/irqdomain.h> #include <linux/irqchip/chained_irq.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <asm/mach/irq.h> +#ifdef CONFIG_EXYNOS_ATAGS #include <plat/cpu.h> +#endif #include "irqchip.h" @@ -26,17 +29,18 @@ #define COMBINER_ENABLE_CLEAR 0x4 #define COMBINER_INT_STATUS 0xC +#define IRQ_IN_COMBINER 8 + static DEFINE_SPINLOCK(irq_controller_lock); struct combiner_chip_data { - unsigned int irq_offset; + unsigned int hwirq_offset; unsigned int irq_mask; void __iomem *base; unsigned int parent_irq; }; static struct irq_domain *combiner_irq_domain; -static struct combiner_chip_data combiner_data[MAX_COMBINER_NR]; static inline void __iomem *combiner_base(struct irq_data *data) { @@ -77,11 +81,11 @@ static void combiner_handle_cascade_irq(unsigned int irq, struct irq_desc *desc) if (status == 0) goto out; - combiner_irq = __ffs(status); + combiner_irq = chip_data->hwirq_offset + __ffs(status); + cascade_irq = irq_find_mapping(combiner_irq_domain, combiner_irq); - cascade_irq = combiner_irq + (chip_data->irq_offset & ~31); - if (unlikely(cascade_irq >= NR_IRQS)) - do_bad_IRQ(cascade_irq, desc); + if (unlikely(!cascade_irq)) + do_bad_IRQ(irq, desc); else generic_handle_irq(cascade_irq); @@ -113,40 +117,25 @@ static struct irq_chip combiner_chip = { #endif }; -static unsigned int max_combiner_nr(void) -{ - if (soc_is_exynos5250()) - return EXYNOS5_MAX_COMBINER_NR; - else if (soc_is_exynos4412()) - return EXYNOS4412_MAX_COMBINER_NR; - else if (soc_is_exynos4212()) - return EXYNOS4212_MAX_COMBINER_NR; - else - return EXYNOS4210_MAX_COMBINER_NR; -} - -static void __init combiner_cascade_irq(unsigned int combiner_nr, +static void __init combiner_cascade_irq(struct combiner_chip_data *combiner_data, unsigned int irq) { - if (combiner_nr >= max_combiner_nr()) - BUG(); - if (irq_set_handler_data(irq, &combiner_data[combiner_nr]) != 0) + if (irq_set_handler_data(irq, combiner_data) != 0) BUG(); irq_set_chained_handler(irq, combiner_handle_cascade_irq); } -static void __init combiner_init_one(unsigned int combiner_nr, +static void __init combiner_init_one(struct combiner_chip_data *combiner_data, + unsigned int combiner_nr, void __iomem *base, unsigned int irq) { - combiner_data[combiner_nr].base = base; - combiner_data[combiner_nr].irq_offset = irq_find_mapping( - combiner_irq_domain, combiner_nr * MAX_IRQ_IN_COMBINER); - combiner_data[combiner_nr].irq_mask = 0xff << ((combiner_nr % 4) << 3); - combiner_data[combiner_nr].parent_irq = irq; + combiner_data->base = base; + combiner_data->hwirq_offset = (combiner_nr & ~3) * IRQ_IN_COMBINER; + combiner_data->irq_mask = 0xff << ((combiner_nr % 4) << 3); + combiner_data->parent_irq = irq; /* Disable all interrupts */ - __raw_writel(combiner_data[combiner_nr].irq_mask, - base + COMBINER_ENABLE_CLEAR); + __raw_writel(combiner_data->irq_mask, base + COMBINER_ENABLE_CLEAR); } #ifdef CONFIG_OF @@ -162,7 +151,7 @@ static int combiner_irq_domain_xlate(struct irq_domain *d, if (intsize < 2) return -EINVAL; - *out_hwirq = intspec[0] * MAX_IRQ_IN_COMBINER + intspec[1]; + *out_hwirq = intspec[0] * IRQ_IN_COMBINER + intspec[1]; *out_type = 0; return 0; @@ -181,6 +170,8 @@ static int combiner_irq_domain_xlate(struct irq_domain *d, static int combiner_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) { + struct combiner_chip_data *combiner_data = d->host_data; + irq_set_chip_and_handler(irq, &combiner_chip, handle_level_irq); irq_set_chip_data(irq, &combiner_data[hw >> 3]); set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); @@ -193,8 +184,12 @@ static struct irq_domain_ops combiner_irq_domain_ops = { .map = combiner_irq_domain_map, }; -static unsigned int exynos4x12_combiner_extra_irq(int group) +static unsigned int combiner_lookup_irq(int group) { +#ifdef CONFIG_EXYNOS_ATAGS + if (group < EXYNOS4210_MAX_COMBINER_NR || soc_is_exynos5250()) + return IRQ_SPI(group); + switch (group) { case 16: return IRQ_SPI(107); @@ -204,53 +199,46 @@ static unsigned int exynos4x12_combiner_extra_irq(int group) return IRQ_SPI(48); case 19: return IRQ_SPI(42); - default: - return 0; } +#endif + return 0; } void __init combiner_init(void __iomem *combiner_base, - struct device_node *np) + struct device_node *np, + unsigned int max_nr, + int irq_base) { - int i, irq, irq_base; - unsigned int max_nr, nr_irq; + int i, irq; + unsigned int nr_irq; + struct combiner_chip_data *combiner_data; - max_nr = max_combiner_nr(); + nr_irq = max_nr * IRQ_IN_COMBINER; - if (np) { - if (of_property_read_u32(np, "samsung,combiner-nr", &max_nr)) { - pr_info("%s: number of combiners not specified, " - "setting default as %d.\n", - __func__, max_nr); - } - } - - nr_irq = max_nr * MAX_IRQ_IN_COMBINER; - - irq_base = irq_alloc_descs(COMBINER_IRQ(0, 0), 1, nr_irq, 0); - if (IS_ERR_VALUE(irq_base)) { - irq_base = COMBINER_IRQ(0, 0); - pr_warning("%s: irq desc alloc failed. Continuing with %d as linux irq base\n", __func__, irq_base); + combiner_data = kcalloc(max_nr, sizeof (*combiner_data), GFP_KERNEL); + if (!combiner_data) { + pr_warning("%s: could not allocate combiner data\n", __func__); + return; } - combiner_irq_domain = irq_domain_add_legacy(np, nr_irq, irq_base, 0, - &combiner_irq_domain_ops, &combiner_data); + combiner_irq_domain = irq_domain_add_simple(np, nr_irq, irq_base, + &combiner_irq_domain_ops, combiner_data); if (WARN_ON(!combiner_irq_domain)) { pr_warning("%s: irq domain init failed\n", __func__); return; } for (i = 0; i < max_nr; i++) { - if (i < EXYNOS4210_MAX_COMBINER_NR || soc_is_exynos5250()) - irq = IRQ_SPI(i); - else - irq = exynos4x12_combiner_extra_irq(i); #ifdef CONFIG_OF if (np) irq = irq_of_parse_and_map(np, i); + else #endif - combiner_init_one(i, combiner_base + (i >> 2) * 0x10, irq); - combiner_cascade_irq(i, irq); + irq = combiner_lookup_irq(i); + + combiner_init_one(&combiner_data[i], i, + combiner_base + (i >> 2) * 0x10, irq); + combiner_cascade_irq(&combiner_data[i], irq); } } @@ -259,6 +247,8 @@ static int __init combiner_of_init(struct device_node *np, struct device_node *parent) { void __iomem *combiner_base; + unsigned int max_nr = 20; + int irq_base = -1; combiner_base = of_iomap(np, 0); if (!combiner_base) { @@ -266,7 +256,20 @@ static int __init combiner_of_init(struct device_node *np, return -ENXIO; } - combiner_init(combiner_base, np); + if (of_property_read_u32(np, "samsung,combiner-nr", &max_nr)) { + pr_info("%s: number of combiners not specified, " + "setting default as %d.\n", + __func__, max_nr); + } + + /* + * FIXME: This is a hardwired COMBINER_IRQ(0,0). Once all devices + * get their IRQ from DT, remove this in order to get dynamic + * allocation. + */ + irq_base = 160; + + combiner_init(combiner_base, np, max_nr, irq_base); return 0; } diff --git a/include/clocksource/samsung_pwm.h b/include/clocksource/samsung_pwm.h new file mode 100644 index 00000000000..5c449c8199e --- /dev/null +++ b/include/clocksource/samsung_pwm.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef __CLOCKSOURCE_SAMSUNG_PWM_H +#define __CLOCKSOURCE_SAMSUNG_PWM_H + +#include <linux/spinlock.h> + +#define SAMSUNG_PWM_NUM 5 + +extern spinlock_t samsung_pwm_lock; + +struct samsung_pwm_variant { + u8 bits; + u8 div_base; + u8 tclk_mask; + u8 output_mask; + bool has_tint_cstat; +}; + +void samsung_pwm_clocksource_init(void __iomem *base, + unsigned int *irqs, struct samsung_pwm_variant *variant); + +#endif /* __CLOCKSOURCE_SAMSUNG_PWM_H */ |