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 */ |