diff --git a/Documentation/DocBook/drm.tmpl b/Documentation/DocBook/drm.tmpl index ed1d6d28902213..1e5800fdd8fd3b 100644 --- a/Documentation/DocBook/drm.tmpl +++ b/Documentation/DocBook/drm.tmpl @@ -1161,7 +1161,7 @@ int max_width, max_height; If a page flip can be successfully scheduled the driver must set the - drm_crtc-<fb field to the new framebuffer pointed to + drm_crtc->fb field to the new framebuffer pointed to by fb. This is important so that the reference counting on framebuffers stays balanced. diff --git a/Documentation/android.txt b/Documentation/android.txt new file mode 100644 index 00000000000000..0f40a78b045f77 --- /dev/null +++ b/Documentation/android.txt @@ -0,0 +1,121 @@ + ============= + A N D R O I D + ============= + +Copyright (C) 2009 Google, Inc. +Written by Mike Chan + +CONTENTS: +--------- + +1. Android + 1.1 Required enabled config options + 1.2 Required disabled config options + 1.3 Recommended enabled config options +2. Contact + + +1. Android +========== + +Android (www.android.com) is an open source operating system for mobile devices. +This document describes configurations needed to run the Android framework on +top of the Linux kernel. + +To see a working defconfig look at msm_defconfig or goldfish_defconfig +which can be found at http://android.git.kernel.org in kernel/common.git +and kernel/msm.git + + +1.1 Required enabled config options +----------------------------------- +After building a standard defconfig, ensure that these options are enabled in +your .config or defconfig if they are not already. Based off the msm_defconfig. +You should keep the rest of the default options enabled in the defconfig +unless you know what you are doing. + +ANDROID_PARANOID_NETWORK +ASHMEM +CONFIG_FB_MODE_HELPERS +CONFIG_FONT_8x16 +CONFIG_FONT_8x8 +CONFIG_YAFFS_SHORT_NAMES_IN_RAM +DAB +EARLYSUSPEND +FB +FB_CFB_COPYAREA +FB_CFB_FILLRECT +FB_CFB_IMAGEBLIT +FB_DEFERRED_IO +FB_TILEBLITTING +HIGH_RES_TIMERS +INOTIFY +INOTIFY_USER +INPUT_EVDEV +INPUT_GPIO +INPUT_MISC +LEDS_CLASS +LEDS_GPIO +LOCK_KERNEL +LkOGGER +LOW_MEMORY_KILLER +MISC_DEVICES +NEW_LEDS +NO_HZ +POWER_SUPPLY +PREEMPT +RAMFS +RTC_CLASS +RTC_LIB +SWITCH +SWITCH_GPIO +TMPFS +UID_STAT +UID16 +USB_FUNCTION +USB_FUNCTION_ADB +USER_WAKELOCK +VIDEO_OUTPUT_CONTROL +WAKELOCK +YAFFS_AUTO_YAFFS2 +YAFFS_FS +YAFFS_YAFFS1 +YAFFS_YAFFS2 + + +1.2 Required disabled config options +------------------------------------ +CONFIG_YAFFS_DISABLE_LAZY_LOAD +DNOTIFY + + +1.3 Recommended enabled config options +------------------------------ +ANDROID_PMEM +PSTORE_CONSOLE +PSTORE_RAM +SCHEDSTATS +DEBUG_PREEMPT +DEBUG_MUTEXES +DEBUG_SPINLOCK_SLEEP +DEBUG_INFO +FRAME_POINTER +CPU_FREQ +CPU_FREQ_TABLE +CPU_FREQ_DEFAULT_GOV_ONDEMAND +CPU_FREQ_GOV_ONDEMAND +CRC_CCITT +EMBEDDED +INPUT_TOUCHSCREEN +I2C +I2C_BOARDINFO +LOG_BUF_SHIFT=17 +SERIAL_CORE +SERIAL_CORE_CONSOLE + + +2. Contact +========== +website: http://android.git.kernel.org + +mailing-lists: android-kernel@googlegroups.com diff --git a/Documentation/arm64/memory.txt b/Documentation/arm64/memory.txt index 5e054bfe4dde8e..85e24c4f215c45 100644 --- a/Documentation/arm64/memory.txt +++ b/Documentation/arm64/memory.txt @@ -35,11 +35,13 @@ ffffffbc00000000 ffffffbdffffffff 8GB vmemmap ffffffbe00000000 ffffffbffbbfffff ~8GB [guard, future vmmemap] -ffffffbffbc00000 ffffffbffbdfffff 2MB earlyprintk device +ffffffbffa000000 ffffffbffaffffff 16MB PCI I/O space + +ffffffbffb000000 ffffffbffbbfffff 12MB [guard] -ffffffbffbe00000 ffffffbffbe0ffff 64KB PCI I/O space +ffffffbffbc00000 ffffffbffbdfffff 2MB earlyprintk device -ffffffbffbe10000 ffffffbcffffffff ~2MB [guard] +ffffffbffbe00000 ffffffbffbffffff 2MB [guard] ffffffbffc000000 ffffffbfffffffff 64MB modules @@ -60,11 +62,13 @@ fffffdfc00000000 fffffdfdffffffff 8GB vmemmap fffffdfe00000000 fffffdfffbbfffff ~8GB [guard, future vmmemap] -fffffdfffbc00000 fffffdfffbdfffff 2MB earlyprintk device +fffffdfffa000000 fffffdfffaffffff 16MB PCI I/O space + +fffffdfffb000000 fffffdfffbbfffff 12MB [guard] -fffffdfffbe00000 fffffdfffbe0ffff 64KB PCI I/O space +fffffdfffbc00000 fffffdfffbdfffff 2MB earlyprintk device -fffffdfffbe10000 fffffdfffbffffff ~2MB [guard] +fffffdfffbe00000 fffffdfffbffffff 2MB [guard] fffffdfffc000000 fffffdffffffffff 64MB modules diff --git a/Documentation/cgroups/cgroups.txt b/Documentation/cgroups/cgroups.txt index 821de56d15802c..91bd5e0d34e40b 100644 --- a/Documentation/cgroups/cgroups.txt +++ b/Documentation/cgroups/cgroups.txt @@ -578,6 +578,15 @@ is completely unused; @cgrp->parent is still valid. (Note - can also be called for a newly-created cgroup if an error occurs after this subsystem's create() method has been called for the new cgroup). +int allow_attach(struct cgroup *cgrp, struct cgroup_taskset *tset) +(cgroup_mutex held by caller) + +Called prior to moving a task into a cgroup; if the subsystem +returns an error, this will abort the attach operation. Used +to extend the permission checks - if all subsystems in a cgroup +return 0, the attach will be allowed to proceed, even if the +default permission check (root or same user) fails. + int can_attach(struct cgroup *cgrp, struct cgroup_taskset *tset) (cgroup_mutex held by caller) diff --git a/Documentation/cpu-freq/governors.txt b/Documentation/cpu-freq/governors.txt index 77ec21574fb187..2ed766ec75fc29 100644 --- a/Documentation/cpu-freq/governors.txt +++ b/Documentation/cpu-freq/governors.txt @@ -28,6 +28,7 @@ Contents: 2.3 Userspace 2.4 Ondemand 2.5 Conservative +2.6 Interactive 3. The Governor Interface in the CPUfreq Core @@ -218,6 +219,90 @@ a decision on when to decrease the frequency while running in any speed. Load for frequency increase is still evaluated every sampling rate. +2.6 Interactive +--------------- + +The CPUfreq governor "interactive" is designed for latency-sensitive, +interactive workloads. This governor sets the CPU speed depending on +usage, similar to "ondemand" and "conservative" governors, but with a +different set of configurable behaviors. + +The tuneable values for this governor are: + +target_loads: CPU load values used to adjust speed to influence the +current CPU load toward that value. In general, the lower the target +load, the more often the governor will raise CPU speeds to bring load +below the target. The format is a single target load, optionally +followed by pairs of CPU speeds and CPU loads to target at or above +those speeds. Colons can be used between the speeds and associated +target loads for readability. For example: + + 85 1000000:90 1700000:99 + +targets CPU load 85% below speed 1GHz, 90% at or above 1GHz, until +1.7GHz and above, at which load 99% is targeted. If speeds are +specified these must appear in ascending order. Higher target load +values are typically specified for higher speeds, that is, target load +values also usually appear in an ascending order. The default is +target load 90% for all speeds. + +min_sample_time: The minimum amount of time to spend at the current +frequency before ramping down. Default is 80000 uS. + +hispeed_freq: An intermediate "hi speed" at which to initially ramp +when CPU load hits the value specified in go_hispeed_load. If load +stays high for the amount of time specified in above_hispeed_delay, +then speed may be bumped higher. Default is the maximum speed +allowed by the policy at governor initialization time. + +go_hispeed_load: The CPU load at which to ramp to hispeed_freq. +Default is 99%. + +above_hispeed_delay: When speed is at or above hispeed_freq, wait for +this long before raising speed in response to continued high load. +The format is a single delay value, optionally followed by pairs of +CPU speeds and the delay to use at or above those speeds. Colons can +be used between the speeds and associated delays for readability. For +example: + + 80000 1300000:200000 1500000:40000 + +uses delay 80000 uS until CPU speed 1.3 GHz, at which speed delay +200000 uS is used until speed 1.5 GHz, at which speed (and above) +delay 40000 uS is used. If speeds are specified these must appear in +ascending order. Default is 20000 uS. + +timer_rate: Sample rate for reevaluating CPU load when the CPU is not +idle. A deferrable timer is used, such that the CPU will not be woken +from idle to service this timer until something else needs to run. +(The maximum time to allow deferring this timer when not running at +minimum speed is configurable via timer_slack.) Default is 20000 uS. + +timer_slack: Maximum additional time to defer handling the governor +sampling timer beyond timer_rate when running at speeds above the +minimum. For platforms that consume additional power at idle when +CPUs are running at speeds greater than minimum, this places an upper +bound on how long the timer will be deferred prior to re-evaluating +load and dropping speed. For example, if timer_rate is 20000uS and +timer_slack is 10000uS then timers will be deferred for up to 30msec +when not at lowest speed. A value of -1 means defer timers +indefinitely at all speeds. Default is 80000 uS. + +boost: If non-zero, immediately boost speed of all CPUs to at least +hispeed_freq until zero is written to this attribute. If zero, allow +CPU speeds to drop below hispeed_freq according to load as usual. +Default is zero. + +boostpulse: On each write, immediately boost speed of all CPUs to +hispeed_freq for at least the period of time specified by +boostpulse_duration, after which speeds are allowed to drop below +hispeed_freq according to load as usual. + +boostpulse_duration: Length of time to hold CPU speed at hispeed_freq +on a write to boostpulse, before allowing speed to drop according to +load as usual. Default is 80000 uS. + + 3. The Governor Interface in the CPUfreq Core ============================================= diff --git a/Documentation/devicetree/bindings/arm/bcm/bcm21664.txt b/Documentation/devicetree/bindings/arm/bcm/bcm21664.txt new file mode 100644 index 00000000000000..e0774255e1a643 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/bcm/bcm21664.txt @@ -0,0 +1,15 @@ +Broadcom BCM21664 device tree bindings +-------------------------------------- + +This document describes the device tree bindings for boards with the BCM21664 +SoC. + +Required root node property: + - compatible: brcm,bcm21664 + +Example: + / { + model = "BCM21664 SoC"; + compatible = "brcm,bcm21664"; + [...] + } diff --git a/Documentation/devicetree/bindings/arm/bcm/kona-resetmgr.txt b/Documentation/devicetree/bindings/arm/bcm/kona-resetmgr.txt new file mode 100644 index 00000000000000..93f31ca1ef4b30 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/bcm/kona-resetmgr.txt @@ -0,0 +1,14 @@ +Broadcom Kona Family Reset Manager +---------------------------------- + +The reset manager is used on the Broadcom BCM21664 SoC. + +Required properties: + - compatible: brcm,bcm21664-resetmgr + - reg: memory address & range + +Example: + brcm,resetmgr@35001f00 { + compatible = "brcm,bcm21664-resetmgr"; + reg = <0x35001f00 0x24>; + }; diff --git a/Documentation/devicetree/bindings/arm/bcm4708.txt b/Documentation/devicetree/bindings/arm/bcm4708.txt new file mode 100644 index 00000000000000..6b0f49f6f499f5 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/bcm4708.txt @@ -0,0 +1,8 @@ +Broadcom BCM4708 device tree bindings +------------------------------------------- + +Boards with the BCM4708 SoC shall have the following properties: + +Required root node property: + +compatible = "brcm,bcm4708"; diff --git a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt index 8c7a4653508dab..df0a452b8526de 100644 --- a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt +++ b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt @@ -30,3 +30,17 @@ Example: resume-offset = <0x308>; reboot-offset = <0x4>; }; + +PCTRL: Peripheral misc control register + +Required Properties: +- compatible: "hisilicon,pctrl" +- reg: Address and size of pctrl. + +Example: + + /* for Hi3620 */ + pctrl: pctrl@fca09000 { + compatible = "hisilicon,pctrl"; + reg = <0xfca09000 0x1000>; + }; diff --git a/Documentation/devicetree/bindings/arm/pmu.txt b/Documentation/devicetree/bindings/arm/pmu.txt index 3e1e498fea96b3..a2dbf67c1cb7c6 100644 --- a/Documentation/devicetree/bindings/arm/pmu.txt +++ b/Documentation/devicetree/bindings/arm/pmu.txt @@ -17,6 +17,9 @@ Required properties: "arm,arm1176-pmu" "arm,arm1136-pmu" - interrupts : 1 combined interrupt or 1 per core. +- cluster : a phandle to the cluster to which it belongs + If there are more than one cluster with same CPU type + then there should be separate PMU nodes per cluster. Example: diff --git a/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt b/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt index 56d1f4961075a5..5286e260fcae5e 100644 --- a/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt +++ b/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt @@ -10,12 +10,12 @@ This binding uses the common clock binding: Required properties: - compatible - Shall have one of the following values: - - "brcm,bcm11351-root-ccu" - - "brcm,bcm11351-aon-ccu" - - "brcm,bcm11351-hub-ccu" - - "brcm,bcm11351-master-ccu" - - "brcm,bcm11351-slave-ccu" + Shall have a value of the form "brcm,--ccu", + where is a Broadcom SoC model number and is + the name of a defined CCU. For example: + "brcm,bcm11351-root-ccu" + The compatible strings used for each supported SoC family + are defined below. - reg Shall define the base and range of the address space containing clock control registers @@ -26,12 +26,48 @@ Required properties: Shall be an ordered list of strings defining the names of the clocks provided by the CCU. +Device tree example: + + slave_ccu: slave_ccu { + compatible = "brcm,bcm11351-slave-ccu"; + reg = <0x3e011000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "uartb", + "uartb2", + "uartb3", + "uartb4"; + }; + + ref_crystal_clk: ref_crystal { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <26000000>; + }; + + uart@3e002000 { + compatible = "brcm,bcm11351-dw-apb-uart", "snps,dw-apb-uart"; + status = "disabled"; + reg = <0x3e002000 0x1000>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_UARTB3>; + interrupts = ; + reg-shift = <2>; + reg-io-width = <4>; + }; + +BCM281XX family +--------------- +CCU compatible string values for SoCs in the BCM281XX family are: + "brcm,bcm11351-root-ccu" + "brcm,bcm11351-aon-ccu" + "brcm,bcm11351-hub-ccu" + "brcm,bcm11351-master-ccu" + "brcm,bcm11351-slave-ccu" -BCM281XX family SoCs use Kona CCUs. The following table defines -the set of CCUs and clock specifiers for BCM281XX clocks. When -a clock consumer references a clocks, its symbolic specifier -(rather than its numeric index value) should be used. These -specifiers are defined in "include/dt-bindings/clock/bcm281xx.h". +The following table defines the set of CCUs and clock specifiers for +BCM281XX family clocks. When a clock consumer references a clocks, +its symbolic specifier (rather than its numeric index value) should +be used. These specifiers are defined in: + "include/dt-bindings/clock/bcm281xx.h" CCU Clock Type Index Specifier --- ----- ---- ----- --------- @@ -64,30 +100,40 @@ specifiers are defined in "include/dt-bindings/clock/bcm281xx.h". slave pwm peri 9 BCM281XX_SLAVE_CCU_PWM -Device tree example: +BCM21664 family +--------------- +CCU compatible string values for SoCs in the BCM21664 family are: + "brcm,bcm21664-root-ccu" + "brcm,bcm21664-aon-ccu" + "brcm,bcm21664-master-ccu" + "brcm,bcm21664-slave-ccu" - slave_ccu: slave_ccu { - compatible = "brcm,bcm11351-slave-ccu"; - reg = <0x3e011000 0x0f00>; - #clock-cells = <1>; - clock-output-names = "uartb", - "uartb2", - "uartb3", - "uartb4"; - }; +The following table defines the set of CCUs and clock specifiers for +BCM21664 family clocks. When a clock consumer references a clocks, +its symbolic specifier (rather than its numeric index value) should +be used. These specifiers are defined in: + "include/dt-bindings/clock/bcm21664.h" - ref_crystal_clk: ref_crystal { - #clock-cells = <0>; - compatible = "fixed-clock"; - clock-frequency = <26000000>; - }; + CCU Clock Type Index Specifier + --- ----- ---- ----- --------- + root frac_1m peri 0 BCM21664_ROOT_CCU_FRAC_1M - uart@3e002000 { - compatible = "brcm,bcm11351-dw-apb-uart", "snps,dw-apb-uart"; - status = "disabled"; - reg = <0x3e002000 0x1000>; - clocks = <&slave_ccu BCM281XX_SLAVE_CCU_UARTB3>; - interrupts = ; - reg-shift = <2>; - reg-io-width = <4>; - }; + aon hub_timer peri 0 BCM21664_AON_CCU_HUB_TIMER + + master sdio1 peri 0 BCM21664_MASTER_CCU_SDIO1 + master sdio2 peri 1 BCM21664_MASTER_CCU_SDIO2 + master sdio3 peri 2 BCM21664_MASTER_CCU_SDIO3 + master sdio4 peri 3 BCM21664_MASTER_CCU_SDIO4 + master sdio1_sleep peri 4 BCM21664_MASTER_CCU_SDIO1_SLEEP + master sdio2_sleep peri 5 BCM21664_MASTER_CCU_SDIO2_SLEEP + master sdio3_sleep peri 6 BCM21664_MASTER_CCU_SDIO3_SLEEP + master sdio4_sleep peri 7 BCM21664_MASTER_CCU_SDIO4_SLEEP + + slave uartb peri 0 BCM21664_SLAVE_CCU_UARTB + slave uartb2 peri 1 BCM21664_SLAVE_CCU_UARTB2 + slave uartb3 peri 2 BCM21664_SLAVE_CCU_UARTB3 + slave uartb4 peri 3 BCM21664_SLAVE_CCU_UARTB4 + slave bsc1 peri 4 BCM21664_SLAVE_CCU_BSC1 + slave bsc2 peri 5 BCM21664_SLAVE_CCU_BSC2 + slave bsc3 peri 6 BCM21664_SLAVE_CCU_BSC3 + slave bsc4 peri 7 BCM21664_SLAVE_CCU_BSC4 diff --git a/Documentation/devicetree/bindings/clock/exynos5410-clock.txt b/Documentation/devicetree/bindings/clock/exynos5410-clock.txt new file mode 100644 index 00000000000000..604a75c10c21e9 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/exynos5410-clock.txt @@ -0,0 +1,54 @@ +* Samsung Exynos5410 Clock Controller + +The Exynos5410 clock controller generates and supplies clock to various +controllers within the Exynos5410 SoC. + +Required Properties: + +- compatible: should be "samsung,exynos5410-clock" + +- reg: physical base address of the controller and length of memory mapped + region. + +- #clock-cells: should be 1. + +All available clocks are defined as preprocessor macros in +dt-bindings/clock/exynos5410.h header and can be used in device +tree sources. + +External clock: + +There is clock that is generated outside the SoC. It is expected +that it is defined using standard clock bindings with following +clock-output-name: + - "fin_pll" - PLL input clock - required. + +Example 1: An example of a clock controller node is listed below. + + clock: clock-controller@0x10010000 { + compatible = "samsung,exynos5410-clock"; + reg = <0x10010000 0x30000>; + #clock-cells = <1>; + }; + +Example 2: Required external clock. + + fin_pll: clock-fin-pll { + compatible = "fixed-clock"; + reg = <0>; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "fin_pll"; + }; + +Example 3: UART controller node that consumes the clock generated by the clock + controller. Refer to the standard clock bindings for information + about 'clocks' and 'clock-names' property. + + serial@12C20000 { + compatible = "samsung,exynos4210-uart"; + reg = <0x12C00000 0x100>; + interrupts = <0 51 0>; + clocks = <&clock CLK_UART0>, <&clock CLK_SCLK_UART0>; + clock-names = "uart", "clk_uart_baud0"; + }; diff --git a/Documentation/devicetree/bindings/clock/hi3620-clock.txt b/Documentation/devicetree/bindings/clock/hi3620-clock.txt index 4b71ab41be5357..dad6269f52c5a6 100644 --- a/Documentation/devicetree/bindings/clock/hi3620-clock.txt +++ b/Documentation/devicetree/bindings/clock/hi3620-clock.txt @@ -7,6 +7,7 @@ Required Properties: - compatible: should be one of the following. - "hisilicon,hi3620-clock" - controller compatible with Hi3620 SoC. + - "hisilicon,hi3620-mmc-clock" - controller specific for Hi3620 mmc. - reg: physical base address of the controller and length of memory mapped region. diff --git a/Documentation/devicetree/bindings/clock/hi3716.txt b/Documentation/devicetree/bindings/clock/hi3716.txt new file mode 100644 index 00000000000000..60af61e1a413e5 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/hi3716.txt @@ -0,0 +1,121 @@ +Device Tree Clock bindings for hi3716 + +This binding uses the common clock binding[1]. + +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt + +Clock control register +Required properties: +- compatible : "hisilicon,clkbase" +- reg : Address and size of clkbase. + +Device Clocks + +Device clocks are required to have one or both of the following sets of +properties: + + +Gated device clocks: + +Required properties: +- compatible : "hisilicon,hi3716-clk-gate" +- gate-reg : shall be the register offset from clkbase and enable bit, reset bit +- clock-output-names : shall be reference name + + +Mux device clocks: + +Required properties: +- compatible : "hisilicon,hi3716-clk-mux" +- mux-reg : shall be the register offset from clkbase and mux_shift mux_width +- mux-table : shall be reg value to be choose clocks accordingly +- clock-output-names : shall be reference name + + +Fixed divisor device clocks: + +Required properties: +- compatible : "hisilicon,hi3716-fixed-divider" +- div-table : shall be divisor sequence +- clock-output-names : shall be reference name according to divisor + + +Fixed pll clocks: + +Required properties: +- compatible : "hisilicon,hi3716-fixed-pll" +- clock-frequency : shall be output clock frequence sequence +- clock-output-names : shall be reference name according to clock-frequnce + +For example: + clkbase@f8a22000 { + compatible = "hisilicon,clkbase"; + reg = <0xf8a22000 0x1000>; + }; + + osc24m: osc { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "osc24mhz"; + }; + + bpll: bpll { + compatible = "hisilicon,hi3716-fixed-pll"; + #clock-cells = <1>; + clocks = <&osc24m>; + clock-frequency = <1200000000>, + <600000000>, + <300000000>, + <200000000>, + <150000000>; + clock-output-names = "bpll_fout0", + "bpll_fout1", + "bpll_fout2", + "bpll_fout3", + "bpll_fout4"; + }; + + bpll_fout0_div: bpll_fout0_div {/* 1200Mhz */ + compatible = "hisilicon,hi3716-fixed-divider"; + #clock-cells = <1>; + clocks = <&bpll 0>; + div-table = <3 14 25 50>; + clock-output-names = "fout0_400m", "fout0_86m", + "fout0_48m", "fout0_24m"; + }; + + bpll_fout3_div: bpll_fout3_div { + compatible = "hisilicon,hi3716-fixed-divider"; + #clock-cells = <1>; + clocks = <&bpll 3>; + div-table = <2 4 5 8>; + clock-output-names = "fout3_100m", "fout3_50m", + "fout3_40m", "fout3_25m"; + }; + + clk_sfc_mux: clk_sfc_mux { + compatible = "hisilicon,hi3716-clk-mux"; + #clock-cells = <0>; + /* clks: 24M 75M 100M 150M 200M */ + clocks = <&osc24m>, <&bpll_fout2_div>, + <&bpll_fout3_div 0>, <&bpll 4>, <&bpll 3>; + + /* offset mux_shift mux_width */ + mux-reg = <0x5c 8 3>; + /* mux reg value to choose clks */ + mux-table = <0 7 6 4 5>; + + clock-output-names = "sfc_mux"; + }; + + clk_sfc: clk_sfc { + compatible = "hisilicon,hi3716-clk-gate"; + #clock-cells = <0>; + clocks = <&clk_sfc_mux>; + + /* offset, enable, reset */ + gate-reg = <0x5c 0 4>; + + clock-output-names = "sfc"; + }; diff --git a/Documentation/devicetree/bindings/gpio/gpio-pl061.txt b/Documentation/devicetree/bindings/gpio/gpio-pl061.txt new file mode 100644 index 00000000000000..687206727bd3e6 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/gpio-pl061.txt @@ -0,0 +1,33 @@ +PL061 GPIO controller bindings + +Required properties: +- compatible: + - "arm,pl061", "arm,primecell". +- #gpio-cells : Should be two. + - first cell is the gpio pin number + - second cell is used to specify the gpio polarity: + 0 = active high + 1 = active low +- gpio-controller : Marks the device node as a GPIO controller. +- interrupt-controller : Marks the device node as an interrupt controller. +- #interrupt-cells : Should be two. + - first cell is the hw irq number + - second cell is used to specify the interrupt type: + 0 = default, unspecified type + 1 = rising edge triggered + 2 = falling edge triggered + 4 = high level triggered + 8 = low level triggered +- linux,gpio-base : Should be the global GPIO number. + +Example: + gpio0: gpio@fc806000 { + compatible = "arm,pl061", "arm,primecell"; + reg = <0xfc806000 0x1000>; + interrupts = <0 64 0x4>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + linux,gpio-base = <0>; + }; diff --git a/Documentation/devicetree/bindings/iommu/samsung,exynos4210-sysmmu.txt b/Documentation/devicetree/bindings/iommu/samsung,exynos4210-sysmmu.txt new file mode 100644 index 00000000000000..e4417bbfac7e90 --- /dev/null +++ b/Documentation/devicetree/bindings/iommu/samsung,exynos4210-sysmmu.txt @@ -0,0 +1,86 @@ +Samsung Exynos IOMMU H/W, System MMU (System Memory Management Unit) + +Samsung's Exynos architecture contains System MMUs that enables scattered +physical memory chunks visible as a contiguous region to DMA-capable peripheral +devices like MFC, FIMC, FIMD, GScaler, FIMC-IS and so forth. + +System MMU is an IOMMU and supports identical translation table format to +ARMv7 translation tables with minimum set of page properties including access +permissions, shareability and security protection. In addition, System MMU has +another capabilities like L2 TLB or block-fetch buffers to minimize translation +latency. + +System MMUs are in many to one relation with peripheral devices, i.e. single +peripheral device might have multiple System MMUs (usually one for each bus +master), but one System MMU can handle transactions from only one peripheral +device. The relation between a System MMU and the peripheral device needs to be +defined in device node of the peripheral device. + +MFC in all Exynos SoCs and FIMD, M2M Scalers and G2D in Exynos5420 has 2 System +MMUs. +* MFC has one System MMU on its left and right bus. +* FIMD in Exynos5420 has one System MMU for window 0 and 4, the other system MMU + for window 1, 2 and 3. +* M2M Scalers and G2D in Exynos5420 has one System MMU on the read channel and + the other System MMU on the write channel. +The drivers must consider how to handle those System MMUs. One of the idea is +to implement child devices or sub-devices which are the client devices of the +System MMU. + +Required properties: +- compatible: Should be one of: + "samsung,sysmmu-v1" + "samsung,sysmmu-v2" + "samsung,sysmmu-v3.1" + "samsung,sysmmu-v3.2" + "samsung,sysmmu-v3.3" + +- reg: A tuple of base address and size of System MMU registers. +- interrupt-parent: The phandle of the interrupt controller of System MMU +- interrupts: An interrupt specifier for interrupt signal of System MMU, + according to the format defined by a particular interrupt + controller. +- clock-names: Should be "sysmmu" if the System MMU is needed to gate its clock. + Please refer to the following documents: + Documentation/devicetree/bindings/clock/clock-bindings.txt + Documentation/devicetree/bindings/clock/exynos4-clock.txt + Documentation/devicetree/bindings/clock/exynos5250-clock.txt + Documentation/devicetree/bindings/clock/exynos5420-clock.txt + Optional "master" if the clock to the System MMU is gated by + another gate clock other than "sysmmu". The System MMU driver + sets "master" the parent of "sysmmu". + Exynos4 SoCs, there needs no "master" clockj. + Exynos5 SoCs, some System MMUs must have "master" clocks. +- clocks: Required if the System MMU is needed to gate its clock. + Please refer to the documents listed above. +- samsung,power-domain: Required if the System MMU is needed to gate its power. + Please refer to the following document: + Documentation/devicetree/bindings/arm/exynos/power_domain.txt +- mmu-masters: A phandle to device nodes representing the master for which + the System MMU can provide a translation. Any additional values + after the phandle will be ignored because a System MMU never + have two or more masters. "#stream-id-cells" specified in the + master's node will be also ignored. + If more than one phandle is specified, only the first phandle + will be treated. + +Examples: + gsc_0: gsc@13e00000 { + compatible = "samsung,exynos5-gsc"; + reg = <0x13e00000 0x1000>; + interrupts = <0 85 0>; + samsung,power-domain = <&pd_gsc>; + clocks = <&clock 256>; + clock-names = "gscl"; + }; + + sysmmu_gsc0: sysmmu@13E80000 { + compatible = "samsung,exynos4210-sysmmu"; + reg = <0x13E80000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <2 0>; + clock-names = "sysmmu", "master"; + clocks = <&clock 262>, <&clock 256>; + samsung,power-domain = <&pd_gsc>; + mmu-masters = <&gsc_0>; + }; diff --git a/Documentation/devicetree/bindings/mfd/bcm590xx.txt b/Documentation/devicetree/bindings/mfd/bcm590xx.txt new file mode 100644 index 00000000000000..1fe30e2b10dae7 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/bcm590xx.txt @@ -0,0 +1,37 @@ +------------------------------- +BCM590xx Power Management Units +------------------------------- + +Required properties: +- compatible: "brcm,bcm59056" +- reg: I2C slave address +- interrupts: interrupt for the PMU. Generic interrupt client node bindings + are described in interrupt-controller/interrupts.txt + +------------------ +Voltage Regulators +------------------ + +Optional child nodes: +- regulators: container node for regulators following the generic + regulator binding in regulator/regulator.txt + + The valid regulator node names for BCM59056 are: + rfldo, camldo1, camldo2, simldo1, simldo2, sdldo, sdxldo, + mmcldo1, mmcldo2, audldo, micldo, usbldo, vibldo, + csr, iosr1, iosr2, msr, sdsr1, sdsr2, vsr + +Example: + pmu: bcm59056@8 { + compatible = "brcm,bcm59056"; + reg = <0x08>; + interrupts = ; + regulators { + rfldo_reg: rfldo { + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <3300000>; + }; + + ... + }; + }; diff --git a/Documentation/devicetree/bindings/mfd/max77xxx.txt b/Documentation/devicetree/bindings/mfd/max77xxx.txt new file mode 100644 index 00000000000000..fde183310b9ad8 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/max77xxx.txt @@ -0,0 +1,114 @@ +Maxim MAX77XXX multi-function device + +MAX77XXX is a Mulitifunction device with PMIC, RTC and Charger on chip. It is +interfaced to host controller using i2c interface. PMIC and Charger submodules +are addressed using same i2c slave address whereas the RTC submodule can use +a different i2c slave address (this feature is used for MAX77686). + +This document describes the binding for mfd device and PMIC submodule. + +Required properties: +- compatible : Must be "maxim,max77686" or "maxim,max77802" +- reg : Specifies the i2c slave address of PMIC block. +- interrupts : This i2c device has an IRQ line connected to the main SoC. +- interrupt-parent : The parent interrupt controller. + +Optional properties: +- max77xxx,pmic-buck-default-dvs-idx: We'll always write this DVS index in the + PMIC for BUCKs with DVS (on 77686 this is bucks 2-4; on 77802 it is 1-4, 6). + NOTE: at the moment these bindings don't include enough details for actual + GPIO-DVS--this just lets you choose which single slot to use. + +- max77xxx,pmic-buck-dvs-gpios: The DVS GPIOs. We'll try to set these GPIOs + to match pmic-buck-default-dvs-idx at probe time if they are defined. If + some or all of these GPIOs are not defined it's assumed that the board has + any missing GPIOs hardwired to match pmic-buck-default-dvs-idx. + +- max77xxx,pmic-buck-selb-gpios: GPIOs to enable DVS-GPIO for BUCKs. + On 77686 should be three values: 2, 3, and 4. On 77802 should be five + values: 1, 2, 3, 4, 6. It is strongly suggested to include these GPIOs + if there's any chance that changing DVS GPIOs one line at a time might + glitch your DVS values. + + +Optional node: +- voltage-regulators : The regulators of max77686 have to be instantiated + under subnode named "voltage-regulators" using the following format. + + regulator_name { + standard regulator constraints.... + }; + refer Documentation/devicetree/bindings/regulator/regulator.txt + +Required properties for each regulator: +- regulator-op-mode : Regulator operating mode, the meaning is regulator- + dependent. Valid values are 0-3. + + The regulator_name should be initialized with a string which matches + the hardware counterparts as follow: + + -LDOn : for LDOs, where n can lie in range 1 to 26 (1 to 35 + for MAX77802). + example: LDO1, LDO2, LDO26. + -BUCKn : for BUCKs, where n can lie in range 1 to 9 (1 TO 10 + FOR max77802) + example: BUCK1, BUCK5, BUCK9. + EN32KHZ_AP, EN32KHZ_CP : fake regulators which actually produce + 32KHx clocks + P32KHZ : MAX77686 only: another 32KHz clock + +Optional subnode (to enable the RTC): + - rtc : Indicates that the PMIC includes a real-time-clock. For MAX77686 + the reg property indicates the i2c slave address to use for talking + to this. For MAX77802 the reg property is not needed and should not + be specified. + + +Example: + + max77686@9 { + compatible = "maxim,max77686"; + interrupt-parent = <&wakeup_eint>; + interrupts = <26 0>; + reg = <0x09>; + #address-cells = <1>; + #size-cells = <0>; + + max77xxx,pmic-buck-default-dvs-idx = <1>; + max77xxx,pmic-buck-dvs-gpios = <&gpy7 6 0>, + <&gpj4 2 0>, + <&gpj4 3 0>; + max77xxx,pmic-buck-selb-gpios = <&gph0 2 0>, + <&gph0 3 0>, + <&gph0 4 0>, + <&gph0 5 0>, + <&gph0 6 0>; + + voltage-regulators { + LDO11 { + regulator-name = "vdd_ldo11"; + regulator-min-microvolt = <1900000>; + regulator-max-microvolt = <1900000>; + regulator-always-on; + regulator-op-mode = <3>; + }; + + BUCK1 { + regulator-name = "vdd_mif"; + regulator-min-microvolt = <950000>; + regulator-max-microvolt = <1300000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <3>; + }; + + EN32KHZ_CP { + regulator-name = "en32khz_cp"; + regulator-always-on; + regulator-op-mode = <1>; + }; + + rtc@c { + reg = <0xc>; + }; + } diff --git a/Documentation/devicetree/bindings/misc/arm,scpi-mhu.txt b/Documentation/devicetree/bindings/misc/arm,scpi-mhu.txt new file mode 100644 index 00000000000000..753a219de84f5d --- /dev/null +++ b/Documentation/devicetree/bindings/misc/arm,scpi-mhu.txt @@ -0,0 +1,29 @@ +ARM SCPI MHU driver. + +This driver uses the Message Handling Unit hardware to talk with +a System Controll Processor using the SCPI interface. + +Required properties: + - compatible: "arm,scpi-mhu" + - reg: two pairs of register address and size data, first for the + start of the MHU registers area, second for the payload area. + - interrupts: Interrupt list for low priority and high priority + channel, in that order. + - #clock-cells: from common clock bindings; should be set to 1 if + the SCP offers support for controlling oscillator frequencies, 0 + otherwise. + - clock-output-names: List of names for the the oscillator output(s). + + +Examples: + scp: scp@2b1f0000 { + compatible = "arm,scpi-mhu"; + reg = <0x0 0x2b1f0000 0x0 0x10000>, /* MHU registers */ + <0x0 0x2e000000 0x0 0x10000>; /* Payload area */ + interrupts = <0 36 4>, /* low priority channel interrupt */ + <0 35 4>, /* high priority channel interrupt */ + <0 37 4>; /* secure channel interrupt */ + #clock-cells = <1>; + clock-output-names= "a57", "a53", "gpu", hdlcd0", "hdlcd1"; + }; + diff --git a/Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt b/Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt new file mode 100644 index 00000000000000..6bc542a58d377b --- /dev/null +++ b/Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt @@ -0,0 +1,28 @@ +* Hisilicon hip04 Ethernet Controller + +Required properties: +- compatible: should be "hisilicon,hip04-mac". +- reg: address and length of the register set for the device. +- interrupts: interrupt for the device. +- port: port number combined with the controller: range from 0 to 31. +- speed: 100 (100M) or 1000 (1000M). + +Example: + ge0: ethernet@2800000 { + compatible = "hisilicon,hip04-mac"; + reg = <0x2800000 0x10000>; + interrupts = <0 434 4>; + port = <0>; + speed = <1000>; + }; + +* Hisilicon hip04 Ethernet ppe: control rx & rx fifos of all ethnet controllers + +Required properties: +- compatible: should be "hisilicon,ppebase". + +Example: + ppebase: ppebase@28c0000 { + compatible = "hisilicon,ppebase"; + reg = <0x28c0000 0x10000>; + }; diff --git a/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt index 257677de3e6bad..26892c4a8b0449 100644 --- a/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt +++ b/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt @@ -16,6 +16,7 @@ Required Properties: - "samsung,exynos4210-pinctrl": for Exynos4210 compatible pin-controller. - "samsung,exynos4x12-pinctrl": for Exynos4x12 compatible pin-controller. - "samsung,exynos5250-pinctrl": for Exynos5250 compatible pin-controller. + - "samsung,exynos5410-pinctrl": for Exynos5410 compatible pin-controller. - "samsung,exynos5420-pinctrl": for Exynos5420 compatible pin-controller. - reg: Base address of the pin controller hardware module and length of diff --git a/Documentation/devicetree/bindings/pwm/bcm-kona-pwm.txt b/Documentation/devicetree/bindings/pwm/bcm-kona-pwm.txt new file mode 100644 index 00000000000000..c8e2d1319b6897 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/bcm-kona-pwm.txt @@ -0,0 +1,24 @@ +Broadcom Kona PWM controller device tree bindings + +This controller has 6 channels. + +Required Properties : +- compatible: should be "brcm,kona-pwm" +- reg: physical base address and length of the controller's registers +- clocks: phandle + clock specifier pair for the external clock +- #pwm-cells: should be 3. The first cell specifies the per-chip index + of the PWM to use, the second cell is the period in nanoseconds, and + the third cell is the flags. + +Refer to pwm/pwm.txt for generic pwm controller node properties. + +Refer to clocks/clock-bindings.txt for generic clock consumer properties. + +Example: + +pwm: pwm@3e01a000 { + compatible = "brcm,bcm11351-pwm", "brcm,kona-pwm"; + reg = <0x3e01a000 0xc4>; + clocks = <&pwm_clk>; + #pwm-cells = <3>; +}; diff --git a/Documentation/devicetree/bindings/thermal/exynos-thermal.txt b/Documentation/devicetree/bindings/thermal/exynos-thermal.txt index 284f5300fd8bbe..79c4055a190d13 100644 --- a/Documentation/devicetree/bindings/thermal/exynos-thermal.txt +++ b/Documentation/devicetree/bindings/thermal/exynos-thermal.txt @@ -6,16 +6,34 @@ "samsung,exynos4412-tmu" "samsung,exynos4210-tmu" "samsung,exynos5250-tmu" + "samsung,exynos5420-tmu" for TMU channel 0, 1 on Exynos5420 + "samsung,exynos5420-tmu-ext-triminfo" for TMU channels 2, 3 and 4 + Exynos5420 (Must pass triminfo base and triminfo clock) "samsung,exynos5440-tmu" - interrupt-parent : The phandle for the interrupt controller - reg : Address range of the thermal registers. For soc's which has multiple instances of TMU and some registers are shared across all TMU's like interrupt related then 2 set of register has to supplied. First set - belongs to each instance of TMU and second set belongs to common TMU - registers. + belongs to register set of TMU instance and second set belongs to + registers shared with the TMU instance. + + NOTE: On Exynos5420, the TRIMINFO register is misplaced for TMU + channels 2, 3 and 4 + Use "samsung,exynos5420-tmu-ext-triminfo" in cases, there is a misplaced + register, also provide clock to access that base. + + TRIMINFO at 0x1006c000 contains data for TMU channel 3 + TRIMINFO at 0x100a0000 contains data for TMU channel 4 + TRIMINFO at 0x10068000 contains data for TMU channel 2 + - interrupts : Should contain interrupt for thermal system -- clocks : The main clock for TMU device +- clocks : The main clocks for TMU device + -- 1. operational clock for TMU channel + -- 2. optional clock to access the shared registers of TMU channel - clock-names : Thermal system clock name + -- "tmu_apbif" operational clock for current TMU channel + -- "tmu_triminfo_apbif" clock to access the shared triminfo register + for current TMU channel - vtmu-supply: This entry is optional and provides the regulator node supplying voltage to TMU. If needed this entry can be placed inside board/platform specific dts file. @@ -43,6 +61,31 @@ Example 2): clock-names = "tmu_apbif"; }; +Example 3): (In case of Exynos5420 "with misplaced TRIMINFO register") + tmu_cpu2: tmu@10068000 { + compatible = "samsung,exynos5420-tmu-ext-triminfo"; + reg = <0x10068000 0x100>, <0x1006c000 0x4>; + interrupts = <0 184 0>; + clocks = <&clock 318>, <&clock 318>; + clock-names = "tmu_apbif", "tmu_triminfo_apbif"; + }; + + tmu_cpu3: tmu@1006c000 { + compatible = "samsung,exynos5420-tmu-ext-triminfo"; + reg = <0x1006c000 0x100>, <0x100a0000 0x4>; + interrupts = <0 185 0>; + clocks = <&clock 318>, <&clock 319>; + clock-names = "tmu_apbif", "tmu_triminfo_apbif"; + }; + + tmu_gpu: tmu@100a0000 { + compatible = "samsung,exynos5420-tmu-ext-triminfo"; + reg = <0x100a0000 0x100>, <0x10068000 0x4>; + interrupts = <0 215 0>; + clocks = <&clock 319>, <&clock 318>; + clock-names = "tmu_apbif", "tmu_triminfo_apbif"; + }; + Note: For multi-instance tmu each instance should have an alias correctly numbered in "aliases" node. diff --git a/Documentation/devicetree/bindings/usb/samsung-usbphy.txt b/Documentation/devicetree/bindings/usb/samsung-usbphy.txt index 33fd3543f3f84e..44b8d1a240646f 100644 --- a/Documentation/devicetree/bindings/usb/samsung-usbphy.txt +++ b/Documentation/devicetree/bindings/usb/samsung-usbphy.txt @@ -23,6 +23,14 @@ Exynos5250: - reg : base physical address of the phy registers and length of memory mapped region. +Exynos5420: +- compatible : should be "samsung,exynos5420-usb2phy" +- reg : base physical address of the phy registers and length of memory mapped + region. +- clocks: Clock IDs array as required by the controller. +- clock-names: names of clock correseponding IDs clock property as requested + by the controller driver. + Optional properties: - #address-cells: should be '1' when usbphy node has a child node with 'reg' property. @@ -79,6 +87,14 @@ Exynos5250: - clock-names: names of clocks correseponding to IDs in the clock property as requested by the controller driver. +Exynos5420: +- compatible : should be "samsung,exynos5420-usb3phy" +- reg : base physical address of the phy registers and length of memory mapped + region. +- clocks: Clock IDs array as required by the controller. +- clock-names: names of clock correseponding IDs clock property as requested + by the controller driver. + Optional properties: - #address-cells: should be '1' when usbphy node has a child node with 'reg' property. @@ -99,6 +115,11 @@ Optional properties: USBDEVICE_PHY_CONTROL and USBHOST_PHY_CONTROL registers in exynos4x). and so on. +- Aliases: exynos5420 has multiple usb 3.0 phy transceivers interacting with multiple + dwc3 controllers. So the usb 3.0 phy controller nodes should be represented + in the aliases node using the following format: + 'usb3phy{n}' where n is a unique number for the alias. + Example: usbphy@12100000 { compatible = "samsung,exynos5250-usb3phy"; diff --git a/Documentation/devicetree/bindings/usb/usb3503.txt b/Documentation/devicetree/bindings/usb/usb3503.txt index a018da4a7ad752..c07e2b49a279e7 100644 --- a/Documentation/devicetree/bindings/usb/usb3503.txt +++ b/Documentation/devicetree/bindings/usb/usb3503.txt @@ -15,6 +15,7 @@ Optional properties: - reset-gpios: Should specify GPIO for reset. - initial-mode: Should specify initial mode. (1 for HUB mode, 2 for STANDBY mode) +- secondary-refclk: select the secondary REFCLK frequencies. Examples: usb3503@08 { @@ -25,4 +26,5 @@ Examples: intn-gpios = <&gpx3 4 1>; reset-gpios = <&gpx3 5 1>; initial-mode = <1>; + secondary-refclk; }; diff --git a/Documentation/devicetree/bindings/video/exynos_hdmi.txt b/Documentation/devicetree/bindings/video/exynos_hdmi.txt index 50decf8e1b90a5..240eca528a5bff 100644 --- a/Documentation/devicetree/bindings/video/exynos_hdmi.txt +++ b/Documentation/devicetree/bindings/video/exynos_hdmi.txt @@ -25,6 +25,7 @@ Required properties: sclk_pixel. - clock-names: aliases as per driver requirements for above clock IDs: "hdmi", "sclk_hdmi", "sclk_pixel", "sclk_hdmiphy" and "mout_hdmi". +- phy: it points to hdmiphy dt node. Example: hdmi { @@ -32,4 +33,5 @@ Example: reg = <0x14530000 0x100000>; interrupts = <0 95 0>; hpd-gpio = <&gpx3 7 1>; + phy = <&hdmiphy>; }; diff --git a/Documentation/devicetree/bindings/video/exynos_hdmiphy.txt b/Documentation/devicetree/bindings/video/exynos_hdmiphy.txt index 162f641f7639c0..91e6578483c060 100644 --- a/Documentation/devicetree/bindings/video/exynos_hdmiphy.txt +++ b/Documentation/devicetree/bindings/video/exynos_hdmiphy.txt @@ -5,11 +5,17 @@ Required properties: 1) "samsung,exynos5-hdmiphy" 2) "samsung,exynos4210-hdmiphy". 3) "samsung,exynos4212-hdmiphy". -- reg: I2C address of the hdmiphy device. +- reg: Physical address of the hdmiphy device. +- phy-power-control: this child node represents phy power control + register which is inside the pmu block (power management unit). Example: hdmiphy { compatible = "samsung,exynos4210-hdmiphy"; reg = <0x38>; + + phy-power-control { + reg = <0x10040700 0x04>; + }; }; diff --git a/Documentation/dontdiff b/Documentation/dontdiff index b89a739a32761d..9de9813d0ec5df 100644 --- a/Documentation/dontdiff +++ b/Documentation/dontdiff @@ -1,5 +1,6 @@ *.a *.aux +*.bc *.bin *.bz2 *.cis @@ -21,6 +22,7 @@ *.i *.jpeg *.ko +*.ll *.log *.lst *.lzma @@ -35,6 +37,7 @@ *.out *.patch *.pdf +*.plist *.png *.pot *.ps diff --git a/Documentation/filesystems/proc.txt b/Documentation/filesystems/proc.txt index f00bee144addc5..90adc374351dab 100644 --- a/Documentation/filesystems/proc.txt +++ b/Documentation/filesystems/proc.txt @@ -369,6 +369,8 @@ is not associated with a file: [stack:1001] = the stack of the thread with tid 1001 [vdso] = the "virtual dynamic shared object", the kernel system call handler + [anon:] = an anonymous mapping that has been + named by userspace or if empty, the mapping is anonymous. @@ -419,6 +421,7 @@ KernelPageSize: 4 kB MMUPageSize: 4 kB Locked: 374 kB VmFlags: rd ex mr mw me de +Name: name from userspace the first of these lines shows the same information as is displayed for the mapping in /proc/PID/maps. The remaining lines show the size of the mapping @@ -470,6 +473,9 @@ Note that there is no guarantee that every flag and associated mnemonic will be present in all further kernel releases. Things get changed, the flags may be vanished or the reverse -- new added. +The "Name" field will only be present on a mapping that has been named by +userspace, and will show the name passed in by userspace. + This file is only present if the CONFIG_MMU kernel configuration option is enabled. diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 7116fda7077ffc..b8502220ee0eca 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1669,6 +1669,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted. ltpc= [NET] Format: ,, + mac= [NET] + Used ASIX drivers. + Example: mac=12:34:56:78:ab:cd + machvec= [IA-64] Force the use of a particular machine-vector (machvec) in a generic kernel. Example: machvec=hpzx1_swiotlb diff --git a/Documentation/sync.txt b/Documentation/sync.txt new file mode 100644 index 00000000000000..a2d05e7fa1932a --- /dev/null +++ b/Documentation/sync.txt @@ -0,0 +1,75 @@ +Motivation: + +In complicated DMA pipelines such as graphics (multimedia, camera, gpu, display) +a consumer of a buffer needs to know when the producer has finished producing +it. Likewise the producer needs to know when the consumer is finished with the +buffer so it can reuse it. A particular buffer may be consumed by multiple +consumers which will retain the buffer for different amounts of time. In +addition, a consumer may consume multiple buffers atomically. +The sync framework adds an API which allows synchronization between the +producers and consumers in a generic way while also allowing platforms which +have shared hardware synchronization primitives to exploit them. + +Goals: + * provide a generic API for expressing synchronization dependencies + * allow drivers to exploit hardware synchronization between hardware + blocks + * provide a userspace API that allows a compositor to manage + dependencies. + * provide rich telemetry data to allow debugging slowdowns and stalls of + the graphics pipeline. + +Objects: + * sync_timeline + * sync_pt + * sync_fence + +sync_timeline: + +A sync_timeline is an abstract monotonically increasing counter. In general, +each driver/hardware block context will have one of these. They can be backed +by the appropriate hardware or rely on the generic sw_sync implementation. +Timelines are only ever created through their specific implementations +(i.e. sw_sync.) + +sync_pt: + +A sync_pt is an abstract value which marks a point on a sync_timeline. Sync_pts +have a single timeline parent. They have 3 states: active, signaled, and error. +They start in active state and transition, once, to either signaled (when the +timeline counter advances beyond the sync_pt’s value) or error state. + +sync_fence: + +Sync_fences are the primary primitives used by drivers to coordinate +synchronization of their buffers. They are a collection of sync_pts which may +or may not have the same timeline parent. A sync_pt can only exist in one fence +and the fence's list of sync_pts is immutable once created. Fences can be +waited on synchronously or asynchronously. Two fences can also be merged to +create a third fence containing a copy of the two fences’ sync_pts. Fences are +backed by file descriptors to allow userspace to coordinate the display pipeline +dependencies. + +Use: + +A driver implementing sync support should have a work submission function which: + * takes a fence argument specifying when to begin work + * asynchronously queues that work to kick off when the fence is signaled + * returns a fence to indicate when its work will be done. + * signals the returned fence once the work is completed. + +Consider an imaginary display driver that has the following API: +/* + * assumes buf is ready to be displayed. + * blocks until the buffer is on screen. + */ + void display_buffer(struct dma_buf *buf); + +The new API will become: +/* + * will display buf when fence is signaled. + * returns immediately with a fence that will signal when buf + * is no longer displayed. + */ +struct sync_fence* display_buffer(struct dma_buf *buf, + struct sync_fence *fence); diff --git a/Documentation/sysctl/vm.txt b/Documentation/sysctl/vm.txt index d614a9b6a28048..9dc0b8a780ec90 100644 --- a/Documentation/sysctl/vm.txt +++ b/Documentation/sysctl/vm.txt @@ -29,6 +29,7 @@ Currently, these files are in /proc/sys/vm: - dirty_writeback_centisecs - drop_caches - extfrag_threshold +- extra_free_kbytes - hugepages_treat_as_movable - hugetlb_shm_group - laptop_mode @@ -204,6 +205,21 @@ fragmentation index is <= extfrag_threshold. The default value is 500. ============================================================== +extra_free_kbytes + +This parameter tells the VM to keep extra free memory between the threshold +where background reclaim (kswapd) kicks in, and the threshold where direct +reclaim (by allocating processes) kicks in. + +This is useful for workloads that require low latency memory allocations +and have a bounded burstiness in memory allocations, for example a +realtime application that receives and transmits network traffic +(causing in-kernel memory allocations) with a maximum total message burst +size of 200MB may need 200MB of extra free memory to avoid direct reclaim +related latencies. + +============================================================== + hugepages_treat_as_movable This parameter controls whether we can allocate hugepages from ZONE_MOVABLE diff --git a/Documentation/trace/ftrace.txt b/Documentation/trace/ftrace.txt index bd365988e8d8d8..a0dcdbc112ee47 100644 --- a/Documentation/trace/ftrace.txt +++ b/Documentation/trace/ftrace.txt @@ -2017,6 +2017,35 @@ will produce: 1) 1.449 us | } +You can disable the hierarchical function call formatting and instead print a +flat list of function entry and return events. This uses the format described +in the Output Formatting section and respects all the trace options that +control that formatting. Hierarchical formatting is the default. + + hierachical: echo nofuncgraph-flat > trace_options + flat: echo funcgraph-flat > trace_options + + ie: + + # tracer: function_graph + # + # entries-in-buffer/entries-written: 68355/68355 #P:2 + # + # _-----=> irqs-off + # / _----=> need-resched + # | / _---=> hardirq/softirq + # || / _--=> preempt-depth + # ||| / delay + # TASK-PID CPU# |||| TIMESTAMP FUNCTION + # | | | |||| | | + sh-1806 [001] d... 198.843443: graph_ent: func=_raw_spin_lock + sh-1806 [001] d... 198.843445: graph_ent: func=__raw_spin_lock + sh-1806 [001] d..1 198.843447: graph_ret: func=__raw_spin_lock + sh-1806 [001] d..1 198.843449: graph_ret: func=_raw_spin_lock + sh-1806 [001] d..1 198.843451: graph_ent: func=_raw_spin_unlock_irqrestore + sh-1806 [001] d... 198.843453: graph_ret: func=_raw_spin_unlock_irqrestore + + You might find other useful features for this tracer in the following "dynamic ftrace" section such as tracing only specific functions or tasks. diff --git a/MAINTAINERS b/MAINTAINERS index 900d98eec2fcb6..ac60dccc334c32 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1883,6 +1883,14 @@ F: arch/arm/boot/dts/bcm2835* F: arch/arm/configs/bcm2835_defconfig F: drivers/*/*bcm2835* +BROADCOM BCM5301X ARM ARCHICTURE +M: Hauke Mehrtens +L: linux-arm-kernel@lists.infradead.org +S: Maintained +F: arch/arm/mach-bcm/bcm_5301x.c +F: arch/arm/boot/dts/bcm5301x.dtsi +F: arch/arm/boot/dts/bcm470* + BROADCOM TG3 GIGABIT ETHERNET DRIVER M: Nithin Nayak Sujir M: Michael Chan diff --git a/Makefile b/Makefile index e5ac8a62e6e57c..2c22f15f35cc96 100644 --- a/Makefile +++ b/Makefile @@ -247,6 +247,11 @@ HOSTCXX = g++ HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer HOSTCXXFLAGS = -O2 +ifeq ($(shell $(HOSTCC) -v 2>&1 | grep -c "clang version"), 1) +HOSTCFLAGS += -Wno-unused-value -Wno-unused-parameter \ + -Wno-missing-field-initializers -fno-delete-null-pointer-checks +endif + # Decide whether to build built-in, modular, or both. # Normally, just do built-in. @@ -323,6 +328,14 @@ endif export quiet Q KBUILD_VERBOSE +ifneq ($(CC),) +ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1) +COMPILER := clang +else +COMPILER := gcc +endif +export COMPILER +endif # Look for make include files relative to root of kernel src MAKEFLAGS += --include-dir=$(srctree) @@ -382,10 +395,11 @@ KBUILD_CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \ -fno-strict-aliasing -fno-common \ -Werror-implicit-function-declaration \ -Wno-format-security \ - -fno-delete-null-pointer-checks + $(call cc-option,-no-integrated-as,) \ + $(call cc-option,-fno-delete-null-pointer-checks,) KBUILD_AFLAGS_KERNEL := KBUILD_CFLAGS_KERNEL := -KBUILD_AFLAGS := -D__ASSEMBLY__ +KBUILD_AFLAGS := -D__ASSEMBLY__ $(call cc-option,-no-integrated-as,) KBUILD_AFLAGS_MODULE := -DMODULE KBUILD_CFLAGS_MODULE := -DMODULE KBUILD_LDFLAGS_MODULE := -T $(srctree)/scripts/module-common.lds @@ -607,6 +621,7 @@ ifdef CONFIG_CC_STACKPROTECTOR_REGULAR ifeq ($(call cc-option, $(stackp-flag)),) $(warning Cannot use CONFIG_CC_STACKPROTECTOR_REGULAR: \ -fstack-protector not supported by compiler) + stackp-flag := endif else ifdef CONFIG_CC_STACKPROTECTOR_STRONG @@ -614,6 +629,7 @@ ifdef CONFIG_CC_STACKPROTECTOR_STRONG ifeq ($(call cc-option, $(stackp-flag)),) $(warning Cannot use CONFIG_CC_STACKPROTECTOR_STRONG: \ -fstack-protector-strong not supported by compiler) + stackp-flag := endif else # Force off for distro compilers that enable stack protector by default. @@ -622,9 +638,24 @@ endif endif KBUILD_CFLAGS += $(stackp-flag) +ifeq ($(COMPILER),clang) +KBUILD_CPPFLAGS += $(call cc-option,-Qunused-arguments,) +KBUILD_CPPFLAGS += $(call cc-option,-Wno-unknown-warning-option,) +KBUILD_CFLAGS += $(call cc-disable-warning, unused-variable) +KBUILD_CFLAGS += $(call cc-disable-warning, format-invalid-specifier) +KBUILD_CFLAGS += $(call cc-disable-warning, gnu) +# Quiet clang warning: comparison of unsigned expression < 0 is always false +KBUILD_CFLAGS += $(call cc-disable-warning, tautological-compare) +# CLANG uses a _MergedGlobals as optimization, but this breaks modpost, as the +# source of a reference will be _MergedGlobals and not on of the whitelisted names. +# See modpost pattern 2 +KBUILD_CFLAGS += $(call cc-option, -mno-global-merge,) +else + # This warning generated too much noise in a regular build. # Use make W=1 to enable this warning (see scripts/Makefile.build) KBUILD_CFLAGS += $(call cc-disable-warning, unused-but-set-variable) +endif ifdef CONFIG_FRAME_POINTER KBUILD_CFLAGS += -fno-omit-frame-pointer -fno-optimize-sibling-calls @@ -641,7 +672,7 @@ endif ifdef CONFIG_DEBUG_INFO KBUILD_CFLAGS += -g -KBUILD_AFLAGS += -Wa,--gdwarf-2 +KBUILD_AFLAGS += -Wa,-gdwarf-2 endif ifdef CONFIG_DEBUG_INFO_REDUCED @@ -848,7 +879,6 @@ endef include/config/kernel.release: include/config/auto.conf FORCE $(call filechk,kernel.release) - # Things we need to do before we recursively start building the kernel # or the modules are listed in "prepare". # A multi level approach is used. prepareN is processed before prepareN-1. @@ -898,7 +928,9 @@ define filechk_utsrelease.h echo '"$(KERNELRELEASE)" exceeds $(uts_len) characters' >&2; \ exit 1; \ fi; \ - (echo \#define UTS_RELEASE \"$(KERNELRELEASE)\";) + (echo \#define UTS_RELEASE \"$(KERNELRELEASE)\"; \ + echo \#define KERNEL_GIT_ID \"$(shell \ + git rev-parse --verify --short HEAD 2>/dev/null)\";) endef define filechk_version.h diff --git a/android/configs/README b/android/configs/README new file mode 100644 index 00000000000000..8798731f89048e --- /dev/null +++ b/android/configs/README @@ -0,0 +1,15 @@ +The files in this directory are meant to be used as a base for an Android +kernel config. All devices should have the options in android-base.cfg enabled. +While not mandatory, the options in android-recommended.cfg enable advanced +Android features. + +Assuming you already have a minimalist defconfig for your device, a possible +way to enable these options would be: + + ARCH= scripts/kconfig/merge_config.sh /_defconfig android/configs/android-base.cfg android/configs/android-recommended.cfg + +This will generate a .config that can then be used to save a new defconfig or +compile a new kernel with Android features enabled. + +Because there is no tool to consistently generate these config fragments, +lets keep them alphabetically sorted instead of random. diff --git a/android/configs/android-base.cfg b/android/configs/android-base.cfg new file mode 100644 index 00000000000000..04c7148cf63724 --- /dev/null +++ b/android/configs/android-base.cfg @@ -0,0 +1,140 @@ +# KEEP ALPHABETICALLY SORTED +# CONFIG_INET_LRO is not set +# CONFIG_MODULES is not set +# CONFIG_OABI_COMPAT is not set +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_INTF_ALARM_DEV=y +CONFIG_ANDROID_LOGGER=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_ASHMEM=y +CONFIG_BLK_DEV_DM=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_DEBUG=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_SCHED=y +CONFIG_DM_CRYPT=y +CONFIG_DM_VERITY=y +CONFIG_EMBEDDED=y +CONFIG_FB=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_INET=y +CONFIG_INET_ESP=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_IP6_NF_RAW=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IP6_NF_TARGET_REJECT_SKERR=y +CONFIG_IPV6_MIP6=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARPTABLES=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MANGLE=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_RAW=y +CONFIG_IP_NF_TARGET_MASQUERADE=y +CONFIG_IP_NF_TARGET_NETMAP=y +CONFIG_IP_NF_TARGET_REDIRECT=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_IP_NF_TARGET_REJECT_SKERR=y +CONFIG_NET=y +CONFIG_NETDEVICES=y +CONFIG_NETFILTER=y +CONFIG_NETFILTER_TPROXY=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +CONFIG_NETFILTER_XT_TARGET_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_TRACE=y +CONFIG_NET_CLS_ACT=y +CONFIG_NET_CLS_U32=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_U32=y +CONFIG_NET_KEY=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_AMANDA=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_H323=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_NF_CONNTRACK_IPV6=y +CONFIG_NF_CONNTRACK_IRC=y +CONFIG_NF_CONNTRACK_NETBIOS_NS=y +CONFIG_NF_CONNTRACK_PPTP=y +CONFIG_NF_CONNTRACK_SANE=y +CONFIG_NF_CONNTRACK_TFTP=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NF_CT_PROTO_DCCP=y +CONFIG_NF_CT_PROTO_SCTP=y +CONFIG_NF_CT_PROTO_UDPLITE=y +CONFIG_NF_NAT=y +CONFIG_NO_HZ=y +CONFIG_PACKET=y +CONFIG_PM_AUTOSLEEP=y +CONFIG_PM_WAKELOCKS=y +CONFIG_PPP=y +CONFIG_PPPOLAC=y +CONFIG_PPPOPNS=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_MPPE=y +CONFIG_PREEMPT=y +CONFIG_RESOURCE_COUNTERS=y +CONFIG_RTC_CLASS=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_STAGING=y +CONFIG_SWITCH=y +CONFIG_SYNC=y +CONFIG_SYSVIPC=y +CONFIG_TUN=y +CONFIG_UNIX=y +CONFIG_USB_GADGET=y +CONFIG_USB_G_ANDROID=y +CONFIG_USB_OTG_WAKELOCK=y +CONFIG_XFRM_USER=y diff --git a/android/configs/android-recommended.cfg b/android/configs/android-recommended.cfg new file mode 100644 index 00000000000000..960b9de2860d29 --- /dev/null +++ b/android/configs/android-recommended.cfg @@ -0,0 +1,121 @@ +# KEEP ALPHABETICALLY SORTED +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_NF_CONNTRACK_SIP is not set +# CONFIG_PM_WAKELOCKS_GC is not set +# CONFIG_VT is not set +CONFIG_ANDROID_TIMED_GPIO=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_COMPACTION=y +CONFIG_DM_UEVENT=y +CONFIG_DRAGONRISE_FF=y +CONFIG_ENABLE_DEFAULT_TRACERS=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_FUSE_FS=y +CONFIG_GREENASIA_FF=y +CONFIG_HIDRAW=y +CONFIG_HID_A4TECH=y +CONFIG_HID_ACRUX=y +CONFIG_HID_ACRUX_FF=y +CONFIG_HID_APPLE=y +CONFIG_HID_BELKIN=y +CONFIG_HID_CHERRY=y +CONFIG_HID_CHICONY=y +CONFIG_HID_CYPRESS=y +CONFIG_HID_DRAGONRISE=y +CONFIG_HID_ELECOM=y +CONFIG_HID_EMS_FF=y +CONFIG_HID_EZKEY=y +CONFIG_HID_GREENASIA=y +CONFIG_HID_GYRATION=y +CONFIG_HID_HOLTEK=y +CONFIG_HID_KENSINGTON=y +CONFIG_HID_KEYTOUCH=y +CONFIG_HID_KYE=y +CONFIG_HID_LCPOWER=y +CONFIG_HID_LOGITECH=y +CONFIG_HID_LOGITECH_DJ=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MONTEREY=y +CONFIG_HID_MULTITOUCH=y +CONFIG_HID_NTRIG=y +CONFIG_HID_ORTEK=y +CONFIG_HID_PANTHERLORD=y +CONFIG_HID_PETALYNX=y +CONFIG_HID_PICOLCD=y +CONFIG_HID_PRIMAX=y +CONFIG_HID_PRODIKEYS=y +CONFIG_HID_ROCCAT=y +CONFIG_HID_SAITEK=y +CONFIG_HID_SAMSUNG=y +CONFIG_HID_SMARTJOYPLUS=y +CONFIG_HID_SONY=y +CONFIG_HID_SPEEDLINK=y +CONFIG_HID_SUNPLUS=y +CONFIG_HID_THRUSTMASTER=y +CONFIG_HID_TIVO=y +CONFIG_HID_TOPSEED=y +CONFIG_HID_TWINHAN=y +CONFIG_HID_UCLOGIC=y +CONFIG_HID_WACOM=y +CONFIG_HID_WALTOP=y +CONFIG_HID_WIIMOTE=y +CONFIG_HID_ZEROPLUS=y +CONFIG_HID_ZYDACRON=y +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_GPIO=y +CONFIG_INPUT_JOYSTICK=y +CONFIG_INPUT_KEYCHORD=y +CONFIG_INPUT_KEYRESET=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_TABLET=y +CONFIG_INPUT_UINPUT=y +CONFIG_ION=y +CONFIG_JOYSTICK_XPAD=y +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +CONFIG_KALLSYMS_ALL=y +CONFIG_KSM=y +CONFIG_LOGIG940_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGITECH_FF=y +CONFIG_MD=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MSDOS_FS=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_PANTHERLORD_FF=y +CONFIG_PERF_EVENTS=y +CONFIG_PM_DEBUG=y +CONFIG_PM_RUNTIME=y +CONFIG_PM_WAKELOCKS_LIMIT=0 +CONFIG_POWER_SUPPLY=y +CONFIG_PSTORE=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_RAM=y +CONFIG_SCHEDSTATS=y +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_SND=y +CONFIG_SOUND=y +CONFIG_SUSPEND_TIME=y +CONFIG_TABLET_USB_ACECAD=y +CONFIG_TABLET_USB_AIPTEK=y +CONFIG_TABLET_USB_GTCO=y +CONFIG_TABLET_USB_HANWANG=y +CONFIG_TABLET_USB_KBTAB=y +CONFIG_TABLET_USB_WACOM=y +CONFIG_TIMER_STATS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_UHID=y +CONFIG_UID_STAT=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_HIDDEV=y +CONFIG_USB_USBNET=y +CONFIG_VFAT_FS=y diff --git a/arch/Kconfig b/arch/Kconfig index 80bbb8ccd0d10b..97ff872c7accf9 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -86,9 +86,7 @@ config KPROBES_ON_FTRACE optimize on top of function tracing. config UPROBES - bool "Transparent user-space probes (EXPERIMENTAL)" - depends on UPROBE_EVENT && PERF_EVENTS - default n + def_bool n select PERCPU_RWSEM help Uprobes is the user-space counterpart to kprobes: they @@ -101,8 +99,6 @@ config UPROBES managed by the kernel and kept transparent to the probed application. ) - If in doubt, say "N". - config HAVE_64BIT_ALIGNED_ACCESS def_bool 64BIT && !HAVE_EFFICIENT_UNALIGNED_ACCESS help diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 15949459611f19..96c4d24a093b01 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -207,6 +207,9 @@ config ZONE_DMA config NEED_DMA_MAP_STATE def_bool y +config ARCH_SUPPORTS_UPROBES + def_bool y + config ARCH_HAS_DMA_SET_COHERENT_MASK bool @@ -1092,7 +1095,7 @@ source arch/arm/mm/Kconfig config ARM_NR_BANKS int - default 16 if ARCH_EP93XX + default 16 if ARCH_EP93XX || ARCH_HIP04 default 8 config IWMMXT @@ -1912,6 +1915,15 @@ config XEN help Say Y if you want to run Linux in a Virtual Machine on Xen on ARM. +config ARM_FLUSH_CONSOLE_ON_RESTART + bool "Force flush the console on restart" + help + If the console is locked while the system is rebooted, the messages + in the temporary logbuffer would not have propogated to all the + console drivers. This option forces the console lock to be + released if it failed to be acquired, which will cause all the + pending messages to be flushed. + endmenu menu "Boot options" @@ -1941,6 +1953,21 @@ config DEPRECATED_PARAM_STRUCT This was deprecated in 2001 and announced to live on for 5 years. Some old boot loaders still use this way. +config BUILD_ARM_APPENDED_DTB_IMAGE + bool "Build a concatenated zImage/dtb by default" + depends on OF + help + Enabling this option will cause a concatenated zImage and list of + DTBs to be built by default (instead of a standalone zImage.) + The image will built in arch/arm/boot/zImage-dtb + +config BUILD_ARM_APPENDED_DTB_IMAGE_NAMES + string "Default dtb names" + depends on BUILD_ARM_APPENDED_DTB_IMAGE + help + Space separated list of names of dtbs to append when + building a concatenated zImage-dtb. + # Compressed boot loader in ROM. Yes, we really want to ask about # TEXT and BSS so we preserve their values in the config files. config ZBOOT_ROM_TEXT diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 0531da8e5216e4..5c6d9da9249c30 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -106,9 +106,14 @@ choice depends on ARCH_BCM2835 select DEBUG_UART_PL01X + config DEBUG_BCM_5301X + bool "Kernel low-level debugging on BCM5301X UART1" + depends on ARCH_BCM_5301X + select DEBUG_UART_PL01X + config DEBUG_BCM_KONA_UART bool "Kernel low-level debugging messages via BCM KONA UART" - depends on ARCH_BCM + depends on ARCH_BCM_MOBILE select DEBUG_UART_8250 help Say Y here if you want kernel low-level debugging support @@ -227,6 +232,14 @@ choice Say Y here if you want kernel low-level debugging support on HI3716 UART. + config DEBUG_HIP04_UART + bool "Hisilicon HiP01 Debug UART" + depends on ARCH_HIP04 + select DEBUG_UART_8250 + help + Say Y here if you want kernel low-level debugging support + on HIP04 UART. + config DEBUG_HIGHBANK_UART bool "Kernel low-level debugging messages via Highbank UART" depends on ARCH_HIGHBANK @@ -1023,6 +1036,7 @@ config DEBUG_UART_PHYS default 0x101f1000 if ARCH_VERSATILE default 0x101fb000 if DEBUG_NOMADIK_UART default 0x16000000 if ARCH_INTEGRATOR + default 0x18000300 if DEBUG_BCM_5301X default 0x1c090000 if DEBUG_VEXPRESS_UART0_RS1 default 0x20060000 if DEBUG_RK29_UART0 default 0x20064000 if DEBUG_RK29_UART1 || DEBUG_RK3X_UART2 @@ -1048,6 +1062,7 @@ config DEBUG_UART_PHYS default 0xd4017000 if DEBUG_MMP_UART2 default 0xd4018000 if DEBUG_MMP_UART3 default 0xe0000000 if ARCH_SPEAR13XX + default 0xe4007000 if DEBUG_HIP04_UART default 0xf0000be0 if ARCH_EBSA110 default 0xf1012000 if DEBUG_MVEBU_UART_ALTERNATE default 0xf1012000 if ARCH_DOVE || ARCH_KIRKWOOD || ARCH_MV78XX0 || \ @@ -1071,6 +1086,7 @@ config DEBUG_UART_VIRT default 0xf0009000 if DEBUG_CNS3XXX default 0xf01fb000 if DEBUG_NOMADIK_UART default 0xf0201000 if DEBUG_BCM2835 + default 0xf1000300 if DEBUG_BCM_5301X default 0xf11f1000 if ARCH_VERSATILE default 0xf1600000 if ARCH_INTEGRATOR default 0xf1c28000 if DEBUG_SUNXI_UART0 @@ -1079,6 +1095,7 @@ config DEBUG_UART_VIRT default 0xf4090000 if ARCH_LPC32XX default 0xf4200000 if ARCH_GEMINI default 0xf7fc9000 if DEBUG_BERLIN_UART + default 0xf8007000 if DEBUG_HIP04_UART default 0xf8009000 if DEBUG_VEXPRESS_UART0_CA9 default 0xf8090000 if DEBUG_VEXPRESS_UART0_RS1 default 0xfb009000 if DEBUG_REALVIEW_STD_PORT @@ -1172,6 +1189,14 @@ config EARLY_PRINTK kernel low-level debugging functions. Add earlyprintk to your kernel parameters to enable this console. +config EARLY_PRINTK_DIRECT + bool "Early printk direct" + depends on DEBUG_LL + help + Say Y here if you want to have an early console using the + kernel low-level debugging functions and EARLY_PRINTK is + not early enough. + config OC_ETM bool "On-chip ETM and ETB" depends on ARM_AMBA diff --git a/arch/arm/Makefile b/arch/arm/Makefile index 08a9ef58d9c356..f9a5ca2a0d7fba 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -23,6 +23,14 @@ OBJCOPYFLAGS :=-O binary -R .comment -S GZFLAGS :=-9 #KBUILD_CFLAGS +=-pipe +ifeq ($(COMPILER),clang) +# Clang options +ifneq ($(CROSS_COMPILE),) +KBUILD_CPPFLAGS += -target $(CROSS_COMPILE:-=) +endif +KBUILD_CFLAGS += -fno-builtin -Wno-asm-operand-widths -Xassembler -mno-warn-deprecated +endif + # Never generate .eh_frame KBUILD_CFLAGS += $(call cc-option,-fno-dwarf2-cfi-asm) @@ -41,11 +49,11 @@ KBUILD_CFLAGS +=-fno-omit-frame-pointer -mapcs -mno-sched-prolog endif ifeq ($(CONFIG_CPU_BIG_ENDIAN),y) -KBUILD_CPPFLAGS += -mbig-endian +KBUILD_CPPFLAGS += $(call cc-option,-mbig-endian,) AS += -EB LD += -EB else -KBUILD_CPPFLAGS += -mlittle-endian +KBUILD_CPPFLAGS += $(call cc-option,-mlittle-endian,) AS += -EL LD += -EL endif @@ -96,7 +104,7 @@ tune-$(CONFIG_CPU_V6K) =$(call cc-option,-mtune=arm1136j-s,-mtune=strongarm) tune-y := $(tune-y) ifeq ($(CONFIG_AEABI),y) -CFLAGS_ABI :=-mabi=aapcs-linux -mno-thumb-interwork -mfpu=vfp +CFLAGS_ABI :=-mabi=aapcs-linux $(call cc-option,-mno-thumb-interwork,) -mfpu=vfp else CFLAGS_ABI :=$(call cc-option,-mapcs-32,-mabi=apcs-gnu) $(call cc-option,-mno-thumb-interwork,) endif @@ -120,7 +128,7 @@ AFLAGS_ISA :=$(CFLAGS_ISA) endif # Need -Uarm for gcc < 3.x -KBUILD_CFLAGS +=$(CFLAGS_ABI) $(CFLAGS_ISA) $(arch-y) $(tune-y) $(call cc-option,-mshort-load-bytes,$(call cc-option,-malignment-traps,)) -msoft-float -Uarm +KBUILD_CFLAGS +=$(CFLAGS_ABI) $(CFLAGS_ISA) $(arch-y) $(tune-y) $(call cc-option,-mshort-load-bytes,$(call cc-option,-malignment-traps,)) -msoft-float $(call cc-option, -Uarm,) KBUILD_AFLAGS +=$(CFLAGS_ABI) $(AFLAGS_ISA) $(arch-y) $(tune-y) -include asm/unified.h -msoft-float CHECKFLAGS += -D__arm__ @@ -156,6 +164,7 @@ machine-$(CONFIG_ARCH_EXYNOS) += exynos machine-$(CONFIG_ARCH_GEMINI) += gemini machine-$(CONFIG_ARCH_HIGHBANK) += highbank machine-$(CONFIG_ARCH_HI3xxx) += hisi +machine-$(CONFIG_ARCH_HIP04) += hisi machine-$(CONFIG_ARCH_INTEGRATOR) += integrator machine-$(CONFIG_ARCH_IOP13XX) += iop13xx machine-$(CONFIG_ARCH_IOP32X) += iop32x @@ -277,6 +286,8 @@ libs-y := arch/arm/lib/ $(libs-y) # Default target when executing plain make ifeq ($(CONFIG_XIP_KERNEL),y) KBUILD_IMAGE := xipImage +else ifeq ($(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE),y) +KBUILD_IMAGE := zImage-dtb else KBUILD_IMAGE := zImage endif @@ -314,6 +325,9 @@ PHONY += dtbs dtbs: scripts $(Q)$(MAKE) $(build)=$(boot)/dts MACHINE=$(MACHINE) dtbs +zImage-dtb: vmlinux scripts dtbs + $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@ + # We use MRPROPER_FILES and CLEAN_FILES now archclean: $(Q)$(MAKE) $(clean)=$(boot) diff --git a/arch/arm/boot/.gitignore b/arch/arm/boot/.gitignore index 3c79f85975aaa2..ad7a0253ea961a 100644 --- a/arch/arm/boot/.gitignore +++ b/arch/arm/boot/.gitignore @@ -4,3 +4,4 @@ xipImage bootpImage uImage *.dtb +zImage-dtb \ No newline at end of file diff --git a/arch/arm/boot/Makefile b/arch/arm/boot/Makefile index ec2f8065f955c5..d09b00ad5fb4b2 100644 --- a/arch/arm/boot/Makefile +++ b/arch/arm/boot/Makefile @@ -14,6 +14,7 @@ ifneq ($(MACHINE),) include $(srctree)/$(MACHINE)/Makefile.boot endif +include $(srctree)/arch/arm/boot/dts/Makefile # Note: the following conditions must always be true: # ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET) @@ -27,6 +28,14 @@ export ZRELADDR INITRD_PHYS PARAMS_PHYS targets := Image zImage xipImage bootpImage uImage +DTB_NAMES := $(subst $\",,$(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE_NAMES)) +ifneq ($(DTB_NAMES),) +DTB_LIST := $(addsuffix .dtb,$(DTB_NAMES)) +else +DTB_LIST := $(dtb-y) +endif +DTB_OBJS := $(addprefix $(obj)/dts/,$(DTB_LIST)) + ifeq ($(CONFIG_XIP_KERNEL),y) $(obj)/xipImage: vmlinux FORCE @@ -55,6 +64,10 @@ $(obj)/zImage: $(obj)/compressed/vmlinux FORCE $(call if_changed,objcopy) @$(kecho) ' Kernel: $@ is ready' +$(obj)/zImage-dtb: $(obj)/zImage $(DTB_OBJS) FORCE + $(call if_changed,cat) + @echo ' Kernel: $@ is ready' + endif ifneq ($(LOADADDR),) diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile index 68c918362b79a7..9aee36e74c9fd9 100644 --- a/arch/arm/boot/compressed/Makefile +++ b/arch/arm/boot/compressed/Makefile @@ -121,7 +121,7 @@ ORIG_CFLAGS := $(KBUILD_CFLAGS) KBUILD_CFLAGS = $(subst -pg, , $(ORIG_CFLAGS)) endif -ccflags-y := -fpic -mno-single-pic-base -fno-builtin -I$(obj) +ccflags-y := -fpic $(call cc-option,-mno-single-pic-base,) -fno-builtin -I$(obj) asflags-y := -DZIMAGE # Supply kernel BSS size to the decompressor via a linker symbol. diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S index 066b03480b63ca..2a2dff9daa369e 100644 --- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S @@ -734,6 +734,8 @@ __armv7_mmu_cache_on: bic r6, r6, #1 << 31 @ 32-bit translation system bic r6, r6, #3 << 0 @ use only ttbr0 mcrne p15, 0, r3, c2, c0, 0 @ load page table pointer + mcrne p15, 0, r0, c8, c7, 0 @ flush I,D TLBs + mcr p15, 0, r0, c7, c5, 4 @ ISB mcrne p15, 0, r1, c3, c0, 0 @ load domain access control mcrne p15, 0, r6, c2, c0, 2 @ load ttb control #endif diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index 032030361bef24..7c8957c4d3130c 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -47,9 +47,10 @@ dtb-$(CONFIG_ARCH_AT91) += sama5d36ek.dtb dtb-$(CONFIG_ARCH_ATLAS6) += atlas6-evb.dtb dtb-$(CONFIG_ARCH_BCM2835) += bcm2835-rpi-b.dtb -dtb-$(CONFIG_ARCH_BCM_MOBILE) += bcm11351-brt.dtb \ - bcm28155-ap.dtb +dtb-$(CONFIG_ARCH_BCM_MOBILE) += bcm28155-ap.dtb \ + bcm21664-garnet.dtb dtb-$(CONFIG_ARCH_BCM2835) += bcm2835-rpi-b.dtb +dtb-$(CONFIG_ARCH_BCM_5301X) += bcm4708-netgear-r6250.dtb dtb-$(CONFIG_ARCH_BERLIN) += \ berlin2-sony-nsz-gs7.dtb \ berlin2cd-google-chromecast.dtb @@ -74,12 +75,16 @@ dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \ exynos5250-smdk5250.dtb \ exynos5250-snow.dtb \ exynos5420-arndale-octa.dtb \ + exynos5410-odroidxu.dtb \ + exynos5410-smdk5410.dtb \ exynos5420-smdk5420.dtb \ exynos5440-sd5v1.dtb \ exynos5440-ssdk5440.dtb -dtb-$(CONFIG_ARCH_HI3xxx) += hi3620-hi4511.dtb +dtb-$(CONFIG_ARCH_HI3xxx) += hi3620-hi4511.dtb \ + hi3716-dkb.dtb dtb-$(CONFIG_ARCH_HIGHBANK) += highbank.dtb \ ecx-2000.dtb +dtb-$(CONFIG_ARCH_HIP04) += hip04-d01.dtb dtb-$(CONFIG_ARCH_INTEGRATOR) += integratorap.dtb \ integratorcp.dtb dtb-$(CONFIG_ARCH_LPC32XX) += ea3250.dtb phy3250.dtb @@ -312,7 +317,14 @@ dtb-$(CONFIG_ARCH_U300) += ste-u300.dtb dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \ vexpress-v2p-ca9.dtb \ vexpress-v2p-ca15-tc1.dtb \ - vexpress-v2p-ca15_a7.dtb + vexpress-v2p-ca15_a7.dtb \ + rtsm_ve-cortex_a9x2.dtb \ + rtsm_ve-cortex_a9x4.dtb \ + rtsm_ve-cortex_a15x1.dtb \ + rtsm_ve-cortex_a15x2.dtb \ + rtsm_ve-cortex_a15x4.dtb \ + rtsm_ve-v2p-ca15x1-ca7x1.dtb \ + rtsm_ve-v2p-ca15x4-ca7x4.dtb dtb-$(CONFIG_ARCH_VIRT) += xenvm-4.2.dtb dtb-$(CONFIG_ARCH_VT8500) += vt8500-bv07.dtb \ wm8505-ref.dtb \ @@ -323,13 +335,20 @@ dtb-$(CONFIG_ARCH_ZYNQ) += zynq-zc702.dtb \ zynq-zc706.dtb \ zynq-zed.dtb +DTB_NAMES := $(subst $\",,$(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE_NAMES)) +ifneq ($(DTB_NAMES),) +DTB_LIST := $(addsuffix .dtb,$(DTB_NAMES)) +else +DTB_LIST := $(dtb-y) +endif + targets += dtbs -targets += $(dtb-y) +targets += $(DTB_LIST) endif # *.dtb used to be generated in the directory above. Clean out the # old build results so people don't accidentally use them. -dtbs: $(addprefix $(obj)/, $(dtb-y)) +dtbs: $(addprefix $(obj)/, $(DTB_LIST)) $(Q)rm -f $(obj)/../*.dtb clean-files := *.dtb diff --git a/arch/arm/boot/dts/bcm11351.dtsi b/arch/arm/boot/dts/bcm11351.dtsi index 792fde1b7f752a..c8977dc4c8f481 100644 --- a/arch/arm/boot/dts/bcm11351.dtsi +++ b/arch/arm/boot/dts/bcm11351.dtsi @@ -14,6 +14,8 @@ #include #include +#include "dt-bindings/clock/bcm281xx.h" + #include "skeleton.dtsi" / { @@ -43,7 +45,7 @@ compatible = "brcm,bcm11351-dw-apb-uart", "snps,dw-apb-uart"; status = "disabled"; reg = <0x3e000000 0x1000>; - clocks = <&uartb_clk>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_UARTB>; interrupts = ; reg-shift = <2>; reg-io-width = <4>; @@ -53,7 +55,7 @@ compatible = "brcm,bcm11351-dw-apb-uart", "snps,dw-apb-uart"; status = "disabled"; reg = <0x3e001000 0x1000>; - clocks = <&uartb2_clk>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_UARTB2>; interrupts = ; reg-shift = <2>; reg-io-width = <4>; @@ -63,7 +65,7 @@ compatible = "brcm,bcm11351-dw-apb-uart", "snps,dw-apb-uart"; status = "disabled"; reg = <0x3e002000 0x1000>; - clocks = <&uartb3_clk>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_UARTB3>; interrupts = ; reg-shift = <2>; reg-io-width = <4>; @@ -73,7 +75,7 @@ compatible = "brcm,bcm11351-dw-apb-uart", "snps,dw-apb-uart"; status = "disabled"; reg = <0x3e003000 0x1000>; - clocks = <&uartb4_clk>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_UARTB4>; interrupts = ; reg-shift = <2>; reg-io-width = <4>; @@ -95,7 +97,7 @@ compatible = "brcm,kona-timer"; reg = <0x35006000 0x1000>; interrupts = ; - clocks = <&hub_timer_clk>; + clocks = <&aon_ccu BCM281XX_AON_CCU_HUB_TIMER>; }; gpio: gpio@35003000 { @@ -118,7 +120,7 @@ compatible = "brcm,kona-sdhci"; reg = <0x3f180000 0x10000>; interrupts = ; - clocks = <&sdio1_clk>; + clocks = <&master_ccu BCM281XX_MASTER_CCU_SDIO1>; status = "disabled"; }; @@ -126,7 +128,7 @@ compatible = "brcm,kona-sdhci"; reg = <0x3f190000 0x10000>; interrupts = ; - clocks = <&sdio2_clk>; + clocks = <&master_ccu BCM281XX_MASTER_CCU_SDIO2>; status = "disabled"; }; @@ -134,7 +136,7 @@ compatible = "brcm,kona-sdhci"; reg = <0x3f1a0000 0x10000>; interrupts = ; - clocks = <&sdio3_clk>; + clocks = <&master_ccu BCM281XX_MASTER_CCU_SDIO3>; status = "disabled"; }; @@ -142,7 +144,7 @@ compatible = "brcm,kona-sdhci"; reg = <0x3f1b0000 0x10000>; interrupts = ; - clocks = <&sdio4_clk>; + clocks = <&master_ccu BCM281XX_MASTER_CCU_SDIO4>; status = "disabled"; }; @@ -157,7 +159,7 @@ interrupts = ; #address-cells = <1>; #size-cells = <0>; - clocks = <&bsc1_clk>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_BSC1>; status = "disabled"; }; @@ -167,7 +169,7 @@ interrupts = ; #address-cells = <1>; #size-cells = <0>; - clocks = <&bsc2_clk>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_BSC2>; status = "disabled"; }; @@ -177,7 +179,7 @@ interrupts = ; #address-cells = <1>; #size-cells = <0>; - clocks = <&bsc3_clk>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_BSC3>; status = "disabled"; }; @@ -187,105 +189,200 @@ interrupts = ; #address-cells = <1>; #size-cells = <0>; - clocks = <&pmu_bsc_clk>; + clocks = <&aon_ccu BCM281XX_AON_CCU_PMU_BSC>; + status = "disabled"; + }; + + pwm: pwm@3e01a000 { + compatible = "brcm,bcm11351-pwm", "brcm,kona-pwm"; + reg = <0x3e01a000 0xcc>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_PWM>; + #pwm-cells = <3>; status = "disabled"; }; clocks { - bsc1_clk: bsc1 { - compatible = "fixed-clock"; - clock-frequency = <13000000>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + root_ccu: root_ccu { + compatible = "brcm,bcm11351-root-ccu"; + reg = <0x35001000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "frac_1m"; + }; + + hub_ccu: hub_ccu { + compatible = "brcm,bcm11351-hub-ccu"; + reg = <0x34000000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "tmon_1m"; + }; + + aon_ccu: aon_ccu { + compatible = "brcm,bcm11351-aon-ccu"; + reg = <0x35002000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "hub_timer", + "pmu_bsc", + "pmu_bsc_var"; + }; + + master_ccu: master_ccu { + compatible = "brcm,bcm11351-master-ccu"; + reg = <0x3f001000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "sdio1", + "sdio2", + "sdio3", + "sdio4", + "usb_ic", + "hsic2_48m", + "hsic2_12m"; + }; + + slave_ccu: slave_ccu { + compatible = "brcm,bcm11351-slave-ccu"; + reg = <0x3e011000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "uartb", + "uartb2", + "uartb3", + "uartb4", + "ssp0", + "ssp2", + "bsc1", + "bsc2", + "bsc3", + "pwm", + "bsc3_apb"; + }; + + ref_1m_clk: ref_1m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <1000000>; }; - bsc2_clk: bsc2 { + ref_32k_clk: ref_32k { + #clock-cells = <0>; compatible = "fixed-clock"; - clock-frequency = <13000000>; + clock-frequency = <32768>; + }; + + bbl_32k_clk: bbl_32k { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <32768>; }; - bsc3_clk: bsc3 { + ref_13m_clk: ref_13m { + #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <13000000>; - #clock-cells = <0>; }; - pmu_bsc_clk: pmu_bsc { + var_13m_clk: var_13m { + #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <13000000>; - #clock-cells = <0>; }; - hub_timer_clk: hub_timer { - compatible = "fixed-clock"; - clock-frequency = <32768>; + dft_19_5m_clk: dft_19_5m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <19500000>; }; - pwm_clk: pwm { + ref_crystal_clk: ref_crystal { + #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <26000000>; - #clock-cells = <0>; }; - sdio1_clk: sdio1 { - compatible = "fixed-clock"; - clock-frequency = <48000000>; + ref_cx40_clk: ref_cx40 { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; }; - sdio2_clk: sdio2 { - compatible = "fixed-clock"; - clock-frequency = <48000000>; + ref_52m_clk: ref_52m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <52000000>; }; - sdio3_clk: sdio3 { - compatible = "fixed-clock"; - clock-frequency = <48000000>; + var_52m_clk: var_52m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <52000000>; }; - sdio4_clk: sdio4 { + usb_otg_ahb_clk: usb_otg_ahb { compatible = "fixed-clock"; - clock-frequency = <48000000>; + clock-frequency = <52000000>; #clock-cells = <0>; }; - tmon_1m_clk: tmon_1m { - compatible = "fixed-clock"; - clock-frequency = <1000000>; + ref_96m_clk: ref_96m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <96000000>; }; - uartb_clk: uartb { - compatible = "fixed-clock"; - clock-frequency = <13000000>; + var_96m_clk: var_96m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <96000000>; }; - uartb2_clk: uartb2 { + ref_104m_clk: ref_104m { + #clock-cells = <0>; compatible = "fixed-clock"; - clock-frequency = <13000000>; + clock-frequency = <104000000>; + }; + + var_104m_clk: var_104m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <104000000>; }; - uartb3_clk: uartb3 { + ref_156m_clk: ref_156m { + #clock-cells = <0>; compatible = "fixed-clock"; - clock-frequency = <13000000>; + clock-frequency = <156000000>; + }; + + var_156m_clk: var_156m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <156000000>; }; - uartb4_clk: uartb4 { + ref_208m_clk: ref_208m { + #clock-cells = <0>; compatible = "fixed-clock"; - clock-frequency = <13000000>; + clock-frequency = <208000000>; + }; + + var_208m_clk: var_208m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <208000000>; }; - usb_otg_ahb_clk: usb_otg_ahb { + ref_312m_clk: ref_312m { + #clock-cells = <0>; compatible = "fixed-clock"; - clock-frequency = <52000000>; + clock-frequency = <312000000>; + }; + + var_312m_clk: var_312m { #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <312000000>; }; }; diff --git a/arch/arm/boot/dts/bcm11351-brt.dts b/arch/arm/boot/dts/bcm21664-garnet.dts similarity index 79% rename from arch/arm/boot/dts/bcm11351-brt.dts rename to arch/arm/boot/dts/bcm21664-garnet.dts index 396b70459cdc0b..e87cb26ddf849f 100644 --- a/arch/arm/boot/dts/bcm11351-brt.dts +++ b/arch/arm/boot/dts/bcm21664-garnet.dts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Broadcom Corporation + * Copyright (C) 2014 Broadcom Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -13,11 +13,13 @@ /dts-v1/; -#include "bcm11351.dtsi" +#include + +#include "bcm21664.dtsi" / { - model = "BCM11351 BRT board"; - compatible = "brcm,bcm11351-brt", "brcm,bcm11351"; + model = "BCM21664 Garnet board"; + compatible = "brcm,bcm21664-garnet", "brcm,bcm21664"; memory { reg = <0x80000000 0x40000000>; /* 1 GB */ @@ -40,7 +42,7 @@ sdio4: sdio@3f1b0000 { max-frequency = <48000000>; - cd-gpios = <&gpio 14 0>; + cd-gpios = <&gpio 91 GPIO_ACTIVE_LOW>; status = "okay"; }; diff --git a/arch/arm/boot/dts/bcm21664.dtsi b/arch/arm/boot/dts/bcm21664.dtsi new file mode 100644 index 00000000000000..8b366822bb43bc --- /dev/null +++ b/arch/arm/boot/dts/bcm21664.dtsi @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "dt-bindings/clock/bcm21664.h" + +#include "skeleton.dtsi" + +/ { + model = "BCM21664 SoC"; + compatible = "brcm,bcm21664"; + interrupt-parent = <&gic>; + + chosen { + bootargs = "console=ttyS0,115200n8"; + }; + + gic: interrupt-controller@3ff00100 { + compatible = "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x3ff01000 0x1000>, + <0x3ff00100 0x100>; + }; + + smc@0x3404e000 { + compatible = "brcm,bcm21664-smc", "brcm,kona-smc"; + reg = <0x3404e000 0x400>; /* 1 KiB in SRAM */ + }; + + uart@3e000000 { + compatible = "brcm,bcm21664-dw-apb-uart", "snps,dw-apb-uart"; + status = "disabled"; + reg = <0x3e000000 0x118>; + clocks = <&slave_ccu BCM21664_SLAVE_CCU_UARTB>; + interrupts = ; + reg-shift = <2>; + reg-io-width = <4>; + }; + + uart@3e001000 { + compatible = "brcm,bcm21664-dw-apb-uart", "snps,dw-apb-uart"; + status = "disabled"; + reg = <0x3e001000 0x118>; + clocks = <&slave_ccu BCM21664_SLAVE_CCU_UARTB2>; + interrupts = ; + reg-shift = <2>; + reg-io-width = <4>; + }; + + uart@3e002000 { + compatible = "brcm,bcm21664-dw-apb-uart", "snps,dw-apb-uart"; + status = "disabled"; + reg = <0x3e002000 0x118>; + clocks = <&slave_ccu BCM21664_SLAVE_CCU_UARTB3>; + interrupts = ; + reg-shift = <2>; + reg-io-width = <4>; + }; + + L2: l2-cache { + compatible = "arm,pl310-cache"; + reg = <0x3ff20000 0x1000>; + cache-unified; + cache-level = <2>; + }; + + brcm,resetmgr@35001f00 { + compatible = "brcm,bcm21664-resetmgr"; + reg = <0x35001f00 0x24>; + }; + + timer@35006000 { + compatible = "brcm,kona-timer"; + reg = <0x35006000 0x1c>; + interrupts = ; + clocks = <&aon_ccu BCM21664_AON_CCU_HUB_TIMER>; + }; + + gpio: gpio@35003000 { + compatible = "brcm,bcm21664-gpio", "brcm,kona-gpio"; + reg = <0x35003000 0x524>; + interrupts = + ; + #gpio-cells = <2>; + #interrupt-cells = <2>; + gpio-controller; + interrupt-controller; + }; + + sdio1: sdio@3f180000 { + compatible = "brcm,kona-sdhci"; + reg = <0x3f180000 0x801c>; + interrupts = ; + clocks = <&master_ccu BCM21664_MASTER_CCU_SDIO1>; + status = "disabled"; + }; + + sdio2: sdio@3f190000 { + compatible = "brcm,kona-sdhci"; + reg = <0x3f190000 0x801c>; + interrupts = ; + clocks = <&master_ccu BCM21664_MASTER_CCU_SDIO2>; + status = "disabled"; + }; + + sdio3: sdio@3f1a0000 { + compatible = "brcm,kona-sdhci"; + reg = <0x3f1a0000 0x801c>; + interrupts = ; + clocks = <&master_ccu BCM21664_MASTER_CCU_SDIO3>; + status = "disabled"; + }; + + sdio4: sdio@3f1b0000 { + compatible = "brcm,kona-sdhci"; + reg = <0x3f1b0000 0x801c>; + interrupts = ; + clocks = <&master_ccu BCM21664_MASTER_CCU_SDIO4>; + status = "disabled"; + }; + + i2c@3e016000 { + compatible = "brcm,bcm21664-i2c", "brcm,kona-i2c"; + reg = <0x3e016000 0x70>; + interrupts = ; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&slave_ccu BCM21664_SLAVE_CCU_BSC1>; + status = "disabled"; + }; + + i2c@3e017000 { + compatible = "brcm,bcm21664-i2c", "brcm,kona-i2c"; + reg = <0x3e017000 0x70>; + interrupts = ; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&slave_ccu BCM21664_SLAVE_CCU_BSC2>; + status = "disabled"; + }; + + i2c@3e018000 { + compatible = "brcm,bcm21664-i2c", "brcm,kona-i2c"; + reg = <0x3e018000 0x70>; + interrupts = ; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&slave_ccu BCM21664_SLAVE_CCU_BSC3>; + status = "disabled"; + }; + + i2c@3e01c000 { + compatible = "brcm,bcm21664-i2c", "brcm,kona-i2c"; + reg = <0x3e01c000 0x70>; + interrupts = ; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&slave_ccu BCM21664_SLAVE_CCU_BSC4>; + status = "disabled"; + }; + + clocks { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + /* + * Fixed clocks are defined before CCUs whose + * clocks may depend on them. + */ + + ref_32k_clk: ref_32k { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <32768>; + }; + + bbl_32k_clk: bbl_32k { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <32768>; + }; + + ref_13m_clk: ref_13m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <13000000>; + }; + + var_13m_clk: var_13m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <13000000>; + }; + + dft_19_5m_clk: dft_19_5m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <19500000>; + }; + + ref_crystal_clk: ref_crystal { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <26000000>; + }; + + ref_52m_clk: ref_52m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <52000000>; + }; + + var_52m_clk: var_52m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <52000000>; + }; + + usb_otg_ahb_clk: usb_otg_ahb { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <52000000>; + }; + + ref_96m_clk: ref_96m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <96000000>; + }; + + var_96m_clk: var_96m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <96000000>; + }; + + ref_104m_clk: ref_104m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <104000000>; + }; + + var_104m_clk: var_104m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <104000000>; + }; + + ref_156m_clk: ref_156m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <156000000>; + }; + + var_156m_clk: var_156m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <156000000>; + }; + + root_ccu: root_ccu { + compatible = BCM21664_DT_ROOT_CCU_COMPAT; + reg = <0x35001000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "frac_1m"; + }; + + aon_ccu: aon_ccu { + compatible = BCM21664_DT_AON_CCU_COMPAT; + reg = <0x35002000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "hub_timer"; + }; + + master_ccu: master_ccu { + compatible = BCM21664_DT_MASTER_CCU_COMPAT; + reg = <0x3f001000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "sdio1", + "sdio2", + "sdio3", + "sdio4", + "sdio1_sleep", + "sdio2_sleep", + "sdio3_sleep", + "sdio4_sleep"; + }; + + slave_ccu: slave_ccu { + compatible = BCM21664_DT_SLAVE_CCU_COMPAT; + reg = <0x3e011000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "uartb", + "uartb2", + "uartb3", + "bsc1", + "bsc2", + "bsc3", + "bsc4"; + }; + }; + + usbotg: usb@3f120000 { + compatible = "snps,dwc2"; + reg = <0x3f120000 0x10000>; + interrupts = ; + clocks = <&usb_otg_ahb_clk>; + clock-names = "otg"; + phys = <&usbphy>; + phy-names = "usb2-phy"; + status = "disabled"; + }; + + usbphy: usb-phy@3f130000 { + compatible = "brcm,kona-usb2-phy"; + reg = <0x3f130000 0x28>; + #phy-cells = <0>; + status = "disabled"; + }; +}; diff --git a/arch/arm/boot/dts/bcm28155-ap.dts b/arch/arm/boot/dts/bcm28155-ap.dts index 5ff2382a49e413..9ce91dd60cb662 100644 --- a/arch/arm/boot/dts/bcm28155-ap.dts +++ b/arch/arm/boot/dts/bcm28155-ap.dts @@ -46,27 +46,36 @@ i2c@3500d000 { status="okay"; - clock-frequency = <400000>; - }; + clock-frequency = <100000>; - sdio1: sdio@3f180000 { - max-frequency = <48000000>; - status = "okay"; + pmu: pmu@8 { + reg = <0x08>; + }; }; sdio2: sdio@3f190000 { non-removable; max-frequency = <48000000>; + vmmc-supply = <&camldo1_reg>; + vqmmc-supply = <&iosr1_reg>; status = "okay"; }; sdio4: sdio@3f1b0000 { max-frequency = <48000000>; cd-gpios = <&gpio 14 GPIO_ACTIVE_LOW>; + vmmc-supply = <&sdldo_reg>; + vqmmc-supply = <&sdxldo_reg>; + status = "okay"; + }; + + pwm: pwm@3e01a000 { status = "okay"; }; usbotg: usb@3f120000 { + vusb_d-supply = <&usbldo_reg>; + vusb_a-supply = <&iosr1_reg>; status = "okay"; }; @@ -74,3 +83,39 @@ status = "okay"; }; }; + +#include "bcm59056.dtsi" + +&pmu { + compatible = "brcm,bcm59056"; + interrupts = ; + regulators { + camldo1_reg: camldo1 { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + sdldo_reg: sdldo { + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + }; + + sdxldo_reg: sdxldo { + regulator-min-microvolt = <2700000>; + regulator-max-microvolt = <3300000>; + }; + + usbldo_reg: usbldo { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + iosr1_reg: iosr1 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + }; +}; diff --git a/arch/arm/boot/dts/bcm4708-netgear-r6250.dts b/arch/arm/boot/dts/bcm4708-netgear-r6250.dts new file mode 100644 index 00000000000000..3b5259de5a38de --- /dev/null +++ b/arch/arm/boot/dts/bcm4708-netgear-r6250.dts @@ -0,0 +1,35 @@ +/* + * Broadcom BCM470X / BCM5301X arm platform code. + * DTS for Netgear R6250 V1 + * + * Copyright 2013 Hauke Mehrtens + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +/dts-v1/; + +#include "bcm4708.dtsi" + +/ { + compatible = "netgear,r6250v1", "brcm,bcm4708"; + model = "Netgear R6250 V1 (BCM4708)"; + + chosen { + bootargs = "console=ttyS0,115200"; + }; + + memory { + reg = <0x00000000 0x08000000>; + }; + + chipcommonA { + uart0: serial@0300 { + status = "okay"; + }; + + uart1: serial@0400 { + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/bcm4708.dtsi b/arch/arm/boot/dts/bcm4708.dtsi new file mode 100644 index 00000000000000..31141e83feddc2 --- /dev/null +++ b/arch/arm/boot/dts/bcm4708.dtsi @@ -0,0 +1,34 @@ +/* + * Broadcom BCM470X / BCM5301X ARM platform code. + * DTS for BCM4708 SoC. + * + * Copyright 2013-2014 Hauke Mehrtens + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include "bcm5301x.dtsi" + +/ { + compatible = "brcm,bcm4708"; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + next-level-cache = <&L2>; + reg = <0x0>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + next-level-cache = <&L2>; + reg = <0x1>; + }; + }; + +}; diff --git a/arch/arm/boot/dts/bcm5301x.dtsi b/arch/arm/boot/dts/bcm5301x.dtsi new file mode 100644 index 00000000000000..53c624f766b485 --- /dev/null +++ b/arch/arm/boot/dts/bcm5301x.dtsi @@ -0,0 +1,95 @@ +/* + * Broadcom BCM470X / BCM5301X ARM platform code. + * Generic DTS part for all BCM53010, BCM53011, BCM53012, BCM53014, BCM53015, + * BCM53016, BCM53017, BCM53018, BCM4707, BCM4708 and BCM4709 SoCs + * + * Copyright 2013-2014 Hauke Mehrtens + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include +#include +#include "skeleton.dtsi" + +/ { + interrupt-parent = <&gic>; + + chipcommonA { + compatible = "simple-bus"; + ranges = <0x00000000 0x18000000 0x00001000>; + #address-cells = <1>; + #size-cells = <1>; + + uart0: serial@0300 { + compatible = "ns16550"; + reg = <0x0300 0x100>; + interrupts = ; + clock-frequency = <100000000>; + status = "disabled"; + }; + + uart1: serial@0400 { + compatible = "ns16550"; + reg = <0x0400 0x100>; + interrupts = ; + clock-frequency = <100000000>; + status = "disabled"; + }; + }; + + mpcore { + compatible = "simple-bus"; + ranges = <0x00000000 0x19020000 0x00003000>; + #address-cells = <1>; + #size-cells = <1>; + + scu@0000 { + compatible = "arm,cortex-a9-scu"; + reg = <0x0000 0x100>; + }; + + timer@0200 { + compatible = "arm,cortex-a9-global-timer"; + reg = <0x0200 0x100>; + interrupts = ; + clocks = <&clk_periph>; + }; + + local-timer@0600 { + compatible = "arm,cortex-a9-twd-timer"; + reg = <0x0600 0x100>; + interrupts = ; + clocks = <&clk_periph>; + }; + + gic: interrupt-controller@1000 { + compatible = "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x1000 0x1000>, + <0x0100 0x100>; + }; + + L2: cache-controller@2000 { + compatible = "arm,pl310-cache"; + reg = <0x2000 0x1000>; + cache-unified; + cache-level = <2>; + }; + }; + + clocks { + #address-cells = <1>; + #size-cells = <0>; + + /* As long as we do not have a real clock driver us this + * fixed clock */ + clk_periph: periph { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <400000000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/bcm59056.dtsi b/arch/arm/boot/dts/bcm59056.dtsi new file mode 100644 index 00000000000000..dfadaaa89b059c --- /dev/null +++ b/arch/arm/boot/dts/bcm59056.dtsi @@ -0,0 +1,74 @@ +/* +* Copyright 2014 Linaro Limited +* Author: Matt Porter +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +&pmu { + compatible = "brcm,bcm59056"; + regulators { + rfldo_reg: rfldo { + }; + + camldo1_reg: camldo1 { + }; + + camldo2_reg: camldo2 { + }; + + simldo1_reg: simldo1 { + }; + + simldo2_reg: simldo2 { + }; + + sdldo_reg: sdldo { + }; + + sdxldo_reg: sdxldo { + }; + + mmcldo1_reg: mmcldo1 { + }; + + mmcldo2_reg: mmcldo2 { + }; + + audldo_reg: audldo { + }; + + micldo_reg: micldo { + }; + + usbldo_reg: usbldo { + }; + + vibldo_reg: vibldo { + }; + + csr_reg: csr { + }; + + iosr1_reg: iosr1 { + }; + + iosr2_reg: iosr2 { + }; + + msr_reg: msr { + }; + + sdsr1_reg: sdsr1 { + }; + + sdsr2_reg: sdsr2 { + }; + + vsr_reg: vsr { + }; + }; +}; diff --git a/arch/arm/boot/dts/clcd-panels.dtsi b/arch/arm/boot/dts/clcd-panels.dtsi new file mode 100644 index 00000000000000..0b0ff6ead4b213 --- /dev/null +++ b/arch/arm/boot/dts/clcd-panels.dtsi @@ -0,0 +1,52 @@ +/* + * ARM Ltd. Versatile Express + * + */ + +/ { + panels { + panel@0 { + compatible = "panel"; + mode = "VGA"; + refresh = <60>; + xres = <640>; + yres = <480>; + pixclock = <39721>; + left_margin = <40>; + right_margin = <24>; + upper_margin = <32>; + lower_margin = <11>; + hsync_len = <96>; + vsync_len = <2>; + sync = <0>; + vmode = "FB_VMODE_NONINTERLACED"; + + tim2 = "TIM2_BCD", "TIM2_IPC"; + cntl = "CNTL_LCDTFT", "CNTL_BGR", "CNTL_LCDVCOMP(1)"; + caps = "CLCD_CAP_5551", "CLCD_CAP_565", "CLCD_CAP_888"; + bpp = <16>; + }; + + panel@1 { + compatible = "panel"; + mode = "XVGA"; + refresh = <60>; + xres = <1024>; + yres = <768>; + pixclock = <15748>; + left_margin = <152>; + right_margin = <48>; + upper_margin = <23>; + lower_margin = <3>; + hsync_len = <104>; + vsync_len = <4>; + sync = <0>; + vmode = "FB_VMODE_NONINTERLACED"; + + tim2 = "TIM2_BCD", "TIM2_IPC"; + cntl = "CNTL_LCDTFT", "CNTL_BGR", "CNTL_LCDVCOMP(1)"; + caps = "CLCD_CAP_5551", "CLCD_CAP_565", "CLCD_CAP_888"; + bpp = <16>; + }; + }; +}; diff --git a/arch/arm/boot/dts/exynos4412.dtsi b/arch/arm/boot/dts/exynos4412.dtsi index 87b339c739de70..788229281ebf0c 100644 --- a/arch/arm/boot/dts/exynos4412.dtsi +++ b/arch/arm/boot/dts/exynos4412.dtsi @@ -35,4 +35,9 @@ <0 107 0>, <0 108 0>, <0 48 0>, <0 42 0>; }; + pmu { + compatible = "arm,cortex-a9-pmu"; + interrupt-parent = <&combiner>; + interrupts = <2 2>, <3 2>, <18 2>, <19 2>; + }; }; diff --git a/arch/arm/boot/dts/exynos5250-arndale.dts b/arch/arm/boot/dts/exynos5250-arndale.dts index b42e658876e5f0..ab98f496491b47 100644 --- a/arch/arm/boot/dts/exynos5250-arndale.dts +++ b/arch/arm/boot/dts/exynos5250-arndale.dts @@ -30,6 +30,29 @@ samsung,mfc-l = <0x51000000 0x800000>; }; + i2s0: i2s@03830000 { + status = "okay"; + }; + + i2s1: i2s@12D60000 { + status = "disabled"; + }; + + i2s2: i2s@12D70000 { + status = "disabled"; + }; + + i2s_stub: i2s-stub { + compatible = "linux,i2s-stub"; + }; + + sound { + compatible = "samsung,smdk-i2s-stub"; + + samsung,i2s-controller = <&i2s0>; + samsung,audio-codec = <&i2s_stub>; + }; + i2c@12C60000 { samsung,i2c-sda-delay = <100>; samsung,i2c-max-bus-freq = <20000>; @@ -364,12 +387,34 @@ samsung,i2c-max-bus-freq = <66000>; samsung,i2c-slave-addr = <0x38>; - hdmiphy@38 { + hdmiphy: hdmiphy@38 { compatible = "samsung,exynos4212-hdmiphy"; reg = <0x38>; + + #address-cells = <1>; + #size-cells = <1>; + + phy-power-control { + reg = <0x10040700 0x04>; + }; + }; + }; + + i2c@121D0000 { + samsung,i2c-sda-delay = <100>; + samsung,i2c-max-bus-freq = <40000>; + samsung,i2c-slave-addr = <0x38>; + + sata-phy { + compatible = "samsung,sata-phy"; + reg = <0x38>; }; }; + sata@122F0000 { + samsung,sata-freq = <66>; + }; + mmc_0: mmc@12200000 { status = "okay"; num-slots = <1>; @@ -463,6 +508,7 @@ vdd_osc-supply = <&ldo10_reg>; vdd_pll-supply = <&ldo8_reg>; vdd-supply = <&ldo8_reg>; + phy = <&hdmiphy>; }; regulators { diff --git a/arch/arm/boot/dts/exynos5250-smdk5250.dts b/arch/arm/boot/dts/exynos5250-smdk5250.dts index 3e69837c435c6b..504969e1862ec7 100644 --- a/arch/arm/boot/dts/exynos5250-smdk5250.dts +++ b/arch/arm/boot/dts/exynos5250-smdk5250.dts @@ -122,9 +122,15 @@ samsung,i2c-max-bus-freq = <66000>; status = "okay"; - hdmiphy@38 { + hdmiphy: hdmiphy@38 { compatible = "samsung,exynos4212-hdmiphy"; reg = <0x38>; + #address-cells = <1>; + #size-cells = <1>; + + phy-power-control { + reg = <0x10040700 0x04>; + }; }; }; @@ -198,6 +204,7 @@ hdmi { hpd-gpio = <&gpx3 7 0>; + phy = <&hdmiphy>; }; codec@11000000 { diff --git a/arch/arm/boot/dts/exynos5250.dtsi b/arch/arm/boot/dts/exynos5250.dtsi index b7dec41e32afd7..5c8515313bac9d 100644 --- a/arch/arm/boot/dts/exynos5250.dtsi +++ b/arch/arm/boot/dts/exynos5250.dtsi @@ -225,15 +225,24 @@ sata@122F0000 { compatible = "samsung,exynos5-sata-ahci"; + samsung,exynos-sata-phy = <&phy0>; reg = <0x122F0000 0x1ff>; interrupts = <0 115 0>; clocks = <&clock 277>, <&clock 143>; clock-names = "sata", "sclk_sata"; }; - sata-phy@12170000 { + phy0:sata-phy@12170000 { compatible = "samsung,exynos5-sata-phy"; reg = <0x12170000 0x1ff>; + clocks = <&clock 287>; + clock-names = "sata_phyctrl"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + sataphy-pmu { + reg = <0x10040724 0x4>; + }; }; i2c_0: i2c@12C60000 { @@ -668,6 +677,12 @@ <&clock 159>, <&clock 1024>; clock-names = "hdmi", "sclk_hdmi", "sclk_pixel", "sclk_hdmiphy", "mout_hdmi"; + #address-cells = <1>; + #size-cells = <1>; + + phy-power-control { + reg = <0x10040700 0x04>; + }; }; mixer { diff --git a/arch/arm/boot/dts/exynos5410-odroidxu.dts b/arch/arm/boot/dts/exynos5410-odroidxu.dts new file mode 100644 index 00000000000000..24587612d224c3 --- /dev/null +++ b/arch/arm/boot/dts/exynos5410-odroidxu.dts @@ -0,0 +1,504 @@ +/* + * Hardkernel odroid-xu board device tree source + * + * Copyright (c) 2013 Hardkernel Electronics Co., Ltd. + * http://www.hardkernel.com + * + * 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 "exynos5410.dtsi" +#include +#include +#include + +/ { + model = "Hardkernel odroid-xu board based on EXYNOS5410"; + compatible = "hardkernel,odroid-xu", "samsung,exynos5410"; + + memory { + reg = <0x40000000 0x80000000>; + }; + + chosen { + bootargs = "console=ttySAC2,115200"; + }; + + codec@11000000 { + samsung,mfc-r = <0x43000000 0x800000>; + samsung,mfc-l = <0x51000000 0x800000>; + }; + + clocks { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + fin_pll: clock-fin-pll { + compatible = "fixed-clock"; + reg = <0>; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "fin_pll"; + }; + }; + + mmc@12200000 { + status = "okay"; + num-slots = <1>; + supports-highspeed; + broken-cd; + card-detect-delay = <200>; + samsung,dw-mshc-ciu-div = <3>; + samsung,dw-mshc-sdr-timing = <0 4>; + samsung,dw-mshc-ddr-timing = <0 2>; + pinctrl-names = "default"; + pinctrl-0 = <&sd0_clk &sd0_cmd &sd0_bus4 &sd0_bus8>; + + slot@0 { + reg = <0>; + bus-width = <8>; + }; + }; + + mmc@12220000 { + status = "okay"; + num-slots = <1>; + supports-highspeed; + card-detect-delay = <200>; + samsung,dw-mshc-ciu-div = <3>; + samsung,dw-mshc-sdr-timing = <2 3>; + samsung,dw-mshc-ddr-timing = <1 2>; + pinctrl-names = "default"; + pinctrl-0 = <&sd2_clk &sd2_cmd &sd2_cd &sd2_bus4>; + + slot@0 { + reg = <0>; + bus-width = <4>; + disable-wp; + }; + }; + + leds { + compatible = "gpio-leds"; + hearbeat { + label = "red:heartbeart"; + gpios = <&gpb2 2 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "heartbeat"; + }; + eMMC { + label = "green:activity"; + gpios = <&gpb2 1 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc0"; + }; + microSD { + label = "blue:activity"; + gpios = <&gpx2 3 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc1"; + }; + }; + + pinctrl@13400000 { + max77802_irq: max77802-irq { + samsung,pins = "gpx0-3"; + samsung,pin-function = <0>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + max98090_irq: max98090-irq { + samsung,pins = "gpx3-2"; + samsung,pin-function = <0>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + hdmi_hpd_irq: hdmi-hpd-irq { + samsung,pins = "gpx3-7"; + samsung,pin-function = <0>; + samsung,pin-pud = <1>; + samsung,pin-drv = <0>; + }; + }; + + pinctrl@14000000 { + usb3_overcur0_u2: usb3-overcur0-u2 { + samsung,pins = "gpk3-0"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + usb3_overcur0_u3: usb3-overcur0-u3 { + samsung,pins = "gpk3-1"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + usb3_overcur1_u2: usb3-overcur1-u2 { + samsung,pins = "gpk2-4"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + usb3_overcur1_u3: usb3-overcur1-u3 { + samsung,pins = "gpk2-5"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + usb3_vbus0_en: usb3-vbus0-en { + samsung,pins = "gpk3-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + usb3_vbus1_en: usb3-vbus1-en { + samsung,pins = "gpk2-7"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + }; + + /* ic21 */ + i2c@12C70000 { + status = "okay"; + clock-frequency = <400000>; + codec@10 { + compatible = "maxim,max98090"; + reg = <0x10>; + interrupts = <2 0>; + interrupt-parent = <&gpx3>; + mclk-pin = <2>; + pinctrl-names = "default"; + pinctrl-0 = <&max98090_irq>; + }; + }; + + /* ic22 */ + i2c@12C80000 { + samsung,i2c-sda-delay = <100>; + samsung,i2c-max-bus-freq = <66000>; + samsung,i2c-slave-addr = <0x50>; + status = "okay"; + + hdmiddc@50 { + compatible = "samsung,exynos4210-hdmiddc"; + reg = <0x50>; + }; + }; + + hdmi { + hpd-gpio = <&gpx3 7 0>; + pinctrl-names = "default"; + pinctrl-0 = <&hdmi_hpd_irq>; + hdmi-en-supply = <&fixed_regulator>; + vdd_osc-supply = <&ldo10_reg>; + vdd_pll-supply = <&ldo8_reg>; + vdd-supply = <&ldo8_reg>; + phy = <&hdmiphy>; + }; + + fixed_regulator: fixed_regulator@0 { + compatible = "regulator-fixed"; + regulator-name = "hdmi-en"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + }; + + /* hsic24 */ + hsi2c@12CA0000 { + status = "okay"; + samsung,hs-mode; + clock-frequency = <1000000>; + + max77802-pmic@9 { + compatible = "maxim,max77802"; + interrupt-parent = <&gpx0>; + interrupts = <3 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&max77802_irq>; + wakeup-source; + reg = <0x9>; + + voltage-regulators { + ldo1_reg: LDO1 { + regulator-name = "vdd_1v0"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1000000>; + regulator-always-on; + regulator-op-mode = <2>; + }; + ldo2_reg: LDO2 { + regulator-name = "vdd_1v2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + }; + ldo3_reg: LDO3 { + regulator-name = "vdd_1v8_3"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-op-mode = <2>; + }; + vqmmc_sdcard: ldo4_reg: LDO4 { + regulator-name = "vdd_sd"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3000000>; + regulator-always-on; + regulator-op-mode = <1>; + }; + ldo5_reg: LDO5 { + regulator-name = "vdd_1v8_5"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-op-mode = <1>; + }; + ldo6_reg: LDO6 { + regulator-name = "vdd_1v8_6"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-op-mode = <1>; + }; + ldo7_reg: LDO7 { + regulator-name = "vdd_1v8_7"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-op-mode = <1>; + }; + ldo8_reg: LDO8 { + regulator-name = "vdd_ldo8"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1000000>; + regulator-always-on; + regulator-op-mode = <3>; + }; + ldo10_reg: LDO10 { + regulator-name = "vdd_ldo10"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-op-mode = <3>; + }; + ldo11_reg: LDO11 { + regulator-name = "vdd_ldo11"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-op-mode = <2>; + }; + ldo12_reg: LDO12 { + regulator-name = "vdd_ldo12"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + regulator-always-on; + regulator-op-mode = <1>; + }; + ldo13_reg: LDO13 { + regulator-name = "vdd_ldo13"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-op-mode = <2>; + }; + ldo14_reg: LDO14 { + regulator-name = "vdd_ldo14"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-op-mode = <1>; + }; + ldo15_reg: LDO15 { + regulator-name = "vdd_ldo15"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1000000>; + regulator-always-on; + regulator-op-mode = <1>; + }; + ldo17_reg: LDO17 { + regulator-name = "ldo_17"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-op-mode = <1>; + }; + ldo18_reg: LDO18 { + regulator-name = "ldo_18"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + ldo20_reg: LDO20 { + regulator-name = "ldo_20"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-op-mode = <0>; + }; + vmmc_sdcard: ldo21_reg: LDO21 { + regulator-name = "ldo_21"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <3300000>; + }; + ldo23_reg: LDO23 { + regulator-name = "ldo_23"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + ldo24_reg: LDO24 { + regulator-name = "ldo_24"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + ldo25_reg: LDO25 { + regulator-name = "ldo_25"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + ldo26_reg: LDO26 { + regulator-name = "ldo_26"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + ldo30_reg: LDO30 { + regulator-name = "ldo_30"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + }; + ldo32_reg: LDO32 { + regulator-name = "ldo_32"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + buck1_reg: BUCK1 { + regulator-name = "vdd_mif"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1300000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <1>; + regulator-ramp-delay = <12500>; + }; + buck2_reg: BUCK2 { + regulator-name = "vdd_arm"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1500000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <1>; + regulator-ramp-delay = <12500>; + }; + buck3_reg: BUCK3 { + regulator-name = "vdd_int"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1400000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <1>; + regulator-ramp-delay = <12500>; + }; + buck4_reg: BUCK4 { + regulator-name = "vdd_g3d"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1400000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <1>; + regulator-ramp-delay = <12500>; + }; + buck5_reg: BUCK5 { + regulator-name = "vdd_mem"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1500000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <1>; + }; + buck6_reg: BUCK6 { + regulator-name = "vdd_kfc"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1500000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <1>; + regulator-ramp-delay = <12500>; + }; + buck7_reg: BUCK7 { + regulator-name = "vdd_1v35"; + regulator-min-microvolt = <1350000>; + regulator-max-microvolt = <1350000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <3>; + }; + buck8_reg: BUCK8 { + regulator-name = "vdd_emmc"; + regulator-min-microvolt = <2850000>; + regulator-max-microvolt = <2850000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <1>; + }; + buck9_reg: BUCK9 { + regulator-name = "vdd_2v"; + regulator-min-microvolt = <2000000>; + regulator-max-microvolt = <2000000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <3>; + }; + buck10_reg: BUCK10 { + regulator-name = "vdd_2v85"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <3000000>; + regulator-always-on; + regulator-boot-on; + regulator-op-mode = <3>; + }; + en32khz_ap: EN32KHZ_AP { + regulator-name = "en32khz_ap"; + regulator-always-on; + }; + en32khz_cp: EN32KHZ_CP { + regulator-name = "en32khz_cp"; + }; + }; + + maxim_rtc: rtc { + }; + + }; + }; + + /* dw usb3_0 */ + usb@12000000 { + status = "okay"; + samsung,vbus-gpio = <&gpk3 3 0>; + pinctrl-names = "default"; + pinctrl-0 = <&usb3_vbus0_en>, <&usb3_overcur0_u3>, <&usb3_overcur0_u2>; + }; + + /* dw usb3_1 */ + usb@12400000 { + samsung,vbus-gpio = <&gpk2 7 0>; + pinctrl-names = "default"; + pinctrl-0 = <&usb3_vbus1_en>, <&usb3_overcur1_u3>, <&usb3_overcur1_u2>; + }; + +}; diff --git a/arch/arm/boot/dts/exynos5410-pinctrl.dtsi b/arch/arm/boot/dts/exynos5410-pinctrl.dtsi new file mode 100644 index 00000000000000..790c4881ed9ad2 --- /dev/null +++ b/arch/arm/boot/dts/exynos5410-pinctrl.dtsi @@ -0,0 +1,714 @@ +/* + * Hardkernel's Odroid-xu based Exynos5410 SoC pin-mux and + * pin-config device tree source + * + * Copyright (c) 2013 Hardkernel Co., Ltd. + * http://www.hardkernel.com + * + * hardkernel's Odroid-xu based Samsung's Exynos5410 SoC pin-mux and + * pin-config options are listed as device + * tree nodes are listed in this file. + * + * 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. +*/ + +/ { + pinctrl@14000000 { + gpj0: gpj0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpj1: gpj1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpj2: gpj2 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpj3: gpj3 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpj4: gpj4 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpk0: gpk0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpk1: gpk1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpk2: gpk2 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpk3: gpk3 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + }; + + pinctrl@13400000 { + gpa0: gpa0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpa1: gpa1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpa2: gpa2 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpb0: gpb0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpb1: gpb1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpb2: gpb2 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpb3: gpb3 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpc0: gpc0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpc3: gpc3 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpc1: gpc1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpc2: gpc2 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpm5: gpm5 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpd1: gpd1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpe0: gpe0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpe1: gpe1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpf0: gpf0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpf1: gpf1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpg0: gpg0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpg1: gpg1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpg2: gpg2 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gph0: gph0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gph1: gph1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpm7: gpm7 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpy0: gpy0 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpy1: gpy1 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpy2: gpy2 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpy3: gpy3 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpy4: gpy4 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpy5: gpy5 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpy6: gpy6 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpy7: gpy7 { + gpio-controller; + #gpio-cells = <2>; + }; + + gpx0: gpx0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + interrupt-parent = <&combiner>; + #interrupt-cells = <2>; + interrupts = <23 0>, <24 0>, <25 0>, <25 1>, + <26 0>, <26 1>, <27 0>, <27 1>; + }; + + gpx1: gpx1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + interrupt-parent = <&combiner>; + #interrupt-cells = <2>; + interrupts = <28 0>, <28 1>, <29 0>, <29 1>, + <30 0>, <30 1>, <31 0>, <31 1>; + }; + + gpx2: gpx2 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpx3: gpx3 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + uart0_data: uart0-data { + samsung,pins = "gpa0-0", "gpa0-1"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + uart0_fctl: uart0-fctl { + samsung,pins = "gpa0-2", "gpa0-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + uart1_data: uart1-data { + samsung,pins = "gpa0-4", "gpa0-5"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + uart1_fctl: uart1-fctl { + samsung,pins = "gpa0-6", "gpa0-7"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + i2c2_bus: i2c2-bus { + samsung,pins = "gpa0-6", "gpa0-7"; + samsung,pin-function = <3>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + uart2_data: uart2-data { + samsung,pins = "gpa1-0", "gpa1-1"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + uart2_fctl: uart2-fctl { + samsung,pins = "gpa1-2", "gpa1-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + i2c3_bus: i2c3-bus { + samsung,pins = "gpa1-2", "gpa1-3"; + samsung,pin-function = <3>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + uart3_data: uart3-data { + samsung,pins = "gpa1-4", "gpa1-5"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + spi0_bus: spi0-bus { + samsung,pins = "gpa2-4", "gpa2-5", "gpa2-6", "gpa2-7"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + spi1_bus: spi1-bus { + samsung,pins = "gpa2-4", "gpa2-6", "gpa2-7"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + i2c4_hs_bus: i2c4-hs-bus { + samsung,pins = "gpa2-0", "gpa2-1"; + samsung,pin-function = <3>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + i2c5_hs_bus: i2c5-hs-bus { + samsung,pins = "gpa2-2", "gpa2-3"; + samsung,pin-function = <3>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + i2s1_bus: i2s1-bus { + samsung,pins = "gpb0-0", "gpb0-1", "gpb0-2", "gpb0-3", + "gpb0-4"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + pcm1_bus: pcm1-bus { + samsung,pins = "gpb0-0", "gpb0-1", "gpb0-2", "gpb0-3", + "gpb0-4"; + samsung,pin-function = <3>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + i2s2_bus: i2s2-bus { + samsung,pins = "gpb1-0", "gpb1-1", "gpb1-2", "gpb1-3", + "gpb1-4"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + pcm2_bus: pcm2-bus { + samsung,pins = "gpb1-0", "gpb1-1", "gpb1-2", "gpb1-3", + "gpb1-4"; + samsung,pin-function = <3>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + spdif_bus: spdif-bus { + samsung,pins = "gpb1-0", "gpb1-1"; + samsung,pin-function = <4>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + spi2_bus: spi2-bus { + samsung,pins = "gpb1-1", "gpb1-3", "gpb1-4"; + samsung,pin-function = <5>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + i2c6_hs_bus: i2c6-hs-bus { + samsung,pins = "gpb1-3", "gpb1-4"; + samsung,pin-function = <4>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + i2c7_hs_bus: i2c7-hs-bus { + samsung,pins = "gpb2-2", "gpb2-3"; + samsung,pin-function = <3>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + i2c0_bus: i2c0-bus { + samsung,pins = "gpb3-0", "gpb3-1"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + i2c1_bus: i2c1-bus { + samsung,pins = "gpb3-2", "gpb3-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + sd0_clk: sd0-clk { + samsung,pins = "gpc0-0"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <3>; + }; + + sd0_cmd: sd0-cmd { + samsung,pins = "gpc0-1"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <3>; + }; + + sd0_cd: sd0-cd { + samsung,pins = "gpc0-2"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd0_bus1: sd0-bus-width1 { + samsung,pins = "gpc0-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd0_bus4: sd0-bus-width4 { + samsung,pins = "gpc0-4", "gpc0-5", "gpc0-6"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd0_bus8: sd0-bus-width8 { + samsung,pins = "gpc3-0", "gpc3-1", "gpc3-2", "gpc3-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd1_clk: sd1-clk { + samsung,pins = "gpc1-0"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <3>; + }; + + sd1_cmd: sd1-cmd { + samsung,pins = "gpc1-1"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <3>; + }; + + sd1_cd: sd1-cd { + samsung,pins = "gpc1-2"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd1_int: sd1-int { + samsung,pins = "gpd1-1"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + + sd1_bus1: sd1-bus-width1 { + samsung,pins = "gpc1-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd1_bus4: sd1-bus-width4 { + samsung,pins = "gpc1-4", "gpc1-5", "gpc1-6"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd1_bus8: sd1-bus-width8 { + samsung,pins = "gpd1-4", "gpd1-5", "gpd1-6", "gpd1-7"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd2_clk: sd2-clk { + samsung,pins = "gpc2-0"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <3>; + }; + + sd2_cmd: sd2-cmd { + samsung,pins = "gpc2-1"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <3>; + }; + + sd2_cd: sd2-cd { + samsung,pins = "gpc2-2"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd2_bus1: sd2-bus-width1 { + samsung,pins = "gpc2-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + sd2_bus4: sd2-bus-width4 { + samsung,pins = "gpc2-4", "gpc2-5", "gpc2-6"; + samsung,pin-function = <2>; + samsung,pin-pud = <3>; + samsung,pin-drv = <3>; + }; + + cam_spi0_bus: cam-spi0-bus { + samsung,pins = "gpa2-4", "gpa2-5", "gpa2-6", "gpa2-7"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + + dp_hpd: dp-hpd { + samsung,pins = "gpx3-0"; + samsung,pin-function = <0>; + samsung,pin-pud = <3>; + samsung,pin-drv = <0>; + }; + }; + + pinctrl@10d10000 { + gpv0: gpv0 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpv1: gpv1 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpv2: gpv2 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpv3: gpv3 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + gpv4: gpv4 { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + }; + + pinctrl@03860000 { + gpz: gpz { + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + i2s0_bus: i2s0-bus { + samsung,pins = "gpz-0", "gpz-1", "gpz-2", "gpz-3", + "gpz-4", "gpz-5", "gpz-6"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; + }; +}; diff --git a/arch/arm/boot/dts/exynos5410-smdk5410.dts b/arch/arm/boot/dts/exynos5410-smdk5410.dts new file mode 100644 index 00000000000000..7ffd351e7f0a18 --- /dev/null +++ b/arch/arm/boot/dts/exynos5410-smdk5410.dts @@ -0,0 +1,72 @@ +/* + * SAMSUNG SMDK5410 board device tree source + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * 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 "exynos5410.dtsi" +/ { + model = "Samsung SMDK5410 board based on EXYNOS5410"; + compatible = "samsung,smdk5410", "samsung,exynos5410"; + + memory { + reg = <0x40000000 0x80000000>; + }; + + chosen { + bootargs = "console=ttySAC2,115200"; + }; + + clocks { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + fin_pll: clock-fin-pll { + compatible = "fixed-clock"; + reg = <0>; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "fin_pll"; + }; + }; + + mmc@12200000 { + status = "okay"; + num-slots = <1>; + supports-highspeed; + broken-cd; + card-detect-delay = <200>; + samsung,dw-mshc-ciu-div = <3>; + samsung,dw-mshc-sdr-timing = <2 3>; + samsung,dw-mshc-ddr-timing = <1 2>; + + slot@0 { + reg = <0>; + bus-width = <8>; + }; + }; + + mmc@12220000 { + status = "okay"; + num-slots = <1>; + supports-highspeed; + card-detect-delay = <200>; + samsung,dw-mshc-ciu-div = <3>; + samsung,dw-mshc-sdr-timing = <2 3>; + samsung,dw-mshc-ddr-timing = <1 2>; + + slot@0 { + reg = <0>; + bus-width = <4>; + disable-wp; + }; + }; + +}; diff --git a/arch/arm/boot/dts/exynos5410.dtsi b/arch/arm/boot/dts/exynos5410.dtsi new file mode 100644 index 00000000000000..0ba7ecbb5dcd6a --- /dev/null +++ b/arch/arm/boot/dts/exynos5410.dtsi @@ -0,0 +1,517 @@ +/* + * SAMSUNG EXYNOS5410 SoC device tree source + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * SAMSUNG EXYNOS5410 SoC device nodes are listed in this file. + * EXYNOS5410 based board files can include this file and provide + * values for board specfic bindings. + * + * 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 +#include "exynos5.dtsi" +#include "exynos5410-pinctrl.dtsi" +/ { + compatible = "samsung,exynos5410"; + + aliases { + pinctrl0 = &pinctrl_0; + pinctrl1 = &pinctrl_1; + pinctrl2 = &pinctrl_2; + pinctrl3 = &pinctrl_3; + i2c0 = &i2c_0; + i2c1 = &i2c_1; + i2c2 = &i2c_2; + i2c3 = &i2c_3; + i2c4 = &hsi2c_4; + i2c5 = &hsi2c_5; + i2c6 = &hsi2c_6; + i2c7 = &hsi2c_7; + i2c8 = &i2c_8; + gsc0 = &gsc_0; + gsc1 = &gsc_1; +/* gsc2 = &gsc_2; + gsc3 = &gsc_3; + gsc4 = &gsc_4; */ + usb3phy0 = &usb3_phy0; + usb3phy1 = &usb3_phy1; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + CPU0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + clock-frequency = <1600000000>; + }; + + CPU1: cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + clock-frequency = <1600000000>; + }; + + CPU2: cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <2>; + clock-frequency = <1600000000>; + }; + + CPU3: cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <3>; + clock-frequency = <1600000000>; + }; + }; + + clock: clock-controller@10010000 { + compatible = "samsung,exynos5410-clock"; + reg = <0x10010000 0x30000>; + #clock-cells = <1>; + }; + + timer@101C0000 { + compatible = "samsung,exynos4210-mct"; + reg = <0x101C0000 0xB00>; + interrupt-parent = <&interrupt_map>; + interrupts = <0>, <1>, <2>, <3>, + <4>, <5>, <6>, <7>, + <8>, <9>, <10>, <11>; + clocks = <&fin_pll>, <&clock CLK_MCT>; + clock-names = "fin_pll", "mct"; + + interrupt_map: interrupt-map { + #interrupt-cells = <1>; + #address-cells = <0>; + #size-cells = <0>; + interrupt-map = <0 &combiner 23 3>, + <1 &combiner 23 4>, + <2 &combiner 25 2>, + <3 &combiner 25 3>, + <4 &gic 0 120 0>, + <5 &gic 0 121 0>, + <6 &gic 0 122 0>, + <7 &gic 0 123 0>, + <8 &gic 0 128 0>, + <9 &gic 0 129 0>, + <10 &gic 0 130 0>, + <11 &gic 0 131 0>; + }; + }; + + gsc_pd: power-domain@10044000 { + compatible = "samsung,exynos4210-pd"; + reg = <0x10044000 0x20>; + }; + + pinctrl_0: pinctrl@13400000 { + compatible = "samsung,exynos5410-pinctrl"; + reg = <0x13400000 0x1000>; + interrupts = <0 45 0>; + + wakeup-interrupt-controller { + compatible = "samsung,exynos4210-wakeup-eint"; + interrupt-parent = <&gic>; + interrupts = <0 32 0>; + }; + }; + + pinctrl_1: pinctrl@14000000 { + compatible = "samsung,exynos5410-pinctrl"; + reg = <0x14000000 0x1000>; + interrupts = <0 46 0>; + }; + + pinctrl_2: pinctrl@10d10000 { + compatible = "samsung,exynos5410-pinctrl"; + reg = <0x10d10000 0x1000>; + interrupts = <0 50 0>; + }; + + pinctrl_3: pinctrl@03860000 { + compatible = "samsung,exynos5410-pinctrl"; + reg = <0x03860000 0x1000>; + interrupts = <0 47 0>; + }; + + i2c_0: i2c@12C60000 { + compatible = "samsung,s3c2440-i2c"; + reg = <0x12C60000 0x100>; + interrupts = <0 56 0>; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_bus>; + clocks = <&clock CLK_I2C0>; + clock-names = "i2c"; + status = "disabled"; + }; + + i2c_1: i2c@12C70000 { + compatible = "samsung,s3c2440-i2c"; + reg = <0x12C70000 0x100>; + interrupts = <0 57 0>; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_bus>; + clocks = <&clock CLK_I2C1>; + clock-names = "i2c"; + status = "disabled"; + }; + + i2c_2: i2c@12C80000 { + compatible = "samsung,s3c2440-i2c"; + reg = <0x12C80000 0x100>; + interrupts = <0 58 0>; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_bus>; + clocks = <&clock CLK_I2C2>; + clock-names = "i2c"; + status = "disabled"; + }; + + i2c_3: i2c@12C90000 { + compatible = "samsung,s3c2440-i2c"; + reg = <0x12C90000 0x100>; + interrupts = <0 59 0>; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&i2c3_bus>; + clocks = <&clock CLK_I2C3>; + clock-names = "i2c"; + status = "disabled"; + }; + + hsi2c_4: hsi2c@12CA0000 { + compatible = "samsung,exynos5-hsi2c"; + reg = <0x12CA0000 0x1000>; + interrupts = <0 60 0>; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&i2c4_hs_bus>; + clocks = <&clock CLK_I2C4>; + clock-names = "hsi2c"; + status = "disabled"; + }; + + hsi2c_5: hsi2c@12CB0000 { + compatible = "samsung,exynos5-hsi2c"; + reg = <0x12CB0000 0x1000>; + interrupts = <0 61 0>; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&i2c5_hs_bus>; + clocks = <&clock CLK_I2C5>; + clock-names = "hsi2c"; + status = "disabled"; + }; + + hsi2c_6: hsi2c@12CC0000 { + compatible = "samsung,exynos5-hsi2c"; + reg = <0x12CC0000 0x1000>; + interrupts = <0 62 0>; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&i2c6_hs_bus>; + clocks = <&clock CLK_I2C6>; + clock-names = "hsi2c"; + status = "disabled"; + }; + + hsi2c_7: hsi2c@12CD0000 { + compatible = "samsung,exynos5-hsi2c"; + reg = <0x12CD0000 0x1000>; + interrupts = <0 63 0>; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&i2c7_hs_bus>; + clocks = <&clock CLK_I2C7>; + clock-names = "hsi2c"; + status = "disabled"; + }; + + i2c_8: i2c@12CE0000 { + compatible = "samsung,s3c2440-hdmiphy-i2c"; + reg = <0x12CE0000 0x1000>; + interrupts = <0 64 0>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&clock CLK_I2C_HDMI>; + clock-names = "i2c"; + }; + + mmc_0: mmc@12200000 { + compatible = "samsung,exynos5250-dw-mshc"; + interrupts = <0 75 0>; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x12200000 0x1000>; + clocks = <&clock CLK_MMC0>, <&clock CLK_SCLK_MMC0>; + clock-names = "biu", "ciu"; + fifo-depth = <0x80>; + status = "disabled"; + }; + + mmc_1: mmc@12210000 { + compatible = "samsung,exynos5250-dw-mshc"; + interrupts = <0 76 0>; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x12210000 0x1000>; + clocks = <&clock CLK_MMC1>, <&clock CLK_SCLK_MMC1>; + clock-names = "biu", "ciu"; + fifo-depth = <0x80>; + status = "disabled"; + }; + + mmc_2: mmc@12220000 { + compatible = "samsung,exynos5250-dw-mshc"; + interrupts = <0 77 0>; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x12220000 0x1000>; + clocks = <&clock CLK_MMC2>, <&clock CLK_SCLK_MMC2>; + clock-names = "biu", "ciu"; + fifo-depth = <0x80>; + status = "disabled"; + }; + + serial@12C00000 { + clocks = <&clock CLK_UART0>, <&clock CLK_SCLK_UART0>; + clock-names = "uart", "clk_uart_baud0"; + }; + + serial@12C10000 { + clocks = <&clock CLK_UART1>, <&clock CLK_SCLK_UART1>; + clock-names = "uart", "clk_uart_baud0"; + }; + + serial@12C20000 { + clocks = <&clock CLK_UART2>, <&clock CLK_SCLK_UART2>; + clock-names = "uart", "clk_uart_baud0"; + }; + + hdmi { + compatible = "samsung,exynos5410-hdmi"; + reg = <0x14530000 0x70000>; + interrupts = <0 95 0>; + clocks = <&clock CLK_HDMI>, <&clock CLK_SCLK_HDMI>, <&clock CLK_SCLK_PIXEL>, + <&clock CLK_SCLK_HDMIPHY>, <&clock CLK_MOUT_HDMI>; + clock-names = "hdmi", "sclk_hdmi", "sclk_pixel", + "sclk_hdmiphy", "mout_hdmi"; + phy = <&hdmiphy>; + }; + + hdmiphy: hdmiphy@145D0000 { + compatible = "samsung,exynos5410-hdmiphy"; + reg = <0x145D0000 0x20>; + #address-cells = <1>; + #size-cells = <1>; + + phy-power-control { + reg = <0x10040700 0x04>; + }; + }; + + mixer { + compatible = "samsung,exynos5250-mixer"; + reg = <0x14450000 0x10000>; + interrupts = <0 94 0>; + clocks = <&clock CLK_MIXER>, <&clock CLK_SCLK_HDMI>; + clock-names = "mixer", "sclk_hdmi"; + }; + + dp_phy: video-phy@10040728 { + compatible = "samsung,exynos5250-dp-video-phy"; + reg = <0x10040728 4>; + #phy-cells = <0>; + }; + + dp-controller@145B0000 { + clocks = <&clock CLK_DP>; + clock-names = "dp"; + phys = <&dp_phy>; + phy-names = "dp"; + }; + + + fimd@14400000 { + /* samsung,power-domain = <&disp_pd>; */ + clocks = <&clock CLK_SCLK_FIMD1>, <&clock CLK_FIMD1>; + clock-names = "sclk_fimd", "fimd"; + }; + + gsc_0: video-scaler@13e00000 { + compatible = "samsung,exynos5-gsc"; + reg = <0x13e00000 0x1000>; + interrupts = <0 85 0>; + clocks = <&clock CLK_GSCL0>; + clock-names = "gscl"; + samsung,power-domain = <&gsc_pd>; + }; + + gsc_1: video-scaler@13e10000 { + compatible = "samsung,exynos5-gsc"; + reg = <0x13e10000 0x1000>; + interrupts = <0 86 0>; + clocks = <&clock CLK_GSCL1>; + clock-names = "gscl"; + samsung,power-domain = <&gsc_pd>; + }; +/* + gsc_2: video-scaler@13e20000 { + compatible = "samsung,exynos5-gsc"; + reg = <0x13e20000 0x1000>; + interrupts = <0 87 0>; + clocks = <&clock CLK_GSCL2>; + clock-names = "gscl"; + samsung,power-domain = <&gsc_pd>; + }; + + gsc_3: video-scaler@13e30000 { + compatible = "samsung,exynos5-gsc"; + reg = <0x13e30000 0x1000>; + interrupts = <0 88 0>; + clocks = <&clock CLK_GSCL3>; + clock-names = "gscl"; + samsung,power-domain = <&gsc_pd>; + }; + + gsc_4: video-scaler@13e40000 { + compatible = "samsung,exynos5-gsc"; + reg = <0x13e40000 0x1000>; + interrupts = <0 90 0>; + clocks = <&clock CLK_GSCL4>; + clock-names = "gscl"; + samsung,power-domain = <&gsc_pd>; + }; +*/ + usb@12000000 { + compatible = "samsung,exynos5250-dwusb3"; + clocks = <&clock CLK_USBD300>; + clock-names = "usbdrd30"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + dwc3 { + compatible = "synopsys,dwc3"; + reg = <0x12000000 0x10000>; + interrupts = <0 72 0>; + usb-phy = <&usb2_phy &usb3_phy0>; + }; + }; + + usb3_phy0: usbphy@12100000 { + compatible = "samsung,exynos5250-usb3phy"; + reg = <0x12100000 0x100>; + clocks = <&fin_pll>, <&clock CLK_USBD300>; + clock-names = "ext_xtal", "usbdrd30"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + usbphy-sys { + reg = <0x10040704 0xc>; + }; + }; + + usb@12110000 { + compatible = "samsung,exynos4210-ehci"; + reg = <0x12110000 0x100>; + interrupts = <0 71 0>; + + clocks = <&clock CLK_USBH20>; + clock-names = "usbhost"; + }; + + usb@12120000 { + compatible = "samsung,exynos4210-ohci"; + reg = <0x12120000 0x100>; + interrupts = <0 71 0>; + + clocks = <&clock CLK_USBH20>; + clock-names = "usbhost"; + }; + + usb2_phy: usbphy@12130000 { + compatible = "samsung,exynos5250-usb2phy"; + reg = <0x12130000 0x100>; + clocks = <&fin_pll>, <&clock CLK_USBH20>; + clock-names = "ext_xtal", "usbhost"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + usbphy-sys { + reg = <0x10040704 0xc>, + <0x10050230 0x4>; + }; + }; + + usb@12400000 { + compatible = "samsung,exynos5250-dwusb3"; + clocks = <&clock CLK_USBD301>; + clock-names = "usbdrd30"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + dwc3 { + compatible = "synopsys,dwc3"; + reg = <0x12400000 0x10000>; + interrupts = <0 200 0>; + usb-phy = <&usb2_phy &usb3_phy1>; + }; + }; + + usb3_phy1: usbphy@12500000 { + compatible = "samsung,exynos5250-usb3phy"; + reg = <0x12500000 0x100>; + clocks = <&fin_pll>, <&clock CLK_USBD301>; + clock-names = "ext_xtal", "usbdrd30"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + usbphy-sys { + reg = <0x10040704 0xc>; + }; + }; + + usb_hub_bus { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + // SMSC USB3503 connected in hardware only mode as a PHY + usb_hub: usb_hub { + compatible = "smsc,usb3503a"; + + reset-gpios = <&gpx1 4 1>; + connect-gpios = <&gpx0 6 1>; + intn-gpios = <&gpx0 7 1>; + secondary-refclk; + }; + }; + +}; diff --git a/arch/arm/boot/dts/exynos5420-arndale-octa.dts b/arch/arm/boot/dts/exynos5420-arndale-octa.dts index 7340745ff979a4..76b0b4a050cefb 100644 --- a/arch/arm/boot/dts/exynos5420-arndale-octa.dts +++ b/arch/arm/boot/dts/exynos5420-arndale-octa.dts @@ -11,6 +11,8 @@ /dts-v1/; #include "exynos5420.dtsi" +#include +#include / { model = "Insignal Arndale Octa evaluation board based on EXYNOS5420"; @@ -24,6 +26,11 @@ bootargs = "console=ttySAC3,115200"; }; + firmware@0203F000 { + compatible = "samsung,secure-firmware"; + reg = <0x0203F000 0x1000>; + }; + fixed-rate-clocks { oscclk { compatible = "samsung,exynos5420-oscclk"; @@ -31,6 +38,18 @@ }; }; + regulators { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + reg_hdmi_en: regulator@0 { + compatible = "regulator-fixed"; + reg = <0>; + regulator-name = "hdmi-en"; + }; + }; + mmc@12200000 { status = "okay"; broken-cd; @@ -63,4 +82,288 @@ bus-width = <4>; }; }; + + i2c_2: i2c@12C80000 { + samsung,i2c-sda-delay = <100>; + samsung,i2c-max-bus-freq = <66000>; + samsung,i2c-slave-addr = <0x50>; + status = "okay"; + + hdmiddc@50 { + compatible = "samsung,exynos4210-hdmiddc"; + reg = <0x50>; + }; + }; + + hsi2c_4: i2c@12CA0000 { + status = "okay"; + + s2mps11_pmic@66 { + compatible = "samsung,s2mps11-pmic"; + reg = <0x66>; + s2mps11,buck2-ramp-delay = <12>; + s2mps11,buck34-ramp-delay = <12>; + s2mps11,buck16-ramp-delay = <12>; + s2mps11,buck6-ramp-enable = <1>; + s2mps11,buck2-ramp-enable = <1>; + s2mps11,buck3-ramp-enable = <1>; + s2mps11,buck4-ramp-enable = <1>; + + interrupt-parent = <&gpx3>; + interrupts = <2 IRQ_TYPE_LEVEL_HIGH>; + + s2mps11_osc: clocks { + #clock-cells = <1>; + clock-output-names = "s2mps11_ap", + "s2mps11_cp", "s2mps11_bt"; + }; + + regulators { + ldo1_reg: LDO1 { + regulator-name = "vdd_ldo1"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1000000>; + regulator-always-on; + }; + + ldo3_reg: LDO3 { + regulator-name = "vdd_ldo3"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo5_reg: LDO5 { + regulator-name = "vdd_ldo5"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo6_reg: LDO6 { + regulator-name = "vdd_ldo6"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1000000>; + regulator-always-on; + }; + + ldo7_reg: LDO7 { + regulator-name = "vdd_ldo7"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo8_reg: LDO8 { + regulator-name = "vdd_ldo8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo9_reg: LDO9 { + regulator-name = "vdd_ldo9"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + regulator-always-on; + }; + + ldo10_reg: LDO10 { + regulator-name = "vdd_ldo10"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo11_reg: LDO11 { + regulator-name = "vdd_ldo11"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1000000>; + regulator-always-on; + }; + + ldo12_reg: LDO12 { + regulator-name = "vdd_ldo12"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo13_reg: LDO13 { + regulator-name = "vdd_ldo13"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + regulator-always-on; + }; + + ldo15_reg: LDO15 { + regulator-name = "vdd_ldo15"; + regulator-min-microvolt = <3100000>; + regulator-max-microvolt = <3100000>; + regulator-always-on; + }; + + ldo16_reg: LDO16 { + regulator-name = "vdd_ldo16"; + regulator-min-microvolt = <2200000>; + regulator-max-microvolt = <2200000>; + regulator-always-on; + }; + + ldo17_reg: LDO17 { + regulator-name = "tsp_avdd"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + ldo19_reg: LDO19 { + regulator-name = "vdd_sd"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + regulator-always-on; + }; + + ldo23_reg: LDO23 { + regulator-name = "vdd_ldo23"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + }; + + ldo24_reg: LDO24 { + regulator-name = "tsp_io"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + regulator-always-on; + }; + + buck1_reg: BUCK1 { + regulator-name = "vdd_mif"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1300000>; + regulator-always-on; + regulator-boot-on; + }; + + buck2_reg: BUCK2 { + regulator-name = "vdd_arm"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1500000>; + regulator-always-on; + regulator-boot-on; + }; + + buck3_reg: BUCK3 { + regulator-name = "vdd_int"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1400000>; + regulator-always-on; + regulator-boot-on; + }; + + buck4_reg: BUCK4 { + regulator-name = "vdd_g3d"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1400000>; + regulator-always-on; + regulator-boot-on; + }; + + buck5_reg: BUCK5 { + regulator-name = "vdd_mem"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1400000>; + regulator-always-on; + regulator-boot-on; + }; + + buck6_reg: BUCK6 { + regulator-name = "vdd_kfc"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1500000>; + regulator-always-on; + regulator-boot-on; + }; + + buck7_reg: BUCK7 { + regulator-name = "vdd_1.0v_ldo"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1500000>; + regulator-always-on; + regulator-boot-on; + }; + + buck8_reg: BUCK8 { + regulator-name = "vdd_1.8v_ldo"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1500000>; + regulator-always-on; + regulator-boot-on; + }; + + buck9_reg: BUCK9 { + regulator-name = "vdd_2.8v_ldo"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3750000>; + regulator-always-on; + regulator-boot-on; + }; + + buck10_reg: BUCK10 { + regulator-name = "vdd_vmem"; + regulator-min-microvolt = <2850000>; + regulator-max-microvolt = <2850000>; + regulator-always-on; + regulator-boot-on; + }; + }; + }; + }; + + hdmi@14530000 { + hpd-gpio = <&gpx3 7 2>; + vdd_osc-supply = <&ldo7_reg>; + vdd_pll-supply = <&ldo6_reg>; + vdd-supply = <&ldo6_reg>; + status = "okay"; + }; + + gpio_keys { + compatible = "gpio-keys"; + + wakeup { + label = "SW-TACT1"; + gpios = <&gpx2 7 1>; + linux,code = ; + gpio-key,wakeup; + }; + }; + + amba { + mdma1: mdma@11C10000 { + /* + * MDMA1 can support both secure and non-secure + * AXI transactions. When this is enabled in the kernel + * for boards that run in secure mode, we are getting + * imprecise external aborts causing the kernel to oops. + */ + status = "disabled"; + }; + }; + + i2s0: i2s@03830000 { + status = "okay"; + }; + + i2s_stub: i2s-stub { + compatible = "linux,i2s-stub"; + }; + + sound { + compatible = "samsung,smdk-i2s-stub"; + + samsung,i2s-controller = <&i2s0>; + samsung,audio-codec = <&i2s_stub>; + }; }; diff --git a/arch/arm/boot/dts/exynos5420.dtsi b/arch/arm/boot/dts/exynos5420.dtsi index 8db792b26f79c1..5e3b3918156f38 100644 --- a/arch/arm/boot/dts/exynos5420.dtsi +++ b/arch/arm/boot/dts/exynos5420.dtsi @@ -46,6 +46,8 @@ spi0 = &spi_0; spi1 = &spi_1; spi2 = &spi_2; + usb3phy0 = &usb3_phy0; + usb3phy1 = &usb3_phy1; }; cpus { @@ -212,6 +214,7 @@ reg = <0x10044060 0x20>; }; +/* disp_pd: power-domain@100440C0 { compatible = "samsung,exynos4210-pd"; reg = <0x100440C0 0x20>; @@ -221,6 +224,7 @@ compatible = "samsung,exynos4210-pd"; reg = <0x100440E0 0x20>; }; +*/ g2d_pd: power-domain@10044100 { compatible = "samsung,exynos4210-pd"; @@ -281,6 +285,17 @@ interrupt-parent = <&gic>; ranges; + adma: adma@03880000 { + compatible = "arm,pl330", "arm,primecell"; + reg = <0x03880000 0x1000>; + interrupts = <0 110 0>; + clocks = <&clock_audss EXYNOS_ADMA>; + clock-names = "apb_pclk"; + #dma-cells = <1>; + #dma-channels = <6>; + #dma-requests = <16>; + }; + pdma0: pdma@121A0000 { compatible = "arm,pl330", "arm,primecell"; reg = <0x121A0000 0x1000>; @@ -326,6 +341,49 @@ }; }; + i2s0: i2s@03830000 { + compatible = "samsung,exynos5420-i2s"; + reg = <0x03830000 0x100>; + dmas = <&adma 0 + &adma 2 + &adma 1>; + dma-names = "tx", "rx", "tx-sec"; + clocks = <&clock_audss EXYNOS_I2S_BUS>, + <&clock_audss EXYNOS_I2S_BUS>, + <&clock_audss EXYNOS_SCLK_I2S>; + clock-names = "iis", "i2s_opclk0", "i2s_opclk1"; + samsung,idma-addr = <0x03000000>; + pinctrl-names = "default"; + pinctrl-0 = <&i2s0_bus>; + status = "disabled"; + }; + + i2s1: i2s@12D60000 { + compatible = "samsung,exynos5420-i2s"; + reg = <0x12D60000 0x100>; + dmas = <&pdma1 12 + &pdma1 11>; + dma-names = "tx", "rx"; + clocks = <&clock 275>, <&clock 138>; + clock-names = "iis", "i2s_opclk0"; + pinctrl-names = "default"; + pinctrl-0 = <&i2s1_bus>; + status = "disabled"; + }; + + i2s2: i2s@12D70000 { + compatible = "samsung,exynos5420-i2s"; + reg = <0x12D70000 0x100>; + dmas = <&pdma0 12 + &pdma0 11>; + dma-names = "tx", "rx"; + clocks = <&clock 276>, <&clock 139>; + clock-names = "iis", "i2s_opclk0"; + pinctrl-names = "default"; + pinctrl-0 = <&i2s2_bus>; + status = "disabled"; + }; + spi_0: spi@12d20000 { compatible = "samsung,exynos4210-spi"; reg = <0x12d20000 0x100>; @@ -416,8 +474,8 @@ phy-names = "dp"; }; - fimd@14400000 { - samsung,power-domain = <&disp_pd>; + fimd: fimd@14400000 { + /* samsung,power-domain = <&disp_pd>; */ clocks = <&clock 147>, <&clock 421>; clock-names = "sclk_fimd", "fimd"; }; @@ -577,7 +635,7 @@ }; hdmi@14530000 { - compatible = "samsung,exynos4212-hdmi"; + compatible = "samsung,exynos5420-hdmi"; reg = <0x14530000 0x70000>; interrupts = <0 95 0>; clocks = <&clock 413>, <&clock 143>, <&clock 768>, @@ -585,9 +643,21 @@ clock-names = "hdmi", "sclk_hdmi", "sclk_pixel", "sclk_hdmiphy", "mout_hdmi"; status = "disabled"; + phy = <&hdmiphy>; }; - mixer@14450000 { + hdmiphy: hdmiphy@145D0000 { + compatible = "samsung,exynos5420-hdmiphy"; + reg = <0x145D0000 0x20>; + #address-cells = <1>; + #size-cells = <1>; + + phy-power-control { + reg = <0x10040700 0x04>; + }; + }; + + mixer: mixer@14450000 { compatible = "samsung,exynos5420-mixer"; reg = <0x14450000 0x10000>; interrupts = <0 94 0>; @@ -652,4 +722,107 @@ clocks = <&clock 319>, <&clock 318>; clock-names = "tmu_apbif", "tmu_triminfo_apbif"; }; + + usb@12000000 { + compatible = "samsung,exynos5250-dwusb3"; + clocks = <&clock 366>; + clock-names = "usbdrd30"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + dwc3 { + compatible = "synopsys,dwc3"; + reg = <0x12000000 0x10000>; + interrupts = <0 72 0>; + usb-phy = <&usb2_phy &usb3_phy0>; + }; + }; + + usb3_phy0: usbphy@12100000 { + compatible = "samsung,exynos5250-usb3phy"; + reg = <0x12100000 0x100>; + clocks = <&clock 1>, <&clock 366>; + clock-names = "ext_xtal", "usbdrd30"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + usbphy-sys { + reg = <0x10040704 0xc>; + }; + }; + + usb@12110000 { + compatible = "samsung,exynos4210-ehci"; + reg = <0x12110000 0x100>; + interrupts = <0 71 0>; + + clocks = <&clock 365>; + clock-names = "usbhost"; + }; + + usb@12120000 { + compatible = "samsung,exynos4210-ohci"; + reg = <0x12120000 0x100>; + interrupts = <0 71 0>; + + clocks = <&clock 365>; + clock-names = "usbhost"; + }; + + usb2_phy: usbphy@12130000 { + compatible = "samsung,exynos5250-usb2phy"; + reg = <0x12130000 0x100>; + clocks = <&clock 1>, <&clock 365>; + clock-names = "ext_xtal", "usbhost"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + usbphy-sys { + reg = <0x10040704 0xc>, + <0x10050230 0x4>; + }; + }; + + usb@12400000 { + compatible = "samsung,exynos5250-dwusb3"; + clocks = <&clock 367>; + clock-names = "usbdrd30"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + dwc3 { + compatible = "synopsys,dwc3"; + reg = <0x12400000 0x10000>; + interrupts = <0 73 0>; + usb-phy = <&usb2_phy &usb3_phy1>; + }; + }; + + usb3_phy1: usbphy@12500000 { + compatible = "samsung,exynos5250-usb3phy"; + reg = <0x12500000 0x100>; + clocks = <&clock 1>, <&clock 367>; + clock-names = "ext_xtal", "usbdrd30"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + usbphy-sys { + reg = <0x10040704 0xc>; + }; + }; + + sysmmu_tv: sysmmu@14650000 { + compatible = "samsung,sysmmu-v3.3"; + reg = <0x14650000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <7 4>; + clock-names = "sysmmu", "master"; + clocks = <&clock 502>, <&clock 431>; + mmu-masters = <&mixer>; + }; }; diff --git a/arch/arm/boot/dts/hi3620-hi4511.dts b/arch/arm/boot/dts/hi3620-hi4511.dts index fe623928f68794..8b2c3f222c482b 100644 --- a/arch/arm/boot/dts/hi3620-hi4511.dts +++ b/arch/arm/boot/dts/hi3620-hi4511.dts @@ -64,6 +64,607 @@ status = "ok"; }; + i2c0: i2c@b08000 { + status = "ok"; + pinctrl-names = "default", "idle"; + pinctrl-0 = <&i2c0_pmx_func &i2c0_cfg_func>; + pinctrl-1 = <&i2c0_pmx_idle &i2c0_cfg_func>; + }; + + i2c1: i2c@b09000 { + status = "ok"; + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pmx_func &i2c1_cfg_func>; + ts_mxt224e: ts@4a { + compatible = "atmel,ts-mxt224e"; + reg = <0x4a>; + ldo-supply = <&ldo6>; + pinctrl-names = "default"; + pinctrl-0 = <&ts_pin_cfg>; + atmel-ts,gpio-irq = <&gpio19 5 0>; + atmel-ts,gpio-reset = <&gpio19 4 0>; + /* min max: x y pressure width */ + atmel-ts,abs = <0 719 0 1279 0 255 0 255>; + atmel-ts,cfg_t6 = /bits/ 8 <0 0 0 0 0 0>; + atmel-ts,cfg_t7 = /bits/ 8 <32 255 10>; + atmel-ts,cfg_t8 = /bits/ 8 <24 0 1 10 0 0 5 60 10 192>; + atmel-ts,cfg_t9 = /bits/ 8 <143 0 0 19 11 0 32 66 2 3 0 2 2 47 10 15 22 10 106 5 + 207 2 0 0 0 0 161 40 183 64 30 20 0 0 1>; + atmel-ts,cfg_t15 = /bits/ 8 <0 0 0 0 0 0 0 0 0 0 0>; + atmel-ts,cfg_t19 = /bits/ 8 <0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0>; + atmel-ts,cfg_t23 = /bits/ 8 <0 0 0 0 0 0 0 0 0 0 0 0 0 0 0>; + atmel-ts,cfg_t25 = /bits/ 8 <0 0 0 0 0 0 0 0 0 0 0 0 0 0>; + atmel-ts,cfg_t40 = /bits/ 8 <0 0 0 0 0>; + atmel-ts,cfg_t42 = /bits/ 8 <0 40 40 80 128 0 0 0>; + atmel-ts,cfg_t46 = /bits/ 8 <0 3 32 32 0 0 0 0 0>; + atmel-ts,cfg_t47 = /bits/ 8 <0 20 50 5 2 40 40 180 0 100>; + atmel-ts,cfg_t48 = /bits/ 8 <1 4 10 0 0 0 0 0 1 1 0 0 0 6 6 0 0 63 6 64 + 10 0 20 5 0 38 0 20 0 0 0 0 0 0 0 40 2 2 2 32 + 10 12 20 241 251 0 0 191 40 183 64 30 15 0>; + atmel-ts,object_crc = /bits/ 8 <0xFD 0x3B 0x8D>; + atmel-ts,cable_config = /bits/ 8 <70 30 32 32>; + atmel-ts,cable_config_t7 = /bits/ 8 <32 16 25>; + atmel-ts,cable_config_t8 = /bits/ 8 <24 0 5 5 0 0 5 60 10 192>; + atmel-ts,cable_config_T9 = /bits/ 8 <139 0 0 19 11 0 32 66 2 3 0 5 2 64 10 + 12 20 10 106 5 207 2 0 0 0 0 161 40 183 64 30 20 0 0 0>; + atmel-ts,cable_config_t46 = /bits/ 8 <0 3 40 40 0 0 0 0 0>; + atmel-ts,cable_config_t48 = /bits/ 8 <1 128 114 0 0 0 0 0 1 2 0 0 0 6 6 + 0 0 63 6 64 10 0 20 5 0 38 0 20 0 0 0 0 0 0 0 + 40 2 2 2 32 10 12 20 241 251 0 0 191 40 183 64 30 15 0>; + atmel-ts,noise_config = /bits/ 8 <70 3 35>; + atmel-ts,filter_level = /bits/ 16 <0 0 539 539>; + atmel-ts,gcaf_level = /bits/ 8 <8 16 24 32 40>; + atmel-ts,atch_nor = /bits/ 8 <0 0 5 60 10 192>; + atmel-ts,atch_nor_20s = /bits/ 8 <0 0 255 1 0 0>; + }; + }; + + dwmmc1@d04000 { + num-slots = <1>; + vmmc-supply = <&ldo0>; + vqmmc-supply = <&ldo5>; + /* emmc fifo register value is incorrect */ + fifo-depth = <0x100>; + broken-cd; + supports-highspeed; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pmx_func &emmc_cfg_func &emmc_cfg_clk_func>; + slot@0 { + reg = <0>; + bus-width = <8>; + disable-wp; + }; + }; + + dwmmc0@d03000 { + num-slots = <1>; + vmmc-supply = <&ldo12>; + fifo-depth = <0x100>; + supports-highspeed; + cd-inverted; + pinctrl-names = "default"; + pinctrl-0 = <&sd_pmx_pins &sd_cfg_func1 &sd_cfg_func2>; + slot@0 { + reg = <0>; + bus-width = <4>; + disable-wp; + cd-gpios = <&gpio10 3 0>; + }; + }; + + dwmmc2@d05000 { + status = "disabled"; + }; + + dwmmc3@d06000 { + status = "disabled"; + }; + pmic: pmic@c00000 { + gpios = <&gpio19 7 0>; + pinctrl-names = "default"; + pinctrl-0 = <&pmic_int_cfg_func>; + ldo0: ldo@20 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO0"; + regulator-min-microvolt = <2850000>; + regulator-max-microvolt = <2850000>; + hisilicon,hi6421-ctrl = <0x20 0x10 0x20>; + hisilicon,hi6421-vset = <0x20 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <10000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo1: ldo@21 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO1"; + regulator-min-microvolt = <1700000>; + regulator-max-microvolt = <2000000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x21 0x10 0x20>; + hisilicon,hi6421-vset = <0x21 0x03>; + hisilicon,hi6421-n-voltages = <4>; + hisilicon,hi6421-vset-table = <1700000>, <1800000>, + <1900000>, <2000000>; + hisilicon,hi6421-off-on-delay-us = <10000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <5000>; + }; + + ldo2: ldo@22 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO2"; + regulator-min-microvolt = <1050000>; + regulator-max-microvolt = <1400000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x22 0x10 0x20>; + hisilicon,hi6421-vset = <0x22 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1050000>, <1100000>, + <1150000>, <1200000>, + <1250000>, <1300000>, + <1350000>, <1400000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo3: ldo@23 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO3"; + regulator-min-microvolt = <1050000>; + regulator-max-microvolt = <1400000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x23 0x10 0x20>; + hisilicon,hi6421-vset = <0x23 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1050000>, <1100000>, + <1150000>, <1200000>, + <1250000>, <1300000>, + <1350000>, <1400000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo4: ldo@24 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO4"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x24 0x10 0x20>; + hisilicon,hi6421-vset = <0x24 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo5: ldo@25 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO5"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x25 0x10 0x20>; + hisilicon,hi6421-vset = <0x25 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo6: ldo@26 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO6"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x26 0x10 0x20>; + hisilicon,hi6421-vset = <0x26 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo7: ldo@27 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO7"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x27 0x10 0x20>; + hisilicon,hi6421-vset = <0x27 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <5000>; + }; + + ldo8: ldo@28 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO8"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x28 0x10 0x20>; + hisilicon,hi6421-vset = <0x28 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2600000>, + <2700000>, <2850000>, + <3000000>, <3300000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo9: ldo@29 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO9"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + hisilicon,hi6421-ctrl = <0x29 0x10 0x20>; + hisilicon,hi6421-vset = <0x29 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo10: ldo@2a { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO10"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + hisilicon,hi6421-ctrl = <0x2a 0x10 0x20>; + hisilicon,hi6421-vset = <0x2a 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo11: ldo@2b { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO11"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + hisilicon,hi6421-ctrl = <0x2b 0x10 0x20>; + hisilicon,hi6421-vset = <0x2b 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo12: ldo@2c { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO12"; + regulator-min-microvolt = <2850000>; + regulator-max-microvolt = <2850000>; + hisilicon,hi6421-ctrl = <0x2c 0x10 0x20>; + hisilicon,hi6421-vset = <0x2c 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo13: ldo@2d { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO13"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + hisilicon,hi6421-ctrl = <0x2d 0x10 0x20>; + hisilicon,hi6421-vset = <0x2d 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo14: ldo@2e { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO14"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + hisilicon,hi6421-ctrl = <0x2e 0x10 0x20>; + hisilicon,hi6421-vset = <0x2e 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo15: ldo@2f { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO15"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3300000>; + hisilicon,hi6421-ctrl = <0x2f 0x10 0x20>; + hisilicon,hi6421-vset = <0x2f 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2600000>, + <2700000>, <2850000>, + <3000000>, <3300000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo16: ldo@30 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO16"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x30 0x10 0x20>; + hisilicon,hi6421-vset = <0x30 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo17: ldo@31 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO17"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x31 0x10 0x20>; + hisilicon,hi6421-vset = <0x31 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo18: ldo@32 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO18"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + hisilicon,hi6421-ctrl = <0x32 0x10 0x20>; + hisilicon,hi6421-vset = <0x32 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo19: ldo@33 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO19"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + hisilicon,hi6421-ctrl = <0x2a 0x10 0x20>; + hisilicon,hi6421-vset = <0x2a 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldo20: ldo@34 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDO20"; + regulator-min-microvolt = <1500000>; + regulator-max-microvolt = <3000000>; + hisilicon,hi6421-ctrl = <0x34 0x10 0x20>; + hisilicon,hi6421-vset = <0x34 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1500000>, <1800000>, + <2400000>, <2500000>, + <2600000>, <2700000>, + <2850000>, <3000000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <8000>; + }; + + ldoaudio: ldo@36 { + compatible = "hisilicon,hi6421-ldo"; + regulator-name = "LDOAUDIO"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <3300000>; + hisilicon,hi6421-ctrl = <0x36 0x01 0x02>; + hisilicon,hi6421-vset = <0x36 0x70>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <2800000>, <2850000>, + <2900000>, <2950000>, + <3000000>, <3100000>, + <3200000>, <3300000>; + hisilicon,hi6421-off-on-delay-us = <40000>; + hisilicon,hi6421-enable-time-us = <250>; + hisilicon,hi6421-eco-microamp = <5000>; + }; + + buck0: buck@0c { + compatible = "hisilicon,hi6421-buck012"; + regulator-name = "BUCK0"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <1600000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x0c 0x01 0x10>; + hisilicon,hi6421-vset = <0x0d 0x7f>; + hisilicon,hi6421-n-voltages = <128>; + hisilicon,hi6421-uv-step = <7086>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <300>; + }; + + buck1: buck@0e { + compatible = "hisilicon,hi6421-buck012"; + regulator-name = "BUCK1"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <1600000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x0e 0x01 0x10>; + hisilicon,hi6421-vset = <0x0f 0x7f>; + hisilicon,hi6421-n-voltages = <128>; + hisilicon,hi6421-uv-step = <7086>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <300>; + }; + + buck2: buck@10 { + compatible = "hisilicon,hi6421-buck012"; + regulator-name = "BUCK2"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <1600000>; + regulator-boot-on; + hisilicon,hi6421-ctrl = <0x10 0x01 0x10>; + hisilicon,hi6421-vset = <0x11 0x7f>; + hisilicon,hi6421-n-voltages = <128>; + hisilicon,hi6421-uv-step = <7086>; + hisilicon,hi6421-off-on-delay-us = <100>; + hisilicon,hi6421-enable-time-us = <250>; + }; + + buck3: buck@12 { + compatible = "hisilicon,hi6421-buck345"; + regulator-name = "BUCK3"; + regulator-min-microvolt = <950000>; + regulator-max-microvolt = <1200000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x12 0x01 0x10>; + hisilicon,hi6421-vset = <0x13 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <950000>, <1050000>, + <1100000>, <1170000>, + <1134000>, <1150000>, + <1167000>, <1200000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + }; + + buck4: buck@14 { + compatible = "hisilicon,hi6421-buck345"; + regulator-name = "BUCK4"; + regulator-min-microvolt = <1150000>; + regulator-max-microvolt = <2000000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x14 0x01 0x10>; + hisilicon,hi6421-vset = <0x15 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1150000>, <1200000>, + <1250000>, <1350000>, + <1700000>, <1800000>, + <1900000>, <2000000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + }; + + buck5: buck@16 { + compatible = "hisilicon,hi6421-buck345"; + regulator-name = "BUCK5"; + regulator-min-microvolt = <1150000>; + regulator-max-microvolt = <1900000>; + regulator-boot-on; + regulator-always-on; + hisilicon,hi6421-ctrl = <0x16 0x01 0x10>; + hisilicon,hi6421-vset = <0x17 0x07>; + hisilicon,hi6421-n-voltages = <8>; + hisilicon,hi6421-vset-table = <1150000>, <1200000>, + <1250000>, <1350000>, + <1600000>, <1700000>, + <1800000>, <1900000>; + hisilicon,hi6421-off-on-delay-us = <20000>; + hisilicon,hi6421-enable-time-us = <250>; + }; + + onkey { + compatible = "hisilicon,hi6421-onkey"; + interrupt-parent = <&pmic>; + interrupts = <7 0>, <6 0>, <5 0>, <4 0>; + interrupt-names = "down", "up", "hold 1s", "hold 10s"; + }; + + rtc { + compatible = "hisilicon,hi6421-rtc"; + interrupt-parent = <&pmic>; + interrupts = <0 0>; + }; + }; /* end of pmic */ + pmx0: pinmux@803000 { pinctrl-names = "default"; pinctrl-0 = <&board_pmx_pins>; @@ -74,6 +675,12 @@ 0x100 0x0 /* USIM_CLK & USIM_DATA (IOMG63) */ >; }; + sd_pmx_pins: pinmux_sd_pins { + pinctrl-single,pins = < + 0x0bc 0x0 /* SD_CLK, SD_CMD, SD_DATA[0:2] */ + 0x0c0 0x0 /* SD_DATA[3] */ + >; + }; uart0_pmx_func: uart0_pmx_func { pinctrl-single,pins = < 0x0f0 0x0 @@ -534,10 +1141,6 @@ emmc_cfg_func: emmc_cfg_func { pinctrl-single,pins = < 0x0ac 0 /* eMMC_CMD (IOCFG40) */ - 0x0b0 0 /* eMMC_CLK (IOCFG41) */ - 0x058 0 /* NAND_CS3_N (IOCFG19) */ - 0x064 0 /* NAND_BUSY2_N (IOCFG22) */ - 0x068 0 /* NAND_BUSY3_N (IOCFG23) */ 0x08c 0 /* NAND_DATA8 (IOCFG32) */ 0x090 0 /* NAND_DATA9 (IOCFG33) */ 0x094 0 /* NAND_DATA10 (IOCFG34) */ @@ -551,7 +1154,15 @@ pinctrl-single,bias-pullup = <1 1 0 1>; pinctrl-single,drive-strength = <0x30 0xf0>; }; - sd_cfg_func1: sd_cfg_func1 { + emmc_cfg_clk_func: pincfg_emmc_clk_func { + pinctrl-single,pins = < + 0x0b0 0 /* eMMC_CLK (IOCFG41) */ + >; + pinctrl-single,bias-pulldown = <0 2 0 2>; + pinctrl-single,bias-pullup = <0 1 0 1>; + pinctrl-single,drive-strength = <0x30 0xf0>; + }; + sd_cfg_func1: pincfg_sd_f1 { pinctrl-single,pins = < 0x18c 0 /* SD_CLK (IOCFG107) */ 0x190 0 /* SD_CMD (IOCFG108) */ @@ -562,13 +1173,14 @@ }; sd_cfg_func2: sd_cfg_func2 { pinctrl-single,pins = < + 0x190 0 /* SD_CMD (IOCFG108) */ 0x194 0 /* SD_DATA0 (IOCFG109) */ 0x198 0 /* SD_DATA1 (IOCFG110) */ 0x19c 0 /* SD_DATA2 (IOCFG111) */ 0x1a0 0 /* SD_DATA3 (IOCFG112) */ >; - pinctrl-single,bias-pulldown = <2 2 0 2>; - pinctrl-single,bias-pullup = <0 1 0 1>; + pinctrl-single,bias-pulldown = <0 2 0 2>; + pinctrl-single,bias-pullup = <1 1 0 1>; pinctrl-single,drive-strength = <0x70 0xf0>; }; nand_cfg_func1: nand_cfg_func1 { @@ -634,6 +1246,19 @@ pinctrl-single,bias-pulldown = <2 2 0 2>; pinctrl-single,bias-pullup = <0 1 0 1>; }; + pmic_int_cfg_func: pincfg_pmic_func { + pinctrl-single,pins = < + 0x018 0 /* GPIO159 (IOCFG003) */ + >; + }; + /* TP_IRQ need pullup */ + ts_pin_cfg: pincfg_ts_func { + pinctrl-single,pins = < + 0x010 0 /* GPIO157 (TP_IRQ) */ + >; + pinctrl-single,bias-pulldown = <0 2 0 2>; + pinctrl-single,bias-pullup = <1 1 0 1>; + }; }; }; diff --git a/arch/arm/boot/dts/hi3620.dtsi b/arch/arm/boot/dts/hi3620.dtsi index ab1116d086be82..6836795040adbf 100644 --- a/arch/arm/boot/dts/hi3620.dtsi +++ b/arch/arm/boot/dts/hi3620.dtsi @@ -73,7 +73,7 @@ L2: l2-cache { compatible = "arm,pl310-cache"; - reg = <0xfc10000 0x100000>; + reg = <0x100000 0x100000>; interrupts = <0 15 4>; cache-unified; cache-level = <2>; @@ -106,6 +106,21 @@ }; }; + pctrl: pctrl@a09000 { + compatible = "hisilicon,pctrl"; + ranges = <0 0xa09000 0x1000>; + reg = <0xa09000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + mmc_clock: clock@1 { + compatible = "hisilicon,hi3620-mmc-clock"; + reg = <0 0x10>; + #clock-cells = <1>; + }; + }; + + dual_timer0: dual_timer@800000 { compatible = "arm,sp804", "arm,primecell"; reg = <0x800000 0x1000>; @@ -531,6 +546,96 @@ clock-names = "apb_pclk"; }; + i2c0: i2c@b08000 { + compatible = "snps,designware-i2c"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xb08000 0x1000>; + interrupts = <0 28 4>; + clocks = <&clock HI3620_I2CCLK0>; + status = "disabled"; + }; + + i2c1: i2c@b09000 { + compatible = "snps,designware-i2c"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xb09000 0x1000>; + interrupts = <0 29 4>; + clocks = <&clock HI3620_I2CCLK1>; + status = "disabled"; + }; + + i2c2: i2c@b0c000 { + compatible = "snps,designware-i2c"; + reg = <0xb0c000 0x1000>; + interrupts = <0 62 4>; + clocks = <&clock HI3620_I2CCLK2>; + status = "disabled"; + }; + + i2c3: i2c@b0d000 { + compatible = "snps,designware-i2c"; + reg = <0xb0d000 0x1000>; + interrupts = <0 63 4>; + clocks = <&clock HI3620_I2CCLK3>; + status = "disabled"; + }; + + /* unremovable emmc as mmcblk0 */ + dwmmc_1: dwmmc1@d04000 { + compatible = "hisilicon,hi4511-dw-mshc"; + reg = <0xd04000 0x1000>; + interrupts = <0 17 4>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&mmc_clock HI3620_MMC_CIUCLK1>, <&clock HI3620_DDRC_PER_CLK>; + clock-names = "ciu", "biu"; + clock-freq-table = + <13000000 50000000 0 0 13000000 50000000 0 100000000>; + + }; + + /* sd as mmcblk1 */ + dwmmc_0: dwmmc0@d03000 { + compatible = "hisilicon,hi4511-dw-mshc"; + reg = <0xd03000 0x1000>; + interrupts = <0 16 4>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&mmc_clock HI3620_SD_CIUCLK>, <&clock HI3620_DDRC_PER_CLK>; + clock-names = "ciu", "biu"; + clock-freq-table = + <25000000 0 50000000 25000000 50000000 100000000 0 50000000>; + }; + + dwmmc_2: dwmmc2@d05000 { + compatible = "hisilicon,hi4511-dw-mshc"; + reg = <0xd05000 0x1000>; + interrupts = <0 18 4>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&mmc_clock HI3620_MMC_CIUCLK2>, <&clock HI3620_DDRC_PER_CLK>; + clock-freq-table = + <25000000 25000000 50000000 50000000>; + }; + + dwmmc_3: dwmmc3@d06000 { + compatible = "hisilicon,hi4511-dw-mshc"; + reg = <0xd06000 0x1000>; + interrupts = <0 19 4>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&mmc_clock HI3620_MMC_CIUCLK3>, <&clock HI3620_DDRC_PER_CLK>; + clock-freq-table = + <25000000 25000000 50000000 50000000>; + }; + pmic: pmic@c00000 { + compatible = "hisilicon,hi6421-pmic"; + reg = <0xc00000 0x0180>; /* 0x60 << 2 */ + #interrupt-cells = <2>; + interrupt-controller; + }; pmx0: pinmux@803000 { compatible = "pinctrl-single"; reg = <0x803000 0x188>; diff --git a/arch/arm/boot/dts/hi3716-dkb.dts b/arch/arm/boot/dts/hi3716-dkb.dts new file mode 100644 index 00000000000000..91beec5f236f48 --- /dev/null +++ b/arch/arm/boot/dts/hi3716-dkb.dts @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2013 Hisilicon Limited. + * + * 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 + * publishhed by the Free Software Foundation. + */ + +/dts-v1/; +/include/ "hi3716.dtsi" + +/ { + model = "Hisilicon Hi3716 Development Board"; + compatible = "hisilicon,hi3716"; + + chosen { + bootargs = "console=ttyAMA0,115200"; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + compatible = "arm,cortex-a9"; + device_type = "cpu"; + reg = <0>; + next-level-cache = <&l2>; + }; + + cpu@1 { + compatible = "arm,cortex-a9"; + device_type = "cpu"; + reg = <1>; + next-level-cache = <&l2>; + }; + }; + + memory { + device_type = "memory"; + reg = <0x00000000 0x80000000>; + }; + + soc { + amba { + timer0: timer@f8002000 { + status = "okay"; + }; + + uart0: uart@f8b00000 { + status = "okay"; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/hi3716.dtsi b/arch/arm/boot/dts/hi3716.dtsi new file mode 100644 index 00000000000000..5eea43c055f5b8 --- /dev/null +++ b/arch/arm/boot/dts/hi3716.dtsi @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2013 Hisilicon Limited. + * + * 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 + * publishhed by the Free Software Foundation. + */ + +/include/ "skeleton.dtsi" + +/ { + aliases { + serial0 = &uart0; + }; + + gic: interrupt-controller@fc001000 { + compatible = "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + /* gic dist base, gic cpu base */ + reg = <0xf8a01000 0x1000>, <0xf8a00100 0x100>; + }; + + soc { + #address-cells = <1>; + #size-cells = <1>; + compatible = "simple-bus"; + device_type = "soc"; + interrupt-parent = <&gic>; + ranges; + + amba { + #address-cells = <1>; + #size-cells = <1>; + compatible = "arm,amba-bus"; + ranges; + + timer0: timer@f8002000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0xf8002000 0x1000>; + /* timer00 & timer01 */ + interrupts = <0 24 4>; + clocks = <&osc24m>; + status = "disabled"; + }; + + timer1: timer@f8a29000 { + /* + * Only used in NORMAL state, not available ins + * SLOW or DOZE state. + * The rate is fixed in 24MHz. + */ + compatible = "arm,sp804", "arm,primecell"; + reg = <0xf8a29000 0x1000>; + /* timer10 & timer11 */ + interrupts = <0 25 4>; + clocks = <&osc24m>; + status = "disabled"; + }; + + timer2: timer@f8a2a000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0xf8a2a000 0x1000>; + /* timer20 & timer21 */ + interrupts = <0 26 4>; + clocks = <&osc24m>; + status = "disabled"; + }; + + timer3: timer@f8a2b000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0xf8a2b000 0x1000>; + /* timer30 & timer31 */ + interrupts = <0 27 4>; + clocks = <&osc24m>; + status = "disabled"; + }; + + timer4: timer@f8a81000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0xf8a81000 0x1000>; + /* timer30 & timer31 */ + interrupts = <0 28 4>; + clocks = <&osc24m>; + status = "disabled"; + }; + + uart0: uart@f8b00000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0xf8b00000 0x1000>; + interrupts = <0 49 4>; + clocks = <&bpll_fout0_div 1>; + clock-names = "apb_pclk"; + status = "disabled"; + }; + + uart1: uart@f8006000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0xf8006000 0x1000>; + interrupts = <0 50 4>; + clocks = <&bpll_fout0_div 1>; + clock-names = "apb_pclk"; + status = "disabled"; + }; + + uart2: uart@f8b02000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0xf8b02000 0x1000>; + interrupts = <0 51 4>; + clocks = <&bpll_fout0_div 1>; + clock-names = "apb_pclk"; + status = "disabled"; + }; + + uart3: uart@f8b03000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0xf8b03000 0x1000>; + interrupts = <0 52 4>; + clocks = <&bpll_fout0_div 1>; + clock-names = "apb_pclk"; + status = "disabled"; + }; + + uart4: uart@f8b04000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0xf8b04000 0x1000>; + interrupts = <0 53 4>; + clocks = <&bpll_fout0_div 1>; + clock-names = "apb_pclk"; + status = "disabled"; + }; + }; + + clocks { + #address-cells = <1>; + #size-cells = <0>; + + osc24m: osc { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "osc24mhz"; + }; + + bpll: bpll { + compatible = "hisilicon,hi3716-fixed-pll"; + #clock-cells = <1>; + clocks = <&osc24m>; + clock-frequency = <1200000000>, + <600000000>, + <300000000>, + <200000000>, + <150000000>; + clock-output-names = "bpll_fout0", + "bpll_fout1", + "bpll_fout2", + "bpll_fout3", + "bpll_fout4"; + }; + + bpll_fout0_div: bpll_fout0_div {/* 1200Mhz */ + compatible = "hisilicon,hi3716-fixed-divider"; + #clock-cells = <1>; + clocks = <&bpll 0>; + div-table = <3 14 25 50>; + clock-output-names = "fout0_400m", "fout0_86m", + "fout0_48m", "fout0_24m"; + }; + + bpll_fout1_div: bpll_fout1_div { + compatible = "hisilicon,hi3716-fixed-divider"; + #clock-cells = <0>; + clocks = <&bpll 1>; + div-table = <10>; + clock-output-names = "fout1_60m"; + }; + + bpll_fout2_div: bpll_fout2_div { + compatible = "hisilicon,hi3716-fixed-divider"; + #clock-cells = <0>; + clocks = <&bpll 2>; + div-table = <4>; + clock-output-names = "fout2_75m"; + }; + + bpll_fout3_div: bpll_fout3_div { + compatible = "hisilicon,hi3716-fixed-divider"; + #clock-cells = <1>; + clocks = <&bpll 3>; + div-table = <2 4 5 8>; + clock-output-names = "fout3_100m", "fout3_50m", + "fout3_40m", "fout3_25m"; + }; + + clk_sfc_mux: clk_sfc_mux { + compatible = "hisilicon,hi3716-clk-mux"; + #clock-cells = <0>; + /* clks: 24M 75M 100M 150M 200M */ + clocks = <&osc24m>, <&bpll_fout2_div>, + <&bpll_fout3_div 0>, <&bpll 4>, <&bpll 3>; + + /* offset mux_shift mux_width */ + mux-reg = <0x5c 8 3>; + /* mux reg value to choose clks */ + mux-table = <0 7 6 4 5>; + + clock-output-names = "sfc_mux"; + }; + + clk_sfc: clk_sfc { + compatible = "hisilicon,hi3716-clk-gate"; + #clock-cells = <0>; + clocks = <&clk_sfc_mux>; + + /* offset, enable, reset */ + gate-reg = <0x5c 0 4>; + + clock-output-names = "sfc"; + }; + }; + + local_timer@f8a00600 { + compatible = "arm,cortex-a9-twd-timer"; + reg = <0xf8a00600 0x20>; + interrupts = <1 13 0xf01>; + }; + + l2: l2-cache { + compatible = "arm,pl310-cache"; + reg = <0xf8a10000 0x100000>; + interrupts = <0 15 4>; + cache-unified; + cache-level = <2>; + hisilicon,l2cache-aux = <0x00050000 0xfff0ffff>; + }; + + sctrl@f8000000 { + compatible = "hisilicon,sctrl"; + reg = <0xf8000000 0x1000>; + smp_reg = <0xc0>; + reboot_reg = <0x4>; + }; + + clkbase@f8a22000 { + compatible = "hisilicon,clkbase"; + reg = <0xf8a22000 0x1000>; + }; + + cpuctrl@f8a22000 { + compatible = "hisilicon,cpuctrl"; + reg = <0xf8a22000 0x2000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/hip04-d01.dts b/arch/arm/boot/dts/hip04-d01.dts new file mode 100644 index 00000000000000..a10dcf3f8d66ae --- /dev/null +++ b/arch/arm/boot/dts/hip04-d01.dts @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013-2014 Linaro Ltd. + * Author: Haojian Zhuang + * + * 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 + * publishhed by the Free Software Foundation. + */ + +/dts-v1/; + +#include "hip04.dtsi" + +/ { + /* memory bus is 64-bit */ + #address-cells = <2>; + #size-cells = <1>; + model = "Hisilicon D01 Development Board"; + compatible = "hisilicon,hip04-d01"; + + memory@0 { + device_type = "memory"; + /* + * Bootloader loads kernel image into 0x1000_0000 region, + * so disables the region between [0000_0000 - 1000_0000] + * temporarily. + * Because the PHYS_TO_VIRT_OFFSET is calculated based on + * the original region that kenrel is loaded. + * This workaround will be removed only after UEFI updated. + */ + reg = <0x00000000 0x10000000 0xc0000000>; + }; + + memory@00000004c0000000 { + device_type = "memory"; + reg = <0x00000004 0xc0000000 0x40000000>; + }; + + memory@0000000500000000 { + device_type = "memory"; + reg = <0x00000005 0x00000000 0x80000000>; + }; + + memory@0000000580000000 { + device_type = "memory"; + reg = <0x00000005 0x80000000 0x80000000>; + }; + + memory@0000000600000000 { + device_type = "memory"; + reg = <0x00000006 0x00000000 0x80000000>; + }; + + memory@0000000680000000 { + device_type = "memory"; + reg = <0x00000006 0x80000000 0x80000000>; + }; + + memory@0000000700000000 { + device_type = "memory"; + reg = <0x00000007 0x00000000 0x80000000>; + }; + + memory@0000000780000000 { + device_type = "memory"; + reg = <0x00000007 0x80000000 0x80000000>; + }; + + soc { + uart0: uart@4007000 { + status = "ok"; + }; + }; +}; diff --git a/arch/arm/boot/dts/hip04.dtsi b/arch/arm/boot/dts/hip04.dtsi new file mode 100644 index 00000000000000..834dde46df41ab --- /dev/null +++ b/arch/arm/boot/dts/hip04.dtsi @@ -0,0 +1,185 @@ +/* + * Hisilicon Ltd. HiP01 SoC + * + * Copyright (C) 2013-2014 Hisilicon Ltd. + * Copyright (C) 2013-2014 Linaro Ltd. + * + * Author: Haojian Zhuang + * + * 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 + * publishhed by the Free Software Foundation. + */ + +#include + +/ { + /* memory bus is 64-bit */ + #address-cells = <2>; + #size-cells = <1>; + + aliases { + serial0 = &uart0; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu-map { + cluster0 { + core0 { + cpu = <&CPU0>; + }; + core1 { + cpu = <&CPU1>; + }; + core2 { + cpu = <&CPU2>; + }; + core3 { + cpu = <&CPU3>; + }; + }; + }; + CPU0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + }; + CPU1: cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + }; + CPU2: cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <2>; + }; + CPU3: cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <3>; + }; + }; + + soc { + /* It's a 32-bit SoC. */ + #address-cells = <1>; + #size-cells = <1>; + compatible = "arm,amba-bus", "simple-bus"; + device_type = "soc"; + interrupt-parent = <&gic>; + ranges = <0 0 0xe0000000 0x10000000>; + + gic: interrupt-controller@c01000 { + compatible = "hisilicon,hip04-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + + /* gic dist base, gic cpu base */ + reg = <0xc01000 0x1000>, <0xc02000 0x1000>; + }; + + mcpm: mcpm { + compatible = "hisilicon,hip04-mcpm"; + reg = <0x100 0x1000>, <0x3e00000 0x00100000>, + <0x302a000 0x1000>; + }; + + clock: clock { + compatible = "hisilicon,hip04-clock"; + /* FIXME: the base of clock controller */ + reg = <0 0x1000>; + #clock-cells = <1>; + }; + + dual_timer0: dual_timer@3000000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x3000000 0x1000>; + interrupts = <0 224 4>; + clocks = <&clock HIP04_CLK_50M>; + clock-names = "apb_pclk"; + status = "ok"; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + uart0: uart@4007000 { + compatible = "snps,dw-apb-uart"; + reg = <0x4007000 0x1000>; + interrupts = <0 381 4>; + clocks = <&clock HIP04_CLK_168M>; + clock-names = "uartclk"; + reg-shift = <2>; + status = "disabled"; + }; + + sata@a000000 { + compatible = "hisilicon,hisi-ahci"; + reg = <0xa000000 0x1000000>; + interrupts = <0 372 4>; + }; + + mdio { + compatible = "hisilicon,hip04-mdio"; + reg = <0x28f1000 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + + phy0: ethernet-phy@0 { + reg = <0>; + marvell,reg-init = <18 0x14 0 0x8001>; + device_type = "ethernet-phy"; + }; + + phy1: ethernet-phy@1 { + reg = <1>; + marvell,reg-init = <18 0x14 0 0x8001>; + device_type = "ethernet-phy"; + }; + }; + + ppebase: ppebase@28c0000 { + compatible = "hisilicon,hip04-ppebase"; + reg = <0x28c0000 0x10000>; + }; + + fe: ethernet@28b0000 { + compatible = "hisilicon,hip04-mac"; + reg = <0x28b0000 0x10000>; + interrupts = <0 413 4>; + port = <31>; + speed = <100>; + id = <0>; + }; + + ge0: ethernet@2800000 { + compatible = "hisilicon,hip04-mac"; + reg = <0x2800000 0x10000>; + interrupts = <0 402 4>; + port = <0>; + speed = <1000>; + id = <1>; + phy-handle = <&phy0>; + }; + + ge8: ethernet@2880000 { + compatible = "hisilicon,hip04-mac"; + reg = <0x2880000 0x10000>; + interrupts = <0 410 4>; + port = <8>; + speed = <1000>; + id = <2>; + phy-handle = <&phy1>; + }; + }; +}; diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts new file mode 100644 index 00000000000000..c9eee916aa7ead --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts @@ -0,0 +1,159 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x1CT + * + * RTSM_VE_Cortex_A15x1.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA15x1"; + arm,vexpress,site = <0xf>; + compatible = "arm,rtsm_ve,cortex_a15x1", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts new file mode 100644 index 00000000000000..853a166e3c32b6 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts @@ -0,0 +1,165 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x2CT + * + * RTSM_VE_Cortex_A15x2.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA15x2"; + arm,vexpress,site = <0xf>; + compatible = "arm,rtsm_ve,cortex_a15x2", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts new file mode 100644 index 00000000000000..c1947a3a5c8895 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts @@ -0,0 +1,177 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x4CT + * + * RTSM_VE_Cortex_A15x4.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA15x4"; + arm,vexpress,site = <0xf>; + compatible = "arm,rtsm_ve,cortex_a15x4", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + }; + + cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <2>; + }; + + cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <3>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts new file mode 100644 index 00000000000000..fca6b2f79677f6 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts @@ -0,0 +1,171 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA9MPx2CT + * + * RTSM_VE_Cortex_A9x2.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA9x2"; + arm,vexpress,site = <0xf>; + compatible = "arm,rtsm_ve,cortex_a9x2", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <1>; + #size-cells = <1>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <0>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <1>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x80000000 0x80000000>; + }; + + scu@2c000000 { + compatible = "arm,cortex-a9-scu"; + reg = <0x2c000000 0x58>; + }; + + timer@2c000600 { + compatible = "arm,cortex-a9-twd-timer"; + reg = <0x2c000600 0x20>; + interrupts = <1 13 0xf04>; + }; + + watchdog@2c000620 { + compatible = "arm,cortex-a9-twd-wdt"; + reg = <0x2c000620 0x20>; + interrupts = <1 14 0xf04>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x2c001000 0x1000>, + <0x2c000100 0x100>; + }; + + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0x08000000 0x04000000>, + <1 0 0x14000000 0x04000000>, + <2 0 0x18000000 0x04000000>, + <3 0 0x1c000000 0x04000000>, + <4 0 0x0c000000 0x04000000>, + <5 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts new file mode 100644 index 00000000000000..fd8a6ed97a0427 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts @@ -0,0 +1,183 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA9MPx4CT + * + * RTSM_VE_Cortex_A9x4.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA9x4"; + arm,vexpress,site = <0xf>; + compatible = "arm,rtsm_ve,cortex_a9x4", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <1>; + #size-cells = <1>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <0>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <1>; + }; + + cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <2>; + }; + + cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <3>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x80000000 0x80000000>; + }; + + scu@2c000000 { + compatible = "arm,cortex-a9-scu"; + reg = <0x2c000000 0x58>; + }; + + timer@2c000600 { + compatible = "arm,cortex-a9-twd-timer"; + reg = <0x2c000600 0x20>; + interrupts = <1 13 0xf04>; + }; + + watchdog@2c000620 { + compatible = "arm,cortex-a9-twd-wdt"; + reg = <0x2c000620 0x20>; + interrupts = <1 14 0xf04>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x2c001000 0x1000>, + <0x2c000100 0x100>; + }; + + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0x08000000 0x04000000>, + <1 0 0x14000000 0x04000000>, + <2 0 0x18000000 0x04000000>, + <3 0 0x1c000000 0x04000000>, + <4 0 0x0c000000 0x04000000>, + <5 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi b/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi new file mode 100644 index 00000000000000..a2d895ee5faabb --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi @@ -0,0 +1,231 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * Motherboard component + * + * VEMotherBoard.lisa + */ + + motherboard { + compatible = "arm,vexpress,v2m-p1", "simple-bus"; + arm,hbi = <0x190>; + arm,vexpress,site = <0>; + arm,v2m-memory-map = "rs1"; + #address-cells = <2>; /* SMB chipselect number and offset */ + #size-cells = <1>; + #interrupt-cells = <1>; + ranges; + + flash@0,00000000 { + compatible = "arm,vexpress-flash", "cfi-flash"; + reg = <0 0x00000000 0x04000000>, + <4 0x00000000 0x04000000>; + bank-width = <4>; + }; + + vram@2,00000000 { + compatible = "arm,vexpress-vram"; + reg = <2 0x00000000 0x00800000>; + }; + + ethernet@2,02000000 { + compatible = "smsc,lan91c111"; + reg = <2 0x02000000 0x10000>; + interrupts = <15>; + }; + + iofpga@3,00000000 { + compatible = "arm,amba-bus", "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 3 0 0x200000>; + + v2m_sysreg: sysreg@010000 { + compatible = "arm,vexpress-sysreg"; + reg = <0x010000 0x1000>; + gpio-controller; + #gpio-cells = <2>; + }; + + v2m_sysctl: sysctl@020000 { + compatible = "arm,sp810", "arm,primecell"; + reg = <0x020000 0x1000>; + clocks = <&v2m_refclk32khz>, <&v2m_refclk1mhz>, <&smbclk>; + clock-names = "refclk", "timclk", "apb_pclk"; + #clock-cells = <1>; + clock-output-names = "timerclken0", "timerclken1", "timerclken2", "timerclken3"; + }; + + aaci@040000 { + compatible = "arm,pl041", "arm,primecell"; + reg = <0x040000 0x1000>; + interrupts = <11>; + clocks = <&smbclk>; + clock-names = "apb_pclk"; + }; + + mmci@050000 { + compatible = "arm,pl180", "arm,primecell"; + reg = <0x050000 0x1000>; + interrupts = <9 10>; + cd-gpios = <&v2m_sysreg 0 0>; + wp-gpios = <&v2m_sysreg 1 0>; + max-frequency = <12000000>; + vmmc-supply = <&v2m_fixed_3v3>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "mclk", "apb_pclk"; + }; + + kmi@060000 { + compatible = "arm,pl050", "arm,primecell"; + reg = <0x060000 0x1000>; + interrupts = <12>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "KMIREFCLK", "apb_pclk"; + }; + + kmi@070000 { + compatible = "arm,pl050", "arm,primecell"; + reg = <0x070000 0x1000>; + interrupts = <13>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "KMIREFCLK", "apb_pclk"; + }; + + v2m_serial0: uart@090000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x090000 0x1000>; + interrupts = <5>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "uartclk", "apb_pclk"; + }; + + v2m_serial1: uart@0a0000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0a0000 0x1000>; + interrupts = <6>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "uartclk", "apb_pclk"; + }; + + v2m_serial2: uart@0b0000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0b0000 0x1000>; + interrupts = <7>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "uartclk", "apb_pclk"; + }; + + v2m_serial3: uart@0c0000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0c0000 0x1000>; + interrupts = <8>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "uartclk", "apb_pclk"; + }; + + wdt@0f0000 { + compatible = "arm,sp805", "arm,primecell"; + reg = <0x0f0000 0x1000>; + interrupts = <0>; + clocks = <&v2m_refclk32khz>, <&smbclk>; + clock-names = "wdogclk", "apb_pclk"; + }; + + v2m_timer01: timer@110000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x110000 0x1000>; + interrupts = <2>; + clocks = <&v2m_sysctl 0>, <&v2m_sysctl 1>, <&smbclk>; + clock-names = "timclken1", "timclken2", "apb_pclk"; + }; + + v2m_timer23: timer@120000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x120000 0x1000>; + interrupts = <3>; + clocks = <&v2m_sysctl 2>, <&v2m_sysctl 3>, <&smbclk>; + clock-names = "timclken1", "timclken2", "apb_pclk"; + }; + + rtc@170000 { + compatible = "arm,pl031", "arm,primecell"; + reg = <0x170000 0x1000>; + interrupts = <4>; + clocks = <&smbclk>; + clock-names = "apb_pclk"; + }; + + clcd@1f0000 { + compatible = "arm,pl111", "arm,primecell"; + reg = <0x1f0000 0x1000>; + interrupts = <14>; + clocks = <&v2m_oscclk1>, <&smbclk>; + clock-names = "v2m:oscclk1", "apb_pclk"; + mode = "VGA"; + use_dma = <0>; + framebuffer = <0x18000000 0x00180000>; + }; + + virtio_block@0130000 { + compatible = "virtio,mmio"; + reg = <0x130000 0x200>; + interrupts = <42>; + }; + + }; + + v2m_fixed_3v3: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "3V3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + v2m_clk24mhz: clk24mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "v2m:clk24mhz"; + }; + + v2m_refclk1mhz: refclk1mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <1000000>; + clock-output-names = "v2m:refclk1mhz"; + }; + + v2m_refclk32khz: refclk32khz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <32768>; + clock-output-names = "v2m:refclk32khz"; + }; + + mcc { + compatible = "simple-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + v2m_oscclk1: osc@1 { + /* CLCD clock */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <23750000 63500000>; + #clock-cells = <0>; + clock-output-names = "v2m:oscclk1"; + }; + + muxfpga@0 { + compatible = "arm,vexpress-muxfpga"; + arm,vexpress-sysreg,func = <7 0>; + }; + + shutdown@0 { + compatible = "arm,vexpress-shutdown"; + arm,vexpress-sysreg,func = <8 0>; + }; + }; + }; diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts new file mode 100644 index 00000000000000..c59f4b5cdf9eb3 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts @@ -0,0 +1,233 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x4CT + * ARMCortexA7x4CT + * RTSM_VE_Cortex_A15x1_A7x1.lisa + */ + +/dts-v1/; + +/memreserve/ 0xff000000 0x01000000; + +/ { + model = "RTSM_VE_CortexA15x1-A7x1"; + arm,vexpress,site = <0xf>; + compatible = "arm,rtsm_ve,cortex_a15x1_a7x1", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + clusters { + #address-cells = <1>; + #size-cells = <0>; + + cluster0: cluster@0 { + reg = <0>; +// freqs = <500000000 600000000 700000000 800000000 900000000 1000000000 1100000000 1200000000>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core0: core@0 { + reg = <0>; + }; + + }; + }; + + cluster1: cluster@1 { + reg = <1>; +// freqs = <350000000 400000000 500000000 600000000 700000000 800000000 900000000 1000000000>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core1: core@0 { + reg = <0>; + }; + + }; + }; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + cluster = <&cluster0>; + core = <&core0>; +// clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; + }; + + cpu1: cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x100>; + cluster = <&cluster1>; + core = <&core1>; +// clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + cci@2c090000 { + compatible = "arm,cci-400", "arm,cci"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0 0x2c090000 0 0x1000>; + ranges = <0x0 0x0 0x2c090000 0x10000>; + + cci_control1: slave-if@4000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x4000 0x1000>; + }; + + cci_control2: slave-if@5000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x5000 0x1000>; + }; + }; + + dcscb@60000000 { + compatible = "arm,rtsm,dcscb"; + reg = <0 0x60000000 0 0x1000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts new file mode 100644 index 00000000000000..5f1e727be28c60 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts @@ -0,0 +1,317 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x4CT + * ARMCortexA7x4CT + * RTSM_VE_Cortex_A15x4_A7x4.lisa + */ + +/dts-v1/; + +/memreserve/ 0xff000000 0x01000000; + +/ { + model = "RTSM_VE_CortexA15x4-A7x4"; + arm,vexpress,site = <0xf>; + compatible = "arm,rtsm_ve,cortex_a15x4_a7x4", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + clusters { + #address-cells = <1>; + #size-cells = <0>; + + cluster0: cluster@0 { + reg = <0>; +// freqs = <500000000 600000000 700000000 800000000 900000000 1000000000 1100000000 1200000000>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core0: core@0 { + reg = <0>; + }; + + core1: core@1 { + reg = <1>; + }; + + core2: core@2 { + reg = <2>; + }; + + core3: core@3 { + reg = <3>; + }; + + }; + }; + + cluster1: cluster@1 { + reg = <1>; +// freqs = <350000000 400000000 500000000 600000000 700000000 800000000 900000000 1000000000>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core4: core@0 { + reg = <0>; + }; + + core5: core@1 { + reg = <1>; + }; + + core6: core@2 { + reg = <2>; + }; + + core7: core@3 { + reg = <3>; + }; + + }; + }; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + cluster = <&cluster0>; + core = <&core0>; +// clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; + }; + + cpu1: cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + cluster = <&cluster0>; + core = <&core1>; +// clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; + }; + + cpu2: cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <2>; + cluster = <&cluster0>; + core = <&core2>; +// clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; + }; + + cpu3: cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <3>; + cluster = <&cluster0>; + core = <&core3>; +// clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; + }; + + cpu4: cpu@4 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x100>; + cluster = <&cluster1>; + core = <&core4>; +// clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; + }; + + cpu5: cpu@5 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x101>; + cluster = <&cluster1>; + core = <&core5>; +// clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; + }; + + cpu6: cpu@6 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x102>; + cluster = <&cluster1>; + core = <&core6>; +// clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; + }; + + cpu7: cpu@7 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x103>; + cluster = <&cluster1>; + core = <&core7>; +// clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + cci@2c090000 { + compatible = "arm,cci-400", "arm,cci"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0 0x2c090000 0 0x1000>; + ranges = <0x0 0x0 0x2c090000 0x10000>; + + cci_control1: slave-if@4000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x4000 0x1000>; + }; + + cci_control2: slave-if@5000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x5000 0x1000>; + }; + }; + + dcscb@60000000 { + compatible = "arm,rtsm,dcscb"; + reg = <0 0x60000000 0 0x1000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi index ac870fb3fa0d45..9584232ee6b6c6 100644 --- a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi @@ -228,6 +228,7 @@ }; clcd@1f0000 { + status = "disabled"; compatible = "arm,pl111", "arm,primecell"; reg = <0x1f0000 0x1000>; interrupts = <14>; diff --git a/arch/arm/boot/dts/vexpress-v2m.dtsi b/arch/arm/boot/dts/vexpress-v2m.dtsi index f1420368355bb2..6593398c11ae3b 100644 --- a/arch/arm/boot/dts/vexpress-v2m.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m.dtsi @@ -227,6 +227,7 @@ }; clcd@1f000 { + status = "disabled"; compatible = "arm,pl111", "arm,primecell"; reg = <0x1f000 0x1000>; interrupts = <14>; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts index 9420053acc1463..7cbe38cecf6c21 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts @@ -9,6 +9,8 @@ /dts-v1/; +/memreserve/ 0xbf000000 0x01000000; + / { model = "V2P-CA15"; arm,hbi = <0x237>; @@ -57,6 +59,8 @@ interrupts = <0 85 4>; clocks = <&oscclk5>; clock-names = "pxlclk"; + mode = "1024x768-16@60"; + framebuffer = <0 0xbf000000 0 0x01000000>; }; memory-controller@2b0a0000 { diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 15f98cbcb75a3b..e56356a249cd4b 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -9,6 +9,8 @@ /dts-v1/; +/memreserve/ 0xff000000 0x01000000; + / { model = "V2P-CA15_CA7"; arm,hbi = <0x249>; @@ -29,29 +31,60 @@ i2c1 = &v2m_i2c_pcie; }; - cpus { + clusters { #address-cells = <1>; #size-cells = <0>; - cpu0: cpu@0 { - device_type = "cpu"; - compatible = "arm,cortex-a15"; + cluster0: cluster@0 { reg = <0>; - cci-control-port = <&cci_control1>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core0: core@0 { + reg = <0>; + }; + + core1: core@1 { + reg = <1>; + }; + + }; }; - cpu1: cpu@1 { - device_type = "cpu"; - compatible = "arm,cortex-a15"; + cluster1: cluster@1 { reg = <1>; - cci-control-port = <&cci_control1>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core2: core@0 { + reg = <0>; + }; + + core3: core@1 { + reg = <1>; + }; + + core4: core@2 { + reg = <2>; + }; + }; }; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; cpu2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a7"; reg = <0x100>; cci-control-port = <&cci_control2>; + cluster = <&cluster1>; + core = <&core2>; + clock-frequency = <800000000>; }; cpu3: cpu@3 { @@ -59,6 +92,9 @@ compatible = "arm,cortex-a7"; reg = <0x101>; cci-control-port = <&cci_control2>; + cluster = <&cluster1>; + core = <&core3>; + clock-frequency = <800000000>; }; cpu4: cpu@4 { @@ -66,12 +102,35 @@ compatible = "arm,cortex-a7"; reg = <0x102>; cci-control-port = <&cci_control2>; + cluster = <&cluster1>; + core = <&core4>; + clock-frequency = <800000000>; + }; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + cci-control-port = <&cci_control1>; + cluster = <&cluster0>; + core = <&core0>; + clock-frequency = <1000000000>; + }; + + cpu1: cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + cci-control-port = <&cci_control1>; + cluster = <&cluster0>; + core = <&core1>; + clock-frequency = <1000000000>; }; }; memory@80000000 { device_type = "memory"; - reg = <0 0x80000000 0 0x40000000>; + reg = <0 0x80000000 0 0x80000000>; }; wdt@2a490000 { @@ -86,6 +145,8 @@ compatible = "arm,hdlcd"; reg = <0 0x2b000000 0 0x1000>; interrupts = <0 85 4>; + mode = "1024x768-16@60"; + framebuffer = <0 0xff000000 0 0x01000000>; clocks = <&oscclk5>; clock-names = "pxlclk"; }; @@ -127,6 +188,16 @@ interface-type = "ace"; reg = <0x5000 0x1000>; }; + + pmu@9000 { + compatible = "arm,cci-400-pmu"; + reg = <0x9000 0x5000>; + interrupts = <0 101 4>, + <0 102 4>, + <0 103 4>, + <0 104 4>, + <0 105 4>; + }; }; memory-controller@7ffd0000 { @@ -164,12 +235,21 @@ <1 10 0xf08>; }; - pmu { + pmu_a15 { compatible = "arm,cortex-a15-pmu"; + cluster = <&cluster0>; interrupts = <0 68 4>, <0 69 4>; }; + pmu_a7 { + compatible = "arm,cortex-a7-pmu"; + cluster = <&cluster1>; + interrupts = <0 128 4>, + <0 129 4>, + <0 130 4>; + }; + oscclk6a: oscclk6a { /* Reference 24MHz clock */ compatible = "fixed-clock"; @@ -178,6 +258,19 @@ clock-output-names = "oscclk6a"; }; +/* PSCI requires support from firmware and is not present in the normal TC2 + * distribution, so this node is commented out by default... + + psci { + compatible = "arm,psci"; + method = "smc"; + cpu_suspend = <0x80100001>; + cpu_off = <0x80100002>; + cpu_on = <0x80100003>; + migrate = <0x80100004>; + }; +*/ + dcc { compatible = "arm,vexpress,config-bus"; arm,vexpress,config-bridge = <&v2m_sysreg>; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts index c544a55045918e..cf633ed6a1b485 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts @@ -9,6 +9,8 @@ /dts-v1/; +/memreserve/ 0xbf000000 0x01000000; + / { model = "V2P-CA5s"; arm,hbi = <0x225>; @@ -59,6 +61,8 @@ interrupts = <0 85 4>; clocks = <&oscclk3>; clock-names = "pxlclk"; + mode = "640x480-16@60"; + framebuffer = <0xbf000000 0x01000000>; }; memory-controller@2a150000 { diff --git a/arch/arm/boot/dts/vexpress-v2p-ca9.dts b/arch/arm/boot/dts/vexpress-v2p-ca9.dts index 62d9b225dcceec..f83706bd3f9a87 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca9.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca9.dts @@ -9,6 +9,8 @@ /dts-v1/; +/include/ "clcd-panels.dtsi" + / { model = "V2P-CA9"; arm,hbi = <0x191>; @@ -73,6 +75,8 @@ interrupts = <0 44 4>; clocks = <&oscclk1>, <&oscclk2>; clock-names = "clcdclk", "apb_pclk"; + mode = "XVGA"; + use_dma = <1>; }; memory-controller@100e0000 { diff --git a/arch/arm/common/Kconfig b/arch/arm/common/Kconfig index c3a4e9ceba34e3..4fb337ca0cb7d6 100644 --- a/arch/arm/common/Kconfig +++ b/arch/arm/common/Kconfig @@ -20,3 +20,53 @@ config SHARP_SCOOP config TI_PRIV_EDMA bool + +config FIQ_GLUE + bool + select FIQ + +config FIQ_DEBUGGER + bool "FIQ Mode Serial Debugger" + select FIQ + select FIQ_GLUE + default n + help + The FIQ serial debugger can accept commands even when the + kernel is unresponsive due to being stuck with interrupts + disabled. + + +config FIQ_DEBUGGER_NO_SLEEP + bool "Keep serial debugger active" + depends on FIQ_DEBUGGER + default n + help + Enables the serial debugger at boot. Passing + fiq_debugger.no_sleep on the kernel commandline will + override this config option. + +config FIQ_DEBUGGER_WAKEUP_IRQ_ALWAYS_ON + bool "Don't disable wakeup IRQ when debugger is active" + depends on FIQ_DEBUGGER + default n + help + Don't disable the wakeup irq when enabling the uart clock. This will + cause extra interrupts, but it makes the serial debugger usable with + on some MSM radio builds that ignore the uart clock request in power + collapse. + +config FIQ_DEBUGGER_CONSOLE + bool "Console on FIQ Serial Debugger port" + depends on FIQ_DEBUGGER + default n + help + Enables a console so that printk messages are displayed on + the debugger serial port as the occur. + +config FIQ_DEBUGGER_CONSOLE_DEFAULT_ENABLE + bool "Put the FIQ debugger into console mode by default" + depends on FIQ_DEBUGGER_CONSOLE + default n + help + If enabled, this puts the fiq debugger into console mode by default. + Otherwise, the fiq debugger will start out in debug mode. diff --git a/arch/arm/common/Makefile b/arch/arm/common/Makefile index 4bdc41622c3668..14e592312033e9 100644 --- a/arch/arm/common/Makefile +++ b/arch/arm/common/Makefile @@ -4,6 +4,8 @@ obj-y += firmware.o +obj-$(CONFIG_FIQ_DEBUGGER) += fiq_debugger.o +obj-$(CONFIG_FIQ_GLUE) += fiq_glue.o fiq_glue_setup.o obj-$(CONFIG_ICST) += icst.o obj-$(CONFIG_SA1111) += sa1111.o obj-$(CONFIG_DMABOUNCE) += dmabounce.o @@ -13,6 +15,7 @@ obj-$(CONFIG_SHARP_SCOOP) += scoop.o obj-$(CONFIG_PCI_HOST_ITE8152) += it8152.o obj-$(CONFIG_ARM_TIMER_SP804) += timer-sp.o obj-$(CONFIG_MCPM) += mcpm_head.o mcpm_entry.o mcpm_platsmp.o vlock.o +CFLAGS_REMOVE_mcpm_entry.o = -pg AFLAGS_mcpm_head.o := -march=armv7-a AFLAGS_vlock.o := -march=armv7-a obj-$(CONFIG_TI_PRIV_EDMA) += edma.o diff --git a/arch/arm/common/fiq_debugger.c b/arch/arm/common/fiq_debugger.c new file mode 100644 index 00000000000000..65b943c7630044 --- /dev/null +++ b/arch/arm/common/fiq_debugger.c @@ -0,0 +1,1376 @@ +/* + * arch/arm/common/fiq_debugger.c + * + * Serial Debugger Interface accessed through an FIQ interrupt. + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "fiq_debugger_ringbuf.h" + +#define DEBUG_MAX 64 +#define MAX_UNHANDLED_FIQ_COUNT 1000000 + +#define MAX_FIQ_DEBUGGER_PORTS 4 + +#define THREAD_INFO(sp) ((struct thread_info *) \ + ((unsigned long)(sp) & ~(THREAD_SIZE - 1))) + +struct fiq_debugger_state { + struct fiq_glue_handler handler; + + int fiq; + int uart_irq; + int signal_irq; + int wakeup_irq; + bool wakeup_irq_no_set_wake; + struct clk *clk; + struct fiq_debugger_pdata *pdata; + struct platform_device *pdev; + + char debug_cmd[DEBUG_MAX]; + int debug_busy; + int debug_abort; + + char debug_buf[DEBUG_MAX]; + int debug_count; + + bool no_sleep; + bool debug_enable; + bool ignore_next_wakeup_irq; + struct timer_list sleep_timer; + spinlock_t sleep_timer_lock; + bool uart_enabled; + struct wake_lock debugger_wake_lock; + bool console_enable; + int current_cpu; + atomic_t unhandled_fiq_count; + bool in_fiq; + + struct work_struct work; + spinlock_t work_lock; + char work_cmd[DEBUG_MAX]; + +#ifdef CONFIG_FIQ_DEBUGGER_CONSOLE + spinlock_t console_lock; + struct console console; + struct tty_port tty_port; + struct fiq_debugger_ringbuf *tty_rbuf; + bool syslog_dumping; +#endif + + unsigned int last_irqs[NR_IRQS]; + unsigned int last_local_timer_irqs[NR_CPUS]; +}; + +#ifdef CONFIG_FIQ_DEBUGGER_CONSOLE +struct tty_driver *fiq_tty_driver; +#endif + +#ifdef CONFIG_FIQ_DEBUGGER_NO_SLEEP +static bool initial_no_sleep = true; +#else +static bool initial_no_sleep; +#endif + +#ifdef CONFIG_FIQ_DEBUGGER_CONSOLE_DEFAULT_ENABLE +static bool initial_debug_enable = true; +static bool initial_console_enable = true; +#else +static bool initial_debug_enable; +static bool initial_console_enable; +#endif + +static bool fiq_kgdb_enable; + +module_param_named(no_sleep, initial_no_sleep, bool, 0644); +module_param_named(debug_enable, initial_debug_enable, bool, 0644); +module_param_named(console_enable, initial_console_enable, bool, 0644); +module_param_named(kgdb_enable, fiq_kgdb_enable, bool, 0644); + +#ifdef CONFIG_FIQ_DEBUGGER_WAKEUP_IRQ_ALWAYS_ON +static inline void enable_wakeup_irq(struct fiq_debugger_state *state) {} +static inline void disable_wakeup_irq(struct fiq_debugger_state *state) {} +#else +static inline void enable_wakeup_irq(struct fiq_debugger_state *state) +{ + if (state->wakeup_irq < 0) + return; + enable_irq(state->wakeup_irq); + if (!state->wakeup_irq_no_set_wake) + enable_irq_wake(state->wakeup_irq); +} +static inline void disable_wakeup_irq(struct fiq_debugger_state *state) +{ + if (state->wakeup_irq < 0) + return; + disable_irq_nosync(state->wakeup_irq); + if (!state->wakeup_irq_no_set_wake) + disable_irq_wake(state->wakeup_irq); +} +#endif + +static bool inline debug_have_fiq(struct fiq_debugger_state *state) +{ + return (state->fiq >= 0); +} + +static void debug_force_irq(struct fiq_debugger_state *state) +{ + unsigned int irq = state->signal_irq; + + if (WARN_ON(!debug_have_fiq(state))) + return; + if (state->pdata->force_irq) { + state->pdata->force_irq(state->pdev, irq); + } else { + struct irq_chip *chip = irq_get_chip(irq); + if (chip && chip->irq_retrigger) + chip->irq_retrigger(irq_get_irq_data(irq)); + } +} + +static void debug_uart_enable(struct fiq_debugger_state *state) +{ + if (state->clk) + clk_enable(state->clk); + if (state->pdata->uart_enable) + state->pdata->uart_enable(state->pdev); +} + +static void debug_uart_disable(struct fiq_debugger_state *state) +{ + if (state->pdata->uart_disable) + state->pdata->uart_disable(state->pdev); + if (state->clk) + clk_disable(state->clk); +} + +static void debug_uart_flush(struct fiq_debugger_state *state) +{ + if (state->pdata->uart_flush) + state->pdata->uart_flush(state->pdev); +} + +static void debug_putc(struct fiq_debugger_state *state, char c) +{ + state->pdata->uart_putc(state->pdev, c); +} + +static void debug_puts(struct fiq_debugger_state *state, char *s) +{ + unsigned c; + while ((c = *s++)) { + if (c == '\n') + debug_putc(state, '\r'); + debug_putc(state, c); + } +} + +static void debug_prompt(struct fiq_debugger_state *state) +{ + debug_puts(state, "debug> "); +} + +static void dump_kernel_log(struct fiq_debugger_state *state) +{ + char buf[512]; + size_t len; + struct kmsg_dumper dumper = { .active = true }; + + + kmsg_dump_rewind_nolock(&dumper); + while (kmsg_dump_get_line_nolock(&dumper, true, buf, + sizeof(buf) - 1, &len)) { + buf[len] = 0; + debug_puts(state, buf); + } +} + +static char *mode_name(unsigned cpsr) +{ + switch (cpsr & MODE_MASK) { + case USR_MODE: return "USR"; + case FIQ_MODE: return "FIQ"; + case IRQ_MODE: return "IRQ"; + case SVC_MODE: return "SVC"; + case ABT_MODE: return "ABT"; + case UND_MODE: return "UND"; + case SYSTEM_MODE: return "SYS"; + default: return "???"; + } +} + +static int debug_printf(void *cookie, const char *fmt, ...) +{ + struct fiq_debugger_state *state = cookie; + char buf[256]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + debug_puts(state, buf); + return state->debug_abort; +} + +/* Safe outside fiq context */ +static int debug_printf_nfiq(void *cookie, const char *fmt, ...) +{ + struct fiq_debugger_state *state = cookie; + char buf[256]; + va_list ap; + unsigned long irq_flags; + + va_start(ap, fmt); + vsnprintf(buf, 128, fmt, ap); + va_end(ap); + + local_irq_save(irq_flags); + debug_puts(state, buf); + debug_uart_flush(state); + local_irq_restore(irq_flags); + return state->debug_abort; +} + +static void dump_regs(struct fiq_debugger_state *state, unsigned *regs) +{ + debug_printf(state, " r0 %08x r1 %08x r2 %08x r3 %08x\n", + regs[0], regs[1], regs[2], regs[3]); + debug_printf(state, " r4 %08x r5 %08x r6 %08x r7 %08x\n", + regs[4], regs[5], regs[6], regs[7]); + debug_printf(state, " r8 %08x r9 %08x r10 %08x r11 %08x mode %s\n", + regs[8], regs[9], regs[10], regs[11], + mode_name(regs[16])); + if ((regs[16] & MODE_MASK) == USR_MODE) + debug_printf(state, " ip %08x sp %08x lr %08x pc %08x " + "cpsr %08x\n", regs[12], regs[13], regs[14], + regs[15], regs[16]); + else + debug_printf(state, " ip %08x sp %08x lr %08x pc %08x " + "cpsr %08x spsr %08x\n", regs[12], regs[13], + regs[14], regs[15], regs[16], regs[17]); +} + +struct mode_regs { + unsigned long sp_svc; + unsigned long lr_svc; + unsigned long spsr_svc; + + unsigned long sp_abt; + unsigned long lr_abt; + unsigned long spsr_abt; + + unsigned long sp_und; + unsigned long lr_und; + unsigned long spsr_und; + + unsigned long sp_irq; + unsigned long lr_irq; + unsigned long spsr_irq; + + unsigned long r8_fiq; + unsigned long r9_fiq; + unsigned long r10_fiq; + unsigned long r11_fiq; + unsigned long r12_fiq; + unsigned long sp_fiq; + unsigned long lr_fiq; + unsigned long spsr_fiq; +}; + +void __naked get_mode_regs(struct mode_regs *regs) +{ + asm volatile ( + "mrs r1, cpsr\n" + "msr cpsr_c, #0xd3 @(SVC_MODE | PSR_I_BIT | PSR_F_BIT)\n" + "stmia r0!, {r13 - r14}\n" + "mrs r2, spsr\n" + "msr cpsr_c, #0xd7 @(ABT_MODE | PSR_I_BIT | PSR_F_BIT)\n" + "stmia r0!, {r2, r13 - r14}\n" + "mrs r2, spsr\n" + "msr cpsr_c, #0xdb @(UND_MODE | PSR_I_BIT | PSR_F_BIT)\n" + "stmia r0!, {r2, r13 - r14}\n" + "mrs r2, spsr\n" + "msr cpsr_c, #0xd2 @(IRQ_MODE | PSR_I_BIT | PSR_F_BIT)\n" + "stmia r0!, {r2, r13 - r14}\n" + "mrs r2, spsr\n" + "msr cpsr_c, #0xd1 @(FIQ_MODE | PSR_I_BIT | PSR_F_BIT)\n" + "stmia r0!, {r2, r8 - r14}\n" + "mrs r2, spsr\n" + "stmia r0!, {r2}\n" + "msr cpsr_c, r1\n" + "bx lr\n"); +} + + +static void dump_allregs(struct fiq_debugger_state *state, unsigned *regs) +{ + struct mode_regs mode_regs; + dump_regs(state, regs); + get_mode_regs(&mode_regs); + debug_printf(state, " svc: sp %08x lr %08x spsr %08x\n", + mode_regs.sp_svc, mode_regs.lr_svc, mode_regs.spsr_svc); + debug_printf(state, " abt: sp %08x lr %08x spsr %08x\n", + mode_regs.sp_abt, mode_regs.lr_abt, mode_regs.spsr_abt); + debug_printf(state, " und: sp %08x lr %08x spsr %08x\n", + mode_regs.sp_und, mode_regs.lr_und, mode_regs.spsr_und); + debug_printf(state, " irq: sp %08x lr %08x spsr %08x\n", + mode_regs.sp_irq, mode_regs.lr_irq, mode_regs.spsr_irq); + debug_printf(state, " fiq: r8 %08x r9 %08x r10 %08x r11 %08x " + "r12 %08x\n", + mode_regs.r8_fiq, mode_regs.r9_fiq, mode_regs.r10_fiq, + mode_regs.r11_fiq, mode_regs.r12_fiq); + debug_printf(state, " fiq: sp %08x lr %08x spsr %08x\n", + mode_regs.sp_fiq, mode_regs.lr_fiq, mode_regs.spsr_fiq); +} + +static void dump_irqs(struct fiq_debugger_state *state) +{ + int n; + struct irq_desc *desc; + + debug_printf(state, "irqnr total since-last status name\n"); + for_each_irq_desc(n, desc) { + struct irqaction *act = desc->action; + if (!act && !kstat_irqs(n)) + continue; + debug_printf(state, "%5d: %10u %11u %8x %s\n", n, + kstat_irqs(n), + kstat_irqs(n) - state->last_irqs[n], + desc->status_use_accessors, + (act && act->name) ? act->name : "???"); + state->last_irqs[n] = kstat_irqs(n); + } +} + +struct stacktrace_state { + struct fiq_debugger_state *state; + unsigned int depth; +}; + +static int report_trace(struct stackframe *frame, void *d) +{ + struct stacktrace_state *sts = d; + + if (sts->depth) { + debug_printf(sts->state, + " pc: %p (%pF), lr %p (%pF), sp %p, fp %p\n", + frame->pc, frame->pc, frame->lr, frame->lr, + frame->sp, frame->fp); + sts->depth--; + return 0; + } + debug_printf(sts->state, " ...\n"); + + return sts->depth == 0; +} + +struct frame_tail { + struct frame_tail *fp; + unsigned long sp; + unsigned long lr; +} __attribute__((packed)); + +static struct frame_tail *user_backtrace(struct fiq_debugger_state *state, + struct frame_tail *tail) +{ + struct frame_tail buftail[2]; + + /* Also check accessibility of one struct frame_tail beyond */ + if (!access_ok(VERIFY_READ, tail, sizeof(buftail))) { + debug_printf(state, " invalid frame pointer %p\n", tail); + return NULL; + } + if (__copy_from_user_inatomic(buftail, tail, sizeof(buftail))) { + debug_printf(state, + " failed to copy frame pointer %p\n", tail); + return NULL; + } + + debug_printf(state, " %p\n", buftail[0].lr); + + /* frame pointers should strictly progress back up the stack + * (towards higher addresses) */ + if (tail >= buftail[0].fp) + return NULL; + + return buftail[0].fp-1; +} + +void dump_stacktrace(struct fiq_debugger_state *state, + struct pt_regs * const regs, unsigned int depth, void *ssp) +{ + struct frame_tail *tail; + struct thread_info *real_thread_info = THREAD_INFO(ssp); + struct stacktrace_state sts; + + sts.depth = depth; + sts.state = state; + *current_thread_info() = *real_thread_info; + + if (!current) + debug_printf(state, "current NULL\n"); + else + debug_printf(state, "pid: %d comm: %s\n", + current->pid, current->comm); + dump_regs(state, (unsigned *)regs); + + if (!user_mode(regs)) { + struct stackframe frame; + frame.fp = regs->ARM_fp; + frame.sp = regs->ARM_sp; + frame.lr = regs->ARM_lr; + frame.pc = regs->ARM_pc; + debug_printf(state, + " pc: %p (%pF), lr %p (%pF), sp %p, fp %p\n", + regs->ARM_pc, regs->ARM_pc, regs->ARM_lr, regs->ARM_lr, + regs->ARM_sp, regs->ARM_fp); + walk_stackframe(&frame, report_trace, &sts); + return; + } + + tail = ((struct frame_tail *) regs->ARM_fp) - 1; + while (depth-- && tail && !((unsigned long) tail & 3)) + tail = user_backtrace(state, tail); +} + +static void do_ps(struct fiq_debugger_state *state) +{ + struct task_struct *g; + struct task_struct *p; + unsigned task_state; + static const char stat_nam[] = "RSDTtZX"; + + debug_printf(state, "pid ppid prio task pc\n"); + read_lock(&tasklist_lock); + do_each_thread(g, p) { + task_state = p->state ? __ffs(p->state) + 1 : 0; + debug_printf(state, + "%5d %5d %4d ", p->pid, p->parent->pid, p->prio); + debug_printf(state, "%-13.13s %c", p->comm, + task_state >= sizeof(stat_nam) ? '?' : stat_nam[task_state]); + if (task_state == TASK_RUNNING) + debug_printf(state, " running\n"); + else + debug_printf(state, " %08lx\n", thread_saved_pc(p)); + } while_each_thread(g, p); + read_unlock(&tasklist_lock); +} + +#ifdef CONFIG_FIQ_DEBUGGER_CONSOLE +static void begin_syslog_dump(struct fiq_debugger_state *state) +{ + state->syslog_dumping = true; +} + +static void end_syslog_dump(struct fiq_debugger_state *state) +{ + state->syslog_dumping = false; +} +#else +extern int do_syslog(int type, char __user *bug, int count); +static void begin_syslog_dump(struct fiq_debugger_state *state) +{ + do_syslog(5 /* clear */, NULL, 0); +} + +static void end_syslog_dump(struct fiq_debugger_state *state) +{ + dump_kernel_log(state); +} +#endif + +static void do_sysrq(struct fiq_debugger_state *state, char rq) +{ + if ((rq == 'g' || rq == 'G') && !fiq_kgdb_enable) { + debug_printf(state, "sysrq-g blocked\n"); + return; + } + begin_syslog_dump(state); + handle_sysrq(rq); + end_syslog_dump(state); +} + +#ifdef CONFIG_KGDB +static void do_kgdb(struct fiq_debugger_state *state) +{ + if (!fiq_kgdb_enable) { + debug_printf(state, "kgdb through fiq debugger not enabled\n"); + return; + } + + debug_printf(state, "enabling console and triggering kgdb\n"); + state->console_enable = true; + handle_sysrq('g'); +} +#endif + +static void debug_schedule_work(struct fiq_debugger_state *state, char *cmd) +{ + unsigned long flags; + + spin_lock_irqsave(&state->work_lock, flags); + if (state->work_cmd[0] != '\0') { + debug_printf(state, "work command processor busy\n"); + spin_unlock_irqrestore(&state->work_lock, flags); + return; + } + + strlcpy(state->work_cmd, cmd, sizeof(state->work_cmd)); + spin_unlock_irqrestore(&state->work_lock, flags); + + schedule_work(&state->work); +} + +static void debug_work(struct work_struct *work) +{ + struct fiq_debugger_state *state; + char work_cmd[DEBUG_MAX]; + char *cmd; + unsigned long flags; + + state = container_of(work, struct fiq_debugger_state, work); + + spin_lock_irqsave(&state->work_lock, flags); + + strlcpy(work_cmd, state->work_cmd, sizeof(work_cmd)); + state->work_cmd[0] = '\0'; + + spin_unlock_irqrestore(&state->work_lock, flags); + + cmd = work_cmd; + if (!strncmp(cmd, "reboot", 6)) { + cmd += 6; + while (*cmd == ' ') + cmd++; + if (cmd != '\0') + kernel_restart(cmd); + else + kernel_restart(NULL); + } else { + debug_printf(state, "unknown work command '%s'\n", work_cmd); + } +} + +/* This function CANNOT be called in FIQ context */ +static void debug_irq_exec(struct fiq_debugger_state *state, char *cmd) +{ + if (!strcmp(cmd, "ps")) + do_ps(state); + if (!strcmp(cmd, "sysrq")) + do_sysrq(state, 'h'); + if (!strncmp(cmd, "sysrq ", 6)) + do_sysrq(state, cmd[6]); +#ifdef CONFIG_KGDB + if (!strcmp(cmd, "kgdb")) + do_kgdb(state); +#endif + if (!strncmp(cmd, "reboot", 6)) + debug_schedule_work(state, cmd); +} + +static void debug_help(struct fiq_debugger_state *state) +{ + debug_printf(state, "FIQ Debugger commands:\n" + " pc PC status\n" + " regs Register dump\n" + " allregs Extended Register dump\n" + " bt Stack trace\n" + " reboot [] Reboot with command \n" + " reset [] Hard reset with command \n" + " irqs Interupt status\n" + " kmsg Kernel log\n" + " version Kernel version\n"); + debug_printf(state, " sleep Allow sleep while in FIQ\n" + " nosleep Disable sleep while in FIQ\n" + " console Switch terminal to console\n" + " cpu Current CPU\n" + " cpu Switch to CPU\n"); + debug_printf(state, " ps Process list\n" + " sysrq sysrq options\n" + " sysrq Execute sysrq with \n"); +#ifdef CONFIG_KGDB + debug_printf(state, " kgdb Enter kernel debugger\n"); +#endif +} + +static void take_affinity(void *info) +{ + struct fiq_debugger_state *state = info; + struct cpumask cpumask; + + cpumask_clear(&cpumask); + cpumask_set_cpu(get_cpu(), &cpumask); + + irq_set_affinity(state->uart_irq, &cpumask); +} + +static void switch_cpu(struct fiq_debugger_state *state, int cpu) +{ + if (!debug_have_fiq(state)) + smp_call_function_single(cpu, take_affinity, state, false); + state->current_cpu = cpu; +} + +static bool debug_fiq_exec(struct fiq_debugger_state *state, + const char *cmd, unsigned *regs, void *svc_sp) +{ + bool signal_helper = false; + + if (!strcmp(cmd, "help") || !strcmp(cmd, "?")) { + debug_help(state); + } else if (!strcmp(cmd, "pc")) { + debug_printf(state, " pc %08x cpsr %08x mode %s\n", + regs[15], regs[16], mode_name(regs[16])); + } else if (!strcmp(cmd, "regs")) { + dump_regs(state, regs); + } else if (!strcmp(cmd, "allregs")) { + dump_allregs(state, regs); + } else if (!strcmp(cmd, "bt")) { + dump_stacktrace(state, (struct pt_regs *)regs, 100, svc_sp); + } else if (!strncmp(cmd, "reset", 5)) { + cmd += 5; + while (*cmd == ' ') + cmd++; + if (*cmd) { + char tmp_cmd[32]; + strlcpy(tmp_cmd, cmd, sizeof(tmp_cmd)); + machine_restart(tmp_cmd); + } else { + machine_restart(NULL); + } + } else if (!strcmp(cmd, "irqs")) { + dump_irqs(state); + } else if (!strcmp(cmd, "kmsg")) { + dump_kernel_log(state); + } else if (!strcmp(cmd, "version")) { + debug_printf(state, "%s\n", linux_banner); + } else if (!strcmp(cmd, "sleep")) { + state->no_sleep = false; + debug_printf(state, "enabling sleep\n"); + } else if (!strcmp(cmd, "nosleep")) { + state->no_sleep = true; + debug_printf(state, "disabling sleep\n"); + } else if (!strcmp(cmd, "console")) { + debug_printf(state, "console mode\n"); + debug_uart_flush(state); + state->console_enable = true; + } else if (!strcmp(cmd, "cpu")) { + debug_printf(state, "cpu %d\n", state->current_cpu); + } else if (!strncmp(cmd, "cpu ", 4)) { + unsigned long cpu = 0; + if (strict_strtoul(cmd + 4, 10, &cpu) == 0) + switch_cpu(state, cpu); + else + debug_printf(state, "invalid cpu\n"); + debug_printf(state, "cpu %d\n", state->current_cpu); + } else { + if (state->debug_busy) { + debug_printf(state, + "command processor busy. trying to abort.\n"); + state->debug_abort = -1; + } else { + strcpy(state->debug_cmd, cmd); + state->debug_busy = 1; + } + + return true; + } + if (!state->console_enable) + debug_prompt(state); + + return signal_helper; +} + +static void sleep_timer_expired(unsigned long data) +{ + struct fiq_debugger_state *state = (struct fiq_debugger_state *)data; + unsigned long flags; + + spin_lock_irqsave(&state->sleep_timer_lock, flags); + if (state->uart_enabled && !state->no_sleep) { + if (state->debug_enable && !state->console_enable) { + state->debug_enable = false; + debug_printf_nfiq(state, "suspending fiq debugger\n"); + } + state->ignore_next_wakeup_irq = true; + debug_uart_disable(state); + state->uart_enabled = false; + enable_wakeup_irq(state); + } + wake_unlock(&state->debugger_wake_lock); + spin_unlock_irqrestore(&state->sleep_timer_lock, flags); +} + +static void handle_wakeup(struct fiq_debugger_state *state) +{ + unsigned long flags; + + spin_lock_irqsave(&state->sleep_timer_lock, flags); + if (state->wakeup_irq >= 0 && state->ignore_next_wakeup_irq) { + state->ignore_next_wakeup_irq = false; + } else if (!state->uart_enabled) { + wake_lock(&state->debugger_wake_lock); + debug_uart_enable(state); + state->uart_enabled = true; + disable_wakeup_irq(state); + mod_timer(&state->sleep_timer, jiffies + HZ / 2); + } + spin_unlock_irqrestore(&state->sleep_timer_lock, flags); +} + +static irqreturn_t wakeup_irq_handler(int irq, void *dev) +{ + struct fiq_debugger_state *state = dev; + + if (!state->no_sleep) + debug_puts(state, "WAKEUP\n"); + handle_wakeup(state); + + return IRQ_HANDLED; +} + +static void debug_handle_console_irq_context(struct fiq_debugger_state *state) +{ +#if defined(CONFIG_FIQ_DEBUGGER_CONSOLE) + if (state->tty_port.ops) { + int i; + int count = fiq_debugger_ringbuf_level(state->tty_rbuf); + for (i = 0; i < count; i++) { + int c = fiq_debugger_ringbuf_peek(state->tty_rbuf, 0); + tty_insert_flip_char(&state->tty_port, c, TTY_NORMAL); + if (!fiq_debugger_ringbuf_consume(state->tty_rbuf, 1)) + pr_warn("fiq tty failed to consume byte\n"); + } + tty_flip_buffer_push(&state->tty_port); + } +#endif +} + +static void debug_handle_irq_context(struct fiq_debugger_state *state) +{ + if (!state->no_sleep) { + unsigned long flags; + + spin_lock_irqsave(&state->sleep_timer_lock, flags); + wake_lock(&state->debugger_wake_lock); + mod_timer(&state->sleep_timer, jiffies + HZ * 5); + spin_unlock_irqrestore(&state->sleep_timer_lock, flags); + } + debug_handle_console_irq_context(state); + if (state->debug_busy) { + debug_irq_exec(state, state->debug_cmd); + if (!state->console_enable) + debug_prompt(state); + state->debug_busy = 0; + } +} + +static int debug_getc(struct fiq_debugger_state *state) +{ + return state->pdata->uart_getc(state->pdev); +} + +static bool debug_handle_uart_interrupt(struct fiq_debugger_state *state, + int this_cpu, void *regs, void *svc_sp) +{ + int c; + static int last_c; + int count = 0; + bool signal_helper = false; + + if (this_cpu != state->current_cpu) { + if (state->in_fiq) + return false; + + if (atomic_inc_return(&state->unhandled_fiq_count) != + MAX_UNHANDLED_FIQ_COUNT) + return false; + + debug_printf(state, "fiq_debugger: cpu %d not responding, " + "reverting to cpu %d\n", state->current_cpu, + this_cpu); + + atomic_set(&state->unhandled_fiq_count, 0); + switch_cpu(state, this_cpu); + return false; + } + + state->in_fiq = true; + + while ((c = debug_getc(state)) != FIQ_DEBUGGER_NO_CHAR) { + count++; + if (!state->debug_enable) { + if ((c == 13) || (c == 10)) { + state->debug_enable = true; + state->debug_count = 0; + debug_prompt(state); + } + } else if (c == FIQ_DEBUGGER_BREAK) { + state->console_enable = false; + debug_puts(state, "fiq debugger mode\n"); + state->debug_count = 0; + debug_prompt(state); +#ifdef CONFIG_FIQ_DEBUGGER_CONSOLE + } else if (state->console_enable && state->tty_rbuf) { + fiq_debugger_ringbuf_push(state->tty_rbuf, c); + signal_helper = true; +#endif + } else if ((c >= ' ') && (c < 127)) { + if (state->debug_count < (DEBUG_MAX - 1)) { + state->debug_buf[state->debug_count++] = c; + debug_putc(state, c); + } + } else if ((c == 8) || (c == 127)) { + if (state->debug_count > 0) { + state->debug_count--; + debug_putc(state, 8); + debug_putc(state, ' '); + debug_putc(state, 8); + } + } else if ((c == 13) || (c == 10)) { + if (c == '\r' || (c == '\n' && last_c != '\r')) { + debug_putc(state, '\r'); + debug_putc(state, '\n'); + } + if (state->debug_count) { + state->debug_buf[state->debug_count] = 0; + state->debug_count = 0; + signal_helper |= + debug_fiq_exec(state, state->debug_buf, + regs, svc_sp); + } else { + debug_prompt(state); + } + } + last_c = c; + } + if (!state->console_enable) + debug_uart_flush(state); + if (state->pdata->fiq_ack) + state->pdata->fiq_ack(state->pdev, state->fiq); + + /* poke sleep timer if necessary */ + if (state->debug_enable && !state->no_sleep) + signal_helper = true; + + atomic_set(&state->unhandled_fiq_count, 0); + state->in_fiq = false; + + return signal_helper; +} + +static void debug_fiq(struct fiq_glue_handler *h, void *regs, void *svc_sp) +{ + struct fiq_debugger_state *state = + container_of(h, struct fiq_debugger_state, handler); + unsigned int this_cpu = THREAD_INFO(svc_sp)->cpu; + bool need_irq; + + need_irq = debug_handle_uart_interrupt(state, this_cpu, regs, svc_sp); + if (need_irq) + debug_force_irq(state); +} + +/* + * When not using FIQs, we only use this single interrupt as an entry point. + * This just effectively takes over the UART interrupt and does all the work + * in this context. + */ +static irqreturn_t debug_uart_irq(int irq, void *dev) +{ + struct fiq_debugger_state *state = dev; + bool not_done; + + handle_wakeup(state); + + /* handle the debugger irq in regular context */ + not_done = debug_handle_uart_interrupt(state, smp_processor_id(), + get_irq_regs(), + current_thread_info()); + if (not_done) + debug_handle_irq_context(state); + + return IRQ_HANDLED; +} + +/* + * If FIQs are used, not everything can happen in fiq context. + * FIQ handler does what it can and then signals this interrupt to finish the + * job in irq context. + */ +static irqreturn_t debug_signal_irq(int irq, void *dev) +{ + struct fiq_debugger_state *state = dev; + + if (state->pdata->force_irq_ack) + state->pdata->force_irq_ack(state->pdev, state->signal_irq); + + debug_handle_irq_context(state); + + return IRQ_HANDLED; +} + +static void debug_resume(struct fiq_glue_handler *h) +{ + struct fiq_debugger_state *state = + container_of(h, struct fiq_debugger_state, handler); + if (state->pdata->uart_resume) + state->pdata->uart_resume(state->pdev); +} + +#if defined(CONFIG_FIQ_DEBUGGER_CONSOLE) +struct tty_driver *debug_console_device(struct console *co, int *index) +{ + *index = co->index; + return fiq_tty_driver; +} + +static void debug_console_write(struct console *co, + const char *s, unsigned int count) +{ + struct fiq_debugger_state *state; + unsigned long flags; + + state = container_of(co, struct fiq_debugger_state, console); + + if (!state->console_enable && !state->syslog_dumping) + return; + + debug_uart_enable(state); + spin_lock_irqsave(&state->console_lock, flags); + while (count--) { + if (*s == '\n') + debug_putc(state, '\r'); + debug_putc(state, *s++); + } + debug_uart_flush(state); + spin_unlock_irqrestore(&state->console_lock, flags); + debug_uart_disable(state); +} + +static struct console fiq_debugger_console = { + .name = "ttyFIQ", + .device = debug_console_device, + .write = debug_console_write, + .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_ENABLED, +}; + +int fiq_tty_open(struct tty_struct *tty, struct file *filp) +{ + int line = tty->index; + struct fiq_debugger_state **states = tty->driver->driver_state; + struct fiq_debugger_state *state = states[line]; + + return tty_port_open(&state->tty_port, tty, filp); +} + +void fiq_tty_close(struct tty_struct *tty, struct file *filp) +{ + tty_port_close(tty->port, tty, filp); +} + +int fiq_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + int i; + int line = tty->index; + struct fiq_debugger_state **states = tty->driver->driver_state; + struct fiq_debugger_state *state = states[line]; + + if (!state->console_enable) + return count; + + debug_uart_enable(state); + spin_lock_irq(&state->console_lock); + for (i = 0; i < count; i++) + debug_putc(state, *buf++); + spin_unlock_irq(&state->console_lock); + debug_uart_disable(state); + + return count; +} + +int fiq_tty_write_room(struct tty_struct *tty) +{ + return 16; +} + +#ifdef CONFIG_CONSOLE_POLL +static int fiq_tty_poll_init(struct tty_driver *driver, int line, char *options) +{ + return 0; +} + +static int fiq_tty_poll_get_char(struct tty_driver *driver, int line) +{ + struct fiq_debugger_state **states = driver->driver_state; + struct fiq_debugger_state *state = states[line]; + int c = NO_POLL_CHAR; + + debug_uart_enable(state); + if (debug_have_fiq(state)) { + int count = fiq_debugger_ringbuf_level(state->tty_rbuf); + if (count > 0) { + c = fiq_debugger_ringbuf_peek(state->tty_rbuf, 0); + fiq_debugger_ringbuf_consume(state->tty_rbuf, 1); + } + } else { + c = debug_getc(state); + if (c == FIQ_DEBUGGER_NO_CHAR) + c = NO_POLL_CHAR; + } + debug_uart_disable(state); + + return c; +} + +static void fiq_tty_poll_put_char(struct tty_driver *driver, int line, char ch) +{ + struct fiq_debugger_state **states = driver->driver_state; + struct fiq_debugger_state *state = states[line]; + debug_uart_enable(state); + debug_putc(state, ch); + debug_uart_disable(state); +} +#endif + +static const struct tty_port_operations fiq_tty_port_ops; + +static const struct tty_operations fiq_tty_driver_ops = { + .write = fiq_tty_write, + .write_room = fiq_tty_write_room, + .open = fiq_tty_open, + .close = fiq_tty_close, +#ifdef CONFIG_CONSOLE_POLL + .poll_init = fiq_tty_poll_init, + .poll_get_char = fiq_tty_poll_get_char, + .poll_put_char = fiq_tty_poll_put_char, +#endif +}; + +static int fiq_debugger_tty_init(void) +{ + int ret; + struct fiq_debugger_state **states = NULL; + + states = kzalloc(sizeof(*states) * MAX_FIQ_DEBUGGER_PORTS, GFP_KERNEL); + if (!states) { + pr_err("Failed to allocate fiq debugger state structres\n"); + return -ENOMEM; + } + + fiq_tty_driver = alloc_tty_driver(MAX_FIQ_DEBUGGER_PORTS); + if (!fiq_tty_driver) { + pr_err("Failed to allocate fiq debugger tty\n"); + ret = -ENOMEM; + goto err_free_state; + } + + fiq_tty_driver->owner = THIS_MODULE; + fiq_tty_driver->driver_name = "fiq-debugger"; + fiq_tty_driver->name = "ttyFIQ"; + fiq_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + fiq_tty_driver->subtype = SERIAL_TYPE_NORMAL; + fiq_tty_driver->init_termios = tty_std_termios; + fiq_tty_driver->flags = TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV; + fiq_tty_driver->driver_state = states; + + fiq_tty_driver->init_termios.c_cflag = + B115200 | CS8 | CREAD | HUPCL | CLOCAL; + fiq_tty_driver->init_termios.c_ispeed = 115200; + fiq_tty_driver->init_termios.c_ospeed = 115200; + + tty_set_operations(fiq_tty_driver, &fiq_tty_driver_ops); + + ret = tty_register_driver(fiq_tty_driver); + if (ret) { + pr_err("Failed to register fiq tty: %d\n", ret); + goto err_free_tty; + } + + pr_info("Registered FIQ tty driver\n"); + return 0; + +err_free_tty: + put_tty_driver(fiq_tty_driver); + fiq_tty_driver = NULL; +err_free_state: + kfree(states); + return ret; +} + +static int fiq_debugger_tty_init_one(struct fiq_debugger_state *state) +{ + int ret; + struct device *tty_dev; + struct fiq_debugger_state **states = fiq_tty_driver->driver_state; + + states[state->pdev->id] = state; + + state->tty_rbuf = fiq_debugger_ringbuf_alloc(1024); + if (!state->tty_rbuf) { + pr_err("Failed to allocate fiq debugger ringbuf\n"); + ret = -ENOMEM; + goto err; + } + + tty_port_init(&state->tty_port); + state->tty_port.ops = &fiq_tty_port_ops; + + tty_dev = tty_port_register_device(&state->tty_port, fiq_tty_driver, + state->pdev->id, &state->pdev->dev); + if (IS_ERR(tty_dev)) { + pr_err("Failed to register fiq debugger tty device\n"); + ret = PTR_ERR(tty_dev); + goto err; + } + + device_set_wakeup_capable(tty_dev, 1); + + pr_info("Registered fiq debugger ttyFIQ%d\n", state->pdev->id); + + return 0; + +err: + fiq_debugger_ringbuf_free(state->tty_rbuf); + state->tty_rbuf = NULL; + return ret; +} +#endif + +static int fiq_debugger_dev_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct fiq_debugger_state *state = platform_get_drvdata(pdev); + + if (state->pdata->uart_dev_suspend) + return state->pdata->uart_dev_suspend(pdev); + return 0; +} + +static int fiq_debugger_dev_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct fiq_debugger_state *state = platform_get_drvdata(pdev); + + if (state->pdata->uart_dev_resume) + return state->pdata->uart_dev_resume(pdev); + return 0; +} + +static int fiq_debugger_probe(struct platform_device *pdev) +{ + int ret; + struct fiq_debugger_pdata *pdata = dev_get_platdata(&pdev->dev); + struct fiq_debugger_state *state; + int fiq; + int uart_irq; + + if (pdev->id >= MAX_FIQ_DEBUGGER_PORTS) + return -EINVAL; + + if (!pdata->uart_getc || !pdata->uart_putc) + return -EINVAL; + if ((pdata->uart_enable && !pdata->uart_disable) || + (!pdata->uart_enable && pdata->uart_disable)) + return -EINVAL; + + fiq = platform_get_irq_byname(pdev, "fiq"); + uart_irq = platform_get_irq_byname(pdev, "uart_irq"); + + /* uart_irq mode and fiq mode are mutually exclusive, but one of them + * is required */ + if ((uart_irq < 0 && fiq < 0) || (uart_irq >= 0 && fiq >= 0)) + return -EINVAL; + if (fiq >= 0 && !pdata->fiq_enable) + return -EINVAL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + setup_timer(&state->sleep_timer, sleep_timer_expired, + (unsigned long)state); + state->pdata = pdata; + state->pdev = pdev; + state->no_sleep = initial_no_sleep; + state->debug_enable = initial_debug_enable; + state->console_enable = initial_console_enable; + + state->fiq = fiq; + state->uart_irq = uart_irq; + state->signal_irq = platform_get_irq_byname(pdev, "signal"); + state->wakeup_irq = platform_get_irq_byname(pdev, "wakeup"); + + INIT_WORK(&state->work, debug_work); + spin_lock_init(&state->work_lock); + + platform_set_drvdata(pdev, state); + + spin_lock_init(&state->sleep_timer_lock); + + if (state->wakeup_irq < 0 && debug_have_fiq(state)) + state->no_sleep = true; + state->ignore_next_wakeup_irq = !state->no_sleep; + + wake_lock_init(&state->debugger_wake_lock, + WAKE_LOCK_SUSPEND, "serial-debug"); + + state->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(state->clk)) + state->clk = NULL; + + /* do not call pdata->uart_enable here since uart_init may still + * need to do some initialization before uart_enable can work. + * So, only try to manage the clock during init. + */ + if (state->clk) + clk_enable(state->clk); + + if (pdata->uart_init) { + ret = pdata->uart_init(pdev); + if (ret) + goto err_uart_init; + } + + debug_printf_nfiq(state, "\n", + state->no_sleep ? "" : "twice "); + + if (debug_have_fiq(state)) { + state->handler.fiq = debug_fiq; + state->handler.resume = debug_resume; + ret = fiq_glue_register_handler(&state->handler); + if (ret) { + pr_err("%s: could not install fiq handler\n", __func__); + goto err_register_fiq; + } + + pdata->fiq_enable(pdev, state->fiq, 1); + } else { + ret = request_irq(state->uart_irq, debug_uart_irq, + IRQF_NO_SUSPEND, "debug", state); + if (ret) { + pr_err("%s: could not install irq handler\n", __func__); + goto err_register_irq; + } + + /* for irq-only mode, we want this irq to wake us up, if it + * can. + */ + enable_irq_wake(state->uart_irq); + } + + if (state->clk) + clk_disable(state->clk); + + if (state->signal_irq >= 0) { + ret = request_irq(state->signal_irq, debug_signal_irq, + IRQF_TRIGGER_RISING, "debug-signal", state); + if (ret) + pr_err("serial_debugger: could not install signal_irq"); + } + + if (state->wakeup_irq >= 0) { + ret = request_irq(state->wakeup_irq, wakeup_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_DISABLED, + "debug-wakeup", state); + if (ret) { + pr_err("serial_debugger: " + "could not install wakeup irq\n"); + state->wakeup_irq = -1; + } else { + ret = enable_irq_wake(state->wakeup_irq); + if (ret) { + pr_err("serial_debugger: " + "could not enable wakeup\n"); + state->wakeup_irq_no_set_wake = true; + } + } + } + if (state->no_sleep) + handle_wakeup(state); + +#if defined(CONFIG_FIQ_DEBUGGER_CONSOLE) + spin_lock_init(&state->console_lock); + state->console = fiq_debugger_console; + state->console.index = pdev->id; + if (!console_set_on_cmdline) + add_preferred_console(state->console.name, + state->console.index, NULL); + register_console(&state->console); + fiq_debugger_tty_init_one(state); +#endif + return 0; + +err_register_irq: +err_register_fiq: + if (pdata->uart_free) + pdata->uart_free(pdev); +err_uart_init: + if (state->clk) + clk_disable(state->clk); + if (state->clk) + clk_put(state->clk); + wake_lock_destroy(&state->debugger_wake_lock); + platform_set_drvdata(pdev, NULL); + kfree(state); + return ret; +} + +static const struct dev_pm_ops fiq_debugger_dev_pm_ops = { + .suspend = fiq_debugger_dev_suspend, + .resume = fiq_debugger_dev_resume, +}; + +static struct platform_driver fiq_debugger_driver = { + .probe = fiq_debugger_probe, + .driver = { + .name = "fiq_debugger", + .pm = &fiq_debugger_dev_pm_ops, + }, +}; + +static int __init fiq_debugger_init(void) +{ +#if defined(CONFIG_FIQ_DEBUGGER_CONSOLE) + fiq_debugger_tty_init(); +#endif + return platform_driver_register(&fiq_debugger_driver); +} + +postcore_initcall(fiq_debugger_init); diff --git a/arch/arm/common/fiq_debugger_ringbuf.h b/arch/arm/common/fiq_debugger_ringbuf.h new file mode 100644 index 00000000000000..2649b5581088ca --- /dev/null +++ b/arch/arm/common/fiq_debugger_ringbuf.h @@ -0,0 +1,94 @@ +/* + * arch/arm/common/fiq_debugger_ringbuf.c + * + * simple lockless ringbuffer + * + * Copyright (C) 2010 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include +#include + +struct fiq_debugger_ringbuf { + int len; + int head; + int tail; + u8 buf[]; +}; + + +static inline struct fiq_debugger_ringbuf *fiq_debugger_ringbuf_alloc(int len) +{ + struct fiq_debugger_ringbuf *rbuf; + + rbuf = kzalloc(sizeof(*rbuf) + len, GFP_KERNEL); + if (rbuf == NULL) + return NULL; + + rbuf->len = len; + rbuf->head = 0; + rbuf->tail = 0; + smp_mb(); + + return rbuf; +} + +static inline void fiq_debugger_ringbuf_free(struct fiq_debugger_ringbuf *rbuf) +{ + kfree(rbuf); +} + +static inline int fiq_debugger_ringbuf_level(struct fiq_debugger_ringbuf *rbuf) +{ + int level = rbuf->head - rbuf->tail; + + if (level < 0) + level = rbuf->len + level; + + return level; +} + +static inline int fiq_debugger_ringbuf_room(struct fiq_debugger_ringbuf *rbuf) +{ + return rbuf->len - fiq_debugger_ringbuf_level(rbuf) - 1; +} + +static inline u8 +fiq_debugger_ringbuf_peek(struct fiq_debugger_ringbuf *rbuf, int i) +{ + return rbuf->buf[(rbuf->tail + i) % rbuf->len]; +} + +static inline int +fiq_debugger_ringbuf_consume(struct fiq_debugger_ringbuf *rbuf, int count) +{ + count = min(count, fiq_debugger_ringbuf_level(rbuf)); + + rbuf->tail = (rbuf->tail + count) % rbuf->len; + smp_mb(); + + return count; +} + +static inline int +fiq_debugger_ringbuf_push(struct fiq_debugger_ringbuf *rbuf, u8 datum) +{ + if (fiq_debugger_ringbuf_room(rbuf) == 0) + return 0; + + rbuf->buf[rbuf->head] = datum; + smp_mb(); + rbuf->head = (rbuf->head + 1) % rbuf->len; + smp_mb(); + + return 1; +} diff --git a/arch/arm/common/fiq_glue.S b/arch/arm/common/fiq_glue.S new file mode 100644 index 00000000000000..24b42cec4813fa --- /dev/null +++ b/arch/arm/common/fiq_glue.S @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include + + .text + + .global fiq_glue_end + + /* fiq stack: r0-r15,cpsr,spsr of interrupted mode */ + +ENTRY(fiq_glue) + /* store pc, cpsr from previous mode, reserve space for spsr */ + mrs r12, spsr + sub lr, lr, #4 + subs r10, #1 + bne nested_fiq + + str r12, [sp, #-8]! + str lr, [sp, #-4]! + + /* store r8-r14 from previous mode */ + sub sp, sp, #(7 * 4) + stmia sp, {r8-r14}^ + nop + + /* store r0-r7 from previous mode */ + stmfd sp!, {r0-r7} + + /* setup func(data,regs) arguments */ + mov r0, r9 + mov r1, sp + mov r3, r8 + + mov r7, sp + + /* Get sp and lr from non-user modes */ + and r4, r12, #MODE_MASK + cmp r4, #USR_MODE + beq fiq_from_usr_mode + + mov r7, sp + orr r4, r4, #(PSR_I_BIT | PSR_F_BIT) + msr cpsr_c, r4 + str sp, [r7, #(4 * 13)] + str lr, [r7, #(4 * 14)] + mrs r5, spsr + str r5, [r7, #(4 * 17)] + + cmp r4, #(SVC_MODE | PSR_I_BIT | PSR_F_BIT) + /* use fiq stack if we reenter this mode */ + subne sp, r7, #(4 * 3) + +fiq_from_usr_mode: + msr cpsr_c, #(SVC_MODE | PSR_I_BIT | PSR_F_BIT) + mov r2, sp + sub sp, r7, #12 + stmfd sp!, {r2, ip, lr} + /* call func(data,regs) */ + blx r3 + ldmfd sp, {r2, ip, lr} + mov sp, r2 + + /* restore/discard saved state */ + cmp r4, #USR_MODE + beq fiq_from_usr_mode_exit + + msr cpsr_c, r4 + ldr sp, [r7, #(4 * 13)] + ldr lr, [r7, #(4 * 14)] + msr spsr_cxsf, r5 + +fiq_from_usr_mode_exit: + msr cpsr_c, #(FIQ_MODE | PSR_I_BIT | PSR_F_BIT) + + ldmfd sp!, {r0-r7} + ldr lr, [sp, #(4 * 7)] + ldr r12, [sp, #(4 * 8)] + add sp, sp, #(10 * 4) +exit_fiq: + msr spsr_cxsf, r12 + add r10, #1 + cmp r11, #0 + moveqs pc, lr + bx r11 /* jump to custom fiq return function */ + +nested_fiq: + orr r12, r12, #(PSR_F_BIT) + b exit_fiq + +fiq_glue_end: + +ENTRY(fiq_glue_setup) /* func, data, sp, smc call number */ + stmfd sp!, {r4} + mrs r4, cpsr + msr cpsr_c, #(FIQ_MODE | PSR_I_BIT | PSR_F_BIT) + movs r8, r0 + mov r9, r1 + mov sp, r2 + mov r11, r3 + moveq r10, #0 + movne r10, #1 + msr cpsr_c, r4 + ldmfd sp!, {r4} + bx lr + diff --git a/arch/arm/common/fiq_glue_setup.c b/arch/arm/common/fiq_glue_setup.c new file mode 100644 index 00000000000000..8cb1b611c6d57d --- /dev/null +++ b/arch/arm/common/fiq_glue_setup.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include +#include +#include +#include +#include + +extern unsigned char fiq_glue, fiq_glue_end; +extern void fiq_glue_setup(void *func, void *data, void *sp, + fiq_return_handler_t fiq_return_handler); + +static struct fiq_handler fiq_debbuger_fiq_handler = { + .name = "fiq_glue", +}; +DEFINE_PER_CPU(void *, fiq_stack); +static struct fiq_glue_handler *current_handler; +static fiq_return_handler_t fiq_return_handler; +static DEFINE_MUTEX(fiq_glue_lock); + +static void fiq_glue_setup_helper(void *info) +{ + struct fiq_glue_handler *handler = info; + fiq_glue_setup(handler->fiq, handler, + __get_cpu_var(fiq_stack) + THREAD_START_SP, + fiq_return_handler); +} + +int fiq_glue_register_handler(struct fiq_glue_handler *handler) +{ + int ret; + int cpu; + + if (!handler || !handler->fiq) + return -EINVAL; + + mutex_lock(&fiq_glue_lock); + if (fiq_stack) { + ret = -EBUSY; + goto err_busy; + } + + for_each_possible_cpu(cpu) { + void *stack; + stack = (void *)__get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER); + if (WARN_ON(!stack)) { + ret = -ENOMEM; + goto err_alloc_fiq_stack; + } + per_cpu(fiq_stack, cpu) = stack; + } + + ret = claim_fiq(&fiq_debbuger_fiq_handler); + if (WARN_ON(ret)) + goto err_claim_fiq; + + current_handler = handler; + on_each_cpu(fiq_glue_setup_helper, handler, true); + set_fiq_handler(&fiq_glue, &fiq_glue_end - &fiq_glue); + + mutex_unlock(&fiq_glue_lock); + return 0; + +err_claim_fiq: +err_alloc_fiq_stack: + for_each_possible_cpu(cpu) { + __free_pages(per_cpu(fiq_stack, cpu), THREAD_SIZE_ORDER); + per_cpu(fiq_stack, cpu) = NULL; + } +err_busy: + mutex_unlock(&fiq_glue_lock); + return ret; +} + +static void fiq_glue_update_return_handler(void (*fiq_return)(void)) +{ + fiq_return_handler = fiq_return; + if (current_handler) + on_each_cpu(fiq_glue_setup_helper, current_handler, true); +} + +int fiq_glue_set_return_handler(void (*fiq_return)(void)) +{ + int ret; + + mutex_lock(&fiq_glue_lock); + if (fiq_return_handler) { + ret = -EBUSY; + goto err_busy; + } + fiq_glue_update_return_handler(fiq_return); + ret = 0; +err_busy: + mutex_unlock(&fiq_glue_lock); + + return ret; +} +EXPORT_SYMBOL(fiq_glue_set_return_handler); + +int fiq_glue_clear_return_handler(void (*fiq_return)(void)) +{ + int ret; + + mutex_lock(&fiq_glue_lock); + if (WARN_ON(fiq_return_handler != fiq_return)) { + ret = -EINVAL; + goto err_inval; + } + fiq_glue_update_return_handler(NULL); + ret = 0; +err_inval: + mutex_unlock(&fiq_glue_lock); + + return ret; +} +EXPORT_SYMBOL(fiq_glue_clear_return_handler); + +/** + * fiq_glue_resume - Restore fiqs after suspend or low power idle states + * + * This must be called before calling local_fiq_enable after returning from a + * power state where the fiq mode registers were lost. If a driver provided + * a resume hook when it registered the handler it will be called. + */ + +void fiq_glue_resume(void) +{ + if (!current_handler) + return; + fiq_glue_setup(current_handler->fiq, current_handler, + __get_cpu_var(fiq_stack) + THREAD_START_SP, + fiq_return_handler); + if (current_handler->resume) + current_handler->resume(current_handler); +} + diff --git a/arch/arm/configs/bcm_defconfig b/arch/arm/configs/bcm_defconfig index 2519d6de064012..ef5dce336114cb 100644 --- a/arch/arm/configs/bcm_defconfig +++ b/arch/arm/configs/bcm_defconfig @@ -79,11 +79,19 @@ CONFIG_HW_RANDOM=y CONFIG_I2C=y CONFIG_I2C_CHARDEV=y # CONFIG_HWMON is not set +CONFIG_MFD_BCM590XX=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_VIRTUAL_CONSUMER=y +CONFIG_REGULATOR_USERSPACE_CONSUMER=y +CONFIG_REGULATOR_BCM590XX=y + CONFIG_VIDEO_OUTPUT_CONTROL=y CONFIG_FB=y CONFIG_BACKLIGHT_LCD_SUPPORT=y CONFIG_LCD_CLASS_DEVICE=y CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_BACKLIGHT_PWM=y # CONFIG_USB_SUPPORT is not set CONFIG_MMC=y CONFIG_MMC_UNSAFE_RESUME=y @@ -97,6 +105,7 @@ CONFIG_LEDS_TRIGGERS=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_LEDS_TRIGGER_HEARTBEAT=y CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_PWM=y CONFIG_EXT4_FS=y CONFIG_EXT4_FS_POSIX_ACL=y CONFIG_EXT4_FS_SECURITY=y diff --git a/arch/arm/configs/hip04_defconfig b/arch/arm/configs/hip04_defconfig new file mode 100644 index 00000000000000..afaa0be18ea00b --- /dev/null +++ b/arch/arm/configs/hip04_defconfig @@ -0,0 +1,64 @@ +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_IRQ_DOMAIN_DEBUG=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_RD_GZIP=y +CONFIG_ARCH_HIP04=y +CONFIG_SMP=y +CONFIG_NR_CPUS=16 +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_ARM_APPENDED_DTB=y +CONFIG_ARM_ATAG_DTB_COMPAT=y +CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_FROM_BOOTLOADER=y +CONFIG_HIGHMEM=y +CONFIG_VFP=y +CONFIG_NEON=y +CONFIG_NET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_BLK_DEV=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=8 +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=4096 +CONFIG_BLK_DEV_SD=y +CONFIG_ATA=y +CONFIG_SATA_AHCI_PLATFORM=y +CONFIG_NETDEVICES=y +CONFIG_HIP04_ETH=y +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=2 +CONFIG_SERIAL_8250_RUNTIME_UARTS=2 +CONFIG_SERIAL_8250_DW=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_I2C_DESIGNWARE_PLATFORM=y +CONFIG_PINCTRL_SINGLE=y +CONFIG_GPIO_GENERIC_PLATFORM=y +CONFIG_REGULATOR_GPIO=y +CONFIG_DRM=y +CONFIG_FB_SIMPLE=y +CONFIG_RTC_CLASS=y +CONFIG_EXT4_FS=y +CONFIG_TMPFS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_ROOT_NFS=y +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_KERNEL=y +CONFIG_DEBUG_LL=y +CONFIG_DEBUG_UART_8250=y +CONFIG_EARLY_PRINTK=y +CONFIG_LOCKUP_DETECTOR=y +CONFIG_DEBUG_USER=y diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig index ee6982976d661d..39e1e0d760c3c8 100644 --- a/arch/arm/configs/multi_v7_defconfig +++ b/arch/arm/configs/multi_v7_defconfig @@ -11,13 +11,13 @@ CONFIG_ARCH_MVEBU=y CONFIG_MACH_ARMADA_370=y CONFIG_MACH_ARMADA_XP=y CONFIG_ARCH_BCM=y +CONFIG_ARCH_BCM_5301X=y CONFIG_ARCH_BCM_MOBILE=y CONFIG_ARCH_BERLIN=y CONFIG_MACH_BERLIN_BG2=y CONFIG_MACH_BERLIN_BG2CD=y CONFIG_GPIO_PCA953X=y CONFIG_ARCH_HIGHBANK=y -CONFIG_ARCH_HI3xxx=y CONFIG_ARCH_KEYSTONE=y CONFIG_ARCH_MXC=y CONFIG_MACH_IMX51_DT=y diff --git a/arch/arm/configs/odroidxu_defconfig b/arch/arm/configs/odroidxu_defconfig new file mode 100644 index 00000000000000..55f47f0e4f07d9 --- /dev/null +++ b/arch/arm/configs/odroidxu_defconfig @@ -0,0 +1,3236 @@ +# +# Automatically generated file; DO NOT EDIT. +# Linux/arm 3.14.0 Kernel Configuration +# +CONFIG_ARM=y +CONFIG_ARM_HAS_SG_CHAIN=y +CONFIG_NEED_SG_DMA_LENGTH=y +CONFIG_ARM_DMA_USE_IOMMU=y +CONFIG_ARM_DMA_IOMMU_ALIGNMENT=8 +CONFIG_MIGHT_HAVE_PCI=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_HAVE_PROC_CPU=y +CONFIG_NO_IOPORT=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +CONFIG_ARCH_HAS_CPUFREQ=y +CONFIG_ARCH_HAS_BANDGAP=y +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_NEED_DMA_MAP_STATE=y +CONFIG_ARCH_SUPPORTS_UPROBES=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_ARM_PATCH_PHYS_VIRT=y +CONFIG_NEED_MACH_MEMORY_H=y +CONFIG_GENERIC_BUG=y +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" +CONFIG_IRQ_WORK=y +CONFIG_BUILDTIME_EXTABLE_SORT=y + +# +# General setup +# +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_CROSS_COMPILE="" +# CONFIG_COMPILE_TEST is not set +CONFIG_LOCALVERSION="" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_HAVE_KERNEL_GZIP=y +CONFIG_HAVE_KERNEL_LZMA=y +CONFIG_HAVE_KERNEL_XZ=y +CONFIG_HAVE_KERNEL_LZO=y +CONFIG_HAVE_KERNEL_LZ4=y +CONFIG_KERNEL_GZIP=y +# CONFIG_KERNEL_LZMA is not set +# CONFIG_KERNEL_XZ is not set +# CONFIG_KERNEL_LZO is not set +# CONFIG_KERNEL_LZ4 is not set +CONFIG_DEFAULT_HOSTNAME="(none)" +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +CONFIG_POSIX_MQUEUE=y +CONFIG_POSIX_MQUEUE_SYSCTL=y +# CONFIG_FHANDLE is not set +CONFIG_AUDIT=y +CONFIG_AUDITSYSCALL=y +CONFIG_AUDIT_WATCH=y +CONFIG_AUDIT_TREE=y + +# +# IRQ subsystem +# +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_GENERIC_IRQ_SHOW=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_CHIP=y +CONFIG_IRQ_DOMAIN=y +# CONFIG_IRQ_DOMAIN_DEBUG is not set +CONFIG_IRQ_FORCED_THREADING=y +CONFIG_SPARSE_IRQ=y +CONFIG_KTIME_SCALAR=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_ARCH_HAS_TICK_BROADCAST=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y + +# +# Timers subsystem +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ_COMMON=y +# CONFIG_HZ_PERIODIC is not set +CONFIG_NO_HZ_IDLE=y +# CONFIG_NO_HZ_FULL is not set +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y + +# +# CPU/Task time and stats accounting +# +CONFIG_TICK_CPU_ACCOUNTING=y +# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set +# CONFIG_IRQ_TIME_ACCOUNTING is not set +CONFIG_BSD_PROCESS_ACCT=y +# CONFIG_BSD_PROCESS_ACCT_V3 is not set +# CONFIG_TASKSTATS is not set + +# +# RCU Subsystem +# +CONFIG_TREE_RCU=y +# CONFIG_PREEMPT_RCU is not set +CONFIG_RCU_STALL_COMMON=y +# CONFIG_RCU_USER_QS is not set +CONFIG_RCU_FANOUT=32 +CONFIG_RCU_FANOUT_LEAF=16 +# CONFIG_RCU_FANOUT_EXACT is not set +# CONFIG_RCU_FAST_NO_HZ is not set +# CONFIG_TREE_RCU_TRACE is not set +# CONFIG_RCU_NOCB_CPU is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=16 +CONFIG_GENERIC_SCHED_CLOCK=y +CONFIG_CGROUPS=y +# CONFIG_CGROUP_DEBUG is not set +# CONFIG_CGROUP_FREEZER is not set +# CONFIG_CGROUP_DEVICE is not set +# CONFIG_CPUSETS is not set +# CONFIG_CGROUP_CPUACCT is not set +# CONFIG_RESOURCE_COUNTERS is not set +# CONFIG_CGROUP_PERF is not set +# CONFIG_CGROUP_SCHED is not set +# CONFIG_BLK_CGROUP is not set +# CONFIG_CHECKPOINT_RESTORE is not set +# CONFIG_NAMESPACES is not set +# CONFIG_SCHED_AUTOGROUP is not set +# CONFIG_SYSFS_DEPRECATED is not set +# CONFIG_RELAY is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_RD_GZIP=y +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_XZ is not set +# CONFIG_RD_LZO is not set +# CONFIG_RD_LZ4 is not set +# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set +CONFIG_SYSCTL=y +CONFIG_ANON_INODES=y +CONFIG_HAVE_UID16=y +CONFIG_EXPERT=y +CONFIG_UID16=y +# CONFIG_SYSCTL_SYSCALL is not set +CONFIG_KALLSYMS=y +CONFIG_KALLSYMS_ALL=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_EMBEDDED=y +CONFIG_HAVE_PERF_EVENTS=y +CONFIG_PERF_USE_VMALLOC=y + +# +# Kernel Performance Events And Counters +# +CONFIG_PERF_EVENTS=y +# CONFIG_DEBUG_PERF_USE_VMALLOC is not set +CONFIG_VM_EVENT_COUNTERS=y +# CONFIG_COMPAT_BRK is not set +CONFIG_SLAB=y +# CONFIG_SLUB is not set +# CONFIG_SLOB is not set +CONFIG_PROFILING=y +CONFIG_TRACEPOINTS=y +CONFIG_OPROFILE=y +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +# CONFIG_JUMP_LABEL is not set +# CONFIG_UPROBES is not set +# CONFIG_HAVE_64BIT_ALIGNED_ACCESS is not set +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_ARCH_USE_BUILTIN_BSWAP=y +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_DMA_ATTRS=y +CONFIG_HAVE_DMA_CONTIGUOUS=y +CONFIG_GENERIC_SMP_IDLE_THREAD=y +CONFIG_GENERIC_IDLE_POLL_SETUP=y +CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y +CONFIG_HAVE_CLK=y +CONFIG_HAVE_DMA_API_DEBUG=y +CONFIG_HAVE_HW_BREAKPOINT=y +CONFIG_HAVE_PERF_REGS=y +CONFIG_HAVE_PERF_USER_STACK_DUMP=y +CONFIG_HAVE_ARCH_JUMP_LABEL=y +CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y +CONFIG_HAVE_ARCH_SECCOMP_FILTER=y +CONFIG_SECCOMP_FILTER=y +CONFIG_HAVE_CC_STACKPROTECTOR=y +# CONFIG_CC_STACKPROTECTOR is not set +CONFIG_CC_STACKPROTECTOR_NONE=y +# CONFIG_CC_STACKPROTECTOR_REGULAR is not set +# CONFIG_CC_STACKPROTECTOR_STRONG is not set +CONFIG_HAVE_CONTEXT_TRACKING=y +CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y +CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y +CONFIG_HAVE_MOD_ARCH_SPECIFIC=y +CONFIG_MODULES_USE_ELF_REL=y +CONFIG_CLONE_BACKWARDS=y +CONFIG_OLD_SIGSUSPEND3=y +CONFIG_OLD_SIGACTION=y + +# +# GCOV-based kernel profiling +# +# CONFIG_GCOV_KERNEL is not set +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +CONFIG_BASE_SMALL=0 +# CONFIG_SYSTEM_TRUSTED_KEYRING is not set +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +# CONFIG_MODULE_FORCE_UNLOAD is not set +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_SRCVERSION_ALL is not set +# CONFIG_MODULE_SIG is not set +CONFIG_STOP_MACHINE=y +CONFIG_BLOCK=y +CONFIG_LBDAF=y +CONFIG_BLK_DEV_BSG=y +# CONFIG_BLK_DEV_BSGLIB is not set +# CONFIG_BLK_DEV_INTEGRITY is not set +# CONFIG_BLK_CMDLINE_PARSER is not set + +# +# Partition Types +# +CONFIG_PARTITION_ADVANCED=y +# CONFIG_ACORN_PARTITION is not set +# CONFIG_AIX_PARTITION is not set +# CONFIG_OSF_PARTITION is not set +# CONFIG_AMIGA_PARTITION is not set +# CONFIG_ATARI_PARTITION is not set +# CONFIG_MAC_PARTITION is not set +CONFIG_MSDOS_PARTITION=y +# CONFIG_BSD_DISKLABEL is not set +# CONFIG_MINIX_SUBPARTITION is not set +# CONFIG_SOLARIS_X86_PARTITION is not set +# CONFIG_UNIXWARE_DISKLABEL is not set +# CONFIG_LDM_PARTITION is not set +# CONFIG_SGI_PARTITION is not set +# CONFIG_ULTRIX_PARTITION is not set +# CONFIG_SUN_PARTITION is not set +# CONFIG_KARMA_PARTITION is not set +CONFIG_EFI_PARTITION=y +# CONFIG_SYSV68_PARTITION is not set +# CONFIG_CMDLINE_PARTITION is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_UNINLINE_SPIN_UNLOCK=y +CONFIG_FREEZER=y + +# +# System Type +# +CONFIG_MMU=y +# CONFIG_ARCH_MULTIPLATFORM is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_GEMINI is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_DOVE is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_MV78XX0 is not set +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_MMP is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_W90X900 is not set +# CONFIG_ARCH_LPC32XX is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_MSM_NODT is not set +# CONFIG_ARCH_SHMOBILE_LEGACY is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C24XX is not set +# CONFIG_ARCH_S3C64XX is not set +# CONFIG_ARCH_S5P64X0 is not set +# CONFIG_ARCH_S5PC100 is not set +# CONFIG_ARCH_S5PV210 is not set +CONFIG_ARCH_EXYNOS=y +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP1 is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_KEYBOARD_GPIO_POLLED is not set + +# +# Hisilicon platform type +# +CONFIG_PLAT_SAMSUNG=y +CONFIG_SAMSUNG_PM=y + +# +# Boot options +# +# CONFIG_S3C_BOOT_ERROR_RESET is not set +CONFIG_S3C_BOOT_UART_FORCE_FIFO=y +CONFIG_S3C_LOWLEVEL_UART_PORT=2 +# CONFIG_SAMSUNG_ATAGS is not set +CONFIG_SAMSUNG_DMADEV=y +CONFIG_S5P_DEV_MFC=y + +# +# Power management +# +# CONFIG_SAMSUNG_PM_DEBUG is not set +# CONFIG_SAMSUNG_PM_CHECK is not set +CONFIG_S5P_PM=y +CONFIG_S5P_SLEEP=y +# CONFIG_PLAT_SPEAR is not set + +# +# SAMSUNG EXYNOS SoCs Support +# +CONFIG_ARCH_EXYNOS4=y +CONFIG_ARCH_EXYNOS5=y + +# +# EXYNOS SoCs +# +CONFIG_CPU_EXYNOS4210=y +CONFIG_SOC_EXYNOS4212=y +CONFIG_SOC_EXYNOS4412=y +CONFIG_SOC_EXYNOS5250=y +CONFIG_SOC_EXYNOS5410=y +CONFIG_SOC_EXYNOS5420=y +CONFIG_SOC_EXYNOS5440=y + +# +# Processor Type +# +CONFIG_CPU_V7=y +CONFIG_CPU_32v6K=y +CONFIG_CPU_32v7=y +CONFIG_CPU_ABRT_EV7=y +CONFIG_CPU_PABRT_V7=y +CONFIG_CPU_CACHE_V7=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_TLB_V7=y +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +# CONFIG_ARM_LPAE is not set +# CONFIG_ARCH_PHYS_ADDR_T_64BIT is not set +CONFIG_ARM_THUMB=y +# CONFIG_ARM_THUMBEE is not set +CONFIG_ARM_VIRT_EXT=y +CONFIG_SWP_EMULATE=y +# CONFIG_CPU_BIG_ENDIAN is not set +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_KUSER_HELPERS=y +CONFIG_OUTER_CACHE=y +CONFIG_OUTER_CACHE_SYNC=y +CONFIG_MIGHT_HAVE_CACHE_L2X0=y +CONFIG_CACHE_L2X0=y +CONFIG_CACHE_PL310=y +CONFIG_ARM_L1_CACHE_SHIFT_6=y +CONFIG_ARM_L1_CACHE_SHIFT=6 +CONFIG_ARM_DMA_MEM_BUFFERABLE=y +CONFIG_ARCH_SUPPORTS_BIG_ENDIAN=y +CONFIG_ARM_NR_BANKS=8 +CONFIG_MULTI_IRQ_HANDLER=y +# CONFIG_ARM_ERRATA_430973 is not set +# CONFIG_ARM_ERRATA_458693 is not set +# CONFIG_ARM_ERRATA_460075 is not set +# CONFIG_ARM_ERRATA_742230 is not set +# CONFIG_ARM_ERRATA_742231 is not set +# CONFIG_PL310_ERRATA_588369 is not set +# CONFIG_ARM_ERRATA_643719 is not set +# CONFIG_ARM_ERRATA_720789 is not set +# CONFIG_PL310_ERRATA_727915 is not set +# CONFIG_ARM_ERRATA_743622 is not set +# CONFIG_ARM_ERRATA_751472 is not set +# CONFIG_PL310_ERRATA_753970 is not set +# CONFIG_ARM_ERRATA_754322 is not set +# CONFIG_ARM_ERRATA_754327 is not set +# CONFIG_ARM_ERRATA_764369 is not set +# CONFIG_PL310_ERRATA_769419 is not set +# CONFIG_ARM_ERRATA_775420 is not set +# CONFIG_ARM_ERRATA_798181 is not set +# CONFIG_ARM_ERRATA_773022 is not set +# CONFIG_FIQ_DEBUGGER is not set + +# +# Bus support +# +CONFIG_ARM_AMBA=y +# CONFIG_PCI is not set +# CONFIG_PCI_SYSCALL is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +CONFIG_HAVE_SMP=y +CONFIG_SMP=y +CONFIG_SMP_ON_UP=y +CONFIG_ARM_CPU_TOPOLOGY=y +CONFIG_SCHED_MC=y +CONFIG_SCHED_SMT=y +CONFIG_HAVE_ARM_SCU=y +CONFIG_HAVE_ARM_ARCH_TIMER=y +# CONFIG_MCPM is not set +# CONFIG_BIG_LITTLE is not set +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_NR_CPUS=8 +CONFIG_HOTPLUG_CPU=y +# CONFIG_ARM_PSCI is not set +CONFIG_ARCH_NR_GPIO=512 +CONFIG_PREEMPT_NONE=y +# CONFIG_PREEMPT_VOLUNTARY is not set +# CONFIG_PREEMPT is not set +CONFIG_HZ_FIXED=200 +CONFIG_HZ=200 +CONFIG_SCHED_HRTICK=y +CONFIG_THUMB2_KERNEL=y +CONFIG_THUMB2_AVOID_R_ARM_THM_JUMP11=y +CONFIG_ARM_ASM_UNIFIED=y +CONFIG_AEABI=y +CONFIG_ARCH_HAS_HOLES_MEMORYMODEL=y +CONFIG_ARCH_SPARSEMEM_ENABLE=y +CONFIG_ARCH_SPARSEMEM_DEFAULT=y +CONFIG_ARCH_SELECT_MEMORY_MODEL=y +CONFIG_HAVE_ARCH_PFN_VALID=y +CONFIG_HIGHMEM=y +# CONFIG_HIGHPTE is not set +CONFIG_HW_PERF_EVENTS=y +CONFIG_ARCH_WANT_GENERAL_HUGETLB=y +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_SPARSEMEM_MANUAL=y +CONFIG_SPARSEMEM=y +CONFIG_HAVE_MEMORY_PRESENT=y +CONFIG_SPARSEMEM_EXTREME=y +CONFIG_HAVE_MEMBLOCK=y +CONFIG_NO_BOOTMEM=y +CONFIG_MEMORY_ISOLATION=y +# CONFIG_HAVE_BOOTMEM_INFO_NODE is not set +CONFIG_SPLIT_PTLOCK_CPUS=4 +CONFIG_COMPACTION=y +CONFIG_MIGRATION=y +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=0 +CONFIG_BOUNCE=y +# CONFIG_KSM is not set +CONFIG_DEFAULT_MMAP_MIN_ADDR=32768 +CONFIG_CROSS_MEMORY_ATTACH=y +# CONFIG_CLEANCACHE is not set +# CONFIG_FRONTSWAP is not set +CONFIG_CMA=y +# CONFIG_CMA_DEBUG is not set +# CONFIG_ZBUD is not set +# CONFIG_ZSMALLOC is not set +CONFIG_FORCE_MAX_ZONEORDER=11 +CONFIG_ALIGNMENT_TRAP=y +# CONFIG_UACCESS_WITH_MEMCPY is not set +CONFIG_SECCOMP=y +CONFIG_SWIOTLB=y +CONFIG_IOMMU_HELPER=y +# CONFIG_XEN is not set +# CONFIG_ARM_FLUSH_CONSOLE_ON_RESTART is not set + +# +# Boot options +# +CONFIG_USE_OF=y +CONFIG_ATAGS=y +# CONFIG_DEPRECATED_PARAM_STRUCT is not set +# CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE is not set +CONFIG_ZBOOT_ROM_TEXT=0 +CONFIG_ZBOOT_ROM_BSS=0 +CONFIG_ARM_APPENDED_DTB=y +CONFIG_ARM_ATAG_DTB_COMPAT=y +CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_FROM_BOOTLOADER=y +# CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND is not set +CONFIG_CMDLINE="root=/dev/ram0 rw ramdisk=8192 initrd=0x41000000,8M console=ttySAC3,115200 init=/linuxrc mem=256M" +CONFIG_CMDLINE_FROM_BOOTLOADER=y +# CONFIG_CMDLINE_EXTEND is not set +# CONFIG_CMDLINE_FORCE is not set +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set +# CONFIG_CRASH_DUMP is not set +CONFIG_AUTO_ZRELADDR=y + +# +# CPU Power Management +# + +# +# CPU Frequency scaling +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_COMMON=y +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_GOV_USERSPACE is not set +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +# CONFIG_CPU_FREQ_GOV_INTERACTIVE is not set +# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set +# CONFIG_GENERIC_CPUFREQ_CPU0 is not set + +# +# ARM CPU frequency scaling drivers +# +CONFIG_ARM_EXYNOS_CPUFREQ=y +CONFIG_ARM_EXYNOS4210_CPUFREQ=y +CONFIG_ARM_EXYNOS4X12_CPUFREQ=y +CONFIG_ARM_EXYNOS5250_CPUFREQ=y +CONFIG_ARM_EXYNOS5440_CPUFREQ=y +# CONFIG_ARM_EXYNOS_CPU_FREQ_BOOST_SW is not set +# CONFIG_ARM_KIRKWOOD_CPUFREQ is not set + +# +# CPU Idle +# +CONFIG_CPU_IDLE=y +# CONFIG_CPU_IDLE_MULTIPLE_DRIVERS is not set +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_CPU_IDLE_GOV_MENU=y + +# +# ARM CPU Idle Drivers +# +# CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_VFP=y +CONFIG_VFPv3=y +CONFIG_NEON=y +# CONFIG_KERNEL_MODE_NEON is not set + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE=y +CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y +CONFIG_BINFMT_SCRIPT=y +# CONFIG_HAVE_AOUT is not set +CONFIG_BINFMT_MISC=y +CONFIG_COREDUMP=y + +# +# Power management options +# +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +CONFIG_HAS_WAKELOCK=y +CONFIG_WAKELOCK=y +CONFIG_PM_SLEEP=y +CONFIG_PM_SLEEP_SMP=y +# CONFIG_PM_AUTOSLEEP is not set +# CONFIG_PM_WAKELOCKS is not set +CONFIG_PM_RUNTIME=y +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_HAS_OPP=y +CONFIG_PM_OPP=y +CONFIG_PM_CLK=y +CONFIG_PM_GENERIC_DOMAINS=y +# CONFIG_WQ_POWER_EFFICIENT_DEFAULT is not set +CONFIG_PM_GENERIC_DOMAINS_SLEEP=y +CONFIG_PM_GENERIC_DOMAINS_RUNTIME=y +CONFIG_CPU_PM=y +# CONFIG_SUSPEND_TIME is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_ARM_CPU_SUSPEND=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +# CONFIG_PACKET_DIAG is not set +CONFIG_UNIX=y +# CONFIG_UNIX_DIAG is not set +CONFIG_XFRM=y +CONFIG_XFRM_ALGO=y +CONFIG_XFRM_USER=y +# CONFIG_XFRM_SUB_POLICY is not set +CONFIG_XFRM_MIGRATE=y +# CONFIG_XFRM_STATISTICS is not set +CONFIG_NET_KEY=y +CONFIG_NET_KEY_MIGRATE=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_RARP=y +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE_DEMUX is not set +CONFIG_NET_IP_TUNNEL=y +# CONFIG_IP_MROUTE is not set +CONFIG_SYN_COOKIES=y +# CONFIG_NET_IPVTI is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +CONFIG_INET_TUNNEL=y +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_INET_UDP_DIAG is not set +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +CONFIG_IPV6=y +# CONFIG_IPV6_ROUTER_PREF is not set +# CONFIG_IPV6_OPTIMISTIC_DAD is not set +# CONFIG_INET6_AH is not set +# CONFIG_INET6_ESP is not set +# CONFIG_INET6_IPCOMP is not set +# CONFIG_IPV6_MIP6 is not set +# CONFIG_INET6_XFRM_TUNNEL is not set +# CONFIG_INET6_TUNNEL is not set +CONFIG_INET6_XFRM_MODE_TRANSPORT=y +CONFIG_INET6_XFRM_MODE_TUNNEL=y +CONFIG_INET6_XFRM_MODE_BEET=y +# CONFIG_INET6_XFRM_MODE_ROUTEOPTIMIZATION is not set +# CONFIG_IPV6_VTI is not set +CONFIG_IPV6_SIT=y +# CONFIG_IPV6_SIT_6RD is not set +CONFIG_IPV6_NDISC_NODETYPE=y +# CONFIG_IPV6_TUNNEL is not set +# CONFIG_IPV6_GRE is not set +# CONFIG_IPV6_MULTIPLE_TABLES is not set +# CONFIG_IPV6_MROUTE is not set +CONFIG_NETLABEL=y +# CONFIG_ANDROID_PARANOID_NETWORK is not set +# CONFIG_NET_ACTIVITY_STATS is not set +CONFIG_NETWORK_SECMARK=y +# CONFIG_NETWORK_PHY_TIMESTAMPING is not set +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_DEBUG is not set +CONFIG_NETFILTER_ADVANCED=y +CONFIG_BRIDGE_NETFILTER=y + +# +# Core Netfilter Configuration +# +# CONFIG_NETFILTER_NETLINK_ACCT is not set +# CONFIG_NETFILTER_NETLINK_QUEUE is not set +# CONFIG_NETFILTER_NETLINK_LOG is not set +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_MARK=y +# CONFIG_NF_CONNTRACK_SECMARK is not set +CONFIG_NF_CONNTRACK_PROCFS=y +# CONFIG_NF_CONNTRACK_EVENTS is not set +# CONFIG_NF_CONNTRACK_TIMEOUT is not set +# CONFIG_NF_CONNTRACK_TIMESTAMP is not set +# CONFIG_NF_CT_PROTO_DCCP is not set +# CONFIG_NF_CT_PROTO_SCTP is not set +# CONFIG_NF_CT_PROTO_UDPLITE is not set +# CONFIG_NF_CONNTRACK_AMANDA is not set +# CONFIG_NF_CONNTRACK_FTP is not set +# CONFIG_NF_CONNTRACK_H323 is not set +# CONFIG_NF_CONNTRACK_IRC is not set +# CONFIG_NF_CONNTRACK_NETBIOS_NS is not set +# CONFIG_NF_CONNTRACK_SNMP is not set +# CONFIG_NF_CONNTRACK_PPTP is not set +# CONFIG_NF_CONNTRACK_SANE is not set +# CONFIG_NF_CONNTRACK_SIP is not set +# CONFIG_NF_CONNTRACK_TFTP is not set +# CONFIG_NF_CT_NETLINK is not set +# CONFIG_NF_CT_NETLINK_TIMEOUT is not set +CONFIG_NF_NAT=m +CONFIG_NF_NAT_NEEDED=y +# CONFIG_NF_NAT_AMANDA is not set +# CONFIG_NF_NAT_FTP is not set +# CONFIG_NF_NAT_IRC is not set +# CONFIG_NF_NAT_SIP is not set +# CONFIG_NF_NAT_TFTP is not set +# CONFIG_NF_TABLES is not set +CONFIG_NETFILTER_XTABLES=m + +# +# Xtables combined modules +# +CONFIG_NETFILTER_XT_MARK=m +CONFIG_NETFILTER_XT_CONNMARK=m + +# +# Xtables targets +# +# CONFIG_NETFILTER_XT_TARGET_AUDIT is not set +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +# CONFIG_NETFILTER_XT_TARGET_CLASSIFY is not set +# CONFIG_NETFILTER_XT_TARGET_CONNMARK is not set +# CONFIG_NETFILTER_XT_TARGET_DSCP is not set +# CONFIG_NETFILTER_XT_TARGET_HL is not set +# CONFIG_NETFILTER_XT_TARGET_HMARK is not set +# CONFIG_NETFILTER_XT_TARGET_IDLETIMER is not set +# CONFIG_NETFILTER_XT_TARGET_LED is not set +CONFIG_NETFILTER_XT_TARGET_LOG=m +# CONFIG_NETFILTER_XT_TARGET_MARK is not set +# CONFIG_NETFILTER_XT_TARGET_NETMAP is not set +# CONFIG_NETFILTER_XT_TARGET_NFLOG is not set +# CONFIG_NETFILTER_XT_TARGET_NFQUEUE is not set +# CONFIG_NETFILTER_XT_TARGET_RATEEST is not set +CONFIG_NETFILTER_XT_TARGET_REDIRECT=m +# CONFIG_NETFILTER_XT_TARGET_TEE is not set +# CONFIG_NETFILTER_XT_TARGET_TPROXY is not set +# CONFIG_NETFILTER_XT_TARGET_SECMARK is not set +# CONFIG_NETFILTER_XT_TARGET_TCPMSS is not set +# CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP is not set + +# +# Xtables matches +# +# CONFIG_NETFILTER_XT_MATCH_ADDRTYPE is not set +# CONFIG_NETFILTER_XT_MATCH_BPF is not set +# CONFIG_NETFILTER_XT_MATCH_CGROUP is not set +# CONFIG_NETFILTER_XT_MATCH_CLUSTER is not set +# CONFIG_NETFILTER_XT_MATCH_COMMENT is not set +# CONFIG_NETFILTER_XT_MATCH_CONNBYTES is not set +# CONFIG_NETFILTER_XT_MATCH_CONNLABEL is not set +# CONFIG_NETFILTER_XT_MATCH_CONNLIMIT is not set +# CONFIG_NETFILTER_XT_MATCH_CONNMARK is not set +# CONFIG_NETFILTER_XT_MATCH_CONNTRACK is not set +# CONFIG_NETFILTER_XT_MATCH_CPU is not set +# CONFIG_NETFILTER_XT_MATCH_DCCP is not set +# CONFIG_NETFILTER_XT_MATCH_DEVGROUP is not set +# CONFIG_NETFILTER_XT_MATCH_DSCP is not set +# CONFIG_NETFILTER_XT_MATCH_ECN is not set +# CONFIG_NETFILTER_XT_MATCH_ESP is not set +# CONFIG_NETFILTER_XT_MATCH_HASHLIMIT is not set +# CONFIG_NETFILTER_XT_MATCH_HELPER is not set +# CONFIG_NETFILTER_XT_MATCH_HL is not set +# CONFIG_NETFILTER_XT_MATCH_IPCOMP is not set +# CONFIG_NETFILTER_XT_MATCH_IPRANGE is not set +# CONFIG_NETFILTER_XT_MATCH_L2TP is not set +# CONFIG_NETFILTER_XT_MATCH_LENGTH is not set +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +# CONFIG_NETFILTER_XT_MATCH_MAC is not set +# CONFIG_NETFILTER_XT_MATCH_MARK is not set +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +# CONFIG_NETFILTER_XT_MATCH_NFACCT is not set +# CONFIG_NETFILTER_XT_MATCH_OWNER is not set +# CONFIG_NETFILTER_XT_MATCH_POLICY is not set +# CONFIG_NETFILTER_XT_MATCH_PHYSDEV is not set +# CONFIG_NETFILTER_XT_MATCH_PKTTYPE is not set +# CONFIG_NETFILTER_XT_MATCH_QUOTA is not set +# CONFIG_NETFILTER_XT_MATCH_QUOTA2 is not set +# CONFIG_NETFILTER_XT_MATCH_RATEEST is not set +# CONFIG_NETFILTER_XT_MATCH_REALM is not set +# CONFIG_NETFILTER_XT_MATCH_RECENT is not set +# CONFIG_NETFILTER_XT_MATCH_SCTP is not set +# CONFIG_NETFILTER_XT_MATCH_SOCKET is not set +# CONFIG_NETFILTER_XT_MATCH_STATE is not set +# CONFIG_NETFILTER_XT_MATCH_STATISTIC is not set +# CONFIG_NETFILTER_XT_MATCH_STRING is not set +# CONFIG_NETFILTER_XT_MATCH_TCPMSS is not set +# CONFIG_NETFILTER_XT_MATCH_TIME is not set +# CONFIG_NETFILTER_XT_MATCH_U32 is not set +# CONFIG_IP_SET is not set +# CONFIG_IP_VS is not set + +# +# IP: Netfilter Configuration +# +CONFIG_NF_DEFRAG_IPV4=m +CONFIG_NF_CONNTRACK_IPV4=m +CONFIG_NF_CONNTRACK_PROC_COMPAT=y +CONFIG_IP_NF_IPTABLES=m +# CONFIG_IP_NF_MATCH_AH is not set +# CONFIG_IP_NF_MATCH_ECN is not set +# CONFIG_IP_NF_MATCH_RPFILTER is not set +# CONFIG_IP_NF_MATCH_TTL is not set +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +# CONFIG_IP_NF_TARGET_SYNPROXY is not set +# CONFIG_IP_NF_TARGET_ULOG is not set +CONFIG_NF_NAT_IPV4=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +# CONFIG_IP_NF_TARGET_NETMAP is not set +# CONFIG_IP_NF_TARGET_REDIRECT is not set +# CONFIG_NF_NAT_PPTP is not set +# CONFIG_NF_NAT_H323 is not set +CONFIG_IP_NF_MANGLE=m +# CONFIG_IP_NF_TARGET_CLUSTERIP is not set +# CONFIG_IP_NF_TARGET_ECN is not set +# CONFIG_IP_NF_TARGET_TTL is not set +# CONFIG_IP_NF_RAW is not set +# CONFIG_IP_NF_SECURITY is not set +# CONFIG_IP_NF_ARPTABLES is not set + +# +# IPv6: Netfilter Configuration +# +CONFIG_NF_DEFRAG_IPV6=m +CONFIG_NF_CONNTRACK_IPV6=m +CONFIG_IP6_NF_IPTABLES=m +# CONFIG_IP6_NF_MATCH_AH is not set +# CONFIG_IP6_NF_MATCH_EUI64 is not set +# CONFIG_IP6_NF_MATCH_FRAG is not set +# CONFIG_IP6_NF_MATCH_OPTS is not set +# CONFIG_IP6_NF_MATCH_HL is not set +# CONFIG_IP6_NF_MATCH_IPV6HEADER is not set +# CONFIG_IP6_NF_MATCH_MH is not set +# CONFIG_IP6_NF_MATCH_RPFILTER is not set +# CONFIG_IP6_NF_MATCH_RT is not set +# CONFIG_IP6_NF_TARGET_HL is not set +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +# CONFIG_IP6_NF_TARGET_SYNPROXY is not set +CONFIG_IP6_NF_MANGLE=m +# CONFIG_IP6_NF_RAW is not set +# CONFIG_IP6_NF_SECURITY is not set +CONFIG_NF_NAT_IPV6=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +# CONFIG_IP6_NF_TARGET_NPT is not set +CONFIG_BRIDGE_NF_EBTABLES=m +# CONFIG_BRIDGE_EBT_BROUTE is not set +# CONFIG_BRIDGE_EBT_T_FILTER is not set +CONFIG_BRIDGE_EBT_T_NAT=m +# CONFIG_BRIDGE_EBT_802_3 is not set +# CONFIG_BRIDGE_EBT_AMONG is not set +# CONFIG_BRIDGE_EBT_ARP is not set +# CONFIG_BRIDGE_EBT_IP is not set +# CONFIG_BRIDGE_EBT_IP6 is not set +# CONFIG_BRIDGE_EBT_LIMIT is not set +# CONFIG_BRIDGE_EBT_MARK is not set +# CONFIG_BRIDGE_EBT_PKTTYPE is not set +# CONFIG_BRIDGE_EBT_STP is not set +# CONFIG_BRIDGE_EBT_VLAN is not set +# CONFIG_BRIDGE_EBT_ARPREPLY is not set +# CONFIG_BRIDGE_EBT_DNAT is not set +CONFIG_BRIDGE_EBT_MARK_T=m +# CONFIG_BRIDGE_EBT_REDIRECT is not set +# CONFIG_BRIDGE_EBT_SNAT is not set +# CONFIG_BRIDGE_EBT_LOG is not set +# CONFIG_BRIDGE_EBT_ULOG is not set +# CONFIG_BRIDGE_EBT_NFLOG is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_RDS is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_L2TP is not set +CONFIG_STP=m +CONFIG_BRIDGE=m +CONFIG_BRIDGE_IGMP_SNOOPING=y +CONFIG_HAVE_NET_DSA=y +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +CONFIG_LLC=m +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_PHONET is not set +# CONFIG_IEEE802154 is not set +# CONFIG_NET_SCHED is not set +# CONFIG_DCB is not set +CONFIG_DNS_RESOLVER=y +# CONFIG_BATMAN_ADV is not set +# CONFIG_OPENVSWITCH is not set +# CONFIG_VSOCKETS is not set +# CONFIG_NETLINK_MMAP is not set +# CONFIG_NETLINK_DIAG is not set +# CONFIG_NET_MPLS_GSO is not set +# CONFIG_HSR is not set +CONFIG_RPS=y +CONFIG_RFS_ACCEL=y +CONFIG_XPS=y +# CONFIG_CGROUP_NET_PRIO is not set +# CONFIG_CGROUP_NET_CLASSID is not set +CONFIG_NET_RX_BUSY_POLL=y +CONFIG_BQL=y +# CONFIG_BPF_JIT is not set +CONFIG_NET_FLOW_LIMIT=y + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_NET_DROP_MONITOR is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +# CONFIG_IRDA is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +CONFIG_WIRELESS=y +# CONFIG_CFG80211 is not set +# CONFIG_LIB80211 is not set + +# +# CFG80211 needs to be enabled for MAC80211 +# +# CONFIG_WIMAX is not set +# CONFIG_RFKILL is not set +# CONFIG_RFKILL_REGULATOR is not set +# CONFIG_NET_9P is not set +# CONFIG_CAIF is not set +# CONFIG_CEPH_LIB is not set +# CONFIG_NFC is not set +CONFIG_HAVE_BPF_JIT=y + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="edid-1920x1080.fw" +CONFIG_EXTRA_FIRMWARE_DIR="firmware" +CONFIG_FW_LOADER_USER_HELPER=y +# CONFIG_DEBUG_DRIVER is not set +# CONFIG_DEBUG_DEVRES is not set +# CONFIG_SYS_HYPERVISOR is not set +# CONFIG_GENERIC_CPU_DEVICES is not set +CONFIG_REGMAP=y +CONFIG_REGMAP_I2C=y +CONFIG_REGMAP_IRQ=y +CONFIG_DMA_SHARED_BUFFER=y +CONFIG_DMA_CMA=y + +# +# Default contiguous memory area size: +# +CONFIG_CMA_SIZE_MBYTES=128 +CONFIG_CMA_SIZE_SEL_MBYTES=y +# CONFIG_CMA_SIZE_SEL_PERCENTAGE is not set +# CONFIG_CMA_SIZE_SEL_MIN is not set +# CONFIG_CMA_SIZE_SEL_MAX is not set +CONFIG_CMA_ALIGNMENT=8 +CONFIG_CMA_AREAS=7 + +# +# Bus devices +# +# CONFIG_ARM_CCI is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_TESTS is not set +# CONFIG_MTD_REDBOOT_PARTS is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +CONFIG_MTD_OF_PARTS=y +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_SM_FTL is not set +CONFIG_MTD_OOPS=y +# CONFIG_MTD_SWAP is not set + +# +# RAM/ROM/Flash chip drivers +# +CONFIG_MTD_CFI=y +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_GEN_PROBE=y +# CONFIG_MTD_CFI_ADV_OPTIONS is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +CONFIG_MTD_CFI_INTELEXT=y +# CONFIG_MTD_CFI_AMDSTD is not set +# CONFIG_MTD_CFI_STAA is not set +CONFIG_MTD_CFI_UTIL=y +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PHYSMAP is not set +# CONFIG_MTD_PHYSMAP_OF is not set +# CONFIG_MTD_PLATRAM is not set + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOCG3 is not set +CONFIG_MTD_NAND_IDS=y +CONFIG_MTD_NAND_ECC=y +# CONFIG_MTD_NAND_ECC_SMC is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_ECC_BCH is not set +# CONFIG_MTD_SM_COMMON is not set +# CONFIG_MTD_NAND_DENALI is not set +# CONFIG_MTD_NAND_GPIO is not set +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_DOCG4 is not set +# CONFIG_MTD_NAND_NANDSIM is not set +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ONENAND is not set + +# +# LPDDR flash memory drivers +# +# CONFIG_MTD_LPDDR is not set +# CONFIG_MTD_UBI is not set +CONFIG_DTC=y +CONFIG_OF=y + +# +# Device Tree and Open Firmware support +# +CONFIG_PROC_DEVICETREE=y +# CONFIG_OF_SELFTEST is not set +CONFIG_OF_FLATTREE=y +CONFIG_OF_EARLY_FLATTREE=y +CONFIG_OF_ADDRESS=y +CONFIG_OF_IRQ=y +CONFIG_OF_NET=y +CONFIG_OF_MDIO=y +CONFIG_OF_MTD=y +# CONFIG_PARPORT is not set +CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_NULL_BLK is not set +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=8 +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_DRBD is not set +# CONFIG_BLK_DEV_NBD is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=65536 +# CONFIG_BLK_DEV_XIP is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +# CONFIG_MG_DISK is not set +# CONFIG_BLK_DEV_RBD is not set + +# +# Misc devices +# +# CONFIG_SENSORS_LIS3LV02D is not set +# CONFIG_AD525X_DPOT is not set +# CONFIG_ATMEL_PWM is not set +# CONFIG_DUMMY_IRQ is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ATMEL_SSC is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_APDS9802ALS is not set +# CONFIG_ISL29003 is not set +# CONFIG_ISL29020 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_SENSORS_BH1780 is not set +# CONFIG_SENSORS_BH1770 is not set +# CONFIG_SENSORS_APDS990X is not set +# CONFIG_HMC6352 is not set +# CONFIG_DS1682 is not set +# CONFIG_UID_STAT is not set +# CONFIG_BMP085_I2C is not set +# CONFIG_USB_SWITCH_FSA9480 is not set +# CONFIG_SRAM is not set +# CONFIG_C2PORT is not set + +# +# EEPROM support +# +# CONFIG_EEPROM_AT24 is not set +# CONFIG_EEPROM_LEGACY is not set +# CONFIG_EEPROM_MAX6875 is not set +CONFIG_EEPROM_93CX6=y + +# +# Texas Instruments shared transport line discipline +# +# CONFIG_TI_ST is not set +# CONFIG_SENSORS_LIS3_I2C is not set + +# +# Altera FPGA firmware download module +# +# CONFIG_ALTERA_STAPL is not set + +# +# Intel MIC Host Driver +# + +# +# Intel MIC Card Driver +# + +# +# SCSI device support +# +CONFIG_SCSI_MOD=y +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +# CONFIG_BLK_DEV_SD is not set +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_BLK_DEV_SR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set +# CONFIG_SCSI_MULTI_LUN is not set +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_ISCSI_BOOT_SYSFS is not set +# CONFIG_SCSI_UFSHCD is not set +# CONFIG_LIBFC is not set +# CONFIG_LIBFCOE is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_DH is not set +# CONFIG_SCSI_OSD_INITIATOR is not set +# CONFIG_ATA is not set +CONFIG_MD=y +# CONFIG_BLK_DEV_MD is not set +# CONFIG_BCACHE is not set +CONFIG_BLK_DEV_DM_BUILTIN=y +CONFIG_BLK_DEV_DM=y +# CONFIG_DM_DEBUG is not set +# CONFIG_DM_CRYPT is not set +# CONFIG_DM_SNAPSHOT is not set +# CONFIG_DM_THIN_PROVISIONING is not set +# CONFIG_DM_CACHE is not set +# CONFIG_DM_MIRROR is not set +# CONFIG_DM_RAID is not set +# CONFIG_DM_ZERO is not set +# CONFIG_DM_MULTIPATH is not set +# CONFIG_DM_DELAY is not set +# CONFIG_DM_UEVENT is not set +# CONFIG_DM_FLAKEY is not set +# CONFIG_DM_VERITY is not set +# CONFIG_DM_SWITCH is not set +# CONFIG_TARGET_CORE is not set +CONFIG_NETDEVICES=y +CONFIG_MII=y +CONFIG_NET_CORE=y +# CONFIG_BONDING is not set +# CONFIG_DUMMY is not set +# CONFIG_EQUALIZER is not set +# CONFIG_NET_TEAM is not set +# CONFIG_MACVLAN is not set +# CONFIG_VXLAN is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_NLMON is not set + +# +# CAIF transport drivers +# + +# +# Distributed Switch Architecture drivers +# +# CONFIG_NET_DSA_MV88E6XXX is not set +# CONFIG_NET_DSA_MV88E6060 is not set +# CONFIG_NET_DSA_MV88E6XXX_NEED_PPU is not set +# CONFIG_NET_DSA_MV88E6131 is not set +# CONFIG_NET_DSA_MV88E6123_61_65 is not set +CONFIG_ETHERNET=y +CONFIG_NET_VENDOR_ARC=y +# CONFIG_ARC_EMAC is not set +CONFIG_NET_CADENCE=y +# CONFIG_ARM_AT91_ETHER is not set +# CONFIG_MACB is not set +CONFIG_NET_VENDOR_BROADCOM=y +# CONFIG_B44 is not set +# CONFIG_NET_CALXEDA_XGMAC is not set +CONFIG_NET_VENDOR_CIRRUS=y +# CONFIG_CS89x0 is not set +# CONFIG_DM9000 is not set +# CONFIG_DNET is not set +CONFIG_NET_VENDOR_FARADAY=y +# CONFIG_FTMAC100 is not set +# CONFIG_FTGMAC100 is not set +CONFIG_NET_VENDOR_INTEL=y +CONFIG_NET_VENDOR_I825XX=y +CONFIG_NET_VENDOR_MARVELL=y +# CONFIG_MVMDIO is not set +CONFIG_NET_VENDOR_MICREL=y +# CONFIG_KS8842 is not set +# CONFIG_KS8851_MLL is not set +CONFIG_NET_VENDOR_NATSEMI=y +CONFIG_NET_VENDOR_8390=y +CONFIG_AX88796=y +CONFIG_AX88796_93CX6=y +# CONFIG_ETHOC is not set +# CONFIG_SH_ETH is not set +CONFIG_NET_VENDOR_SEEQ=y +CONFIG_NET_VENDOR_SMSC=y +# CONFIG_SMC91X is not set +# CONFIG_SMC911X is not set +# CONFIG_SMSC911X is not set +CONFIG_NET_VENDOR_STMICRO=y +# CONFIG_STMMAC_ETH is not set +CONFIG_NET_VENDOR_VIA=y +# CONFIG_VIA_VELOCITY is not set +CONFIG_NET_VENDOR_WIZNET=y +# CONFIG_WIZNET_W5100 is not set +# CONFIG_WIZNET_W5300 is not set +CONFIG_PHYLIB=y + +# +# MII PHY device drivers +# +# CONFIG_AT803X_PHY is not set +# CONFIG_AMD_PHY is not set +# CONFIG_MARVELL_PHY is not set +# CONFIG_DAVICOM_PHY is not set +# CONFIG_QSEMI_PHY is not set +# CONFIG_LXT_PHY is not set +# CONFIG_CICADA_PHY is not set +# CONFIG_VITESSE_PHY is not set +# CONFIG_SMSC_PHY is not set +# CONFIG_BROADCOM_PHY is not set +# CONFIG_BCM87XX_PHY is not set +# CONFIG_ICPLUS_PHY is not set +# CONFIG_REALTEK_PHY is not set +# CONFIG_NATIONAL_PHY is not set +# CONFIG_STE10XP is not set +# CONFIG_LSI_ET1011C_PHY is not set +# CONFIG_MICREL_PHY is not set +# CONFIG_FIXED_PHY is not set +CONFIG_MDIO_BITBANG=y +# CONFIG_MDIO_GPIO is not set +# CONFIG_MDIO_BUS_MUX_GPIO is not set +# CONFIG_MDIO_BUS_MUX_MMIOREG is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +# CONFIG_USB_RTL8152 is not set +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=y +CONFIG_USB_NET_AX88179_178A=y +CONFIG_USB_NET_CDCETHER=y +# CONFIG_USB_NET_CDC_EEM is not set +CONFIG_USB_NET_CDC_NCM=y +# CONFIG_USB_NET_HUAWEI_CDC_NCM is not set +# CONFIG_USB_NET_CDC_MBIM is not set +CONFIG_USB_NET_DM9601=y +# CONFIG_USB_NET_SR9700 is not set +# CONFIG_USB_NET_SR9800 is not set +# CONFIG_USB_NET_SMSC75XX is not set +CONFIG_USB_NET_SMSC95XX=m +# CONFIG_USB_NET_GL620A is not set +CONFIG_USB_NET_NET1080=y +# CONFIG_USB_NET_PLUSB is not set +CONFIG_USB_NET_MCS7830=y +# CONFIG_USB_NET_RNDIS_HOST is not set +CONFIG_USB_NET_CDC_SUBSET=y +# CONFIG_USB_ALI_M5632 is not set +# CONFIG_USB_AN2720 is not set +CONFIG_USB_BELKIN=y +CONFIG_USB_ARMLINUX=y +# CONFIG_USB_EPSON2888 is not set +# CONFIG_USB_KC2190 is not set +CONFIG_USB_NET_ZAURUS=y +# CONFIG_USB_NET_CX82310_ETH is not set +# CONFIG_USB_NET_KALMIA is not set +# CONFIG_USB_NET_QMI_WWAN is not set +# CONFIG_USB_NET_INT51X1 is not set +# CONFIG_USB_IPHETH is not set +# CONFIG_USB_SIERRA_NET is not set +# CONFIG_USB_VL600 is not set +CONFIG_WLAN=y +# CONFIG_USB_ZD1201 is not set +# CONFIG_WIFI_CONTROL_FUNC is not set +# CONFIG_HOSTAP is not set +# CONFIG_WL_TI is not set + +# +# Enable WiMAX (Networking options) to see the WiMAX drivers +# +# CONFIG_WAN is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set +# CONFIG_INPUT_SPARSEKMAP is not set +CONFIG_INPUT_MATRIXKMAP=y + +# +# Userland interfaces +# +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_MOUSEDEV_PSAUX=y +CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768 +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set +# CONFIG_INPUT_KEYRESET is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ADP5588 is not set +# CONFIG_KEYBOARD_ADP5589 is not set +CONFIG_KEYBOARD_ATKBD=y +# CONFIG_KEYBOARD_QT1070 is not set +# CONFIG_KEYBOARD_QT2160 is not set +# CONFIG_KEYBOARD_LKKBD is not set +CONFIG_KEYBOARD_GPIO=y +# CONFIG_KEYBOARD_TCA6416 is not set +# CONFIG_KEYBOARD_TCA8418 is not set +# CONFIG_KEYBOARD_MATRIX is not set +# CONFIG_KEYBOARD_LM8323 is not set +# CONFIG_KEYBOARD_LM8333 is not set +# CONFIG_KEYBOARD_MAX7359 is not set +# CONFIG_KEYBOARD_MCS is not set +# CONFIG_KEYBOARD_MPR121 is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_OPENCORES is not set +CONFIG_KEYBOARD_SAMSUNG=y +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +CONFIG_INPUT_MOUSE=y +CONFIG_MOUSE_PS2=y +CONFIG_MOUSE_PS2_ALPS=y +CONFIG_MOUSE_PS2_LOGIPS2PP=y +CONFIG_MOUSE_PS2_SYNAPTICS=y +CONFIG_MOUSE_PS2_CYPRESS=y +CONFIG_MOUSE_PS2_TRACKPOINT=y +# CONFIG_MOUSE_PS2_ELANTECH is not set +# CONFIG_MOUSE_PS2_SENTELIC is not set +# CONFIG_MOUSE_PS2_TOUCHKIT is not set +# CONFIG_MOUSE_SERIAL is not set +# CONFIG_MOUSE_APPLETOUCH is not set +# CONFIG_MOUSE_BCM5974 is not set +# CONFIG_MOUSE_CYAPA is not set +# CONFIG_MOUSE_VSXXXAA is not set +# CONFIG_MOUSE_GPIO is not set +# CONFIG_MOUSE_SYNAPTICS_I2C is not set +# CONFIG_MOUSE_SYNAPTICS_USB is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_AD7879 is not set +# CONFIG_TOUCHSCREEN_ATMEL_MXT is not set +# CONFIG_TOUCHSCREEN_AUO_PIXCIR is not set +# CONFIG_TOUCHSCREEN_BU21013 is not set +# CONFIG_TOUCHSCREEN_CY8CTMG110 is not set +# CONFIG_TOUCHSCREEN_CYTTSP_CORE is not set +# CONFIG_TOUCHSCREEN_CYTTSP4_CORE is not set +# CONFIG_TOUCHSCREEN_DYNAPRO is not set +# CONFIG_TOUCHSCREEN_HAMPSHIRE is not set +# CONFIG_TOUCHSCREEN_EETI is not set +# CONFIG_TOUCHSCREEN_EGALAX is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_ILI210X is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_WACOM_W8001 is not set +# CONFIG_TOUCHSCREEN_WACOM_I2C is not set +# CONFIG_TOUCHSCREEN_MAX11801 is not set +# CONFIG_TOUCHSCREEN_MCS5000 is not set +# CONFIG_TOUCHSCREEN_MMS114 is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_EDT_FT5X06 is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_PIXCIR is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_TOUCHSCREEN_TSC_SERIO is not set +# CONFIG_TOUCHSCREEN_TSC2007 is not set +# CONFIG_TOUCHSCREEN_W90X900 is not set +# CONFIG_TOUCHSCREEN_ST1232 is not set +# CONFIG_TOUCHSCREEN_SUR40 is not set +# CONFIG_TOUCHSCREEN_TPS6507X is not set +# CONFIG_TOUCHSCREEN_ZFORCE is not set +# CONFIG_TOUCHSCREEN_MXT224E is not set +CONFIG_INPUT_MISC=y +# CONFIG_INPUT_AD714X is not set +# CONFIG_INPUT_BMA150 is not set +# CONFIG_INPUT_MMA8450 is not set +# CONFIG_INPUT_MPU3050 is not set +# CONFIG_INPUT_GP2A is not set +# CONFIG_INPUT_GPIO_BEEPER is not set +# CONFIG_INPUT_GPIO_TILT_POLLED is not set +# CONFIG_INPUT_ATI_REMOTE2 is not set +# CONFIG_INPUT_KEYCHORD is not set +# CONFIG_INPUT_KEYSPAN_REMOTE is not set +# CONFIG_INPUT_KXTJ9 is not set +# CONFIG_INPUT_POWERMATE is not set +# CONFIG_INPUT_YEALINK is not set +# CONFIG_INPUT_CM109 is not set +CONFIG_INPUT_UINPUT=y +# CONFIG_INPUT_GPIO is not set +# CONFIG_INPUT_PCF8574 is not set +# CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set +# CONFIG_INPUT_ADXL34X is not set +# CONFIG_INPUT_IMS_PCU is not set +# CONFIG_INPUT_CMA3000 is not set + +# +# Hardware I/O ports +# +CONFIG_SERIO=y +CONFIG_SERIO_SERPORT=y +# CONFIG_SERIO_AMBAKMI is not set +CONFIG_SERIO_LIBPS2=y +# CONFIG_SERIO_RAW is not set +# CONFIG_SERIO_ALTERA_PS2 is not set +# CONFIG_SERIO_PS2MULT is not set +# CONFIG_SERIO_ARC_PS2 is not set +# CONFIG_SERIO_APBPS2 is not set +# CONFIG_SERIO_OLPC_APSP is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_TTY=y +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_VT_CONSOLE_SLEEP=y +CONFIG_HW_CONSOLE=y +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_UNIX98_PTYS=y +# CONFIG_DEVPTS_MULTIPLE_INSTANCES is not set +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_SERIAL_NONSTANDARD is not set +# CONFIG_N_GSM is not set +# CONFIG_TRACE_SINK is not set +CONFIG_DEVMEM=y +# CONFIG_DEVKMEM is not set + +# +# Serial drivers +# +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_DEPRECATED_OPTIONS=y +# CONFIG_SERIAL_8250_CONSOLE is not set +CONFIG_SERIAL_8250_DMA=y +CONFIG_SERIAL_8250_NR_UARTS=4 +CONFIG_SERIAL_8250_RUNTIME_UARTS=4 +# CONFIG_SERIAL_8250_EXTENDED is not set +# CONFIG_SERIAL_8250_DW is not set +# CONFIG_SERIAL_8250_EM is not set + +# +# Non-8250 serial port support +# +# CONFIG_SERIAL_AMBA_PL010 is not set +# CONFIG_SERIAL_AMBA_PL011 is not set +CONFIG_SERIAL_SAMSUNG=y +CONFIG_SERIAL_SAMSUNG_UARTS_4=y +CONFIG_SERIAL_SAMSUNG_UARTS=4 +CONFIG_SERIAL_SAMSUNG_CONSOLE=y +# CONFIG_SERIAL_SH_SCI is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +# CONFIG_SERIAL_OF_PLATFORM is not set +# CONFIG_SERIAL_SCCNXP is not set +# CONFIG_SERIAL_TIMBERDALE is not set +# CONFIG_SERIAL_ALTERA_JTAGUART is not set +# CONFIG_SERIAL_ALTERA_UART is not set +# CONFIG_SERIAL_XILINX_PS_UART is not set +# CONFIG_SERIAL_ARC is not set +# CONFIG_SERIAL_FSL_LPUART is not set +# CONFIG_SERIAL_ST_ASC is not set +# CONFIG_TTY_PRINTK is not set +# CONFIG_HVC_DCC is not set +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_HW_RANDOM_TIMERIOMEM is not set +# CONFIG_HW_RANDOM_ATMEL is not set +# CONFIG_HW_RANDOM_EXYNOS is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +# CONFIG_DCC_TTY is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_COMPAT=y +# CONFIG_I2C_CHARDEV is not set +# CONFIG_I2C_MUX is not set +CONFIG_I2C_HELPER_AUTO=y +CONFIG_I2C_ALGOBIT=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +# CONFIG_I2C_CBUS_GPIO is not set +# CONFIG_I2C_DESIGNWARE_PLATFORM is not set +CONFIG_I2C_EXYNOS5=y +# CONFIG_I2C_GPIO is not set +# CONFIG_I2C_NOMADIK is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_PXA_PCI is not set +CONFIG_HAVE_S3C2410_I2C=y +CONFIG_I2C_S3C2410=y +# CONFIG_I2C_SIMTEC is not set +# CONFIG_I2C_XILINX is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_DIOLAN_U2C is not set +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_ROBOTFUZZ_OSIF is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_STUB is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_SPI is not set +# CONFIG_HSI is not set + +# +# PPS support +# +# CONFIG_PPS is not set + +# +# PPS generators support +# + +# +# PTP clock support +# +# CONFIG_PTP_1588_CLOCK is not set + +# +# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks. +# +CONFIG_PINCTRL=y + +# +# Pin controllers +# +CONFIG_PINMUX=y +CONFIG_PINCONF=y +# CONFIG_DEBUG_PINCTRL is not set +# CONFIG_PINCTRL_CAPRI is not set +# CONFIG_PINCTRL_MSM8X74 is not set +# CONFIG_PINCTRL_SINGLE is not set +CONFIG_PINCTRL_SAMSUNG=y +CONFIG_PINCTRL_EXYNOS=y +CONFIG_PINCTRL_EXYNOS5440=y +CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y +CONFIG_ARCH_REQUIRE_GPIOLIB=y +CONFIG_GPIOLIB=y +CONFIG_GPIO_DEVRES=y +CONFIG_OF_GPIO=y +# CONFIG_DEBUG_GPIO is not set +# CONFIG_GPIO_SYSFS is not set + +# +# Memory mapped GPIO drivers: +# +# CONFIG_GPIO_GENERIC_PLATFORM is not set +# CONFIG_GPIO_EM is not set +# CONFIG_GPIO_PL061 is not set +# CONFIG_GPIO_RCAR is not set +# CONFIG_GPIO_SCH311X is not set +# CONFIG_GPIO_TS5500 is not set +# CONFIG_GPIO_GRGPIO is not set + +# +# I2C GPIO expanders: +# +# CONFIG_GPIO_MAX7300 is not set +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCF857X is not set +# CONFIG_GPIO_SX150X is not set +# CONFIG_GPIO_ADP5588 is not set +# CONFIG_GPIO_ADNP is not set + +# +# PCI GPIO expanders: +# + +# +# SPI GPIO expanders: +# +# CONFIG_GPIO_MCP23S08 is not set + +# +# AC97 GPIO expanders: +# + +# +# LPC GPIO expanders: +# + +# +# MODULbus GPIO expanders: +# +# CONFIG_GPIO_BCM_KONA is not set + +# +# USB GPIO expanders: +# +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_POWER_AVS is not set +CONFIG_HWMON=y +# CONFIG_HWMON_VID is not set +# CONFIG_HWMON_DEBUG_CHIP is not set + +# +# Native drivers +# +# CONFIG_SENSORS_AD7414 is not set +# CONFIG_SENSORS_AD7418 is not set +# CONFIG_SENSORS_ADM1021 is not set +# CONFIG_SENSORS_ADM1025 is not set +# CONFIG_SENSORS_ADM1026 is not set +# CONFIG_SENSORS_ADM1029 is not set +# CONFIG_SENSORS_ADM1031 is not set +# CONFIG_SENSORS_ADM9240 is not set +# CONFIG_SENSORS_ADT7410 is not set +# CONFIG_SENSORS_ADT7411 is not set +# CONFIG_SENSORS_ADT7462 is not set +# CONFIG_SENSORS_ADT7470 is not set +# CONFIG_SENSORS_ADT7475 is not set +# CONFIG_SENSORS_ASC7621 is not set +# CONFIG_SENSORS_ATXP1 is not set +# CONFIG_SENSORS_DS620 is not set +# CONFIG_SENSORS_DS1621 is not set +# CONFIG_SENSORS_F71805F is not set +# CONFIG_SENSORS_F71882FG is not set +# CONFIG_SENSORS_F75375S is not set +# CONFIG_SENSORS_G760A is not set +# CONFIG_SENSORS_G762 is not set +# CONFIG_SENSORS_GL518SM is not set +# CONFIG_SENSORS_GL520SM is not set +# CONFIG_SENSORS_GPIO_FAN is not set +# CONFIG_SENSORS_HIH6130 is not set +# CONFIG_SENSORS_HTU21 is not set +# CONFIG_SENSORS_IT87 is not set +# CONFIG_SENSORS_JC42 is not set +# CONFIG_SENSORS_LINEAGE is not set +# CONFIG_SENSORS_LM63 is not set +# CONFIG_SENSORS_LM73 is not set +# CONFIG_SENSORS_LM75 is not set +# CONFIG_SENSORS_LM77 is not set +# CONFIG_SENSORS_LM78 is not set +# CONFIG_SENSORS_LM80 is not set +# CONFIG_SENSORS_LM83 is not set +# CONFIG_SENSORS_LM85 is not set +# CONFIG_SENSORS_LM87 is not set +# CONFIG_SENSORS_LM90 is not set +# CONFIG_SENSORS_LM92 is not set +# CONFIG_SENSORS_LM93 is not set +# CONFIG_SENSORS_LTC4151 is not set +# CONFIG_SENSORS_LTC4215 is not set +# CONFIG_SENSORS_LTC4245 is not set +# CONFIG_SENSORS_LTC4261 is not set +# CONFIG_SENSORS_LM95234 is not set +# CONFIG_SENSORS_LM95241 is not set +# CONFIG_SENSORS_LM95245 is not set +# CONFIG_SENSORS_MAX16065 is not set +# CONFIG_SENSORS_MAX1619 is not set +# CONFIG_SENSORS_MAX1668 is not set +# CONFIG_SENSORS_MAX197 is not set +# CONFIG_SENSORS_MAX6639 is not set +# CONFIG_SENSORS_MAX6642 is not set +# CONFIG_SENSORS_MAX6650 is not set +# CONFIG_SENSORS_MAX6697 is not set +# CONFIG_SENSORS_MCP3021 is not set +# CONFIG_SENSORS_NCT6775 is not set +# CONFIG_SENSORS_PC87360 is not set +# CONFIG_SENSORS_PC87427 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_PMBUS is not set +# CONFIG_SENSORS_SHT15 is not set +# CONFIG_SENSORS_SHT21 is not set +# CONFIG_SENSORS_SMM665 is not set +# CONFIG_SENSORS_DME1737 is not set +# CONFIG_SENSORS_EMC1403 is not set +# CONFIG_SENSORS_EMC2103 is not set +# CONFIG_SENSORS_EMC6W201 is not set +# CONFIG_SENSORS_SMSC47M1 is not set +# CONFIG_SENSORS_SMSC47M192 is not set +# CONFIG_SENSORS_SMSC47B397 is not set +# CONFIG_SENSORS_SCH56XX_COMMON is not set +# CONFIG_SENSORS_ADS1015 is not set +# CONFIG_SENSORS_ADS7828 is not set +# CONFIG_SENSORS_AMC6821 is not set +# CONFIG_SENSORS_INA209 is not set +# CONFIG_SENSORS_INA2XX is not set +# CONFIG_SENSORS_THMC50 is not set +# CONFIG_SENSORS_TMP102 is not set +# CONFIG_SENSORS_TMP401 is not set +# CONFIG_SENSORS_TMP421 is not set +# CONFIG_SENSORS_VT1211 is not set +# CONFIG_SENSORS_W83781D is not set +# CONFIG_SENSORS_W83791D is not set +# CONFIG_SENSORS_W83792D is not set +# CONFIG_SENSORS_W83793 is not set +# CONFIG_SENSORS_W83795 is not set +# CONFIG_SENSORS_W83L785TS is not set +# CONFIG_SENSORS_W83L786NG is not set +# CONFIG_SENSORS_W83627HF is not set +# CONFIG_SENSORS_W83627EHF is not set +CONFIG_THERMAL=y +CONFIG_THERMAL_HWMON=y +CONFIG_THERMAL_OF=y +CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y +# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set +# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set +# CONFIG_THERMAL_GOV_FAIR_SHARE is not set +CONFIG_THERMAL_GOV_STEP_WISE=y +# CONFIG_THERMAL_GOV_USER_SPACE is not set +CONFIG_CPU_THERMAL=y +# CONFIG_THERMAL_EMULATION is not set + +# +# Texas Instruments thermal drivers +# +# CONFIG_TI_SOC_THERMAL is not set + +# +# Samsung thermal drivers +# +CONFIG_EXYNOS_THERMAL=y +# CONFIG_EXYNOS_THERMAL_CORE is not set +# CONFIG_WATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set +CONFIG_BCMA_POSSIBLE=y + +# +# Broadcom specific AMBA +# +# CONFIG_BCMA is not set + +# +# Multifunction device drivers +# +CONFIG_MFD_CORE=y +# CONFIG_MFD_AS3711 is not set +# CONFIG_MFD_AS3722 is not set +# CONFIG_PMIC_ADP5520 is not set +# CONFIG_MFD_AAT2870_CORE is not set +# CONFIG_MFD_BCM590XX is not set +# CONFIG_MFD_CROS_EC is not set +# CONFIG_MFD_ASIC3 is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_DA9052_I2C is not set +# CONFIG_MFD_DA9055 is not set +# CONFIG_MFD_DA9063 is not set +# CONFIG_MFD_MC13XXX_I2C is not set +# CONFIG_HTC_EGPIO is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_HTC_I2CPLD is not set +# CONFIG_MFD_KEMPLD is not set +# CONFIG_MFD_88PM800 is not set +# CONFIG_MFD_88PM805 is not set +# CONFIG_MFD_88PM860X is not set +# CONFIG_MFD_MAX14577 is not set +# CONFIG_MFD_MAX77686 is not set +CONFIG_MFD_MAX77XXX=y +# CONFIG_MFD_MAX77693 is not set +# CONFIG_MFD_MAX8907 is not set +# CONFIG_MFD_MAX8925 is not set +# CONFIG_MFD_MAX8997 is not set +# CONFIG_MFD_MAX8998 is not set +# CONFIG_MFD_VIPERBOARD is not set +# CONFIG_MFD_RETU is not set +# CONFIG_MFD_PCF50633 is not set +# CONFIG_MFD_RC5T583 is not set +CONFIG_MFD_SEC_CORE=y +# CONFIG_MFD_SI476X_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_MFD_SMSC is not set +# CONFIG_ABX500_CORE is not set +# CONFIG_MFD_STMPE is not set +# CONFIG_MFD_SYSCON is not set +# CONFIG_MFD_TI_AM335X_TSCADC is not set +# CONFIG_MFD_LP3943 is not set +# CONFIG_MFD_LP8788 is not set +# CONFIG_MFD_PALMAS is not set +# CONFIG_TPS6105X is not set +# CONFIG_TPS65010 is not set +# CONFIG_TPS6507X is not set +# CONFIG_MFD_TPS65090 is not set +# CONFIG_MFD_TPS65217 is not set +# CONFIG_MFD_TPS6586X is not set +# CONFIG_MFD_TPS65910 is not set +# CONFIG_MFD_TPS65912 is not set +# CONFIG_MFD_TPS65912_I2C is not set +# CONFIG_MFD_TPS80031 is not set +# CONFIG_TWL4030_CORE is not set +# CONFIG_TWL6040_CORE is not set +# CONFIG_MFD_WL1273_CORE is not set +# CONFIG_MFD_LM3533 is not set +# CONFIG_MFD_TC3589X is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_MFD_T7L66XB is not set +# CONFIG_MFD_TC6387XB is not set +# CONFIG_MFD_TC6393XB is not set +# CONFIG_MFD_ARIZONA_I2C is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM831X_I2C is not set +# CONFIG_MFD_WM8350_I2C is not set +# CONFIG_MFD_WM8994 is not set +# CONFIG_MFD_HI6421_PMIC is not set +# CONFIG_VEXPRESS_CONFIG is not set +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +CONFIG_REGULATOR_FIXED_VOLTAGE=y +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_USERSPACE_CONSUMER is not set +# CONFIG_REGULATOR_ACT8865 is not set +# CONFIG_REGULATOR_AD5398 is not set +# CONFIG_REGULATOR_DA9210 is not set +# CONFIG_REGULATOR_FAN53555 is not set +# CONFIG_REGULATOR_GPIO is not set +# CONFIG_REGULATOR_ISL6271A is not set +# CONFIG_REGULATOR_LP3971 is not set +# CONFIG_REGULATOR_LP3972 is not set +# CONFIG_REGULATOR_LP872X is not set +# CONFIG_REGULATOR_LP8755 is not set +# CONFIG_REGULATOR_MAX1586 is not set +# CONFIG_REGULATOR_MAX8649 is not set +# CONFIG_REGULATOR_MAX8660 is not set +# CONFIG_REGULATOR_MAX8952 is not set +# CONFIG_REGULATOR_MAX8973 is not set +CONFIG_REGULATOR_MAX77XXX=y +# CONFIG_REGULATOR_PFUZE100 is not set +CONFIG_REGULATOR_S2MPS11=y +# CONFIG_REGULATOR_S5M8767 is not set +# CONFIG_REGULATOR_TPS51632 is not set +# CONFIG_REGULATOR_TPS62360 is not set +# CONFIG_REGULATOR_TPS65023 is not set +# CONFIG_REGULATOR_TPS6507X is not set +# CONFIG_MEDIA_SUPPORT is not set + +# +# Graphics support +# +CONFIG_DRM=y +CONFIG_DRM_KMS_HELPER=y +CONFIG_DRM_KMS_FB_HELPER=y +CONFIG_DRM_LOAD_EDID_FIRMWARE=y + +# +# I2C encoder or helper chips +# +# CONFIG_DRM_I2C_CH7006 is not set +# CONFIG_DRM_I2C_SIL164 is not set +# CONFIG_DRM_I2C_NXP_TDA998X is not set +CONFIG_DRM_EXYNOS=y +CONFIG_DRM_EXYNOS_DMABUF=y +CONFIG_DRM_EXYNOS_FIMD=y +CONFIG_DRM_EXYNOS_HDMI=y +CONFIG_DRM_EXYNOS_VIDI=y +CONFIG_DRM_EXYNOS_G2D=y +CONFIG_DRM_EXYNOS_IPP=y +CONFIG_DRM_EXYNOS_ROTATOR=y +CONFIG_DRM_EXYNOS_GSC=y +# CONFIG_DRM_UDL is not set +# CONFIG_DRM_ARMADA is not set +# CONFIG_DRM_RCAR_DU is not set +# CONFIG_DRM_SHMOBILE is not set +# CONFIG_DRM_TILCDC is not set +# CONFIG_VGASTATE is not set +CONFIG_VIDEO_OUTPUT_CONTROL=y +CONFIG_VIDEOMODE_HELPERS=y +CONFIG_HDMI=y +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +CONFIG_FB_MODE_HELPERS=y +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +# CONFIG_FB_ARMCLCD is not set +# CONFIG_FB_ARMHDLCD is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_OPENCORES is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_TMIO is not set +# CONFIG_FB_S3C is not set +# CONFIG_FB_SMSCUFX is not set +# CONFIG_FB_UDL is not set +# CONFIG_FB_GOLDFISH is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_BROADSHEET is not set +# CONFIG_FB_AUO_K190X is not set +# CONFIG_FB_SIMPLE is not set +CONFIG_EXYNOS_VIDEO=y +# CONFIG_EXYNOS_MIPI_DSI is not set +CONFIG_EXYNOS_DP=y +# CONFIG_BACKLIGHT_LCD_SUPPORT is not set + +# +# Console display driver support +# +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +CONFIG_LOGO=y +CONFIG_LOGO_LINUX_MONO=y +CONFIG_LOGO_LINUX_VGA16=y +CONFIG_LOGO_LINUX_CLUT224=y +# CONFIG_FB_SSD1307 is not set +CONFIG_SOUND=y +# CONFIG_SOUND_OSS_CORE is not set +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +CONFIG_SND_DMAENGINE_PCM=y +CONFIG_SND_COMPRESS_OFFLOAD=y +CONFIG_SND_JACK=y +# CONFIG_SND_SEQUENCER is not set +# CONFIG_SND_MIXER_OSS is not set +# CONFIG_SND_PCM_OSS is not set +# CONFIG_SND_HRTIMER is not set +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +# CONFIG_SND_RAWMIDI_SEQ is not set +# CONFIG_SND_OPL3_LIB_SEQ is not set +# CONFIG_SND_OPL4_LIB_SEQ is not set +# CONFIG_SND_SBAWE_SEQ is not set +# CONFIG_SND_EMU10K1_SEQ is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_ALOOP is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_ARM=y +# CONFIG_SND_ARMAACI is not set +CONFIG_SND_USB=y +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_UA101 is not set +# CONFIG_SND_USB_CAIAQ is not set +# CONFIG_SND_USB_6FIRE is not set +# CONFIG_SND_USB_HIFACE is not set +CONFIG_SND_SOC=y +CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM=y +# CONFIG_SND_ATMEL_SOC is not set +# CONFIG_SND_DESIGNWARE_I2S is not set +CONFIG_SND_SOC_SAMSUNG=y +CONFIG_SND_S3C_DMA=y +CONFIG_SND_SAMSUNG_I2S=y +# CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994 is not set +# CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF is not set +# CONFIG_SND_SOC_SMDK_WM8994_PCM is not set +CONFIG_SND_SOC_SMDK_I2S_STUB=y +CONFIG_SND_SOC_I2C_AND_SPI=y +CONFIG_SND_SOC_I2S_STUB=y +# CONFIG_SND_SIMPLE_CARD is not set +# CONFIG_SOUND_PRIME is not set + +# +# HID support +# +CONFIG_HID=y +# CONFIG_HIDRAW is not set +# CONFIG_UHID is not set +CONFIG_HID_GENERIC=y + +# +# Special HID drivers +# +# CONFIG_HID_A4TECH is not set +# CONFIG_HID_ACRUX is not set +# CONFIG_HID_APPLE is not set +# CONFIG_HID_APPLEIR is not set +# CONFIG_HID_AUREAL is not set +# CONFIG_HID_BELKIN is not set +# CONFIG_HID_CHERRY is not set +# CONFIG_HID_CHICONY is not set +# CONFIG_HID_PRODIKEYS is not set +# CONFIG_HID_CYPRESS is not set +# CONFIG_HID_DRAGONRISE is not set +# CONFIG_HID_EMS_FF is not set +# CONFIG_HID_ELECOM is not set +# CONFIG_HID_ELO is not set +# CONFIG_HID_EZKEY is not set +# CONFIG_HID_HOLTEK is not set +# CONFIG_HID_HUION is not set +# CONFIG_HID_KEYTOUCH is not set +# CONFIG_HID_KYE is not set +# CONFIG_HID_UCLOGIC is not set +# CONFIG_HID_WALTOP is not set +# CONFIG_HID_GYRATION is not set +# CONFIG_HID_ICADE is not set +# CONFIG_HID_TWINHAN is not set +# CONFIG_HID_KENSINGTON is not set +# CONFIG_HID_LCPOWER is not set +# CONFIG_HID_LENOVO_TPKBD is not set +# CONFIG_HID_LOGITECH is not set +# CONFIG_HID_MAGICMOUSE is not set +# CONFIG_HID_MICROSOFT is not set +# CONFIG_HID_MONTEREY is not set +# CONFIG_HID_MULTITOUCH is not set +# CONFIG_HID_NTRIG is not set +# CONFIG_HID_ORTEK is not set +# CONFIG_HID_PANTHERLORD is not set +# CONFIG_HID_PETALYNX is not set +# CONFIG_HID_PICOLCD is not set +# CONFIG_HID_PRIMAX is not set +# CONFIG_HID_ROCCAT is not set +# CONFIG_HID_SAITEK is not set +# CONFIG_HID_SAMSUNG is not set +# CONFIG_HID_SONY is not set +# CONFIG_HID_SPEEDLINK is not set +# CONFIG_HID_STEELSERIES is not set +# CONFIG_HID_SUNPLUS is not set +# CONFIG_HID_GREENASIA is not set +# CONFIG_HID_SMARTJOYPLUS is not set +# CONFIG_HID_TIVO is not set +# CONFIG_HID_TOPSEED is not set +# CONFIG_HID_THINGM is not set +# CONFIG_HID_THRUSTMASTER is not set +# CONFIG_HID_WACOM is not set +# CONFIG_HID_WIIMOTE is not set +# CONFIG_HID_XINMO is not set +# CONFIG_HID_ZEROPLUS is not set +# CONFIG_HID_ZYDACRON is not set +# CONFIG_HID_SENSOR_HUB is not set + +# +# USB HID support +# +CONFIG_USB_HID=y +# CONFIG_HID_PID is not set +# CONFIG_USB_HIDDEV is not set + +# +# I2C HID support +# +# CONFIG_I2C_HID is not set +CONFIG_USB_OHCI_LITTLE_ENDIAN=y +CONFIG_USB_ARCH_HAS_XHCI=y +CONFIG_USB_SUPPORT=y +CONFIG_USB_COMMON=y +CONFIG_USB_ARCH_HAS_HCD=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y + +# +# Miscellaneous USB options +# +CONFIG_USB_DEFAULT_PERSIST=y +# CONFIG_USB_DYNAMIC_MINORS is not set +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +# CONFIG_USB_MON is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_PLATFORM=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +CONFIG_USB_EHCI_TT_NEWSCHED=y +CONFIG_USB_EHCI_EXYNOS=y +# CONFIG_USB_EHCI_HCD_PLATFORM is not set +# CONFIG_USB_OXU210HP_HCD is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_ISP1760_HCD is not set +# CONFIG_USB_ISP1362_HCD is not set +# CONFIG_USB_FUSBH200_HCD is not set +# CONFIG_USB_FOTG210_HCD is not set +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_OHCI_EXYNOS=y +# CONFIG_USB_OHCI_HCD_PLATFORM is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HCD_TEST_MODE is not set +# CONFIG_USB_RENESAS_USBHS is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may +# + +# +# also be needed; see USB_STORAGE Help for more info +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_REALTEK is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_STORAGE_ENE_UB6250 is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set +# CONFIG_USB_MUSB_HDRC is not set +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_HOST=y +# CONFIG_USB_DWC3_GADGET is not set +# CONFIG_USB_DWC3_DUAL_ROLE is not set + +# +# Platform Glue Driver Support +# +# CONFIG_USB_DWC3_OMAP is not set +CONFIG_USB_DWC3_EXYNOS=y +CONFIG_USB_DWC3_KEYSTONE=y + +# +# Debugging features +# +# CONFIG_USB_DWC3_DEBUG is not set +# CONFIG_USB_DWC2 is not set +# CONFIG_USB_CHIPIDEA is not set + +# +# USB port drivers +# +CONFIG_USB_SERIAL=m +# CONFIG_USB_SERIAL_GENERIC is not set +# CONFIG_USB_SERIAL_SIMPLE is not set +# CONFIG_USB_SERIAL_AIRCABLE is not set +# CONFIG_USB_SERIAL_ARK3116 is not set +# CONFIG_USB_SERIAL_BELKIN is not set +# CONFIG_USB_SERIAL_CH341 is not set +# CONFIG_USB_SERIAL_WHITEHEAT is not set +# CONFIG_USB_SERIAL_DIGI_ACCELEPORT is not set +# CONFIG_USB_SERIAL_CP210X is not set +# CONFIG_USB_SERIAL_CYPRESS_M8 is not set +# CONFIG_USB_SERIAL_EMPEG is not set +# CONFIG_USB_SERIAL_FTDI_SIO is not set +# CONFIG_USB_SERIAL_VISOR is not set +# CONFIG_USB_SERIAL_IPAQ is not set +# CONFIG_USB_SERIAL_IR is not set +# CONFIG_USB_SERIAL_EDGEPORT is not set +# CONFIG_USB_SERIAL_EDGEPORT_TI is not set +# CONFIG_USB_SERIAL_F81232 is not set +# CONFIG_USB_SERIAL_GARMIN is not set +# CONFIG_USB_SERIAL_IPW is not set +# CONFIG_USB_SERIAL_IUU is not set +# CONFIG_USB_SERIAL_KEYSPAN_PDA is not set +# CONFIG_USB_SERIAL_KEYSPAN is not set +# CONFIG_USB_SERIAL_KLSI is not set +# CONFIG_USB_SERIAL_KOBIL_SCT is not set +# CONFIG_USB_SERIAL_MCT_U232 is not set +# CONFIG_USB_SERIAL_METRO is not set +# CONFIG_USB_SERIAL_MOS7720 is not set +# CONFIG_USB_SERIAL_MOS7840 is not set +# CONFIG_USB_SERIAL_MXUPORT is not set +# CONFIG_USB_SERIAL_NAVMAN is not set +# CONFIG_USB_SERIAL_PL2303 is not set +# CONFIG_USB_SERIAL_OTI6858 is not set +# CONFIG_USB_SERIAL_QCAUX is not set +# CONFIG_USB_SERIAL_QUALCOMM is not set +# CONFIG_USB_SERIAL_SPCP8X5 is not set +# CONFIG_USB_SERIAL_SAFE is not set +# CONFIG_USB_SERIAL_SIERRAWIRELESS is not set +# CONFIG_USB_SERIAL_SYMBOL is not set +# CONFIG_USB_SERIAL_TI is not set +# CONFIG_USB_SERIAL_CYBERJACK is not set +# CONFIG_USB_SERIAL_XIRCOM is not set +# CONFIG_USB_SERIAL_OPTION is not set +# CONFIG_USB_SERIAL_OMNINET is not set +# CONFIG_USB_SERIAL_OPTICON is not set +# CONFIG_USB_SERIAL_XSENS_MT is not set +# CONFIG_USB_SERIAL_WISHBONE is not set +# CONFIG_USB_SERIAL_ZTE is not set +# CONFIG_USB_SERIAL_SSU100 is not set +# CONFIG_USB_SERIAL_QT2 is not set +# CONFIG_USB_SERIAL_DEBUG is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_EHSET_TEST_FIXTURE is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_YUREX is not set +# CONFIG_USB_EZUSB_FX2 is not set +CONFIG_USB_HSIC_USB3503=y + +# +# USB Physical Layer drivers +# +CONFIG_USB_PHY=y +# CONFIG_USB_OTG_FSM is not set +# CONFIG_USB_OTG_WAKELOCK is not set +# CONFIG_NOP_USB_XCEIV is not set +# CONFIG_AM335X_PHY_USB is not set +CONFIG_SAMSUNG_USBPHY=y +CONFIG_SAMSUNG_USB2PHY=y +CONFIG_SAMSUNG_USB3PHY=y +# CONFIG_USB_GPIO_VBUS is not set +# CONFIG_USB_ISP1301 is not set +# CONFIG_USB_RCAR_PHY is not set +# CONFIG_USB_ULPI is not set +CONFIG_USB_GADGET=y +# CONFIG_USB_GADGET_DEBUG is not set +# CONFIG_USB_GADGET_DEBUG_FILES is not set +# CONFIG_USB_GADGET_DEBUG_FS is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS=2 + +# +# USB Peripheral Controller +# +# CONFIG_USB_FUSB300 is not set +# CONFIG_USB_FOTG210_UDC is not set +# CONFIG_USB_GR_UDC is not set +# CONFIG_USB_R8A66597 is not set +# CONFIG_USB_PXA27X is not set +# CONFIG_USB_S3C_HSOTG is not set +# CONFIG_USB_MV_UDC is not set +# CONFIG_USB_MV_U3D is not set +# CONFIG_USB_M66592 is not set +# CONFIG_USB_NET2272 is not set +# CONFIG_USB_DUMMY_HCD is not set +# CONFIG_USB_CONFIGFS is not set +# CONFIG_USB_ZERO is not set +# CONFIG_USB_AUDIO is not set +# CONFIG_USB_ETH is not set +# CONFIG_USB_G_NCM is not set +# CONFIG_USB_GADGETFS is not set +# CONFIG_USB_FUNCTIONFS is not set +# CONFIG_USB_MASS_STORAGE is not set +# CONFIG_USB_G_SERIAL is not set +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +# CONFIG_USB_G_ACM_MS is not set +# CONFIG_USB_G_MULTI is not set +# CONFIG_USB_G_HID is not set +# CONFIG_USB_G_DBGP is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y +# CONFIG_MMC_CLKGATE is not set +# CONFIG_MMC_EMBEDDED_SDIO is not set +# CONFIG_MMC_PARANOID_SD_INIT is not set + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_MINORS=8 +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_MMC_BLOCK_DEFERRED_RESUME is not set +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_ARMMMCI is not set +# CONFIG_MMC_SDHCI is not set +# CONFIG_MMC_SDHCI_PXAV3 is not set +# CONFIG_MMC_SDHCI_PXAV2 is not set +CONFIG_MMC_DW=y +CONFIG_MMC_DW_IDMAC=y +CONFIG_MMC_DW_PLTFM=y +CONFIG_MMC_DW_EXYNOS=y +# CONFIG_MMC_DW_K3 is not set +# CONFIG_MMC_VUB300 is not set +# CONFIG_MMC_USHC is not set +# CONFIG_MEMSTICK is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y + +# +# LED drivers +# +# CONFIG_LEDS_LM3530 is not set +# CONFIG_LEDS_LM3642 is not set +# CONFIG_LEDS_PCA9532 is not set +CONFIG_LEDS_GPIO=y +# CONFIG_LEDS_LP3944 is not set +# CONFIG_LEDS_LP5521 is not set +# CONFIG_LEDS_LP5523 is not set +# CONFIG_LEDS_LP5562 is not set +# CONFIG_LEDS_LP8501 is not set +# CONFIG_LEDS_PCA955X is not set +# CONFIG_LEDS_PCA963X is not set +# CONFIG_LEDS_PCA9685 is not set +# CONFIG_LEDS_REGULATOR is not set +# CONFIG_LEDS_BD2802 is not set +# CONFIG_LEDS_LT3593 is not set +# CONFIG_LEDS_TCA6507 is not set +# CONFIG_LEDS_LM355x is not set +# CONFIG_LEDS_OT200 is not set +# CONFIG_LEDS_BLINKM is not set + +# +# LED Triggers +# +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +# CONFIG_LEDS_TRIGGER_BACKLIGHT is not set +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_GPIO=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y + +# +# iptables trigger is under Netfilter config (LED target) +# +CONFIG_LEDS_TRIGGER_TRANSIENT=y +# CONFIG_LEDS_TRIGGER_CAMERA is not set +# CONFIG_SWITCH is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_EDAC is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_SYSTOHC=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_DS3232 is not set +# CONFIG_RTC_DRV_HYM8563 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +CONFIG_RTC_DRV_MAX77XXX=y +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_ISL12022 is not set +# CONFIG_RTC_DRV_ISL12057 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF2127 is not set +# CONFIG_RTC_DRV_PCF8523 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_BQ32K is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set +# CONFIG_RTC_DRV_RX8025 is not set +# CONFIG_RTC_DRV_EM3027 is not set +# CONFIG_RTC_DRV_RV3029C2 is not set +# CONFIG_RTC_DRV_S5M is not set + +# +# SPI RTC drivers +# + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_MSM6242 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_RP5C01 is not set +# CONFIG_RTC_DRV_V3020 is not set +# CONFIG_RTC_DRV_DS2404 is not set + +# +# on-CPU RTC drivers +# +CONFIG_HAVE_S3C_RTC=y +CONFIG_RTC_DRV_S3C=y +# CONFIG_RTC_DRV_PL030 is not set +# CONFIG_RTC_DRV_PL031 is not set +# CONFIG_RTC_DRV_SNVS is not set +# CONFIG_RTC_DRV_MOXART is not set + +# +# HID Sensor RTC drivers +# +# CONFIG_RTC_DRV_HID_SENSOR_TIME is not set +CONFIG_DMADEVICES=y +# CONFIG_DMADEVICES_DEBUG is not set + +# +# DMA Devices +# +# CONFIG_AMBA_PL08X is not set +# CONFIG_DW_DMAC_CORE is not set +# CONFIG_DW_DMAC is not set +# CONFIG_TIMB_DMA is not set +CONFIG_PL330_DMA=y +CONFIG_DMA_ENGINE=y +CONFIG_DMA_OF=y + +# +# DMA Clients +# +# CONFIG_ASYNC_TX_DMA is not set +# CONFIG_DMATEST is not set +# CONFIG_AUXDISPLAY is not set +# CONFIG_UIO is not set +# CONFIG_VIRT_DRIVERS is not set + +# +# Virtio drivers +# +# CONFIG_VIRTIO_MMIO is not set + +# +# Microsoft Hyper-V guest support +# +# CONFIG_STAGING is not set +CONFIG_CLKDEV_LOOKUP=y +CONFIG_HAVE_CLK_PREPARE=y +CONFIG_COMMON_CLK=y + +# +# Common Clock Framework +# +# CONFIG_COMMON_CLK_SI5351 is not set +# CONFIG_COMMON_CLK_SI570 is not set +# CONFIG_COMMON_CLK_S2MPS11 is not set +# CONFIG_COMMON_CLK_QCOM is not set + +# +# Hardware Spinlock drivers +# +CONFIG_CLKSRC_OF=y +CONFIG_ARM_ARCH_TIMER=y +CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y +CONFIG_CLKSRC_EXYNOS_MCT=y +CONFIG_CLKSRC_SAMSUNG_PWM=y +# CONFIG_MAILBOX is not set +CONFIG_IOMMU_SUPPORT=y +CONFIG_OF_IOMMU=y +# CONFIG_EXYNOS_IOMMU is not set + +# +# Remoteproc drivers +# +# CONFIG_STE_MODEM_RPROC is not set + +# +# Rpmsg drivers +# +# CONFIG_PM_DEVFREQ is not set +CONFIG_EXTCON=y + +# +# Extcon Device Drivers +# +CONFIG_OF_EXTCON=y +# CONFIG_EXTCON_GPIO is not set +# CONFIG_MEMORY is not set +# CONFIG_IIO is not set +# CONFIG_PWM is not set +CONFIG_IRQCHIP=y +CONFIG_ARM_GIC=y +CONFIG_GIC_NON_BANKED=y +# CONFIG_IPACK_BUS is not set +# CONFIG_RESET_CONTROLLER is not set +# CONFIG_FMC is not set + +# +# PHY Subsystem +# +# CONFIG_GENERIC_PHY is not set +# CONFIG_PHY_EXYNOS_MIPI_VIDEO is not set +# CONFIG_PHY_EXYNOS_DP_VIDEO is not set +# CONFIG_POWERCAP is not set +CONFIG_GATOR=m + +# +# File systems +# +CONFIG_DCACHE_WORD_ACCESS=y +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +CONFIG_EXT3_FS=y +CONFIG_EXT3_DEFAULTS_TO_ORDERED=y +CONFIG_EXT3_FS_XATTR=y +# CONFIG_EXT3_FS_POSIX_ACL is not set +# CONFIG_EXT3_FS_SECURITY is not set +CONFIG_EXT4_FS=y +# CONFIG_EXT4_FS_POSIX_ACL is not set +# CONFIG_EXT4_FS_SECURITY is not set +# CONFIG_EXT4_DEBUG is not set +CONFIG_JBD=y +# CONFIG_JBD_DEBUG is not set +CONFIG_JBD2=y +# CONFIG_JBD2_DEBUG is not set +CONFIG_FS_MBCACHE=y +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_XFS_FS is not set +# CONFIG_GFS2_FS is not set +CONFIG_BTRFS_FS=y +# CONFIG_BTRFS_FS_POSIX_ACL is not set +# CONFIG_BTRFS_FS_CHECK_INTEGRITY is not set +# CONFIG_BTRFS_FS_RUN_SANITY_TESTS is not set +# CONFIG_BTRFS_DEBUG is not set +# CONFIG_BTRFS_ASSERT is not set +# CONFIG_NILFS2_FS is not set +CONFIG_FS_POSIX_ACL=y +CONFIG_FILE_LOCKING=y +CONFIG_FSNOTIFY=y +CONFIG_DNOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_FANOTIFY is not set +CONFIG_QUOTA=y +# CONFIG_QUOTA_NETLINK_INTERFACE is not set +CONFIG_PRINT_QUOTA_WARNING=y +# CONFIG_QUOTA_DEBUG is not set +CONFIG_QUOTA_TREE=y +# CONFIG_QFMT_V1 is not set +CONFIG_QFMT_V2=y +CONFIG_QUOTACTL=y +CONFIG_AUTOFS4_FS=y +# CONFIG_FUSE_FS is not set + +# +# Caches +# +# CONFIG_FSCACHE is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_TMPFS_XATTR=y +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set +CONFIG_MISC_FILESYSTEMS=y +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +CONFIG_ECRYPT_FS=y +# CONFIG_ECRYPT_FS_MESSAGING is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +CONFIG_JFFS2_SUMMARY=y +CONFIG_JFFS2_FS_XATTR=y +CONFIG_JFFS2_FS_POSIX_ACL=y +CONFIG_JFFS2_FS_SECURITY=y +CONFIG_JFFS2_COMPRESSION_OPTIONS=y +CONFIG_JFFS2_ZLIB=y +CONFIG_JFFS2_LZO=y +CONFIG_JFFS2_RTIME=y +CONFIG_JFFS2_RUBIN=y +# CONFIG_JFFS2_CMODE_NONE is not set +CONFIG_JFFS2_CMODE_PRIORITY=y +# CONFIG_JFFS2_CMODE_SIZE is not set +# CONFIG_JFFS2_CMODE_FAVOURLZO is not set +# CONFIG_LOGFS is not set +CONFIG_CRAMFS=y +# CONFIG_SQUASHFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_QNX6FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_PSTORE is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +# CONFIG_F2FS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +# CONFIG_NFS_V2 is not set +CONFIG_NFS_V3=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +# CONFIG_NFS_SWAP is not set +# CONFIG_NFS_V4_1 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFS_USE_LEGACY_DNS is not set +CONFIG_NFS_USE_KERNEL_DNS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_ACL_SUPPORT=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +CONFIG_SUNRPC_GSS=y +# CONFIG_SUNRPC_DEBUG is not set +# CONFIG_CEPH_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +# CONFIG_NLS_ASCII is not set +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +# CONFIG_NLS_MAC_ROMAN is not set +# CONFIG_NLS_MAC_CELTIC is not set +# CONFIG_NLS_MAC_CENTEURO is not set +# CONFIG_NLS_MAC_CROATIAN is not set +# CONFIG_NLS_MAC_CYRILLIC is not set +# CONFIG_NLS_MAC_GAELIC is not set +# CONFIG_NLS_MAC_GREEK is not set +# CONFIG_NLS_MAC_ICELAND is not set +# CONFIG_NLS_MAC_INUIT is not set +# CONFIG_NLS_MAC_ROMANIAN is not set +# CONFIG_NLS_MAC_TURKISH is not set +# CONFIG_NLS_UTF8 is not set + +# +# Kernel hacking +# + +# +# printk and dmesg options +# +CONFIG_PRINTK_TIME=y +CONFIG_DEFAULT_MESSAGE_LOGLEVEL=4 +# CONFIG_BOOT_PRINTK_DELAY is not set +# CONFIG_DYNAMIC_DEBUG is not set + +# +# Compile-time checks and compiler options +# +# CONFIG_DEBUG_INFO is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_STRIP_ASM_SYMS is not set +# CONFIG_READABLE_ASM is not set +# CONFIG_UNUSED_SYMBOLS is not set +CONFIG_DEBUG_FS=y +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_SECTION_MISMATCH is not set +# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set +CONFIG_MAGIC_SYSRQ=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1 +CONFIG_DEBUG_KERNEL=y + +# +# Memory Debugging +# +# CONFIG_DEBUG_PAGEALLOC is not set +# CONFIG_DEBUG_OBJECTS is not set +# CONFIG_DEBUG_SLAB is not set +CONFIG_HAVE_DEBUG_KMEMLEAK=y +# CONFIG_DEBUG_KMEMLEAK is not set +# CONFIG_DEBUG_STACK_USAGE is not set +# CONFIG_DEBUG_VM is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +# CONFIG_DEBUG_PER_CPU_MAPS is not set +# CONFIG_DEBUG_HIGHMEM is not set +# CONFIG_DEBUG_SHIRQ is not set + +# +# Debug Lockups and Hangs +# +# CONFIG_LOCKUP_DETECTOR is not set +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 +# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set +CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0 +# CONFIG_PANIC_ON_OOPS is not set +CONFIG_PANIC_ON_OOPS_VALUE=0 +CONFIG_PANIC_TIMEOUT=0 +CONFIG_SCHED_DEBUG=y +CONFIG_SCHEDSTATS=y +CONFIG_TIMER_STATS=y + +# +# Lock Debugging (spinlocks, mutexes, etc...) +# +CONFIG_DEBUG_RT_MUTEXES=y +CONFIG_DEBUG_PI_LIST=y +# CONFIG_RT_MUTEX_TESTER is not set +CONFIG_DEBUG_SPINLOCK=y +CONFIG_DEBUG_MUTEXES=y +# CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set +# CONFIG_DEBUG_LOCK_ALLOC is not set +# CONFIG_PROVE_LOCKING is not set +# CONFIG_LOCK_STAT is not set +# CONFIG_DEBUG_ATOMIC_SLEEP is not set +# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set +CONFIG_STACKTRACE=y +# CONFIG_DEBUG_KOBJECT is not set +CONFIG_DEBUG_BUGVERBOSE=y +# CONFIG_DEBUG_WRITECOUNT is not set +# CONFIG_DEBUG_LIST is not set +# CONFIG_DEBUG_SG is not set +# CONFIG_DEBUG_NOTIFIERS is not set +# CONFIG_DEBUG_CREDENTIALS is not set + +# +# RCU Debugging +# +# CONFIG_SPARSE_RCU_POINTER is not set +# CONFIG_RCU_TORTURE_TEST is not set +CONFIG_RCU_CPU_STALL_TIMEOUT=21 +# CONFIG_RCU_CPU_STALL_INFO is not set +# CONFIG_RCU_TRACE is not set +# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set +# CONFIG_NOTIFIER_ERROR_INJECTION is not set +# CONFIG_FAULT_INJECTION is not set +CONFIG_NOP_TRACER=y +CONFIG_HAVE_FUNCTION_TRACER=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y +CONFIG_HAVE_SYSCALL_TRACEPOINTS=y +CONFIG_HAVE_C_RECORDMCOUNT=y +CONFIG_TRACE_CLOCK=y +CONFIG_RING_BUFFER=y +CONFIG_EVENT_TRACING=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_RING_BUFFER_ALLOW_SWAP=y +CONFIG_TRACING=y +CONFIG_GENERIC_TRACER=y +CONFIG_TRACING_SUPPORT=y +CONFIG_FTRACE=y +CONFIG_FUNCTION_TRACER=y +# CONFIG_IRQSOFF_TRACER is not set +# CONFIG_SCHED_TRACER is not set +# CONFIG_FTRACE_SYSCALLS is not set +# CONFIG_TRACER_SNAPSHOT is not set +CONFIG_BRANCH_PROFILE_NONE=y +# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set +# CONFIG_PROFILE_ALL_BRANCHES is not set +# CONFIG_STACK_TRACER is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_UPROBE_EVENT is not set +# CONFIG_PROBE_EVENTS is not set +CONFIG_DYNAMIC_FTRACE=y +# CONFIG_FUNCTION_PROFILER is not set +CONFIG_FTRACE_MCOUNT_RECORD=y +# CONFIG_FTRACE_STARTUP_TEST is not set +# CONFIG_RING_BUFFER_BENCHMARK is not set +# CONFIG_RING_BUFFER_STARTUP_TEST is not set + +# +# Runtime Testing +# +# CONFIG_LKDTM is not set +# CONFIG_TEST_LIST_SORT is not set +# CONFIG_BACKTRACE_SELF_TEST is not set +# CONFIG_RBTREE_TEST is not set +# CONFIG_INTERVAL_TREE_TEST is not set +# CONFIG_PERCPU_TEST is not set +# CONFIG_ATOMIC64_SELFTEST is not set +# CONFIG_TEST_STRING_HELPERS is not set +# CONFIG_TEST_KSTRTOX is not set +# CONFIG_DMA_API_DEBUG is not set +# CONFIG_TEST_MODULE is not set +# CONFIG_TEST_USER_COPY is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_KGDB is not set +# CONFIG_ARM_PTDUMP is not set +CONFIG_STRICT_DEVMEM=y +CONFIG_ARM_UNWIND=y +CONFIG_DEBUG_USER=y +# CONFIG_DEBUG_LL is not set +CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S" +# CONFIG_DEBUG_UART_PL01X is not set +# CONFIG_DEBUG_UART_8250 is not set +CONFIG_UNCOMPRESS_INCLUDE="mach/uncompress.h" +# CONFIG_OC_ETM is not set +# CONFIG_PID_IN_CONTEXTIDR is not set +# CONFIG_DEBUG_SET_MODULE_RONX is not set + +# +# Security options +# +CONFIG_KEYS=y +# CONFIG_PERSISTENT_KEYRINGS is not set +# CONFIG_BIG_KEYS is not set +# CONFIG_ENCRYPTED_KEYS is not set +# CONFIG_KEYS_DEBUG_PROC_KEYS is not set +# CONFIG_SECURITY_DMESG_RESTRICT is not set +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +CONFIG_SECURITY_NETWORK=y +# CONFIG_SECURITY_NETWORK_XFRM is not set +CONFIG_SECURITY_PATH=y +CONFIG_LSM_MMAP_MIN_ADDR=0 +CONFIG_SECURITY_SELINUX=y +# CONFIG_SECURITY_SELINUX_BOOTPARAM is not set +# CONFIG_SECURITY_SELINUX_DISABLE is not set +CONFIG_SECURITY_SELINUX_DEVELOP=y +CONFIG_SECURITY_SELINUX_AVC_STATS=y +CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE=1 +# CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX is not set +CONFIG_SECURITY_SMACK=y +# CONFIG_SECURITY_TOMOYO is not set +CONFIG_SECURITY_APPARMOR=y +CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE=1 +CONFIG_SECURITY_APPARMOR_HASH=y +# CONFIG_SECURITY_YAMA is not set +# CONFIG_IMA is not set +# CONFIG_EVM is not set +# CONFIG_DEFAULT_SECURITY_SELINUX is not set +# CONFIG_DEFAULT_SECURITY_SMACK is not set +CONFIG_DEFAULT_SECURITY_APPARMOR=y +# CONFIG_DEFAULT_SECURITY_DAC is not set +CONFIG_DEFAULT_SECURITY="apparmor" +CONFIG_XOR_BLOCKS=y +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_BLKCIPHER=y +CONFIG_CRYPTO_BLKCIPHER2=y +CONFIG_CRYPTO_HASH=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_RNG=m +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_PCOMP2=y +CONFIG_CRYPTO_MANAGER=y +CONFIG_CRYPTO_MANAGER2=y +# CONFIG_CRYPTO_USER is not set +CONFIG_CRYPTO_MANAGER_DISABLE_TESTS=y +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_PCRYPT is not set +CONFIG_CRYPTO_WORKQUEUE=y +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +CONFIG_CRYPTO_CBC=y +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +CONFIG_CRYPTO_ECB=y +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_CMAC is not set +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set +# CONFIG_CRYPTO_VMAC is not set + +# +# Digest +# +CONFIG_CRYPTO_CRC32C=y +# CONFIG_CRYPTO_CRC32 is not set +CONFIG_CRYPTO_CRCT10DIF=y +# CONFIG_CRYPTO_GHASH is not set +# CONFIG_CRYPTO_MD4 is not set +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_MICHAEL_MIC=y +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +CONFIG_CRYPTO_SHA1=y +# CONFIG_CRYPTO_SHA1_ARM is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +CONFIG_CRYPTO_AES=y +# CONFIG_CRYPTO_AES_ARM is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_ZLIB is not set +# CONFIG_CRYPTO_LZO is not set +# CONFIG_CRYPTO_LZ4 is not set +# CONFIG_CRYPTO_LZ4HC is not set + +# +# Random Number Generation +# +CONFIG_CRYPTO_ANSI_CPRNG=m +# CONFIG_CRYPTO_USER_API_HASH is not set +# CONFIG_CRYPTO_USER_API_SKCIPHER is not set +CONFIG_CRYPTO_HW=y +# CONFIG_ASYMMETRIC_KEY_TYPE is not set +CONFIG_BINARY_PRINTF=y + +# +# Library routines +# +CONFIG_RAID6_PQ=y +CONFIG_BITREVERSE=y +CONFIG_GENERIC_STRNCPY_FROM_USER=y +CONFIG_GENERIC_STRNLEN_USER=y +CONFIG_GENERIC_NET_UTILS=y +CONFIG_GENERIC_PCI_IOMAP=y +CONFIG_GENERIC_IO=y +CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y +CONFIG_CRC_CCITT=y +CONFIG_CRC16=y +CONFIG_CRC_T10DIF=y +CONFIG_CRC_ITU_T=y +CONFIG_CRC32=y +# CONFIG_CRC32_SELFTEST is not set +CONFIG_CRC32_SLICEBY8=y +# CONFIG_CRC32_SLICEBY4 is not set +# CONFIG_CRC32_SARWATE is not set +# CONFIG_CRC32_BIT is not set +CONFIG_CRC7=y +CONFIG_LIBCRC32C=y +# CONFIG_CRC8 is not set +CONFIG_AUDIT_GENERIC=y +# CONFIG_RANDOM32_SELFTEST is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +# CONFIG_XZ_DEC is not set +# CONFIG_XZ_DEC_BCJ is not set +CONFIG_DECOMPRESS_GZIP=y +CONFIG_ASSOCIATIVE_ARRAY=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_DMA=y +CONFIG_CPU_RMAP=y +CONFIG_DQL=y +CONFIG_NLATTR=y +CONFIG_ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE=y +# CONFIG_AVERAGE is not set +# CONFIG_CORDIC is not set +# CONFIG_DDR is not set +CONFIG_OID_REGISTRY=y +CONFIG_FONT_SUPPORT=y +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +# CONFIG_VIRTUALIZATION is not set diff --git a/arch/arm/include/asm/arch_timer.h b/arch/arm/include/asm/arch_timer.h index 0704e0cf557146..be467f3780dd44 100644 --- a/arch/arm/include/asm/arch_timer.h +++ b/arch/arm/include/asm/arch_timer.h @@ -107,7 +107,6 @@ static inline void arch_counter_set_user_access(void) /* Also disable virtual event stream */ cntkctl &= ~(ARCH_TIMER_USR_PT_ACCESS_EN | ARCH_TIMER_USR_VT_ACCESS_EN - | ARCH_TIMER_VIRT_EVT_EN | ARCH_TIMER_USR_VCT_ACCESS_EN | ARCH_TIMER_USR_PCT_ACCESS_EN); arch_timer_set_cntkctl(cntkctl); diff --git a/arch/arm/include/asm/compiler.h b/arch/arm/include/asm/compiler.h index 8155db2f7fa16e..009eafa2ea0da4 100644 --- a/arch/arm/include/asm/compiler.h +++ b/arch/arm/include/asm/compiler.h @@ -11,5 +11,37 @@ */ #define __asmeq(x, y) ".ifnc " x "," y " ; .err ; .endif\n\t" +#if defined(CONFIG_SMP) && !defined(CONFIG_CPU_V6) +/* + * Read TPIDRPRW. + * GCC requires a workaround as it does not treat a "memory" clobber on a + * non-volatile asm block as a side-effect. + * We want to allow caching the value, so for GCC avoid using volatile and + * instead use a fake stack read to hazard against barrier(). + */ +#if defined(__clang__) +static inline unsigned long read_TPIDRPRW(void) +{ + unsigned long off; + + /* + * Read TPIDRPRW. + */ + asm("mrc p15, 0, %0, c13, c0, 4" : "=r" (off) : : "memory"); + + return off; +} +#else +static inline unsigned long read_TPIDRPRW(void) +{ + unsigned long off; + register unsigned long *sp asm ("sp"); + + asm("mrc p15, 0, %0, c13, c0, 4" : "=r" (off) : "Q" (*sp)); + + return off; +} +#endif +#endif #endif /* __ASM_ARM_COMPILER_H */ diff --git a/arch/arm/include/asm/fiq_debugger.h b/arch/arm/include/asm/fiq_debugger.h new file mode 100644 index 00000000000000..4d274883ba6a3e --- /dev/null +++ b/arch/arm/include/asm/fiq_debugger.h @@ -0,0 +1,64 @@ +/* + * arch/arm/include/asm/fiq_debugger.h + * + * Copyright (C) 2010 Google, Inc. + * Author: Colin Cross + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef _ARCH_ARM_MACH_TEGRA_FIQ_DEBUGGER_H_ +#define _ARCH_ARM_MACH_TEGRA_FIQ_DEBUGGER_H_ + +#include + +#define FIQ_DEBUGGER_NO_CHAR NO_POLL_CHAR +#define FIQ_DEBUGGER_BREAK 0x00ff0100 + +#define FIQ_DEBUGGER_FIQ_IRQ_NAME "fiq" +#define FIQ_DEBUGGER_SIGNAL_IRQ_NAME "signal" +#define FIQ_DEBUGGER_WAKEUP_IRQ_NAME "wakeup" + +/** + * struct fiq_debugger_pdata - fiq debugger platform data + * @uart_resume: used to restore uart state right before enabling + * the fiq. + * @uart_enable: Do the work necessary to communicate with the uart + * hw (enable clocks, etc.). This must be ref-counted. + * @uart_disable: Do the work necessary to disable the uart hw + * (disable clocks, etc.). This must be ref-counted. + * @uart_dev_suspend: called during PM suspend, generally not needed + * for real fiq mode debugger. + * @uart_dev_resume: called during PM resume, generally not needed + * for real fiq mode debugger. + */ +struct fiq_debugger_pdata { + int (*uart_init)(struct platform_device *pdev); + void (*uart_free)(struct platform_device *pdev); + int (*uart_resume)(struct platform_device *pdev); + int (*uart_getc)(struct platform_device *pdev); + void (*uart_putc)(struct platform_device *pdev, unsigned int c); + void (*uart_flush)(struct platform_device *pdev); + void (*uart_enable)(struct platform_device *pdev); + void (*uart_disable)(struct platform_device *pdev); + + int (*uart_dev_suspend)(struct platform_device *pdev); + int (*uart_dev_resume)(struct platform_device *pdev); + + void (*fiq_enable)(struct platform_device *pdev, unsigned int fiq, + bool enable); + void (*fiq_ack)(struct platform_device *pdev, unsigned int fiq); + + void (*force_irq)(struct platform_device *pdev, unsigned int irq); + void (*force_irq_ack)(struct platform_device *pdev, unsigned int irq); +}; + +#endif diff --git a/arch/arm/include/asm/fiq_glue.h b/arch/arm/include/asm/fiq_glue.h new file mode 100644 index 00000000000000..a9e244f9f197c8 --- /dev/null +++ b/arch/arm/include/asm/fiq_glue.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __ASM_FIQ_GLUE_H +#define __ASM_FIQ_GLUE_H + +struct fiq_glue_handler { + void (*fiq)(struct fiq_glue_handler *h, void *regs, void *svc_sp); + void (*resume)(struct fiq_glue_handler *h); +}; +typedef void (*fiq_return_handler_t)(void); + +int fiq_glue_register_handler(struct fiq_glue_handler *handler); +int fiq_glue_set_return_handler(fiq_return_handler_t fiq_return); +int fiq_glue_clear_return_handler(fiq_return_handler_t fiq_return); + +#ifdef CONFIG_FIQ_GLUE +void fiq_glue_resume(void); +#else +static inline void fiq_glue_resume(void) {} +#endif + +#endif diff --git a/arch/arm/include/asm/ftrace.h b/arch/arm/include/asm/ftrace.h index f89515adac60ef..2bb8cac28b9e34 100644 --- a/arch/arm/include/asm/ftrace.h +++ b/arch/arm/include/asm/ftrace.h @@ -45,7 +45,7 @@ void *return_address(unsigned int); #else -extern inline void *return_address(unsigned int level) +static inline void *return_address(unsigned int level) { return NULL; } diff --git a/arch/arm/include/asm/glue-cache.h b/arch/arm/include/asm/glue-cache.h index c81adc08b3fba5..a3c24cd5b7c8ee 100644 --- a/arch/arm/include/asm/glue-cache.h +++ b/arch/arm/include/asm/glue-cache.h @@ -130,22 +130,22 @@ #endif #ifndef __ASSEMBLER__ -extern inline void nop_flush_icache_all(void) { } -extern inline void nop_flush_kern_cache_all(void) { } -extern inline void nop_flush_kern_cache_louis(void) { } -extern inline void nop_flush_user_cache_all(void) { } -extern inline void nop_flush_user_cache_range(unsigned long a, +static inline void nop_flush_icache_all(void) { } +static inline void nop_flush_kern_cache_all(void) { } +static inline void nop_flush_kern_cache_louis(void) { } +static inline void nop_flush_user_cache_all(void) { } +static inline void nop_flush_user_cache_range(unsigned long a, unsigned long b, unsigned int c) { } -extern inline void nop_coherent_kern_range(unsigned long a, unsigned long b) { } -extern inline int nop_coherent_user_range(unsigned long a, +static inline void nop_coherent_kern_range(unsigned long a, unsigned long b) { } +static inline int nop_coherent_user_range(unsigned long a, unsigned long b) { return 0; } -extern inline void nop_flush_kern_dcache_area(void *a, size_t s) { } +static inline void nop_flush_kern_dcache_area(void *a, size_t s) { } -extern inline void nop_dma_flush_range(const void *a, const void *b) { } +static inline void nop_dma_flush_range(const void *a, const void *b) { } -extern inline void nop_dma_map_area(const void *s, size_t l, int f) { } -extern inline void nop_dma_unmap_area(const void *s, size_t l, int f) { } +static inline void nop_dma_map_area(const void *s, size_t l, int f) { } +static inline void nop_dma_unmap_area(const void *s, size_t l, int f) { } #endif #ifndef MULTI_CACHE diff --git a/arch/arm/include/asm/hardirq.h b/arch/arm/include/asm/hardirq.h index fe3ea776dc3426..5df33e30ae1b16 100644 --- a/arch/arm/include/asm/hardirq.h +++ b/arch/arm/include/asm/hardirq.h @@ -5,7 +5,7 @@ #include #include -#define NR_IPI 8 +#define NR_IPI 9 typedef struct { unsigned int __softirq_pending; diff --git a/arch/arm/include/asm/hardware/cache-l2x0.h b/arch/arm/include/asm/hardware/cache-l2x0.h index 6795ff743b3dbc..b1344186954c4c 100644 --- a/arch/arm/include/asm/hardware/cache-l2x0.h +++ b/arch/arm/include/asm/hardware/cache-l2x0.h @@ -66,6 +66,7 @@ #define L2X0_STNDBY_MODE_EN (1 << 0) /* Registers shifts and masks */ +#define L2X0_CACHE_ID_REV_MASK (0x3f) #define L2X0_CACHE_ID_PART_MASK (0xf << 6) #define L2X0_CACHE_ID_PART_L210 (1 << 6) #define L2X0_CACHE_ID_PART_L310 (3 << 6) @@ -106,6 +107,8 @@ #define L2X0_WAY_SIZE_SHIFT 3 +#define REV_PL310_R2P0 4 + #ifndef __ASSEMBLY__ extern void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask); #if defined(CONFIG_CACHE_L2X0) && defined(CONFIG_OF) diff --git a/arch/arm/include/asm/hardware/coresight.h b/arch/arm/include/asm/hardware/coresight.h index ad774f37c47cda..b3507650b41c69 100644 --- a/arch/arm/include/asm/hardware/coresight.h +++ b/arch/arm/include/asm/hardware/coresight.h @@ -17,15 +17,23 @@ #define TRACER_ACCESSED_BIT 0 #define TRACER_RUNNING_BIT 1 #define TRACER_CYCLE_ACC_BIT 2 +#define TRACER_TRACE_DATA_BIT 3 +#define TRACER_TIMESTAMP_BIT 4 +#define TRACER_BRANCHOUTPUT_BIT 5 +#define TRACER_RETURN_STACK_BIT 6 #define TRACER_ACCESSED BIT(TRACER_ACCESSED_BIT) #define TRACER_RUNNING BIT(TRACER_RUNNING_BIT) #define TRACER_CYCLE_ACC BIT(TRACER_CYCLE_ACC_BIT) +#define TRACER_TRACE_DATA BIT(TRACER_TRACE_DATA_BIT) +#define TRACER_TIMESTAMP BIT(TRACER_TIMESTAMP_BIT) +#define TRACER_BRANCHOUTPUT BIT(TRACER_BRANCHOUTPUT_BIT) +#define TRACER_RETURN_STACK BIT(TRACER_RETURN_STACK_BIT) #define TRACER_TIMEOUT 10000 -#define etm_writel(t, v, x) \ - (writel_relaxed((v), (t)->etm_regs + (x))) -#define etm_readl(t, x) (readl_relaxed((t)->etm_regs + (x))) +#define etm_writel(t, id, v, x) \ + (writel_relaxed((v), (t)->etm_regs[(id)] + (x))) +#define etm_readl(t, id, x) (readl_relaxed((t)->etm_regs[(id)] + (x))) /* CoreSight Management Registers */ #define CSMR_LOCKACCESS 0xfb0 @@ -43,7 +51,7 @@ #define ETMCTRL_POWERDOWN 1 #define ETMCTRL_PROGRAM (1 << 10) #define ETMCTRL_PORTSEL (1 << 11) -#define ETMCTRL_DO_CONTEXTID (3 << 14) +#define ETMCTRL_CONTEXTIDSIZE(x) (((x) & 3) << 14) #define ETMCTRL_PORTMASK1 (7 << 4) #define ETMCTRL_PORTMASK2 (1 << 21) #define ETMCTRL_PORTMASK (ETMCTRL_PORTMASK1 | ETMCTRL_PORTMASK2) @@ -55,9 +63,12 @@ #define ETMCTRL_DATA_DO_BOTH (ETMCTRL_DATA_DO_DATA | ETMCTRL_DATA_DO_ADDR) #define ETMCTRL_BRANCH_OUTPUT (1 << 8) #define ETMCTRL_CYCLEACCURATE (1 << 12) +#define ETMCTRL_TIMESTAMP_EN (1 << 28) +#define ETMCTRL_RETURN_STACK_EN (1 << 29) /* ETM configuration code register */ #define ETMR_CONFCODE (0x04) +#define ETMCCR_ETMIDR_PRESENT BIT(31) /* ETM trace start/stop resource control register */ #define ETMR_TRACESSCTRL (0x18) @@ -113,10 +124,25 @@ #define ETMR_TRACEENCTRL 0x24 #define ETMTE_INCLEXCL BIT(24) #define ETMR_TRACEENEVT 0x20 -#define ETMCTRL_OPTS (ETMCTRL_DO_CPRT | \ - ETMCTRL_DATA_DO_ADDR | \ - ETMCTRL_BRANCH_OUTPUT | \ - ETMCTRL_DO_CONTEXTID) + +#define ETMR_VIEWDATAEVT 0x30 +#define ETMR_VIEWDATACTRL1 0x34 +#define ETMR_VIEWDATACTRL2 0x38 +#define ETMR_VIEWDATACTRL3 0x3c +#define ETMVDC3_EXCLONLY BIT(16) + +#define ETMCTRL_OPTS (ETMCTRL_DO_CPRT) + +#define ETMR_ID 0x1e4 +#define ETMIDR_VERSION(x) (((x) >> 4) & 0xff) +#define ETMIDR_VERSION_3_1 0x21 +#define ETMIDR_VERSION_PFT_1_0 0x30 + +#define ETMR_CCE 0x1e8 +#define ETMCCER_RETURN_STACK_IMPLEMENTED BIT(23) +#define ETMCCER_TIMESTAMPING_IMPLEMENTED BIT(22) + +#define ETMR_TRACEIDR 0x200 /* ETM management registers, "ETM Architecture", 3.5.24 */ #define ETMMR_OSLAR 0x300 @@ -140,14 +166,16 @@ #define ETBFF_TRIGIN BIT(8) #define ETBFF_TRIGEVT BIT(9) #define ETBFF_TRIGFL BIT(10) +#define ETBFF_STOPFL BIT(12) #define etb_writel(t, v, x) \ (writel_relaxed((v), (t)->etb_regs + (x))) #define etb_readl(t, x) (readl_relaxed((t)->etb_regs + (x))) -#define etm_lock(t) do { etm_writel((t), 0, CSMR_LOCKACCESS); } while (0) -#define etm_unlock(t) \ - do { etm_writel((t), CS_LAR_KEY, CSMR_LOCKACCESS); } while (0) +#define etm_lock(t, id) \ + do { etm_writel((t), (id), 0, CSMR_LOCKACCESS); } while (0) +#define etm_unlock(t, id) \ + do { etm_writel((t), (id), CS_LAR_KEY, CSMR_LOCKACCESS); } while (0) #define etb_lock(t) do { etb_writel((t), 0, CSMR_LOCKACCESS); } while (0) #define etb_unlock(t) \ diff --git a/arch/arm/include/asm/irq.h b/arch/arm/include/asm/irq.h index 53c15dec7af6aa..809203a4b71bef 100644 --- a/arch/arm/include/asm/irq.h +++ b/arch/arm/include/asm/irq.h @@ -35,6 +35,9 @@ extern void (*handle_arch_irq)(struct pt_regs *); extern void set_handle_irq(void (*handle_irq)(struct pt_regs *)); #endif +void arch_trigger_all_cpu_backtrace(void); +#define arch_trigger_all_cpu_backtrace arch_trigger_all_cpu_backtrace + #endif #endif diff --git a/arch/arm/include/asm/kprobes.h b/arch/arm/include/asm/kprobes.h index f82ec22eeb1174..49fa0dfaad3365 100644 --- a/arch/arm/include/asm/kprobes.h +++ b/arch/arm/include/asm/kprobes.h @@ -18,7 +18,7 @@ #include #include -#include +#include #define __ARCH_WANT_KPROBES_INSN_SLOT #define MAX_INSN_SIZE 2 @@ -28,21 +28,10 @@ #define kretprobe_blacklist_size 0 typedef u32 kprobe_opcode_t; - struct kprobe; -typedef void (kprobe_insn_handler_t)(struct kprobe *, struct pt_regs *); -typedef unsigned long (kprobe_check_cc)(unsigned long); -typedef void (kprobe_insn_singlestep_t)(struct kprobe *, struct pt_regs *); -typedef void (kprobe_insn_fn_t)(void); +#include -/* Architecture specific copy of original instruction. */ -struct arch_specific_insn { - kprobe_opcode_t *insn; - kprobe_insn_handler_t *insn_handler; - kprobe_check_cc *insn_check_cc; - kprobe_insn_singlestep_t *insn_singlestep; - kprobe_insn_fn_t *insn_fn; -}; +#define arch_specific_insn arch_probes_insn struct prev_kprobe { struct kprobe *kp; diff --git a/arch/arm/include/asm/mach/mmc.h b/arch/arm/include/asm/mach/mmc.h new file mode 100644 index 00000000000000..bca864ac945f60 --- /dev/null +++ b/arch/arm/include/asm/mach/mmc.h @@ -0,0 +1,28 @@ +/* + * arch/arm/include/asm/mach/mmc.h + */ +#ifndef ASMARM_MACH_MMC_H +#define ASMARM_MACH_MMC_H + +#include +#include +#include + +struct embedded_sdio_data { + struct sdio_cis cis; + struct sdio_cccr cccr; + struct sdio_embedded_func *funcs; + int num_funcs; +}; + +struct mmc_platform_data { + unsigned int ocr_mask; /* available voltages */ + int built_in; /* built-in device flag */ + int card_present; /* card detect state */ + u32 (*translate_vdd)(struct device *, unsigned int); + unsigned int (*status)(struct device *); + struct embedded_sdio_data *embedded_sdio; + int (*register_status_notify)(void (*callback)(int card_present, void *dev_id), void *dev_id); +}; + +#endif diff --git a/arch/arm/include/asm/percpu.h b/arch/arm/include/asm/percpu.h index 209e6504922e1c..208c7e5fa7dadb 100644 --- a/arch/arm/include/asm/percpu.h +++ b/arch/arm/include/asm/percpu.h @@ -27,21 +27,10 @@ static inline void set_my_cpu_offset(unsigned long off) asm volatile("mcr p15, 0, %0, c13, c0, 4" : : "r" (off) : "memory"); } -static inline unsigned long __my_cpu_offset(void) -{ - unsigned long off; - register unsigned long *sp asm ("sp"); +#include "asm/compiler.h" - /* - * Read TPIDRPRW. - * We want to allow caching the value, so avoid using volatile and - * instead use a fake stack read to hazard against barrier(). - */ - asm("mrc p15, 0, %0, c13, c0, 4" : "=r" (off) : "Q" (*sp)); +#define __my_cpu_offset read_TPIDRPRW() - return off; -} -#define __my_cpu_offset __my_cpu_offset() #else #define set_my_cpu_offset(x) do {} while(0) diff --git a/arch/arm/include/asm/pmu.h b/arch/arm/include/asm/pmu.h index f24edad26c70fd..0cd7824ca76257 100644 --- a/arch/arm/include/asm/pmu.h +++ b/arch/arm/include/asm/pmu.h @@ -62,9 +62,19 @@ struct pmu_hw_events { raw_spinlock_t pmu_lock; }; +struct cpupmu_regs { + u32 pmc; + u32 pmcntenset; + u32 pmuseren; + u32 pmintenset; + u32 pmxevttype[8]; + u32 pmxevtcnt[8]; +}; + struct arm_pmu { struct pmu pmu; cpumask_t active_irqs; + cpumask_t valid_cpus; char *name; irqreturn_t (*handle_irq)(int irq_num, void *dev); void (*enable)(struct perf_event *event); @@ -81,6 +91,8 @@ struct arm_pmu { int (*request_irq)(struct arm_pmu *, irq_handler_t handler); void (*free_irq)(struct arm_pmu *); int (*map_event)(struct perf_event *event); + void (*save_regs)(struct arm_pmu *, struct cpupmu_regs *); + void (*restore_regs)(struct arm_pmu *, struct cpupmu_regs *); int num_events; atomic_t active_events; struct mutex reserve_mutex; diff --git a/arch/arm/include/asm/probes.h b/arch/arm/include/asm/probes.h new file mode 100644 index 00000000000000..806cfe622a9e31 --- /dev/null +++ b/arch/arm/include/asm/probes.h @@ -0,0 +1,43 @@ +/* + * arch/arm/include/asm/probes.h + * + * Original contents copied from arch/arm/include/asm/kprobes.h + * which contains the following notice... + * + * Copyright (C) 2006, 2007 Motorola Inc. + * + * 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. + */ + +#ifndef _ASM_PROBES_H +#define _ASM_PROBES_H + +typedef u32 probes_opcode_t; + +struct arch_probes_insn; +typedef void (probes_insn_handler_t)(probes_opcode_t, + struct arch_probes_insn *, + struct pt_regs *); +typedef unsigned long (probes_check_cc)(unsigned long); +typedef void (probes_insn_singlestep_t)(probes_opcode_t, + struct arch_probes_insn *, + struct pt_regs *); +typedef void (probes_insn_fn_t)(void); + +/* Architecture specific copy of original instruction. */ +struct arch_probes_insn { + probes_opcode_t *insn; + probes_insn_handler_t *insn_handler; + probes_check_cc *insn_check_cc; + probes_insn_singlestep_t *insn_singlestep; + probes_insn_fn_t *insn_fn; +}; + +#endif diff --git a/arch/arm/include/asm/psci.h b/arch/arm/include/asm/psci.h index c4ae171850f832..eb628a6679e8de 100644 --- a/arch/arm/include/asm/psci.h +++ b/arch/arm/include/asm/psci.h @@ -16,6 +16,10 @@ #define PSCI_POWER_STATE_TYPE_STANDBY 0 #define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 +#define PSCI_POWER_STATE_AFFINITY_LEVEL0 0 +#define PSCI_POWER_STATE_AFFINITY_LEVEL1 1 +#define PSCI_POWER_STATE_AFFINITY_LEVEL2 2 +#define PSCI_POWER_STATE_AFFINITY_LEVEL3 3 struct psci_power_state { u16 id; @@ -42,4 +46,12 @@ static inline void psci_init(void) { } static inline bool psci_smp_available(void) { return false; } #endif +#ifdef CONFIG_ARM_PSCI +extern int psci_probe(void); +#else +static inline int psci_probe(void) +{ + return -ENODEV; +} +#endif #endif /* __ASM_ARM_PSCI_H */ diff --git a/arch/arm/include/asm/ptrace.h b/arch/arm/include/asm/ptrace.h index 04c99f36ff7f90..41e0caca2c370b 100644 --- a/arch/arm/include/asm/ptrace.h +++ b/arch/arm/include/asm/ptrace.h @@ -80,6 +80,12 @@ static inline long regs_return_value(struct pt_regs *regs) #define instruction_pointer(regs) (regs)->ARM_pc +static inline void instruction_pointer_set(struct pt_regs *regs, + unsigned long val) +{ + instruction_pointer(regs) = val; +} + #ifdef CONFIG_SMP extern unsigned long profile_pc(struct pt_regs *regs); #else @@ -138,9 +144,9 @@ static inline unsigned long user_stack_pointer(struct pt_regs *regs) return regs->ARM_sp; } -#define current_pt_regs(void) ({ \ - register unsigned long sp asm ("sp"); \ - (struct pt_regs *)((sp | (THREAD_SIZE - 1)) - 7) - 1; \ +#define current_pt_regs(void) ({ \ + (struct pt_regs *)(((unsigned long)(__builtin_stack_pointer()) \ + | (THREAD_SIZE - 1)) - 7) - 1; \ }) #endif /* __ASSEMBLY__ */ diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 22a3b9b5d4a16f..c3278502ec03e0 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -86,6 +86,8 @@ extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask); extern int register_ipi_completion(struct completion *completion, int cpu); +extern void smp_send_all_cpu_backtrace(void); + struct smp_operations { #ifdef CONFIG_SMP /* diff --git a/arch/arm/include/asm/thread_info.h b/arch/arm/include/asm/thread_info.h index 71a06b293489dd..5d7ae35512337b 100644 --- a/arch/arm/include/asm/thread_info.h +++ b/arch/arm/include/asm/thread_info.h @@ -99,6 +99,25 @@ struct thread_info { #define init_thread_info (init_thread_union.thread_info) #define init_stack (init_thread_union.stack) +/* + * how to get the current stack pointer in C + */ +#if defined(CONFIG_BUILTIN_STACK_POINTER) +/* compiler has __builtin_stack_pointer support already */ + +#elif defined(__clang__) +#define __builtin_stack_pointer() ({ \ + unsigned long current_sp; \ + asm ("mov %0, sp" : "=r" (current_sp)); \ + current_sp; \ +}) +#define current_stack_pointer __builtin_stack_pointer() + +#else /* gcc */ +register unsigned long current_stack_pointer asm ("sp"); +#define __builtin_stack_pointer() current_stack_pointer +#endif + /* * how to get the thread information struct from C */ @@ -106,8 +125,9 @@ static inline struct thread_info *current_thread_info(void) __attribute_const__; static inline struct thread_info *current_thread_info(void) { - register unsigned long sp asm ("sp"); - return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); + return (struct thread_info *) + ((u32)(__builtin_stack_pointer()) + & ~(THREAD_SIZE - 1)); } #define thread_saved_pc(tsk) \ @@ -153,6 +173,7 @@ extern int vfp_restore_user_hwstate(struct user_vfp __user *, #define TIF_SIGPENDING 0 #define TIF_NEED_RESCHED 1 #define TIF_NOTIFY_RESUME 2 /* callback before returning to user */ +#define TIF_UPROBE 7 #define TIF_SYSCALL_TRACE 8 #define TIF_SYSCALL_AUDIT 9 #define TIF_SYSCALL_TRACEPOINT 10 @@ -165,6 +186,7 @@ extern int vfp_restore_user_hwstate(struct user_vfp __user *, #define _TIF_SIGPENDING (1 << TIF_SIGPENDING) #define _TIF_NEED_RESCHED (1 << TIF_NEED_RESCHED) #define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME) +#define _TIF_UPROBE (1 << TIF_UPROBE) #define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE) #define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT) #define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) @@ -178,7 +200,8 @@ extern int vfp_restore_user_hwstate(struct user_vfp __user *, /* * Change these and you break ASM code in entry-common.S */ -#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME) +#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \ + _TIF_NOTIFY_RESUME | _TIF_UPROBE) #endif /* __KERNEL__ */ #endif /* __ASM_ARM_THREAD_INFO_H */ diff --git a/arch/arm/include/asm/topology.h b/arch/arm/include/asm/topology.h index 58b8b84adcd2cf..611edefaeaf195 100644 --- a/arch/arm/include/asm/topology.h +++ b/arch/arm/include/asm/topology.h @@ -26,11 +26,14 @@ extern struct cputopo_arm cpu_topology[NR_CPUS]; void init_cpu_topology(void); void store_cpu_topology(unsigned int cpuid); const struct cpumask *cpu_coregroup_mask(int cpu); +int cluster_to_logical_mask(unsigned int socket_id, cpumask_t *cluster_mask); #else static inline void init_cpu_topology(void) { } static inline void store_cpu_topology(unsigned int cpuid) { } +static inline int cluster_to_logical_mask(unsigned int socket_id, + cpumask_t *cluster_mask) { return -EINVAL; } #endif diff --git a/arch/arm/include/asm/uprobes.h b/arch/arm/include/asm/uprobes.h new file mode 100644 index 00000000000000..9472c20b7d496e --- /dev/null +++ b/arch/arm/include/asm/uprobes.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 Rabin Vincent + * + * 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. + */ + +#ifndef _ASM_UPROBES_H +#define _ASM_UPROBES_H + +#include +#include + +typedef u32 uprobe_opcode_t; + +#define MAX_UINSN_BYTES 4 +#define UPROBE_XOL_SLOT_BYTES 64 + +#define UPROBE_SWBP_ARM_INSN 0xe7f001f9 +#define UPROBE_SS_ARM_INSN 0xe7f001fa +#define UPROBE_SWBP_INSN __opcode_to_mem_arm(UPROBE_SWBP_ARM_INSN) +#define UPROBE_SWBP_INSN_SIZE 4 + +struct arch_uprobe_task { + u32 backup; + unsigned long saved_trap_no; +}; + +struct arch_uprobe { + u8 insn[MAX_UINSN_BYTES]; + unsigned long ixol[2]; + uprobe_opcode_t bpinsn; + bool simulate; + u32 pcreg; + void (*prehandler)(struct arch_uprobe *auprobe, + struct arch_uprobe_task *autask, + struct pt_regs *regs); + void (*posthandler)(struct arch_uprobe *auprobe, + struct arch_uprobe_task *autask, + struct pt_regs *regs); + struct arch_probes_insn asi; +}; + +#endif diff --git a/arch/arm/include/debug/samsung.S b/arch/arm/include/debug/samsung.S index f3a9cff6d5d44f..4e09ca66aea018 100644 --- a/arch/arm/include/debug/samsung.S +++ b/arch/arm/include/debug/samsung.S @@ -9,17 +9,20 @@ * published by the Free Software Foundation. */ +#include #include /* The S5PV210/S5PC110 implementations are as belows. */ .macro fifo_level_s5pv210 rd, rx ldr \rd, [\rx, # S3C2410_UFSTAT] + ARM_BE8(rev \rd, \rd) and \rd, \rd, #S5PV210_UFSTAT_TXMASK .endm .macro fifo_full_s5pv210 rd, rx ldr \rd, [\rx, # S3C2410_UFSTAT] + ARM_BE8(rev \rd, \rd) tst \rd, #S5PV210_UFSTAT_TXFULL .endm @@ -28,6 +31,7 @@ .macro fifo_level_s3c2440 rd, rx ldr \rd, [\rx, # S3C2410_UFSTAT] + ARM_BE8(rev \rd, \rd) and \rd, \rd, #S3C2440_UFSTAT_TXMASK .endm @@ -37,6 +41,7 @@ .macro fifo_full_s3c2440 rd, rx ldr \rd, [\rx, # S3C2410_UFSTAT] + ARM_BE8(rev \rd, \rd) tst \rd, #S3C2440_UFSTAT_TXFULL .endm @@ -50,6 +55,7 @@ .macro busyuart, rd, rx ldr \rd, [\rx, # S3C2410_UFCON] + ARM_BE8(rev \rd, \rd) tst \rd, #S3C2410_UFCON_FIFOMODE @ fifo enabled? beq 1001f @ @ FIFO enabled... @@ -61,6 +67,7 @@ 1001: @ busy waiting for non fifo ldr \rd, [\rx, # S3C2410_UTRSTAT] + ARM_BE8(rev \rd, \rd) tst \rd, #S3C2410_UTRSTAT_TXFE beq 1001b @@ -69,6 +76,7 @@ .macro waituart,rd,rx ldr \rd, [\rx, # S3C2410_UFCON] + ARM_BE8(rev \rd, \rd) tst \rd, #S3C2410_UFCON_FIFOMODE @ fifo enabled? beq 1001f @ @ FIFO enabled... @@ -80,6 +88,7 @@ 1001: @ idle waiting for non fifo ldr \rd, [\rx, # S3C2410_UTRSTAT] + ARM_BE8(rev \rd, \rd) tst \rd, #S3C2410_UTRSTAT_TXFE beq 1001b diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index a30fc9be9e9e6a..a766bcbaf8adfb 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -50,11 +50,12 @@ obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o insn.o obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o insn.o obj-$(CONFIG_JUMP_LABEL) += jump_label.o insn.o patch.o obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o -obj-$(CONFIG_KPROBES) += kprobes.o kprobes-common.o patch.o +obj-$(CONFIG_UPROBES) += probes.o probes-arm.o uprobes.o uprobes-arm.o +obj-$(CONFIG_KPROBES) += probes.o kprobes.o kprobes-common.o patch.o ifdef CONFIG_THUMB2_KERNEL -obj-$(CONFIG_KPROBES) += kprobes-thumb.o +obj-$(CONFIG_KPROBES) += kprobes-thumb.o probes-thumb.o else -obj-$(CONFIG_KPROBES) += kprobes-arm.o +obj-$(CONFIG_KPROBES) += kprobes-arm.o probes-arm.o endif obj-$(CONFIG_ARM_KPROBES_TEST) += test-kprobes.o test-kprobes-objs := kprobes-test.o diff --git a/arch/arm/kernel/etm.c b/arch/arm/kernel/etm.c index 131a6ab5f35504..a634e0ef6d1f14 100644 --- a/arch/arm/kernel/etm.c +++ b/arch/arm/kernel/etm.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -37,26 +38,37 @@ MODULE_AUTHOR("Alexander Shishkin"); struct tracectx { unsigned int etb_bufsz; void __iomem *etb_regs; - void __iomem *etm_regs; + void __iomem **etm_regs; + int etm_regs_count; unsigned long flags; int ncmppairs; int etm_portsz; + int etm_contextid_size; + u32 etb_fc; + unsigned long range_start; + unsigned long range_end; + unsigned long data_range_start; + unsigned long data_range_end; + bool dump_initial_etb; struct device *dev; struct clk *emu_clk; struct mutex mutex; }; -static struct tracectx tracer; +static struct tracectx tracer = { + .range_start = (unsigned long)_stext, + .range_end = (unsigned long)_etext, +}; static inline bool trace_isrunning(struct tracectx *t) { return !!(t->flags & TRACER_RUNNING); } -static int etm_setup_address_range(struct tracectx *t, int n, +static int etm_setup_address_range(struct tracectx *t, int id, int n, unsigned long start, unsigned long end, int exclude, int data) { - u32 flags = ETMAAT_ARM | ETMAAT_IGNCONTEXTID | ETMAAT_NSONLY | \ + u32 flags = ETMAAT_ARM | ETMAAT_IGNCONTEXTID | ETMAAT_IGNSECURITY | ETMAAT_NOVALCMP; if (n < 1 || n > t->ncmppairs) @@ -72,95 +84,185 @@ static int etm_setup_address_range(struct tracectx *t, int n, flags |= ETMAAT_IEXEC; /* first comparator for the range */ - etm_writel(t, flags, ETMR_COMP_ACC_TYPE(n * 2)); - etm_writel(t, start, ETMR_COMP_VAL(n * 2)); + etm_writel(t, id, flags, ETMR_COMP_ACC_TYPE(n * 2)); + etm_writel(t, id, start, ETMR_COMP_VAL(n * 2)); /* second comparator is right next to it */ - etm_writel(t, flags, ETMR_COMP_ACC_TYPE(n * 2 + 1)); - etm_writel(t, end, ETMR_COMP_VAL(n * 2 + 1)); - - flags = exclude ? ETMTE_INCLEXCL : 0; - etm_writel(t, flags | (1 << n), ETMR_TRACEENCTRL); + etm_writel(t, id, flags, ETMR_COMP_ACC_TYPE(n * 2 + 1)); + etm_writel(t, id, end, ETMR_COMP_VAL(n * 2 + 1)); + + if (data) { + flags = exclude ? ETMVDC3_EXCLONLY : 0; + if (exclude) + n += 8; + etm_writel(t, id, flags | BIT(n), ETMR_VIEWDATACTRL3); + } else { + flags = exclude ? ETMTE_INCLEXCL : 0; + etm_writel(t, id, flags | (1 << n), ETMR_TRACEENCTRL); + } return 0; } -static int trace_start(struct tracectx *t) +static int trace_start_etm(struct tracectx *t, int id) { u32 v; unsigned long timeout = TRACER_TIMEOUT; - etb_unlock(t); - - etb_writel(t, 0, ETBR_FORMATTERCTRL); - etb_writel(t, 1, ETBR_CTRL); - - etb_lock(t); - - /* configure etm */ v = ETMCTRL_OPTS | ETMCTRL_PROGRAM | ETMCTRL_PORTSIZE(t->etm_portsz); + v |= ETMCTRL_CONTEXTIDSIZE(t->etm_contextid_size); if (t->flags & TRACER_CYCLE_ACC) v |= ETMCTRL_CYCLEACCURATE; - etm_unlock(t); + if (t->flags & TRACER_BRANCHOUTPUT) + v |= ETMCTRL_BRANCH_OUTPUT; + + if (t->flags & TRACER_TRACE_DATA) + v |= ETMCTRL_DATA_DO_ADDR; + + if (t->flags & TRACER_TIMESTAMP) + v |= ETMCTRL_TIMESTAMP_EN; + + if (t->flags & TRACER_RETURN_STACK) + v |= ETMCTRL_RETURN_STACK_EN; - etm_writel(t, v, ETMR_CTRL); + etm_unlock(t, id); - while (!(etm_readl(t, ETMR_CTRL) & ETMCTRL_PROGRAM) && --timeout) + etm_writel(t, id, v, ETMR_CTRL); + + while (!(etm_readl(t, id, ETMR_CTRL) & ETMCTRL_PROGRAM) && --timeout) ; if (!timeout) { dev_dbg(t->dev, "Waiting for progbit to assert timed out\n"); - etm_lock(t); + etm_lock(t, id); return -EFAULT; } - etm_setup_address_range(t, 1, (unsigned long)_stext, - (unsigned long)_etext, 0, 0); - etm_writel(t, 0, ETMR_TRACEENCTRL2); - etm_writel(t, 0, ETMR_TRACESSCTRL); - etm_writel(t, 0x6f, ETMR_TRACEENEVT); + if (t->range_start || t->range_end) + etm_setup_address_range(t, id, 1, + t->range_start, t->range_end, 0, 0); + else + etm_writel(t, id, ETMTE_INCLEXCL, ETMR_TRACEENCTRL); + + etm_writel(t, id, 0, ETMR_TRACEENCTRL2); + etm_writel(t, id, 0, ETMR_TRACESSCTRL); + etm_writel(t, id, 0x6f, ETMR_TRACEENEVT); + + etm_writel(t, id, 0, ETMR_VIEWDATACTRL1); + etm_writel(t, id, 0, ETMR_VIEWDATACTRL2); + + if (t->data_range_start || t->data_range_end) + etm_setup_address_range(t, id, 2, t->data_range_start, + t->data_range_end, 0, 1); + else + etm_writel(t, id, ETMVDC3_EXCLONLY, ETMR_VIEWDATACTRL3); + + etm_writel(t, id, 0x6f, ETMR_VIEWDATAEVT); v &= ~ETMCTRL_PROGRAM; v |= ETMCTRL_PORTSEL; - etm_writel(t, v, ETMR_CTRL); + etm_writel(t, id, v, ETMR_CTRL); timeout = TRACER_TIMEOUT; - while (etm_readl(t, ETMR_CTRL) & ETMCTRL_PROGRAM && --timeout) + while (etm_readl(t, id, ETMR_CTRL) & ETMCTRL_PROGRAM && --timeout) ; if (!timeout) { dev_dbg(t->dev, "Waiting for progbit to deassert timed out\n"); - etm_lock(t); + etm_lock(t, id); return -EFAULT; } - etm_lock(t); + etm_lock(t, id); + return 0; +} + +static int trace_start(struct tracectx *t) +{ + int ret; + int id; + u32 etb_fc = t->etb_fc; + + etb_unlock(t); + + t->dump_initial_etb = false; + etb_writel(t, 0, ETBR_WRITEADDR); + etb_writel(t, etb_fc, ETBR_FORMATTERCTRL); + etb_writel(t, 1, ETBR_CTRL); + + etb_lock(t); + + /* configure etm(s) */ + for (id = 0; id < t->etm_regs_count; id++) { + ret = trace_start_etm(t, id); + if (ret) + return ret; + } t->flags |= TRACER_RUNNING; return 0; } -static int trace_stop(struct tracectx *t) +static int trace_stop_etm(struct tracectx *t, int id) { unsigned long timeout = TRACER_TIMEOUT; - etm_unlock(t); + etm_unlock(t, id); - etm_writel(t, 0x440, ETMR_CTRL); - while (!(etm_readl(t, ETMR_CTRL) & ETMCTRL_PROGRAM) && --timeout) + etm_writel(t, id, 0x440, ETMR_CTRL); + while (!(etm_readl(t, id, ETMR_CTRL) & ETMCTRL_PROGRAM) && --timeout) ; if (!timeout) { - dev_dbg(t->dev, "Waiting for progbit to assert timed out\n"); - etm_lock(t); + dev_err(t->dev, + "etm%d: Waiting for progbit to assert timed out\n", + id); + etm_lock(t, id); return -EFAULT; } - etm_lock(t); + etm_lock(t, id); + return 0; +} + +static int trace_power_down_etm(struct tracectx *t, int id) +{ + unsigned long timeout = TRACER_TIMEOUT; + etm_unlock(t, id); + while (!(etm_readl(t, id, ETMR_STATUS) & ETMST_PROGBIT) && --timeout) + ; + if (!timeout) { + dev_err(t->dev, "etm%d: Waiting for status progbit to assert timed out\n", + id); + etm_lock(t, id); + return -EFAULT; + } + + etm_writel(t, id, 0x441, ETMR_CTRL); + + etm_lock(t, id); + return 0; +} + +static int trace_stop(struct tracectx *t) +{ + int id; + unsigned long timeout = TRACER_TIMEOUT; + u32 etb_fc = t->etb_fc; + + for (id = 0; id < t->etm_regs_count; id++) + trace_stop_etm(t, id); + + for (id = 0; id < t->etm_regs_count; id++) + trace_power_down_etm(t, id); etb_unlock(t); - etb_writel(t, ETBFF_MANUAL_FLUSH, ETBR_FORMATTERCTRL); + if (etb_fc) { + etb_fc |= ETBFF_STOPFL; + etb_writel(t, t->etb_fc, ETBR_FORMATTERCTRL); + } + etb_writel(t, etb_fc | ETBFF_MANUAL_FLUSH, ETBR_FORMATTERCTRL); timeout = TRACER_TIMEOUT; while (etb_readl(t, ETBR_FORMATTERCTRL) & @@ -185,24 +287,15 @@ static int trace_stop(struct tracectx *t) static int etb_getdatalen(struct tracectx *t) { u32 v; - int rp, wp; + int wp; v = etb_readl(t, ETBR_STATUS); if (v & 1) return t->etb_bufsz; - rp = etb_readl(t, ETBR_READADDR); wp = etb_readl(t, ETBR_WRITEADDR); - - if (rp > wp) { - etb_writel(t, 0, ETBR_READADDR); - etb_writel(t, 0, ETBR_WRITEADDR); - - return 0; - } - - return wp - rp; + return wp; } /* sysrq+v will always stop the running trace and leave it at that */ @@ -235,21 +328,18 @@ static void etm_dump(void) printk("%08x", cpu_to_be32(etb_readl(t, ETBR_READMEM))); printk(KERN_INFO "\n--- ETB buffer end ---\n"); - /* deassert the overflow bit */ - etb_writel(t, 1, ETBR_CTRL); - etb_writel(t, 0, ETBR_CTRL); - - etb_writel(t, 0, ETBR_TRIGGERCOUNT); - etb_writel(t, 0, ETBR_READADDR); - etb_writel(t, 0, ETBR_WRITEADDR); - etb_lock(t); } static void sysrq_etm_dump(int key) { + if (!mutex_trylock(&tracer.mutex)) { + printk(KERN_INFO "Tracing hardware busy\n"); + return; + } dev_dbg(tracer.dev, "Dumping ETB buffer\n"); etm_dump(); + mutex_unlock(&tracer.mutex); } static struct sysrq_key_op sysrq_etm_op = { @@ -276,6 +366,10 @@ static ssize_t etb_read(struct file *file, char __user *data, struct tracectx *t = file->private_data; u32 first = 0; u32 *buf; + int wpos; + int skip; + long wlength; + loff_t pos = *ppos; mutex_lock(&t->mutex); @@ -287,31 +381,39 @@ static ssize_t etb_read(struct file *file, char __user *data, etb_unlock(t); total = etb_getdatalen(t); + if (total == 0 && t->dump_initial_etb) + total = t->etb_bufsz; if (total == t->etb_bufsz) first = etb_readl(t, ETBR_WRITEADDR); + if (pos > total * 4) { + skip = 0; + wpos = total; + } else { + skip = (int)pos % 4; + wpos = (int)pos / 4; + } + total -= wpos; + first = (first + wpos) % t->etb_bufsz; + etb_writel(t, first, ETBR_READADDR); - length = min(total * 4, (int)len); - buf = vmalloc(length); + wlength = min(total, DIV_ROUND_UP(skip + (int)len, 4)); + length = min(total * 4 - skip, (int)len); + buf = vmalloc(wlength * 4); - dev_dbg(t->dev, "ETB buffer length: %d\n", total); + dev_dbg(t->dev, "ETB read %ld bytes to %lld from %ld words at %d\n", + length, pos, wlength, first); + dev_dbg(t->dev, "ETB buffer length: %d\n", total + wpos); dev_dbg(t->dev, "ETB status reg: %x\n", etb_readl(t, ETBR_STATUS)); - for (i = 0; i < length / 4; i++) + for (i = 0; i < wlength; i++) buf[i] = etb_readl(t, ETBR_READMEM); - /* the only way to deassert overflow bit in ETB status is this */ - etb_writel(t, 1, ETBR_CTRL); - etb_writel(t, 0, ETBR_CTRL); - - etb_writel(t, 0, ETBR_WRITEADDR); - etb_writel(t, 0, ETBR_READADDR); - etb_writel(t, 0, ETBR_TRIGGERCOUNT); - etb_lock(t); - length -= copy_to_user(data, buf, length); + length -= copy_to_user(data, (u8 *)buf + skip, length); vfree(buf); + *ppos = pos + length; out: mutex_unlock(&t->mutex); @@ -348,28 +450,17 @@ static int etb_probe(struct amba_device *dev, const struct amba_id *id) if (ret) goto out; + mutex_lock(&t->mutex); t->etb_regs = ioremap_nocache(dev->res.start, resource_size(&dev->res)); if (!t->etb_regs) { ret = -ENOMEM; goto out_release; } + t->dev = &dev->dev; + t->dump_initial_etb = true; amba_set_drvdata(dev, t); - etb_miscdev.parent = &dev->dev; - - ret = misc_register(&etb_miscdev); - if (ret) - goto out_unmap; - - t->emu_clk = clk_get(&dev->dev, "emu_src_ck"); - if (IS_ERR(t->emu_clk)) { - dev_dbg(&dev->dev, "Failed to obtain emu_src_ck.\n"); - return -EFAULT; - } - - clk_enable(t->emu_clk); - etb_unlock(t); t->etb_bufsz = etb_readl(t, ETBR_DEPTH); dev_dbg(&dev->dev, "Size: %x\n", t->etb_bufsz); @@ -378,6 +469,20 @@ static int etb_probe(struct amba_device *dev, const struct amba_id *id) etb_writel(t, 0, ETBR_CTRL); etb_writel(t, 0x1000, ETBR_FORMATTERCTRL); etb_lock(t); + mutex_unlock(&t->mutex); + + etb_miscdev.parent = &dev->dev; + + ret = misc_register(&etb_miscdev); + if (ret) + goto out_unmap; + + /* Get optional clock. Currently used to select clock source on omap3 */ + t->emu_clk = clk_get(&dev->dev, "emu_src_ck"); + if (IS_ERR(t->emu_clk)) + dev_dbg(&dev->dev, "Failed to obtain emu_src_ck.\n"); + else + clk_enable(t->emu_clk); dev_dbg(&dev->dev, "ETB AMBA driver initialized.\n"); @@ -385,9 +490,12 @@ static int etb_probe(struct amba_device *dev, const struct amba_id *id) return ret; out_unmap: + mutex_lock(&t->mutex); iounmap(t->etb_regs); + t->etb_regs = NULL; out_release: + mutex_unlock(&t->mutex); amba_release_regions(dev); return ret; @@ -400,8 +508,10 @@ static int etb_remove(struct amba_device *dev) iounmap(t->etb_regs); t->etb_regs = NULL; - clk_disable(t->emu_clk); - clk_put(t->emu_clk); + if (!IS_ERR(t->emu_clk)) { + clk_disable(t->emu_clk); + clk_put(t->emu_clk); + } amba_release_regions(dev); @@ -445,7 +555,10 @@ static ssize_t trace_running_store(struct kobject *kobj, return -EINVAL; mutex_lock(&tracer.mutex); - ret = value ? trace_start(&tracer) : trace_stop(&tracer); + if (!tracer.etb_regs) + ret = -ENODEV; + else + ret = value ? trace_start(&tracer) : trace_stop(&tracer); mutex_unlock(&tracer.mutex); return ret ? : n; @@ -460,36 +573,50 @@ static ssize_t trace_info_show(struct kobject *kobj, { u32 etb_wa, etb_ra, etb_st, etb_fc, etm_ctrl, etm_st; int datalen; + int id; + int ret; - etb_unlock(&tracer); - datalen = etb_getdatalen(&tracer); - etb_wa = etb_readl(&tracer, ETBR_WRITEADDR); - etb_ra = etb_readl(&tracer, ETBR_READADDR); - etb_st = etb_readl(&tracer, ETBR_STATUS); - etb_fc = etb_readl(&tracer, ETBR_FORMATTERCTRL); - etb_lock(&tracer); - - etm_unlock(&tracer); - etm_ctrl = etm_readl(&tracer, ETMR_CTRL); - etm_st = etm_readl(&tracer, ETMR_STATUS); - etm_lock(&tracer); + mutex_lock(&tracer.mutex); + if (tracer.etb_regs) { + etb_unlock(&tracer); + datalen = etb_getdatalen(&tracer); + etb_wa = etb_readl(&tracer, ETBR_WRITEADDR); + etb_ra = etb_readl(&tracer, ETBR_READADDR); + etb_st = etb_readl(&tracer, ETBR_STATUS); + etb_fc = etb_readl(&tracer, ETBR_FORMATTERCTRL); + etb_lock(&tracer); + } else { + etb_wa = etb_ra = etb_st = etb_fc = ~0; + datalen = -1; + } - return sprintf(buf, "Trace buffer len: %d\nComparator pairs: %d\n" + ret = sprintf(buf, "Trace buffer len: %d\nComparator pairs: %d\n" "ETBR_WRITEADDR:\t%08x\n" "ETBR_READADDR:\t%08x\n" "ETBR_STATUS:\t%08x\n" - "ETBR_FORMATTERCTRL:\t%08x\n" - "ETMR_CTRL:\t%08x\n" - "ETMR_STATUS:\t%08x\n", + "ETBR_FORMATTERCTRL:\t%08x\n", datalen, tracer.ncmppairs, etb_wa, etb_ra, etb_st, - etb_fc, + etb_fc + ); + + for (id = 0; id < tracer.etm_regs_count; id++) { + etm_unlock(&tracer, id); + etm_ctrl = etm_readl(&tracer, id, ETMR_CTRL); + etm_st = etm_readl(&tracer, id, ETMR_STATUS); + etm_lock(&tracer, id); + ret += sprintf(buf + ret, "ETMR_CTRL:\t%08x\n" + "ETMR_STATUS:\t%08x\n", etm_ctrl, etm_st ); + } + mutex_unlock(&tracer.mutex); + + return ret; } static struct kobj_attribute trace_info_attr = @@ -528,42 +655,260 @@ static ssize_t trace_mode_store(struct kobject *kobj, static struct kobj_attribute trace_mode_attr = __ATTR(trace_mode, 0644, trace_mode_show, trace_mode_store); +static ssize_t trace_contextid_size_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + /* 0: No context id tracing, 1: One byte, 2: Two bytes, 3: Four bytes */ + return sprintf(buf, "%d\n", (1 << tracer.etm_contextid_size) >> 1); +} + +static ssize_t trace_contextid_size_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned int contextid_size; + + if (sscanf(buf, "%u", &contextid_size) != 1) + return -EINVAL; + + if (contextid_size == 3 || contextid_size > 4) + return -EINVAL; + + mutex_lock(&tracer.mutex); + tracer.etm_contextid_size = fls(contextid_size); + mutex_unlock(&tracer.mutex); + + return n; +} + +static struct kobj_attribute trace_contextid_size_attr = + __ATTR(trace_contextid_size, 0644, + trace_contextid_size_show, trace_contextid_size_store); + +static ssize_t trace_branch_output_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_BRANCHOUTPUT)); +} + +static ssize_t trace_branch_output_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned int branch_output; + + if (sscanf(buf, "%u", &branch_output) != 1) + return -EINVAL; + + mutex_lock(&tracer.mutex); + if (branch_output) { + tracer.flags |= TRACER_BRANCHOUTPUT; + /* Branch broadcasting is incompatible with the return stack */ + tracer.flags &= ~TRACER_RETURN_STACK; + } else { + tracer.flags &= ~TRACER_BRANCHOUTPUT; + } + mutex_unlock(&tracer.mutex); + + return n; +} + +static struct kobj_attribute trace_branch_output_attr = + __ATTR(trace_branch_output, 0644, + trace_branch_output_show, trace_branch_output_store); + +static ssize_t trace_return_stack_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_RETURN_STACK)); +} + +static ssize_t trace_return_stack_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned int return_stack; + + if (sscanf(buf, "%u", &return_stack) != 1) + return -EINVAL; + + mutex_lock(&tracer.mutex); + if (return_stack) { + tracer.flags |= TRACER_RETURN_STACK; + /* Return stack is incompatible with branch broadcasting */ + tracer.flags &= ~TRACER_BRANCHOUTPUT; + } else { + tracer.flags &= ~TRACER_RETURN_STACK; + } + mutex_unlock(&tracer.mutex); + + return n; +} + +static struct kobj_attribute trace_return_stack_attr = + __ATTR(trace_return_stack, 0644, + trace_return_stack_show, trace_return_stack_store); + +static ssize_t trace_timestamp_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_TIMESTAMP)); +} + +static ssize_t trace_timestamp_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned int timestamp; + + if (sscanf(buf, "%u", ×tamp) != 1) + return -EINVAL; + + mutex_lock(&tracer.mutex); + if (timestamp) + tracer.flags |= TRACER_TIMESTAMP; + else + tracer.flags &= ~TRACER_TIMESTAMP; + mutex_unlock(&tracer.mutex); + + return n; +} + +static struct kobj_attribute trace_timestamp_attr = + __ATTR(trace_timestamp, 0644, + trace_timestamp_show, trace_timestamp_store); + +static ssize_t trace_range_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%08lx %08lx\n", + tracer.range_start, tracer.range_end); +} + +static ssize_t trace_range_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long range_start, range_end; + + if (sscanf(buf, "%lx %lx", &range_start, &range_end) != 2) + return -EINVAL; + + mutex_lock(&tracer.mutex); + tracer.range_start = range_start; + tracer.range_end = range_end; + mutex_unlock(&tracer.mutex); + + return n; +} + + +static struct kobj_attribute trace_range_attr = + __ATTR(trace_range, 0644, trace_range_show, trace_range_store); + +static ssize_t trace_data_range_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long range_start; + u64 range_end; + mutex_lock(&tracer.mutex); + range_start = tracer.data_range_start; + range_end = tracer.data_range_end; + if (!range_end && (tracer.flags & TRACER_TRACE_DATA)) + range_end = 0x100000000ULL; + mutex_unlock(&tracer.mutex); + return sprintf(buf, "%08lx %08llx\n", range_start, range_end); +} + +static ssize_t trace_data_range_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long range_start; + u64 range_end; + + if (sscanf(buf, "%lx %llx", &range_start, &range_end) != 2) + return -EINVAL; + + mutex_lock(&tracer.mutex); + tracer.data_range_start = range_start; + tracer.data_range_end = (unsigned long)range_end; + if (range_end) + tracer.flags |= TRACER_TRACE_DATA; + else + tracer.flags &= ~TRACER_TRACE_DATA; + mutex_unlock(&tracer.mutex); + + return n; +} + + +static struct kobj_attribute trace_data_range_attr = + __ATTR(trace_data_range, 0644, + trace_data_range_show, trace_data_range_store); + static int etm_probe(struct amba_device *dev, const struct amba_id *id) { struct tracectx *t = &tracer; int ret = 0; + void __iomem **new_regs; + int new_count; + u32 etmccr; + u32 etmidr; + u32 etmccer = 0; + u8 etm_version = 0; + + mutex_lock(&t->mutex); + new_count = t->etm_regs_count + 1; + new_regs = krealloc(t->etm_regs, + sizeof(t->etm_regs[0]) * new_count, GFP_KERNEL); - if (t->etm_regs) { - dev_dbg(&dev->dev, "ETM already initialized\n"); - ret = -EBUSY; + if (!new_regs) { + dev_dbg(&dev->dev, "Failed to allocate ETM register array\n"); + ret = -ENOMEM; goto out; } + t->etm_regs = new_regs; ret = amba_request_regions(dev, NULL); if (ret) goto out; - t->etm_regs = ioremap_nocache(dev->res.start, resource_size(&dev->res)); - if (!t->etm_regs) { + t->etm_regs[t->etm_regs_count] = + ioremap_nocache(dev->res.start, resource_size(&dev->res)); + if (!t->etm_regs[t->etm_regs_count]) { ret = -ENOMEM; goto out_release; } - amba_set_drvdata(dev, t); + amba_set_drvdata(dev, t->etm_regs[t->etm_regs_count]); - mutex_init(&t->mutex); - t->dev = &dev->dev; - t->flags = TRACER_CYCLE_ACC; + t->flags = TRACER_CYCLE_ACC | TRACER_TRACE_DATA | TRACER_BRANCHOUTPUT; t->etm_portsz = 1; + t->etm_contextid_size = 3; - etm_unlock(t); - (void)etm_readl(t, ETMMR_PDSR); + etm_unlock(t, t->etm_regs_count); + (void)etm_readl(t, t->etm_regs_count, ETMMR_PDSR); /* dummy first read */ - (void)etm_readl(&tracer, ETMMR_OSSRR); - - t->ncmppairs = etm_readl(t, ETMR_CONFCODE) & 0xf; - etm_writel(t, 0x440, ETMR_CTRL); - etm_lock(t); + (void)etm_readl(&tracer, t->etm_regs_count, ETMMR_OSSRR); + + etmccr = etm_readl(t, t->etm_regs_count, ETMR_CONFCODE); + t->ncmppairs = etmccr & 0xf; + if (etmccr & ETMCCR_ETMIDR_PRESENT) { + etmidr = etm_readl(t, t->etm_regs_count, ETMR_ID); + etm_version = ETMIDR_VERSION(etmidr); + if (etm_version >= ETMIDR_VERSION_3_1) + etmccer = etm_readl(t, t->etm_regs_count, ETMR_CCE); + } + etm_writel(t, t->etm_regs_count, 0x441, ETMR_CTRL); + etm_writel(t, t->etm_regs_count, new_count, ETMR_TRACEIDR); + etm_lock(t, t->etm_regs_count); ret = sysfs_create_file(&dev->dev.kobj, &trace_running_attr.attr); @@ -579,32 +924,97 @@ static int etm_probe(struct amba_device *dev, const struct amba_id *id) if (ret) dev_dbg(&dev->dev, "Failed to create trace_mode in sysfs\n"); - dev_dbg(t->dev, "ETM AMBA driver initialized.\n"); + ret = sysfs_create_file(&dev->dev.kobj, + &trace_contextid_size_attr.attr); + if (ret) + dev_dbg(&dev->dev, + "Failed to create trace_contextid_size in sysfs\n"); + + ret = sysfs_create_file(&dev->dev.kobj, + &trace_branch_output_attr.attr); + if (ret) + dev_dbg(&dev->dev, + "Failed to create trace_branch_output in sysfs\n"); + + if (etmccer & ETMCCER_RETURN_STACK_IMPLEMENTED) { + ret = sysfs_create_file(&dev->dev.kobj, + &trace_return_stack_attr.attr); + if (ret) + dev_dbg(&dev->dev, + "Failed to create trace_return_stack in sysfs\n"); + } + + if (etmccer & ETMCCER_TIMESTAMPING_IMPLEMENTED) { + ret = sysfs_create_file(&dev->dev.kobj, + &trace_timestamp_attr.attr); + if (ret) + dev_dbg(&dev->dev, + "Failed to create trace_timestamp in sysfs\n"); + } + + ret = sysfs_create_file(&dev->dev.kobj, &trace_range_attr.attr); + if (ret) + dev_dbg(&dev->dev, "Failed to create trace_range in sysfs\n"); + + if (etm_version < ETMIDR_VERSION_PFT_1_0) { + ret = sysfs_create_file(&dev->dev.kobj, + &trace_data_range_attr.attr); + if (ret) + dev_dbg(&dev->dev, + "Failed to create trace_data_range in sysfs\n"); + } else { + tracer.flags &= ~TRACER_TRACE_DATA; + } + + dev_dbg(&dev->dev, "ETM AMBA driver initialized.\n"); + + /* Enable formatter if there are multiple trace sources */ + if (new_count > 1) + t->etb_fc = ETBFF_ENFCONT | ETBFF_ENFTC; + + t->etm_regs_count = new_count; out: + mutex_unlock(&t->mutex); return ret; out_unmap: - iounmap(t->etm_regs); + iounmap(t->etm_regs[t->etm_regs_count]); out_release: amba_release_regions(dev); + mutex_unlock(&t->mutex); return ret; } static int etm_remove(struct amba_device *dev) { - struct tracectx *t = amba_get_drvdata(dev); - - iounmap(t->etm_regs); - t->etm_regs = NULL; - - amba_release_regions(dev); + int i; + struct tracectx *t = &tracer; + void __iomem *etm_regs = amba_get_drvdata(dev); sysfs_remove_file(&dev->dev.kobj, &trace_running_attr.attr); sysfs_remove_file(&dev->dev.kobj, &trace_info_attr.attr); sysfs_remove_file(&dev->dev.kobj, &trace_mode_attr.attr); + sysfs_remove_file(&dev->dev.kobj, &trace_range_attr.attr); + sysfs_remove_file(&dev->dev.kobj, &trace_data_range_attr.attr); + + mutex_lock(&t->mutex); + for (i = 0; i < t->etm_regs_count; i++) + if (t->etm_regs[i] == etm_regs) + break; + for (; i < t->etm_regs_count - 1; i++) + t->etm_regs[i] = t->etm_regs[i + 1]; + t->etm_regs_count--; + if (!t->etm_regs_count) { + kfree(t->etm_regs); + t->etm_regs = NULL; + } + mutex_unlock(&t->mutex); + + iounmap(etm_regs); + amba_release_regions(dev); return 0; } @@ -614,6 +1024,10 @@ static struct amba_id etm_ids[] = { .id = 0x0003b921, .mask = 0x0007ffff, }, + { + .id = 0x0003b950, + .mask = 0x0007ffff, + }, { 0, 0 }, }; @@ -631,6 +1045,8 @@ static int __init etm_init(void) { int retval; + mutex_init(&tracer.mutex); + retval = amba_driver_register(&etb_driver); if (retval) { printk(KERN_ERR "Failed to register etb\n"); diff --git a/arch/arm/kernel/kgdb.c b/arch/arm/kernel/kgdb.c index 778c2f7024ff57..b321c8fbb87dc8 100644 --- a/arch/arm/kernel/kgdb.c +++ b/arch/arm/kernel/kgdb.c @@ -144,6 +144,8 @@ int kgdb_arch_handle_exception(int exception_vector, int signo, static int kgdb_brk_fn(struct pt_regs *regs, unsigned int instr) { + if (user_mode(regs)) + return -1; kgdb_handle_exception(1, SIGTRAP, 0, regs); return 0; @@ -151,6 +153,8 @@ static int kgdb_brk_fn(struct pt_regs *regs, unsigned int instr) static int kgdb_compiled_brk_fn(struct pt_regs *regs, unsigned int instr) { + if (user_mode(regs)) + return -1; compiled_break = 1; kgdb_handle_exception(1, SIGTRAP, 0, regs); diff --git a/arch/arm/kernel/kprobes-arm.c b/arch/arm/kernel/kprobes-arm.c index 8a30c89da70ec1..ac300c60d65698 100644 --- a/arch/arm/kernel/kprobes-arm.c +++ b/arch/arm/kernel/kprobes-arm.c @@ -60,13 +60,10 @@ #include #include -#include +#include #include "kprobes.h" - -#define sign_extend(x, signbit) ((x) | (0 - ((x) & (1 << (signbit))))) - -#define branch_displacement(insn) sign_extend(((insn) & 0xffffff) << 2, 25) +#include "probes-arm.h" #if __LINUX_ARM_ARCH__ >= 6 #define BLX(reg) "blx "reg" \n\t" @@ -75,92 +72,11 @@ "mov pc, "reg" \n\t" #endif -/* - * To avoid the complications of mimicing single-stepping on a - * processor without a Next-PC or a single-step mode, and to - * avoid having to deal with the side-effects of boosting, we - * simulate or emulate (almost) all ARM instructions. - * - * "Simulation" is where the instruction's behavior is duplicated in - * C code. "Emulation" is where the original instruction is rewritten - * and executed, often by altering its registers. - * - * By having all behavior of the kprobe'd instruction completed before - * returning from the kprobe_handler(), all locks (scheduler and - * interrupt) can safely be released. There is no need for secondary - * breakpoints, no race with MP or preemptable kernels, nor having to - * clean up resources counts at a later time impacting overall system - * performance. By rewriting the instruction, only the minimum registers - * need to be loaded and saved back optimizing performance. - * - * Calling the insnslot_*_rwflags version of a function doesn't hurt - * anything even when the CPSR flags aren't updated by the - * instruction. It's just a little slower in return for saving - * a little space by not having a duplicate function that doesn't - * update the flags. (The same optimization can be said for - * instructions that do or don't perform register writeback) - * Also, instructions can either read the flags, only write the - * flags, or read and write the flags. To save combinations - * rather than for sheer performance, flag functions just assume - * read and write of flags. - */ - -static void __kprobes simulate_bbl(struct kprobe *p, struct pt_regs *regs) -{ - kprobe_opcode_t insn = p->opcode; - long iaddr = (long)p->addr; - int disp = branch_displacement(insn); - - if (insn & (1 << 24)) - regs->ARM_lr = iaddr + 4; - - regs->ARM_pc = iaddr + 8 + disp; -} - -static void __kprobes simulate_blx1(struct kprobe *p, struct pt_regs *regs) -{ - kprobe_opcode_t insn = p->opcode; - long iaddr = (long)p->addr; - int disp = branch_displacement(insn); - - regs->ARM_lr = iaddr + 4; - regs->ARM_pc = iaddr + 8 + disp + ((insn >> 23) & 0x2); - regs->ARM_cpsr |= PSR_T_BIT; -} - -static void __kprobes simulate_blx2bx(struct kprobe *p, struct pt_regs *regs) -{ - kprobe_opcode_t insn = p->opcode; - int rm = insn & 0xf; - long rmv = regs->uregs[rm]; - - if (insn & (1 << 5)) - regs->ARM_lr = (long)p->addr + 4; - - regs->ARM_pc = rmv & ~0x1; - regs->ARM_cpsr &= ~PSR_T_BIT; - if (rmv & 0x1) - regs->ARM_cpsr |= PSR_T_BIT; -} - -static void __kprobes simulate_mrs(struct kprobe *p, struct pt_regs *regs) -{ - kprobe_opcode_t insn = p->opcode; - int rd = (insn >> 12) & 0xf; - unsigned long mask = 0xf8ff03df; /* Mask out execution state */ - regs->uregs[rd] = regs->ARM_cpsr & mask; -} - -static void __kprobes simulate_mov_ipsp(struct kprobe *p, struct pt_regs *regs) -{ - regs->uregs[12] = regs->uregs[13]; -} - static void __kprobes -emulate_ldrdstrd(struct kprobe *p, struct pt_regs *regs) +emulate_ldrdstrd(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = (unsigned long)p->addr + 8; + unsigned long pc = regs->ARM_pc + 4; int rt = (insn >> 12) & 0xf; int rn = (insn >> 16) & 0xf; int rm = insn & 0xf; @@ -175,7 +91,7 @@ emulate_ldrdstrd(struct kprobe *p, struct pt_regs *regs) BLX("%[fn]") : "=r" (rtv), "=r" (rt2v), "=r" (rnv) : "0" (rtv), "1" (rt2v), "2" (rnv), "r" (rmv), - [fn] "r" (p->ainsn.insn_fn) + [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -186,10 +102,10 @@ emulate_ldrdstrd(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -emulate_ldr(struct kprobe *p, struct pt_regs *regs) +emulate_ldr(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = (unsigned long)p->addr + 8; + unsigned long pc = regs->ARM_pc + 4; int rt = (insn >> 12) & 0xf; int rn = (insn >> 16) & 0xf; int rm = insn & 0xf; @@ -202,7 +118,7 @@ emulate_ldr(struct kprobe *p, struct pt_regs *regs) __asm__ __volatile__ ( BLX("%[fn]") : "=r" (rtv), "=r" (rnv) - : "1" (rnv), "r" (rmv), [fn] "r" (p->ainsn.insn_fn) + : "1" (rnv), "r" (rmv), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -216,11 +132,11 @@ emulate_ldr(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -emulate_str(struct kprobe *p, struct pt_regs *regs) +emulate_str(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long rtpc = (unsigned long)p->addr + str_pc_offset; - unsigned long rnpc = (unsigned long)p->addr + 8; + unsigned long rtpc = regs->ARM_pc - 4 + str_pc_offset; + unsigned long rnpc = regs->ARM_pc + 4; int rt = (insn >> 12) & 0xf; int rn = (insn >> 16) & 0xf; int rm = insn & 0xf; @@ -234,7 +150,7 @@ emulate_str(struct kprobe *p, struct pt_regs *regs) __asm__ __volatile__ ( BLX("%[fn]") : "=r" (rnv) - : "r" (rtv), "0" (rnv), "r" (rmv), [fn] "r" (p->ainsn.insn_fn) + : "r" (rtv), "0" (rnv), "r" (rmv), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -243,10 +159,10 @@ emulate_str(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -emulate_rd12rn16rm0rs8_rwflags(struct kprobe *p, struct pt_regs *regs) +emulate_rd12rn16rm0rs8_rwflags(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = (unsigned long)p->addr + 8; + unsigned long pc = regs->ARM_pc + 4; int rd = (insn >> 12) & 0xf; int rn = (insn >> 16) & 0xf; int rm = insn & 0xf; @@ -266,7 +182,7 @@ emulate_rd12rn16rm0rs8_rwflags(struct kprobe *p, struct pt_regs *regs) "mrs %[cpsr], cpsr \n\t" : "=r" (rdv), [cpsr] "=r" (cpsr) : "0" (rdv), "r" (rnv), "r" (rmv), "r" (rsv), - "1" (cpsr), [fn] "r" (p->ainsn.insn_fn) + "1" (cpsr), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -278,9 +194,9 @@ emulate_rd12rn16rm0rs8_rwflags(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -emulate_rd12rn16rm0_rwflags_nopc(struct kprobe *p, struct pt_regs *regs) +emulate_rd12rn16rm0_rwflags_nopc(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rd = (insn >> 12) & 0xf; int rn = (insn >> 16) & 0xf; int rm = insn & 0xf; @@ -296,7 +212,7 @@ emulate_rd12rn16rm0_rwflags_nopc(struct kprobe *p, struct pt_regs *regs) "mrs %[cpsr], cpsr \n\t" : "=r" (rdv), [cpsr] "=r" (cpsr) : "0" (rdv), "r" (rnv), "r" (rmv), - "1" (cpsr), [fn] "r" (p->ainsn.insn_fn) + "1" (cpsr), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -305,9 +221,10 @@ emulate_rd12rn16rm0_rwflags_nopc(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -emulate_rd16rn12rm0rs8_rwflags_nopc(struct kprobe *p, struct pt_regs *regs) +emulate_rd16rn12rm0rs8_rwflags_nopc(probes_opcode_t insn, + struct arch_probes_insn *asi, + struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rd = (insn >> 16) & 0xf; int rn = (insn >> 12) & 0xf; int rm = insn & 0xf; @@ -325,7 +242,7 @@ emulate_rd16rn12rm0rs8_rwflags_nopc(struct kprobe *p, struct pt_regs *regs) "mrs %[cpsr], cpsr \n\t" : "=r" (rdv), [cpsr] "=r" (cpsr) : "0" (rdv), "r" (rnv), "r" (rmv), "r" (rsv), - "1" (cpsr), [fn] "r" (p->ainsn.insn_fn) + "1" (cpsr), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -334,9 +251,9 @@ emulate_rd16rn12rm0rs8_rwflags_nopc(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -emulate_rd12rm0_noflags_nopc(struct kprobe *p, struct pt_regs *regs) +emulate_rd12rm0_noflags_nopc(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rd = (insn >> 12) & 0xf; int rm = insn & 0xf; @@ -346,7 +263,7 @@ emulate_rd12rm0_noflags_nopc(struct kprobe *p, struct pt_regs *regs) __asm__ __volatile__ ( BLX("%[fn]") : "=r" (rdv) - : "0" (rdv), "r" (rmv), [fn] "r" (p->ainsn.insn_fn) + : "0" (rdv), "r" (rmv), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -354,9 +271,10 @@ emulate_rd12rm0_noflags_nopc(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -emulate_rdlo12rdhi16rn0rm8_rwflags_nopc(struct kprobe *p, struct pt_regs *regs) +emulate_rdlo12rdhi16rn0rm8_rwflags_nopc(probes_opcode_t insn, + struct arch_probes_insn *asi, + struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rdlo = (insn >> 12) & 0xf; int rdhi = (insn >> 16) & 0xf; int rn = insn & 0xf; @@ -374,7 +292,7 @@ emulate_rdlo12rdhi16rn0rm8_rwflags_nopc(struct kprobe *p, struct pt_regs *regs) "mrs %[cpsr], cpsr \n\t" : "=r" (rdlov), "=r" (rdhiv), [cpsr] "=r" (cpsr) : "0" (rdlov), "1" (rdhiv), "r" (rnv), "r" (rmv), - "2" (cpsr), [fn] "r" (p->ainsn.insn_fn) + "2" (cpsr), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -383,623 +301,43 @@ emulate_rdlo12rdhi16rn0rm8_rwflags_nopc(struct kprobe *p, struct pt_regs *regs) regs->ARM_cpsr = (regs->ARM_cpsr & ~APSR_MASK) | (cpsr & APSR_MASK); } -/* - * For the instruction masking and comparisons in all the "space_*" - * functions below, Do _not_ rearrange the order of tests unless - * you're very, very sure of what you are doing. For the sake of - * efficiency, the masks for some tests sometimes assume other test - * have been done prior to them so the number of patterns to test - * for an instruction set can be as broad as possible to reduce the - * number of tests needed. - */ - -static const union decode_item arm_1111_table[] = { - /* Unconditional instructions */ - - /* memory hint 1111 0100 x001 xxxx xxxx xxxx xxxx xxxx */ - /* PLDI (immediate) 1111 0100 x101 xxxx xxxx xxxx xxxx xxxx */ - /* PLDW (immediate) 1111 0101 x001 xxxx xxxx xxxx xxxx xxxx */ - /* PLD (immediate) 1111 0101 x101 xxxx xxxx xxxx xxxx xxxx */ - DECODE_SIMULATE (0xfe300000, 0xf4100000, kprobe_simulate_nop), - - /* memory hint 1111 0110 x001 xxxx xxxx xxxx xxx0 xxxx */ - /* PLDI (register) 1111 0110 x101 xxxx xxxx xxxx xxx0 xxxx */ - /* PLDW (register) 1111 0111 x001 xxxx xxxx xxxx xxx0 xxxx */ - /* PLD (register) 1111 0111 x101 xxxx xxxx xxxx xxx0 xxxx */ - DECODE_SIMULATE (0xfe300010, 0xf6100000, kprobe_simulate_nop), - - /* BLX (immediate) 1111 101x xxxx xxxx xxxx xxxx xxxx xxxx */ - DECODE_SIMULATE (0xfe000000, 0xfa000000, simulate_blx1), - - /* CPS 1111 0001 0000 xxx0 xxxx xxxx xx0x xxxx */ - /* SETEND 1111 0001 0000 0001 xxxx xxxx 0000 xxxx */ - /* SRS 1111 100x x1x0 xxxx xxxx xxxx xxxx xxxx */ - /* RFE 1111 100x x0x1 xxxx xxxx xxxx xxxx xxxx */ - - /* Coprocessor instructions... */ - /* MCRR2 1111 1100 0100 xxxx xxxx xxxx xxxx xxxx */ - /* MRRC2 1111 1100 0101 xxxx xxxx xxxx xxxx xxxx */ - /* LDC2 1111 110x xxx1 xxxx xxxx xxxx xxxx xxxx */ - /* STC2 1111 110x xxx0 xxxx xxxx xxxx xxxx xxxx */ - /* CDP2 1111 1110 xxxx xxxx xxxx xxxx xxx0 xxxx */ - /* MCR2 1111 1110 xxx0 xxxx xxxx xxxx xxx1 xxxx */ - /* MRC2 1111 1110 xxx1 xxxx xxxx xxxx xxx1 xxxx */ - - /* Other unallocated instructions... */ - DECODE_END -}; - -static const union decode_item arm_cccc_0001_0xx0____0xxx_table[] = { - /* Miscellaneous instructions */ - - /* MRS cpsr cccc 0001 0000 xxxx xxxx xxxx 0000 xxxx */ - DECODE_SIMULATEX(0x0ff000f0, 0x01000000, simulate_mrs, - REGS(0, NOPC, 0, 0, 0)), - - /* BX cccc 0001 0010 xxxx xxxx xxxx 0001 xxxx */ - DECODE_SIMULATE (0x0ff000f0, 0x01200010, simulate_blx2bx), - - /* BLX (register) cccc 0001 0010 xxxx xxxx xxxx 0011 xxxx */ - DECODE_SIMULATEX(0x0ff000f0, 0x01200030, simulate_blx2bx, - REGS(0, 0, 0, 0, NOPC)), - - /* CLZ cccc 0001 0110 xxxx xxxx xxxx 0001 xxxx */ - DECODE_EMULATEX (0x0ff000f0, 0x01600010, emulate_rd12rm0_noflags_nopc, - REGS(0, NOPC, 0, 0, NOPC)), - - /* QADD cccc 0001 0000 xxxx xxxx xxxx 0101 xxxx */ - /* QSUB cccc 0001 0010 xxxx xxxx xxxx 0101 xxxx */ - /* QDADD cccc 0001 0100 xxxx xxxx xxxx 0101 xxxx */ - /* QDSUB cccc 0001 0110 xxxx xxxx xxxx 0101 xxxx */ - DECODE_EMULATEX (0x0f9000f0, 0x01000050, emulate_rd12rn16rm0_rwflags_nopc, - REGS(NOPC, NOPC, 0, 0, NOPC)), - - /* BXJ cccc 0001 0010 xxxx xxxx xxxx 0010 xxxx */ - /* MSR cccc 0001 0x10 xxxx xxxx xxxx 0000 xxxx */ - /* MRS spsr cccc 0001 0100 xxxx xxxx xxxx 0000 xxxx */ - /* BKPT 1110 0001 0010 xxxx xxxx xxxx 0111 xxxx */ - /* SMC cccc 0001 0110 xxxx xxxx xxxx 0111 xxxx */ - /* And unallocated instructions... */ - DECODE_END -}; - -static const union decode_item arm_cccc_0001_0xx0____1xx0_table[] = { - /* Halfword multiply and multiply-accumulate */ - - /* SMLALxy cccc 0001 0100 xxxx xxxx xxxx 1xx0 xxxx */ - DECODE_EMULATEX (0x0ff00090, 0x01400080, emulate_rdlo12rdhi16rn0rm8_rwflags_nopc, - REGS(NOPC, NOPC, NOPC, 0, NOPC)), - - /* SMULWy cccc 0001 0010 xxxx xxxx xxxx 1x10 xxxx */ - DECODE_OR (0x0ff000b0, 0x012000a0), - /* SMULxy cccc 0001 0110 xxxx xxxx xxxx 1xx0 xxxx */ - DECODE_EMULATEX (0x0ff00090, 0x01600080, emulate_rd16rn12rm0rs8_rwflags_nopc, - REGS(NOPC, 0, NOPC, 0, NOPC)), - - /* SMLAxy cccc 0001 0000 xxxx xxxx xxxx 1xx0 xxxx */ - DECODE_OR (0x0ff00090, 0x01000080), - /* SMLAWy cccc 0001 0010 xxxx xxxx xxxx 1x00 xxxx */ - DECODE_EMULATEX (0x0ff000b0, 0x01200080, emulate_rd16rn12rm0rs8_rwflags_nopc, - REGS(NOPC, NOPC, NOPC, 0, NOPC)), - - DECODE_END +const union decode_action kprobes_arm_actions[NUM_PROBES_ARM_ACTIONS] = { + [PROBES_EMULATE_NONE] = {.handler = probes_emulate_none}, + [PROBES_SIMULATE_NOP] = {.handler = probes_simulate_nop}, + [PROBES_PRELOAD_IMM] = {.handler = probes_simulate_nop}, + [PROBES_PRELOAD_REG] = {.handler = probes_simulate_nop}, + [PROBES_BRANCH_IMM] = {.handler = simulate_blx1}, + [PROBES_MRS] = {.handler = simulate_mrs}, + [PROBES_BRANCH_REG] = {.handler = simulate_blx2bx}, + [PROBES_CLZ] = {.handler = emulate_rd12rm0_noflags_nopc}, + [PROBES_SATURATING_ARITHMETIC] = { + .handler = emulate_rd12rn16rm0_rwflags_nopc}, + [PROBES_MUL1] = {.handler = emulate_rdlo12rdhi16rn0rm8_rwflags_nopc}, + [PROBES_MUL2] = {.handler = emulate_rd16rn12rm0rs8_rwflags_nopc}, + [PROBES_SWP] = {.handler = emulate_rd12rn16rm0_rwflags_nopc}, + [PROBES_LDRSTRD] = {.handler = emulate_ldrdstrd}, + [PROBES_LOAD_EXTRA] = {.handler = emulate_ldr}, + [PROBES_LOAD] = {.handler = emulate_ldr}, + [PROBES_STORE_EXTRA] = {.handler = emulate_str}, + [PROBES_STORE] = {.handler = emulate_str}, + [PROBES_MOV_IP_SP] = {.handler = simulate_mov_ipsp}, + [PROBES_DATA_PROCESSING_REG] = { + .handler = emulate_rd12rn16rm0rs8_rwflags}, + [PROBES_DATA_PROCESSING_IMM] = { + .handler = emulate_rd12rn16rm0rs8_rwflags}, + [PROBES_MOV_HALFWORD] = {.handler = emulate_rd12rm0_noflags_nopc}, + [PROBES_SEV] = {.handler = probes_emulate_none}, + [PROBES_WFE] = {.handler = probes_simulate_nop}, + [PROBES_SATURATE] = {.handler = emulate_rd12rn16rm0_rwflags_nopc}, + [PROBES_REV] = {.handler = emulate_rd12rm0_noflags_nopc}, + [PROBES_MMI] = {.handler = emulate_rd12rn16rm0_rwflags_nopc}, + [PROBES_PACK] = {.handler = emulate_rd12rn16rm0_rwflags_nopc}, + [PROBES_EXTEND] = {.handler = emulate_rd12rm0_noflags_nopc}, + [PROBES_EXTEND_ADD] = {.handler = emulate_rd12rn16rm0_rwflags_nopc}, + [PROBES_MUL_ADD_LONG] = { + .handler = emulate_rdlo12rdhi16rn0rm8_rwflags_nopc}, + [PROBES_MUL_ADD] = {.handler = emulate_rd16rn12rm0rs8_rwflags_nopc}, + [PROBES_BITFIELD] = {.handler = emulate_rd12rm0_noflags_nopc}, + [PROBES_BRANCH] = {.handler = simulate_bbl}, + [PROBES_LDMSTM] = {.decoder = kprobe_decode_ldmstm} }; - -static const union decode_item arm_cccc_0000_____1001_table[] = { - /* Multiply and multiply-accumulate */ - - /* MUL cccc 0000 0000 xxxx xxxx xxxx 1001 xxxx */ - /* MULS cccc 0000 0001 xxxx xxxx xxxx 1001 xxxx */ - DECODE_EMULATEX (0x0fe000f0, 0x00000090, emulate_rd16rn12rm0rs8_rwflags_nopc, - REGS(NOPC, 0, NOPC, 0, NOPC)), - - /* MLA cccc 0000 0010 xxxx xxxx xxxx 1001 xxxx */ - /* MLAS cccc 0000 0011 xxxx xxxx xxxx 1001 xxxx */ - DECODE_OR (0x0fe000f0, 0x00200090), - /* MLS cccc 0000 0110 xxxx xxxx xxxx 1001 xxxx */ - DECODE_EMULATEX (0x0ff000f0, 0x00600090, emulate_rd16rn12rm0rs8_rwflags_nopc, - REGS(NOPC, NOPC, NOPC, 0, NOPC)), - - /* UMAAL cccc 0000 0100 xxxx xxxx xxxx 1001 xxxx */ - DECODE_OR (0x0ff000f0, 0x00400090), - /* UMULL cccc 0000 1000 xxxx xxxx xxxx 1001 xxxx */ - /* UMULLS cccc 0000 1001 xxxx xxxx xxxx 1001 xxxx */ - /* UMLAL cccc 0000 1010 xxxx xxxx xxxx 1001 xxxx */ - /* UMLALS cccc 0000 1011 xxxx xxxx xxxx 1001 xxxx */ - /* SMULL cccc 0000 1100 xxxx xxxx xxxx 1001 xxxx */ - /* SMULLS cccc 0000 1101 xxxx xxxx xxxx 1001 xxxx */ - /* SMLAL cccc 0000 1110 xxxx xxxx xxxx 1001 xxxx */ - /* SMLALS cccc 0000 1111 xxxx xxxx xxxx 1001 xxxx */ - DECODE_EMULATEX (0x0f8000f0, 0x00800090, emulate_rdlo12rdhi16rn0rm8_rwflags_nopc, - REGS(NOPC, NOPC, NOPC, 0, NOPC)), - - DECODE_END -}; - -static const union decode_item arm_cccc_0001_____1001_table[] = { - /* Synchronization primitives */ - -#if __LINUX_ARM_ARCH__ < 6 - /* Deprecated on ARMv6 and may be UNDEFINED on v7 */ - /* SMP/SWPB cccc 0001 0x00 xxxx xxxx xxxx 1001 xxxx */ - DECODE_EMULATEX (0x0fb000f0, 0x01000090, emulate_rd12rn16rm0_rwflags_nopc, - REGS(NOPC, NOPC, 0, 0, NOPC)), -#endif - /* LDREX/STREX{,D,B,H} cccc 0001 1xxx xxxx xxxx xxxx 1001 xxxx */ - /* And unallocated instructions... */ - DECODE_END -}; - -static const union decode_item arm_cccc_000x_____1xx1_table[] = { - /* Extra load/store instructions */ - - /* STRHT cccc 0000 xx10 xxxx xxxx xxxx 1011 xxxx */ - /* ??? cccc 0000 xx10 xxxx xxxx xxxx 11x1 xxxx */ - /* LDRHT cccc 0000 xx11 xxxx xxxx xxxx 1011 xxxx */ - /* LDRSBT cccc 0000 xx11 xxxx xxxx xxxx 1101 xxxx */ - /* LDRSHT cccc 0000 xx11 xxxx xxxx xxxx 1111 xxxx */ - DECODE_REJECT (0x0f200090, 0x00200090), - - /* LDRD/STRD lr,pc,{... cccc 000x x0x0 xxxx 111x xxxx 1101 xxxx */ - DECODE_REJECT (0x0e10e0d0, 0x0000e0d0), - - /* LDRD (register) cccc 000x x0x0 xxxx xxxx xxxx 1101 xxxx */ - /* STRD (register) cccc 000x x0x0 xxxx xxxx xxxx 1111 xxxx */ - DECODE_EMULATEX (0x0e5000d0, 0x000000d0, emulate_ldrdstrd, - REGS(NOPCWB, NOPCX, 0, 0, NOPC)), - - /* LDRD (immediate) cccc 000x x1x0 xxxx xxxx xxxx 1101 xxxx */ - /* STRD (immediate) cccc 000x x1x0 xxxx xxxx xxxx 1111 xxxx */ - DECODE_EMULATEX (0x0e5000d0, 0x004000d0, emulate_ldrdstrd, - REGS(NOPCWB, NOPCX, 0, 0, 0)), - - /* STRH (register) cccc 000x x0x0 xxxx xxxx xxxx 1011 xxxx */ - DECODE_EMULATEX (0x0e5000f0, 0x000000b0, emulate_str, - REGS(NOPCWB, NOPC, 0, 0, NOPC)), - - /* LDRH (register) cccc 000x x0x1 xxxx xxxx xxxx 1011 xxxx */ - /* LDRSB (register) cccc 000x x0x1 xxxx xxxx xxxx 1101 xxxx */ - /* LDRSH (register) cccc 000x x0x1 xxxx xxxx xxxx 1111 xxxx */ - DECODE_EMULATEX (0x0e500090, 0x00100090, emulate_ldr, - REGS(NOPCWB, NOPC, 0, 0, NOPC)), - - /* STRH (immediate) cccc 000x x1x0 xxxx xxxx xxxx 1011 xxxx */ - DECODE_EMULATEX (0x0e5000f0, 0x004000b0, emulate_str, - REGS(NOPCWB, NOPC, 0, 0, 0)), - - /* LDRH (immediate) cccc 000x x1x1 xxxx xxxx xxxx 1011 xxxx */ - /* LDRSB (immediate) cccc 000x x1x1 xxxx xxxx xxxx 1101 xxxx */ - /* LDRSH (immediate) cccc 000x x1x1 xxxx xxxx xxxx 1111 xxxx */ - DECODE_EMULATEX (0x0e500090, 0x00500090, emulate_ldr, - REGS(NOPCWB, NOPC, 0, 0, 0)), - - DECODE_END -}; - -static const union decode_item arm_cccc_000x_table[] = { - /* Data-processing (register) */ - - /* S PC, ... cccc 000x xxx1 xxxx 1111 xxxx xxxx xxxx */ - DECODE_REJECT (0x0e10f000, 0x0010f000), - - /* MOV IP, SP 1110 0001 1010 0000 1100 0000 0000 1101 */ - DECODE_SIMULATE (0xffffffff, 0xe1a0c00d, simulate_mov_ipsp), - - /* TST (register) cccc 0001 0001 xxxx xxxx xxxx xxx0 xxxx */ - /* TEQ (register) cccc 0001 0011 xxxx xxxx xxxx xxx0 xxxx */ - /* CMP (register) cccc 0001 0101 xxxx xxxx xxxx xxx0 xxxx */ - /* CMN (register) cccc 0001 0111 xxxx xxxx xxxx xxx0 xxxx */ - DECODE_EMULATEX (0x0f900010, 0x01100000, emulate_rd12rn16rm0rs8_rwflags, - REGS(ANY, 0, 0, 0, ANY)), - - /* MOV (register) cccc 0001 101x xxxx xxxx xxxx xxx0 xxxx */ - /* MVN (register) cccc 0001 111x xxxx xxxx xxxx xxx0 xxxx */ - DECODE_EMULATEX (0x0fa00010, 0x01a00000, emulate_rd12rn16rm0rs8_rwflags, - REGS(0, ANY, 0, 0, ANY)), - - /* AND (register) cccc 0000 000x xxxx xxxx xxxx xxx0 xxxx */ - /* EOR (register) cccc 0000 001x xxxx xxxx xxxx xxx0 xxxx */ - /* SUB (register) cccc 0000 010x xxxx xxxx xxxx xxx0 xxxx */ - /* RSB (register) cccc 0000 011x xxxx xxxx xxxx xxx0 xxxx */ - /* ADD (register) cccc 0000 100x xxxx xxxx xxxx xxx0 xxxx */ - /* ADC (register) cccc 0000 101x xxxx xxxx xxxx xxx0 xxxx */ - /* SBC (register) cccc 0000 110x xxxx xxxx xxxx xxx0 xxxx */ - /* RSC (register) cccc 0000 111x xxxx xxxx xxxx xxx0 xxxx */ - /* ORR (register) cccc 0001 100x xxxx xxxx xxxx xxx0 xxxx */ - /* BIC (register) cccc 0001 110x xxxx xxxx xxxx xxx0 xxxx */ - DECODE_EMULATEX (0x0e000010, 0x00000000, emulate_rd12rn16rm0rs8_rwflags, - REGS(ANY, ANY, 0, 0, ANY)), - - /* TST (reg-shift reg) cccc 0001 0001 xxxx xxxx xxxx 0xx1 xxxx */ - /* TEQ (reg-shift reg) cccc 0001 0011 xxxx xxxx xxxx 0xx1 xxxx */ - /* CMP (reg-shift reg) cccc 0001 0101 xxxx xxxx xxxx 0xx1 xxxx */ - /* CMN (reg-shift reg) cccc 0001 0111 xxxx xxxx xxxx 0xx1 xxxx */ - DECODE_EMULATEX (0x0f900090, 0x01100010, emulate_rd12rn16rm0rs8_rwflags, - REGS(ANY, 0, NOPC, 0, ANY)), - - /* MOV (reg-shift reg) cccc 0001 101x xxxx xxxx xxxx 0xx1 xxxx */ - /* MVN (reg-shift reg) cccc 0001 111x xxxx xxxx xxxx 0xx1 xxxx */ - DECODE_EMULATEX (0x0fa00090, 0x01a00010, emulate_rd12rn16rm0rs8_rwflags, - REGS(0, ANY, NOPC, 0, ANY)), - - /* AND (reg-shift reg) cccc 0000 000x xxxx xxxx xxxx 0xx1 xxxx */ - /* EOR (reg-shift reg) cccc 0000 001x xxxx xxxx xxxx 0xx1 xxxx */ - /* SUB (reg-shift reg) cccc 0000 010x xxxx xxxx xxxx 0xx1 xxxx */ - /* RSB (reg-shift reg) cccc 0000 011x xxxx xxxx xxxx 0xx1 xxxx */ - /* ADD (reg-shift reg) cccc 0000 100x xxxx xxxx xxxx 0xx1 xxxx */ - /* ADC (reg-shift reg) cccc 0000 101x xxxx xxxx xxxx 0xx1 xxxx */ - /* SBC (reg-shift reg) cccc 0000 110x xxxx xxxx xxxx 0xx1 xxxx */ - /* RSC (reg-shift reg) cccc 0000 111x xxxx xxxx xxxx 0xx1 xxxx */ - /* ORR (reg-shift reg) cccc 0001 100x xxxx xxxx xxxx 0xx1 xxxx */ - /* BIC (reg-shift reg) cccc 0001 110x xxxx xxxx xxxx 0xx1 xxxx */ - DECODE_EMULATEX (0x0e000090, 0x00000010, emulate_rd12rn16rm0rs8_rwflags, - REGS(ANY, ANY, NOPC, 0, ANY)), - - DECODE_END -}; - -static const union decode_item arm_cccc_001x_table[] = { - /* Data-processing (immediate) */ - - /* MOVW cccc 0011 0000 xxxx xxxx xxxx xxxx xxxx */ - /* MOVT cccc 0011 0100 xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0x0fb00000, 0x03000000, emulate_rd12rm0_noflags_nopc, - REGS(0, NOPC, 0, 0, 0)), - - /* YIELD cccc 0011 0010 0000 xxxx xxxx 0000 0001 */ - DECODE_OR (0x0fff00ff, 0x03200001), - /* SEV cccc 0011 0010 0000 xxxx xxxx 0000 0100 */ - DECODE_EMULATE (0x0fff00ff, 0x03200004, kprobe_emulate_none), - /* NOP cccc 0011 0010 0000 xxxx xxxx 0000 0000 */ - /* WFE cccc 0011 0010 0000 xxxx xxxx 0000 0010 */ - /* WFI cccc 0011 0010 0000 xxxx xxxx 0000 0011 */ - DECODE_SIMULATE (0x0fff00fc, 0x03200000, kprobe_simulate_nop), - /* DBG cccc 0011 0010 0000 xxxx xxxx ffff xxxx */ - /* unallocated hints cccc 0011 0010 0000 xxxx xxxx xxxx xxxx */ - /* MSR (immediate) cccc 0011 0x10 xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0x0fb00000, 0x03200000), - - /* S PC, ... cccc 001x xxx1 xxxx 1111 xxxx xxxx xxxx */ - DECODE_REJECT (0x0e10f000, 0x0210f000), - - /* TST (immediate) cccc 0011 0001 xxxx xxxx xxxx xxxx xxxx */ - /* TEQ (immediate) cccc 0011 0011 xxxx xxxx xxxx xxxx xxxx */ - /* CMP (immediate) cccc 0011 0101 xxxx xxxx xxxx xxxx xxxx */ - /* CMN (immediate) cccc 0011 0111 xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0x0f900000, 0x03100000, emulate_rd12rn16rm0rs8_rwflags, - REGS(ANY, 0, 0, 0, 0)), - - /* MOV (immediate) cccc 0011 101x xxxx xxxx xxxx xxxx xxxx */ - /* MVN (immediate) cccc 0011 111x xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0x0fa00000, 0x03a00000, emulate_rd12rn16rm0rs8_rwflags, - REGS(0, ANY, 0, 0, 0)), - - /* AND (immediate) cccc 0010 000x xxxx xxxx xxxx xxxx xxxx */ - /* EOR (immediate) cccc 0010 001x xxxx xxxx xxxx xxxx xxxx */ - /* SUB (immediate) cccc 0010 010x xxxx xxxx xxxx xxxx xxxx */ - /* RSB (immediate) cccc 0010 011x xxxx xxxx xxxx xxxx xxxx */ - /* ADD (immediate) cccc 0010 100x xxxx xxxx xxxx xxxx xxxx */ - /* ADC (immediate) cccc 0010 101x xxxx xxxx xxxx xxxx xxxx */ - /* SBC (immediate) cccc 0010 110x xxxx xxxx xxxx xxxx xxxx */ - /* RSC (immediate) cccc 0010 111x xxxx xxxx xxxx xxxx xxxx */ - /* ORR (immediate) cccc 0011 100x xxxx xxxx xxxx xxxx xxxx */ - /* BIC (immediate) cccc 0011 110x xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0x0e000000, 0x02000000, emulate_rd12rn16rm0rs8_rwflags, - REGS(ANY, ANY, 0, 0, 0)), - - DECODE_END -}; - -static const union decode_item arm_cccc_0110_____xxx1_table[] = { - /* Media instructions */ - - /* SEL cccc 0110 1000 xxxx xxxx xxxx 1011 xxxx */ - DECODE_EMULATEX (0x0ff000f0, 0x068000b0, emulate_rd12rn16rm0_rwflags_nopc, - REGS(NOPC, NOPC, 0, 0, NOPC)), - - /* SSAT cccc 0110 101x xxxx xxxx xxxx xx01 xxxx */ - /* USAT cccc 0110 111x xxxx xxxx xxxx xx01 xxxx */ - DECODE_OR(0x0fa00030, 0x06a00010), - /* SSAT16 cccc 0110 1010 xxxx xxxx xxxx 0011 xxxx */ - /* USAT16 cccc 0110 1110 xxxx xxxx xxxx 0011 xxxx */ - DECODE_EMULATEX (0x0fb000f0, 0x06a00030, emulate_rd12rn16rm0_rwflags_nopc, - REGS(0, NOPC, 0, 0, NOPC)), - - /* REV cccc 0110 1011 xxxx xxxx xxxx 0011 xxxx */ - /* REV16 cccc 0110 1011 xxxx xxxx xxxx 1011 xxxx */ - /* RBIT cccc 0110 1111 xxxx xxxx xxxx 0011 xxxx */ - /* REVSH cccc 0110 1111 xxxx xxxx xxxx 1011 xxxx */ - DECODE_EMULATEX (0x0fb00070, 0x06b00030, emulate_rd12rm0_noflags_nopc, - REGS(0, NOPC, 0, 0, NOPC)), - - /* ??? cccc 0110 0x00 xxxx xxxx xxxx xxx1 xxxx */ - DECODE_REJECT (0x0fb00010, 0x06000010), - /* ??? cccc 0110 0xxx xxxx xxxx xxxx 1011 xxxx */ - DECODE_REJECT (0x0f8000f0, 0x060000b0), - /* ??? cccc 0110 0xxx xxxx xxxx xxxx 1101 xxxx */ - DECODE_REJECT (0x0f8000f0, 0x060000d0), - /* SADD16 cccc 0110 0001 xxxx xxxx xxxx 0001 xxxx */ - /* SADDSUBX cccc 0110 0001 xxxx xxxx xxxx 0011 xxxx */ - /* SSUBADDX cccc 0110 0001 xxxx xxxx xxxx 0101 xxxx */ - /* SSUB16 cccc 0110 0001 xxxx xxxx xxxx 0111 xxxx */ - /* SADD8 cccc 0110 0001 xxxx xxxx xxxx 1001 xxxx */ - /* SSUB8 cccc 0110 0001 xxxx xxxx xxxx 1111 xxxx */ - /* QADD16 cccc 0110 0010 xxxx xxxx xxxx 0001 xxxx */ - /* QADDSUBX cccc 0110 0010 xxxx xxxx xxxx 0011 xxxx */ - /* QSUBADDX cccc 0110 0010 xxxx xxxx xxxx 0101 xxxx */ - /* QSUB16 cccc 0110 0010 xxxx xxxx xxxx 0111 xxxx */ - /* QADD8 cccc 0110 0010 xxxx xxxx xxxx 1001 xxxx */ - /* QSUB8 cccc 0110 0010 xxxx xxxx xxxx 1111 xxxx */ - /* SHADD16 cccc 0110 0011 xxxx xxxx xxxx 0001 xxxx */ - /* SHADDSUBX cccc 0110 0011 xxxx xxxx xxxx 0011 xxxx */ - /* SHSUBADDX cccc 0110 0011 xxxx xxxx xxxx 0101 xxxx */ - /* SHSUB16 cccc 0110 0011 xxxx xxxx xxxx 0111 xxxx */ - /* SHADD8 cccc 0110 0011 xxxx xxxx xxxx 1001 xxxx */ - /* SHSUB8 cccc 0110 0011 xxxx xxxx xxxx 1111 xxxx */ - /* UADD16 cccc 0110 0101 xxxx xxxx xxxx 0001 xxxx */ - /* UADDSUBX cccc 0110 0101 xxxx xxxx xxxx 0011 xxxx */ - /* USUBADDX cccc 0110 0101 xxxx xxxx xxxx 0101 xxxx */ - /* USUB16 cccc 0110 0101 xxxx xxxx xxxx 0111 xxxx */ - /* UADD8 cccc 0110 0101 xxxx xxxx xxxx 1001 xxxx */ - /* USUB8 cccc 0110 0101 xxxx xxxx xxxx 1111 xxxx */ - /* UQADD16 cccc 0110 0110 xxxx xxxx xxxx 0001 xxxx */ - /* UQADDSUBX cccc 0110 0110 xxxx xxxx xxxx 0011 xxxx */ - /* UQSUBADDX cccc 0110 0110 xxxx xxxx xxxx 0101 xxxx */ - /* UQSUB16 cccc 0110 0110 xxxx xxxx xxxx 0111 xxxx */ - /* UQADD8 cccc 0110 0110 xxxx xxxx xxxx 1001 xxxx */ - /* UQSUB8 cccc 0110 0110 xxxx xxxx xxxx 1111 xxxx */ - /* UHADD16 cccc 0110 0111 xxxx xxxx xxxx 0001 xxxx */ - /* UHADDSUBX cccc 0110 0111 xxxx xxxx xxxx 0011 xxxx */ - /* UHSUBADDX cccc 0110 0111 xxxx xxxx xxxx 0101 xxxx */ - /* UHSUB16 cccc 0110 0111 xxxx xxxx xxxx 0111 xxxx */ - /* UHADD8 cccc 0110 0111 xxxx xxxx xxxx 1001 xxxx */ - /* UHSUB8 cccc 0110 0111 xxxx xxxx xxxx 1111 xxxx */ - DECODE_EMULATEX (0x0f800010, 0x06000010, emulate_rd12rn16rm0_rwflags_nopc, - REGS(NOPC, NOPC, 0, 0, NOPC)), - - /* PKHBT cccc 0110 1000 xxxx xxxx xxxx x001 xxxx */ - /* PKHTB cccc 0110 1000 xxxx xxxx xxxx x101 xxxx */ - DECODE_EMULATEX (0x0ff00030, 0x06800010, emulate_rd12rn16rm0_rwflags_nopc, - REGS(NOPC, NOPC, 0, 0, NOPC)), - - /* ??? cccc 0110 1001 xxxx xxxx xxxx 0111 xxxx */ - /* ??? cccc 0110 1101 xxxx xxxx xxxx 0111 xxxx */ - DECODE_REJECT (0x0fb000f0, 0x06900070), - - /* SXTB16 cccc 0110 1000 1111 xxxx xxxx 0111 xxxx */ - /* SXTB cccc 0110 1010 1111 xxxx xxxx 0111 xxxx */ - /* SXTH cccc 0110 1011 1111 xxxx xxxx 0111 xxxx */ - /* UXTB16 cccc 0110 1100 1111 xxxx xxxx 0111 xxxx */ - /* UXTB cccc 0110 1110 1111 xxxx xxxx 0111 xxxx */ - /* UXTH cccc 0110 1111 1111 xxxx xxxx 0111 xxxx */ - DECODE_EMULATEX (0x0f8f00f0, 0x068f0070, emulate_rd12rm0_noflags_nopc, - REGS(0, NOPC, 0, 0, NOPC)), - - /* SXTAB16 cccc 0110 1000 xxxx xxxx xxxx 0111 xxxx */ - /* SXTAB cccc 0110 1010 xxxx xxxx xxxx 0111 xxxx */ - /* SXTAH cccc 0110 1011 xxxx xxxx xxxx 0111 xxxx */ - /* UXTAB16 cccc 0110 1100 xxxx xxxx xxxx 0111 xxxx */ - /* UXTAB cccc 0110 1110 xxxx xxxx xxxx 0111 xxxx */ - /* UXTAH cccc 0110 1111 xxxx xxxx xxxx 0111 xxxx */ - DECODE_EMULATEX (0x0f8000f0, 0x06800070, emulate_rd12rn16rm0_rwflags_nopc, - REGS(NOPCX, NOPC, 0, 0, NOPC)), - - DECODE_END -}; - -static const union decode_item arm_cccc_0111_____xxx1_table[] = { - /* Media instructions */ - - /* UNDEFINED cccc 0111 1111 xxxx xxxx xxxx 1111 xxxx */ - DECODE_REJECT (0x0ff000f0, 0x07f000f0), - - /* SMLALD cccc 0111 0100 xxxx xxxx xxxx 00x1 xxxx */ - /* SMLSLD cccc 0111 0100 xxxx xxxx xxxx 01x1 xxxx */ - DECODE_EMULATEX (0x0ff00090, 0x07400010, emulate_rdlo12rdhi16rn0rm8_rwflags_nopc, - REGS(NOPC, NOPC, NOPC, 0, NOPC)), - - /* SMUAD cccc 0111 0000 xxxx 1111 xxxx 00x1 xxxx */ - /* SMUSD cccc 0111 0000 xxxx 1111 xxxx 01x1 xxxx */ - DECODE_OR (0x0ff0f090, 0x0700f010), - /* SMMUL cccc 0111 0101 xxxx 1111 xxxx 00x1 xxxx */ - DECODE_OR (0x0ff0f0d0, 0x0750f010), - /* USAD8 cccc 0111 1000 xxxx 1111 xxxx 0001 xxxx */ - DECODE_EMULATEX (0x0ff0f0f0, 0x0780f010, emulate_rd16rn12rm0rs8_rwflags_nopc, - REGS(NOPC, 0, NOPC, 0, NOPC)), - - /* SMLAD cccc 0111 0000 xxxx xxxx xxxx 00x1 xxxx */ - /* SMLSD cccc 0111 0000 xxxx xxxx xxxx 01x1 xxxx */ - DECODE_OR (0x0ff00090, 0x07000010), - /* SMMLA cccc 0111 0101 xxxx xxxx xxxx 00x1 xxxx */ - DECODE_OR (0x0ff000d0, 0x07500010), - /* USADA8 cccc 0111 1000 xxxx xxxx xxxx 0001 xxxx */ - DECODE_EMULATEX (0x0ff000f0, 0x07800010, emulate_rd16rn12rm0rs8_rwflags_nopc, - REGS(NOPC, NOPCX, NOPC, 0, NOPC)), - - /* SMMLS cccc 0111 0101 xxxx xxxx xxxx 11x1 xxxx */ - DECODE_EMULATEX (0x0ff000d0, 0x075000d0, emulate_rd16rn12rm0rs8_rwflags_nopc, - REGS(NOPC, NOPC, NOPC, 0, NOPC)), - - /* SBFX cccc 0111 101x xxxx xxxx xxxx x101 xxxx */ - /* UBFX cccc 0111 111x xxxx xxxx xxxx x101 xxxx */ - DECODE_EMULATEX (0x0fa00070, 0x07a00050, emulate_rd12rm0_noflags_nopc, - REGS(0, NOPC, 0, 0, NOPC)), - - /* BFC cccc 0111 110x xxxx xxxx xxxx x001 1111 */ - DECODE_EMULATEX (0x0fe0007f, 0x07c0001f, emulate_rd12rm0_noflags_nopc, - REGS(0, NOPC, 0, 0, 0)), - - /* BFI cccc 0111 110x xxxx xxxx xxxx x001 xxxx */ - DECODE_EMULATEX (0x0fe00070, 0x07c00010, emulate_rd12rm0_noflags_nopc, - REGS(0, NOPC, 0, 0, NOPCX)), - - DECODE_END -}; - -static const union decode_item arm_cccc_01xx_table[] = { - /* Load/store word and unsigned byte */ - - /* LDRB/STRB pc,[...] cccc 01xx x0xx xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0x0c40f000, 0x0440f000), - - /* STRT cccc 01x0 x010 xxxx xxxx xxxx xxxx xxxx */ - /* LDRT cccc 01x0 x011 xxxx xxxx xxxx xxxx xxxx */ - /* STRBT cccc 01x0 x110 xxxx xxxx xxxx xxxx xxxx */ - /* LDRBT cccc 01x0 x111 xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0x0d200000, 0x04200000), - - /* STR (immediate) cccc 010x x0x0 xxxx xxxx xxxx xxxx xxxx */ - /* STRB (immediate) cccc 010x x1x0 xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0x0e100000, 0x04000000, emulate_str, - REGS(NOPCWB, ANY, 0, 0, 0)), - - /* LDR (immediate) cccc 010x x0x1 xxxx xxxx xxxx xxxx xxxx */ - /* LDRB (immediate) cccc 010x x1x1 xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0x0e100000, 0x04100000, emulate_ldr, - REGS(NOPCWB, ANY, 0, 0, 0)), - - /* STR (register) cccc 011x x0x0 xxxx xxxx xxxx xxxx xxxx */ - /* STRB (register) cccc 011x x1x0 xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0x0e100000, 0x06000000, emulate_str, - REGS(NOPCWB, ANY, 0, 0, NOPC)), - - /* LDR (register) cccc 011x x0x1 xxxx xxxx xxxx xxxx xxxx */ - /* LDRB (register) cccc 011x x1x1 xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0x0e100000, 0x06100000, emulate_ldr, - REGS(NOPCWB, ANY, 0, 0, NOPC)), - - DECODE_END -}; - -static const union decode_item arm_cccc_100x_table[] = { - /* Block data transfer instructions */ - - /* LDM cccc 100x x0x1 xxxx xxxx xxxx xxxx xxxx */ - /* STM cccc 100x x0x0 xxxx xxxx xxxx xxxx xxxx */ - DECODE_CUSTOM (0x0e400000, 0x08000000, kprobe_decode_ldmstm), - - /* STM (user registers) cccc 100x x1x0 xxxx xxxx xxxx xxxx xxxx */ - /* LDM (user registers) cccc 100x x1x1 xxxx 0xxx xxxx xxxx xxxx */ - /* LDM (exception ret) cccc 100x x1x1 xxxx 1xxx xxxx xxxx xxxx */ - DECODE_END -}; - -const union decode_item kprobe_decode_arm_table[] = { - /* - * Unconditional instructions - * 1111 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xf0000000, 0xf0000000, arm_1111_table), - - /* - * Miscellaneous instructions - * cccc 0001 0xx0 xxxx xxxx xxxx 0xxx xxxx - */ - DECODE_TABLE (0x0f900080, 0x01000000, arm_cccc_0001_0xx0____0xxx_table), - - /* - * Halfword multiply and multiply-accumulate - * cccc 0001 0xx0 xxxx xxxx xxxx 1xx0 xxxx - */ - DECODE_TABLE (0x0f900090, 0x01000080, arm_cccc_0001_0xx0____1xx0_table), - - /* - * Multiply and multiply-accumulate - * cccc 0000 xxxx xxxx xxxx xxxx 1001 xxxx - */ - DECODE_TABLE (0x0f0000f0, 0x00000090, arm_cccc_0000_____1001_table), - - /* - * Synchronization primitives - * cccc 0001 xxxx xxxx xxxx xxxx 1001 xxxx - */ - DECODE_TABLE (0x0f0000f0, 0x01000090, arm_cccc_0001_____1001_table), - - /* - * Extra load/store instructions - * cccc 000x xxxx xxxx xxxx xxxx 1xx1 xxxx - */ - DECODE_TABLE (0x0e000090, 0x00000090, arm_cccc_000x_____1xx1_table), - - /* - * Data-processing (register) - * cccc 000x xxxx xxxx xxxx xxxx xxx0 xxxx - * Data-processing (register-shifted register) - * cccc 000x xxxx xxxx xxxx xxxx 0xx1 xxxx - */ - DECODE_TABLE (0x0e000000, 0x00000000, arm_cccc_000x_table), - - /* - * Data-processing (immediate) - * cccc 001x xxxx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0x0e000000, 0x02000000, arm_cccc_001x_table), - - /* - * Media instructions - * cccc 011x xxxx xxxx xxxx xxxx xxx1 xxxx - */ - DECODE_TABLE (0x0f000010, 0x06000010, arm_cccc_0110_____xxx1_table), - DECODE_TABLE (0x0f000010, 0x07000010, arm_cccc_0111_____xxx1_table), - - /* - * Load/store word and unsigned byte - * cccc 01xx xxxx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0x0c000000, 0x04000000, arm_cccc_01xx_table), - - /* - * Block data transfer instructions - * cccc 100x xxxx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0x0e000000, 0x08000000, arm_cccc_100x_table), - - /* B cccc 1010 xxxx xxxx xxxx xxxx xxxx xxxx */ - /* BL cccc 1011 xxxx xxxx xxxx xxxx xxxx xxxx */ - DECODE_SIMULATE (0x0e000000, 0x0a000000, simulate_bbl), - - /* - * Supervisor Call, and coprocessor instructions - */ - - /* MCRR cccc 1100 0100 xxxx xxxx xxxx xxxx xxxx */ - /* MRRC cccc 1100 0101 xxxx xxxx xxxx xxxx xxxx */ - /* LDC cccc 110x xxx1 xxxx xxxx xxxx xxxx xxxx */ - /* STC cccc 110x xxx0 xxxx xxxx xxxx xxxx xxxx */ - /* CDP cccc 1110 xxxx xxxx xxxx xxxx xxx0 xxxx */ - /* MCR cccc 1110 xxx0 xxxx xxxx xxxx xxx1 xxxx */ - /* MRC cccc 1110 xxx1 xxxx xxxx xxxx xxx1 xxxx */ - /* SVC cccc 1111 xxxx xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0x0c000000, 0x0c000000), - - DECODE_END -}; -#ifdef CONFIG_ARM_KPROBES_TEST_MODULE -EXPORT_SYMBOL_GPL(kprobe_decode_arm_table); -#endif - -static void __kprobes arm_singlestep(struct kprobe *p, struct pt_regs *regs) -{ - regs->ARM_pc += 4; - p->ainsn.insn_handler(p, regs); -} - -/* Return: - * INSN_REJECTED If instruction is one not allowed to kprobe, - * INSN_GOOD If instruction is supported and uses instruction slot, - * INSN_GOOD_NO_SLOT If instruction is supported but doesn't use its slot. - * - * For instructions we don't want to kprobe (INSN_REJECTED return result): - * These are generally ones that modify the processor state making - * them "hard" to simulate such as switches processor modes or - * make accesses in alternate modes. Any of these could be simulated - * if the work was put into it, but low return considering they - * should also be very rare. - */ -enum kprobe_insn __kprobes -arm_kprobe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi) -{ - asi->insn_singlestep = arm_singlestep; - asi->insn_check_cc = kprobe_condition_checks[insn>>28]; - return kprobe_decode_insn(insn, asi, kprobe_decode_arm_table, false); -} diff --git a/arch/arm/kernel/kprobes-common.c b/arch/arm/kernel/kprobes-common.c index 18a76282970e6d..0bf5d64eba1d26 100644 --- a/arch/arm/kernel/kprobes-common.c +++ b/arch/arm/kernel/kprobes-common.c @@ -13,178 +13,15 @@ #include #include -#include +#include #include "kprobes.h" -#ifndef find_str_pc_offset - -/* - * For STR and STM instructions, an ARM core may choose to use either - * a +8 or a +12 displacement from the current instruction's address. - * Whichever value is chosen for a given core, it must be the same for - * both instructions and may not change. This function measures it. - */ - -int str_pc_offset; - -void __init find_str_pc_offset(void) -{ - int addr, scratch, ret; - - __asm__ ( - "sub %[ret], pc, #4 \n\t" - "str pc, %[addr] \n\t" - "ldr %[scr], %[addr] \n\t" - "sub %[ret], %[scr], %[ret] \n\t" - : [ret] "=r" (ret), [scr] "=r" (scratch), [addr] "+m" (addr)); - - str_pc_offset = ret; -} - -#endif /* !find_str_pc_offset */ - - -#ifndef test_load_write_pc_interworking - -bool load_write_pc_interworks; - -void __init test_load_write_pc_interworking(void) -{ - int arch = cpu_architecture(); - BUG_ON(arch == CPU_ARCH_UNKNOWN); - load_write_pc_interworks = arch >= CPU_ARCH_ARMv5T; -} - -#endif /* !test_load_write_pc_interworking */ - - -#ifndef test_alu_write_pc_interworking - -bool alu_write_pc_interworks; - -void __init test_alu_write_pc_interworking(void) -{ - int arch = cpu_architecture(); - BUG_ON(arch == CPU_ARCH_UNKNOWN); - alu_write_pc_interworks = arch >= CPU_ARCH_ARMv7; -} - -#endif /* !test_alu_write_pc_interworking */ - - -void __init arm_kprobe_decode_init(void) -{ - find_str_pc_offset(); - test_load_write_pc_interworking(); - test_alu_write_pc_interworking(); -} - - -static unsigned long __kprobes __check_eq(unsigned long cpsr) -{ - return cpsr & PSR_Z_BIT; -} - -static unsigned long __kprobes __check_ne(unsigned long cpsr) -{ - return (~cpsr) & PSR_Z_BIT; -} - -static unsigned long __kprobes __check_cs(unsigned long cpsr) -{ - return cpsr & PSR_C_BIT; -} - -static unsigned long __kprobes __check_cc(unsigned long cpsr) -{ - return (~cpsr) & PSR_C_BIT; -} - -static unsigned long __kprobes __check_mi(unsigned long cpsr) -{ - return cpsr & PSR_N_BIT; -} - -static unsigned long __kprobes __check_pl(unsigned long cpsr) -{ - return (~cpsr) & PSR_N_BIT; -} - -static unsigned long __kprobes __check_vs(unsigned long cpsr) -{ - return cpsr & PSR_V_BIT; -} - -static unsigned long __kprobes __check_vc(unsigned long cpsr) -{ - return (~cpsr) & PSR_V_BIT; -} - -static unsigned long __kprobes __check_hi(unsigned long cpsr) -{ - cpsr &= ~(cpsr >> 1); /* PSR_C_BIT &= ~PSR_Z_BIT */ - return cpsr & PSR_C_BIT; -} - -static unsigned long __kprobes __check_ls(unsigned long cpsr) -{ - cpsr &= ~(cpsr >> 1); /* PSR_C_BIT &= ~PSR_Z_BIT */ - return (~cpsr) & PSR_C_BIT; -} - -static unsigned long __kprobes __check_ge(unsigned long cpsr) -{ - cpsr ^= (cpsr << 3); /* PSR_N_BIT ^= PSR_V_BIT */ - return (~cpsr) & PSR_N_BIT; -} - -static unsigned long __kprobes __check_lt(unsigned long cpsr) -{ - cpsr ^= (cpsr << 3); /* PSR_N_BIT ^= PSR_V_BIT */ - return cpsr & PSR_N_BIT; -} - -static unsigned long __kprobes __check_gt(unsigned long cpsr) -{ - unsigned long temp = cpsr ^ (cpsr << 3); /* PSR_N_BIT ^= PSR_V_BIT */ - temp |= (cpsr << 1); /* PSR_N_BIT |= PSR_Z_BIT */ - return (~temp) & PSR_N_BIT; -} - -static unsigned long __kprobes __check_le(unsigned long cpsr) -{ - unsigned long temp = cpsr ^ (cpsr << 3); /* PSR_N_BIT ^= PSR_V_BIT */ - temp |= (cpsr << 1); /* PSR_N_BIT |= PSR_Z_BIT */ - return temp & PSR_N_BIT; -} - -static unsigned long __kprobes __check_al(unsigned long cpsr) -{ - return true; -} - -kprobe_check_cc * const kprobe_condition_checks[16] = { - &__check_eq, &__check_ne, &__check_cs, &__check_cc, - &__check_mi, &__check_pl, &__check_vs, &__check_vc, - &__check_hi, &__check_ls, &__check_ge, &__check_lt, - &__check_gt, &__check_le, &__check_al, &__check_al -}; - - -void __kprobes kprobe_simulate_nop(struct kprobe *p, struct pt_regs *regs) -{ -} - -void __kprobes kprobe_emulate_none(struct kprobe *p, struct pt_regs *regs) -{ - p->ainsn.insn_fn(); -} - -static void __kprobes simulate_ldm1stm1(struct kprobe *p, struct pt_regs *regs) +static void __kprobes simulate_ldm1stm1(probes_opcode_t insn, + struct arch_probes_insn *asi, + struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rn = (insn >> 16) & 0xf; int lbit = insn & (1 << 20); int wbit = insn & (1 << 21); @@ -223,24 +60,31 @@ static void __kprobes simulate_ldm1stm1(struct kprobe *p, struct pt_regs *regs) } } -static void __kprobes simulate_stm1_pc(struct kprobe *p, struct pt_regs *regs) +static void __kprobes simulate_stm1_pc(probes_opcode_t insn, + struct arch_probes_insn *asi, + struct pt_regs *regs) { - regs->ARM_pc = (long)p->addr + str_pc_offset; - simulate_ldm1stm1(p, regs); - regs->ARM_pc = (long)p->addr + 4; + unsigned long addr = regs->ARM_pc - 4; + + regs->ARM_pc = (long)addr + str_pc_offset; + simulate_ldm1stm1(insn, asi, regs); + regs->ARM_pc = (long)addr + 4; } -static void __kprobes simulate_ldm1_pc(struct kprobe *p, struct pt_regs *regs) +static void __kprobes simulate_ldm1_pc(probes_opcode_t insn, + struct arch_probes_insn *asi, + struct pt_regs *regs) { - simulate_ldm1stm1(p, regs); + simulate_ldm1stm1(insn, asi, regs); load_write_pc(regs->ARM_pc, regs); } static void __kprobes -emulate_generic_r0_12_noflags(struct kprobe *p, struct pt_regs *regs) +emulate_generic_r0_12_noflags(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { register void *rregs asm("r1") = regs; - register void *rfn asm("lr") = p->ainsn.insn_fn; + register void *rfn asm("lr") = asi->insn_fn; __asm__ __volatile__ ( "stmdb sp!, {%[regs], r11} \n\t" @@ -264,22 +108,27 @@ emulate_generic_r0_12_noflags(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -emulate_generic_r2_14_noflags(struct kprobe *p, struct pt_regs *regs) +emulate_generic_r2_14_noflags(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - emulate_generic_r0_12_noflags(p, (struct pt_regs *)(regs->uregs+2)); + emulate_generic_r0_12_noflags(insn, asi, + (struct pt_regs *)(regs->uregs+2)); } static void __kprobes -emulate_ldm_r3_15(struct kprobe *p, struct pt_regs *regs) +emulate_ldm_r3_15(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - emulate_generic_r0_12_noflags(p, (struct pt_regs *)(regs->uregs+3)); + emulate_generic_r0_12_noflags(insn, asi, + (struct pt_regs *)(regs->uregs+3)); load_write_pc(regs->ARM_pc, regs); } -enum kprobe_insn __kprobes -kprobe_decode_ldmstm(kprobe_opcode_t insn, struct arch_specific_insn *asi) +enum probes_insn __kprobes +kprobe_decode_ldmstm(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *h) { - kprobe_insn_handler_t *handler = 0; + probes_insn_handler_t *handler = 0; unsigned reglist = insn & 0xffff; int is_ldm = insn & 0x100000; int rn = (insn >> 16) & 0xf; @@ -305,7 +154,8 @@ kprobe_decode_ldmstm(kprobe_opcode_t insn, struct arch_specific_insn *asi) if (handler) { /* We can emulate the instruction in (possibly) modified form */ - asi->insn[0] = (insn & 0xfff00000) | (rn << 16) | reglist; + asi->insn[0] = __opcode_to_mem_arm((insn & 0xfff00000) | + (rn << 16) | reglist); asi->insn_handler = handler; return INSN_GOOD; } @@ -319,260 +169,3 @@ kprobe_decode_ldmstm(kprobe_opcode_t insn, struct arch_specific_insn *asi) return INSN_GOOD_NO_SLOT; } - -/* - * Prepare an instruction slot to receive an instruction for emulating. - * This is done by placing a subroutine return after the location where the - * instruction will be placed. We also modify ARM instructions to be - * unconditional as the condition code will already be checked before any - * emulation handler is called. - */ -static kprobe_opcode_t __kprobes -prepare_emulated_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi, - bool thumb) -{ -#ifdef CONFIG_THUMB2_KERNEL - if (thumb) { - u16 *thumb_insn = (u16 *)asi->insn; - thumb_insn[1] = 0x4770; /* Thumb bx lr */ - thumb_insn[2] = 0x4770; /* Thumb bx lr */ - return insn; - } - asi->insn[1] = 0xe12fff1e; /* ARM bx lr */ -#else - asi->insn[1] = 0xe1a0f00e; /* mov pc, lr */ -#endif - /* Make an ARM instruction unconditional */ - if (insn < 0xe0000000) - insn = (insn | 0xe0000000) & ~0x10000000; - return insn; -} - -/* - * Write a (probably modified) instruction into the slot previously prepared by - * prepare_emulated_insn - */ -static void __kprobes -set_emulated_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi, - bool thumb) -{ -#ifdef CONFIG_THUMB2_KERNEL - if (thumb) { - u16 *ip = (u16 *)asi->insn; - if (is_wide_instruction(insn)) - *ip++ = insn >> 16; - *ip++ = insn; - return; - } -#endif - asi->insn[0] = insn; -} - -/* - * When we modify the register numbers encoded in an instruction to be emulated, - * the new values come from this define. For ARM and 32-bit Thumb instructions - * this gives... - * - * bit position 16 12 8 4 0 - * ---------------+---+---+---+---+---+ - * register r2 r0 r1 -- r3 - */ -#define INSN_NEW_BITS 0x00020103 - -/* Each nibble has same value as that at INSN_NEW_BITS bit 16 */ -#define INSN_SAMEAS16_BITS 0x22222222 - -/* - * Validate and modify each of the registers encoded in an instruction. - * - * Each nibble in regs contains a value from enum decode_reg_type. For each - * non-zero value, the corresponding nibble in pinsn is validated and modified - * according to the type. - */ -static bool __kprobes decode_regs(kprobe_opcode_t* pinsn, u32 regs) -{ - kprobe_opcode_t insn = *pinsn; - kprobe_opcode_t mask = 0xf; /* Start at least significant nibble */ - - for (; regs != 0; regs >>= 4, mask <<= 4) { - - kprobe_opcode_t new_bits = INSN_NEW_BITS; - - switch (regs & 0xf) { - - case REG_TYPE_NONE: - /* Nibble not a register, skip to next */ - continue; - - case REG_TYPE_ANY: - /* Any register is allowed */ - break; - - case REG_TYPE_SAMEAS16: - /* Replace register with same as at bit position 16 */ - new_bits = INSN_SAMEAS16_BITS; - break; - - case REG_TYPE_SP: - /* Only allow SP (R13) */ - if ((insn ^ 0xdddddddd) & mask) - goto reject; - break; - - case REG_TYPE_PC: - /* Only allow PC (R15) */ - if ((insn ^ 0xffffffff) & mask) - goto reject; - break; - - case REG_TYPE_NOSP: - /* Reject SP (R13) */ - if (((insn ^ 0xdddddddd) & mask) == 0) - goto reject; - break; - - case REG_TYPE_NOSPPC: - case REG_TYPE_NOSPPCX: - /* Reject SP and PC (R13 and R15) */ - if (((insn ^ 0xdddddddd) & 0xdddddddd & mask) == 0) - goto reject; - break; - - case REG_TYPE_NOPCWB: - if (!is_writeback(insn)) - break; /* No writeback, so any register is OK */ - /* fall through... */ - case REG_TYPE_NOPC: - case REG_TYPE_NOPCX: - /* Reject PC (R15) */ - if (((insn ^ 0xffffffff) & mask) == 0) - goto reject; - break; - } - - /* Replace value of nibble with new register number... */ - insn &= ~mask; - insn |= new_bits & mask; - } - - *pinsn = insn; - return true; - -reject: - return false; -} - -static const int decode_struct_sizes[NUM_DECODE_TYPES] = { - [DECODE_TYPE_TABLE] = sizeof(struct decode_table), - [DECODE_TYPE_CUSTOM] = sizeof(struct decode_custom), - [DECODE_TYPE_SIMULATE] = sizeof(struct decode_simulate), - [DECODE_TYPE_EMULATE] = sizeof(struct decode_emulate), - [DECODE_TYPE_OR] = sizeof(struct decode_or), - [DECODE_TYPE_REJECT] = sizeof(struct decode_reject) -}; - -/* - * kprobe_decode_insn operates on data tables in order to decode an ARM - * architecture instruction onto which a kprobe has been placed. - * - * These instruction decoding tables are a concatenation of entries each - * of which consist of one of the following structs: - * - * decode_table - * decode_custom - * decode_simulate - * decode_emulate - * decode_or - * decode_reject - * - * Each of these starts with a struct decode_header which has the following - * fields: - * - * type_regs - * mask - * value - * - * The least significant DECODE_TYPE_BITS of type_regs contains a value - * from enum decode_type, this indicates which of the decode_* structs - * the entry contains. The value DECODE_TYPE_END indicates the end of the - * table. - * - * When the table is parsed, each entry is checked in turn to see if it - * matches the instruction to be decoded using the test: - * - * (insn & mask) == value - * - * If no match is found before the end of the table is reached then decoding - * fails with INSN_REJECTED. - * - * When a match is found, decode_regs() is called to validate and modify each - * of the registers encoded in the instruction; the data it uses to do this - * is (type_regs >> DECODE_TYPE_BITS). A validation failure will cause decoding - * to fail with INSN_REJECTED. - * - * Once the instruction has passed the above tests, further processing - * depends on the type of the table entry's decode struct. - * - */ -int __kprobes -kprobe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi, - const union decode_item *table, bool thumb) -{ - const struct decode_header *h = (struct decode_header *)table; - const struct decode_header *next; - bool matched = false; - - insn = prepare_emulated_insn(insn, asi, thumb); - - for (;; h = next) { - enum decode_type type = h->type_regs.bits & DECODE_TYPE_MASK; - u32 regs = h->type_regs.bits >> DECODE_TYPE_BITS; - - if (type == DECODE_TYPE_END) - return INSN_REJECTED; - - next = (struct decode_header *) - ((uintptr_t)h + decode_struct_sizes[type]); - - if (!matched && (insn & h->mask.bits) != h->value.bits) - continue; - - if (!decode_regs(&insn, regs)) - return INSN_REJECTED; - - switch (type) { - - case DECODE_TYPE_TABLE: { - struct decode_table *d = (struct decode_table *)h; - next = (struct decode_header *)d->table.table; - break; - } - - case DECODE_TYPE_CUSTOM: { - struct decode_custom *d = (struct decode_custom *)h; - return (*d->decoder.decoder)(insn, asi); - } - - case DECODE_TYPE_SIMULATE: { - struct decode_simulate *d = (struct decode_simulate *)h; - asi->insn_handler = d->handler.handler; - return INSN_GOOD_NO_SLOT; - } - - case DECODE_TYPE_EMULATE: { - struct decode_emulate *d = (struct decode_emulate *)h; - asi->insn_handler = d->handler.handler; - set_emulated_insn(insn, asi, thumb); - return INSN_GOOD; - } - - case DECODE_TYPE_OR: - matched = true; - break; - - case DECODE_TYPE_REJECT: - default: - return INSN_REJECTED; - } - } - } diff --git a/arch/arm/kernel/kprobes-test-arm.c b/arch/arm/kernel/kprobes-test-arm.c index 83931290506704..9db4b659d03ef7 100644 --- a/arch/arm/kernel/kprobes-test-arm.c +++ b/arch/arm/kernel/kprobes-test-arm.c @@ -10,6 +10,8 @@ #include #include +#include +#include #include "kprobes-test.h" @@ -158,9 +160,9 @@ void kprobe_arm_test_cases(void) TEST_SUPPORTED("cmp sp, #0x1000"); /* Data-processing with PC as shift*/ - TEST_UNSUPPORTED(".word 0xe15c0f1e @ cmp r12, r14, asl pc") - TEST_UNSUPPORTED(".word 0xe1a0cf1e @ mov r12, r14, asl pc") - TEST_UNSUPPORTED(".word 0xe08caf1e @ add r10, r12, r14, asl pc") + TEST_UNSUPPORTED(__inst_arm(0xe15c0f1e) " @ cmp r12, r14, asl pc") + TEST_UNSUPPORTED(__inst_arm(0xe1a0cf1e) " @ mov r12, r14, asl pc") + TEST_UNSUPPORTED(__inst_arm(0xe08caf1e) " @ add r10, r12, r14, asl pc") /* Data-processing with PC as shift*/ TEST_UNSUPPORTED("movs pc, r1") @@ -202,7 +204,7 @@ void kprobe_arm_test_cases(void) TEST("mrs r0, cpsr") TEST("mrspl r7, cpsr") TEST("mrs r14, cpsr") - TEST_UNSUPPORTED(".word 0xe10ff000 @ mrs r15, cpsr") + TEST_UNSUPPORTED(__inst_arm(0xe10ff000) " @ mrs r15, cpsr") TEST_UNSUPPORTED("mrs r0, spsr") TEST_UNSUPPORTED("mrs lr, spsr") @@ -218,8 +220,8 @@ void kprobe_arm_test_cases(void) TEST_R("clzeq r7, r",14,0x1,"") TEST_R("clz lr, r",7, 0xffffffff,"") TEST( "clz r4, sp") - TEST_UNSUPPORTED(".word 0x016fff10 @ clz pc, r0") - TEST_UNSUPPORTED(".word 0x016f0f1f @ clz r0, pc") + TEST_UNSUPPORTED(__inst_arm(0x016fff10) " @ clz pc, r0") + TEST_UNSUPPORTED(__inst_arm(0x016f0f1f) " @ clz r0, pc") #if __LINUX_ARM_ARCH__ >= 6 TEST_UNSUPPORTED("bxj r0") @@ -228,7 +230,7 @@ void kprobe_arm_test_cases(void) TEST_BF_R("blx r",0,2f,"") TEST_BB_R("blx r",7,2f,"") TEST_BF_R("blxeq r",14,2f,"") - TEST_UNSUPPORTED(".word 0x0120003f @ blx pc") + TEST_UNSUPPORTED(__inst_arm(0x0120003f) " @ blx pc") TEST_RR( "qadd r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "qaddvs lr, r",9, VAL2,", r",8, VAL1,"") @@ -242,190 +244,190 @@ void kprobe_arm_test_cases(void) TEST_RR( "qdsub r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "qdsubvs lr, r",9, VAL2,", r",8, VAL1,"") TEST_R( "qdsub lr, r",9, VAL2,", r13") - TEST_UNSUPPORTED(".word 0xe101f050 @ qadd pc, r0, r1") - TEST_UNSUPPORTED(".word 0xe121f050 @ qsub pc, r0, r1") - TEST_UNSUPPORTED(".word 0xe141f050 @ qdadd pc, r0, r1") - TEST_UNSUPPORTED(".word 0xe161f050 @ qdsub pc, r0, r1") - TEST_UNSUPPORTED(".word 0xe16f2050 @ qdsub r2, r0, pc") - TEST_UNSUPPORTED(".word 0xe161205f @ qdsub r2, pc, r1") + TEST_UNSUPPORTED(__inst_arm(0xe101f050) " @ qadd pc, r0, r1") + TEST_UNSUPPORTED(__inst_arm(0xe121f050) " @ qsub pc, r0, r1") + TEST_UNSUPPORTED(__inst_arm(0xe141f050) " @ qdadd pc, r0, r1") + TEST_UNSUPPORTED(__inst_arm(0xe161f050) " @ qdsub pc, r0, r1") + TEST_UNSUPPORTED(__inst_arm(0xe16f2050) " @ qdsub r2, r0, pc") + TEST_UNSUPPORTED(__inst_arm(0xe161205f) " @ qdsub r2, pc, r1") TEST_UNSUPPORTED("bkpt 0xffff") TEST_UNSUPPORTED("bkpt 0x0000") - TEST_UNSUPPORTED(".word 0xe1600070 @ smc #0") + TEST_UNSUPPORTED(__inst_arm(0xe1600070) " @ smc #0") TEST_GROUP("Halfword multiply and multiply-accumulate") TEST_RRR( "smlabb r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "smlabbge r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") TEST_RR( "smlabb lr, r",1, VAL2,", r",2, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe10f3281 @ smlabb pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe10f3281) " @ smlabb pc, r1, r2, r3") TEST_RRR( "smlatb r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "smlatbge r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") TEST_RR( "smlatb lr, r",1, VAL2,", r",2, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe10f32a1 @ smlatb pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe10f32a1) " @ smlatb pc, r1, r2, r3") TEST_RRR( "smlabt r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "smlabtge r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") TEST_RR( "smlabt lr, r",1, VAL2,", r",2, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe10f32c1 @ smlabt pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe10f32c1) " @ smlabt pc, r1, r2, r3") TEST_RRR( "smlatt r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "smlattge r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") TEST_RR( "smlatt lr, r",1, VAL2,", r",2, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe10f32e1 @ smlatt pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe10f32e1) " @ smlatt pc, r1, r2, r3") TEST_RRR( "smlawb r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "smlawbge r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") TEST_RR( "smlawb lr, r",1, VAL2,", r",2, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe12f3281 @ smlawb pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe12f3281) " @ smlawb pc, r1, r2, r3") TEST_RRR( "smlawt r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "smlawtge r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") TEST_RR( "smlawt lr, r",1, VAL2,", r",2, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe12f32c1 @ smlawt pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe12032cf @ smlawt r0, pc, r2, r3") - TEST_UNSUPPORTED(".word 0xe1203fc1 @ smlawt r0, r1, pc, r3") - TEST_UNSUPPORTED(".word 0xe120f2c1 @ smlawt r0, r1, r2, pc") + TEST_UNSUPPORTED(__inst_arm(0xe12f32c1) " @ smlawt pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe12032cf) " @ smlawt r0, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe1203fc1) " @ smlawt r0, r1, pc, r3") + TEST_UNSUPPORTED(__inst_arm(0xe120f2c1) " @ smlawt r0, r1, r2, pc") TEST_RR( "smulwb r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "smulwbge r7, r",8, VAL3,", r",9, VAL1,"") TEST_R( "smulwb lr, r",1, VAL2,", r13") - TEST_UNSUPPORTED(".word 0xe12f02a1 @ smulwb pc, r1, r2") + TEST_UNSUPPORTED(__inst_arm(0xe12f02a1) " @ smulwb pc, r1, r2") TEST_RR( "smulwt r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "smulwtge r7, r",8, VAL3,", r",9, VAL1,"") TEST_R( "smulwt lr, r",1, VAL2,", r13") - TEST_UNSUPPORTED(".word 0xe12f02e1 @ smulwt pc, r1, r2") + TEST_UNSUPPORTED(__inst_arm(0xe12f02e1) " @ smulwt pc, r1, r2") TEST_RRRR( "smlalbb r",0, VAL1,", r",1, VAL2,", r",2, VAL3,", r",3, VAL4) TEST_RRRR( "smlalbble r",8, VAL4,", r",9, VAL1,", r",10,VAL2,", r",11,VAL3) TEST_RRR( "smlalbb r",14,VAL3,", r",7, VAL4,", r",5, VAL1,", r13") - TEST_UNSUPPORTED(".word 0xe14f1382 @ smlalbb pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe141f382 @ smlalbb r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe14f1382) " @ smlalbb pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe141f382) " @ smlalbb r1, pc, r2, r3") TEST_RRRR( "smlaltb r",0, VAL1,", r",1, VAL2,", r",2, VAL3,", r",3, VAL4) TEST_RRRR( "smlaltble r",8, VAL4,", r",9, VAL1,", r",10,VAL2,", r",11,VAL3) TEST_RRR( "smlaltb r",14,VAL3,", r",7, VAL4,", r",5, VAL1,", r13") - TEST_UNSUPPORTED(".word 0xe14f13a2 @ smlaltb pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe141f3a2 @ smlaltb r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe14f13a2) " @ smlaltb pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe141f3a2) " @ smlaltb r1, pc, r2, r3") TEST_RRRR( "smlalbt r",0, VAL1,", r",1, VAL2,", r",2, VAL3,", r",3, VAL4) TEST_RRRR( "smlalbtle r",8, VAL4,", r",9, VAL1,", r",10,VAL2,", r",11,VAL3) TEST_RRR( "smlalbt r",14,VAL3,", r",7, VAL4,", r",5, VAL1,", r13") - TEST_UNSUPPORTED(".word 0xe14f13c2 @ smlalbt pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe141f3c2 @ smlalbt r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe14f13c2) " @ smlalbt pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe141f3c2) " @ smlalbt r1, pc, r2, r3") TEST_RRRR( "smlaltt r",0, VAL1,", r",1, VAL2,", r",2, VAL3,", r",3, VAL4) TEST_RRRR( "smlalttle r",8, VAL4,", r",9, VAL1,", r",10,VAL2,", r",11,VAL3) TEST_RRR( "smlaltt r",14,VAL3,", r",7, VAL4,", r",5, VAL1,", r13") - TEST_UNSUPPORTED(".word 0xe14f13e2 @ smlalbb pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe140f3e2 @ smlalbb r0, pc, r2, r3") - TEST_UNSUPPORTED(".word 0xe14013ef @ smlalbb r0, r1, pc, r3") - TEST_UNSUPPORTED(".word 0xe1401fe2 @ smlalbb r0, r1, r2, pc") + TEST_UNSUPPORTED(__inst_arm(0xe14f13e2) " @ smlalbb pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe140f3e2) " @ smlalbb r0, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe14013ef) " @ smlalbb r0, r1, pc, r3") + TEST_UNSUPPORTED(__inst_arm(0xe1401fe2) " @ smlalbb r0, r1, r2, pc") TEST_RR( "smulbb r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "smulbbge r7, r",8, VAL3,", r",9, VAL1,"") TEST_R( "smulbb lr, r",1, VAL2,", r13") - TEST_UNSUPPORTED(".word 0xe16f0281 @ smulbb pc, r1, r2") + TEST_UNSUPPORTED(__inst_arm(0xe16f0281) " @ smulbb pc, r1, r2") TEST_RR( "smultb r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "smultbge r7, r",8, VAL3,", r",9, VAL1,"") TEST_R( "smultb lr, r",1, VAL2,", r13") - TEST_UNSUPPORTED(".word 0xe16f02a1 @ smultb pc, r1, r2") + TEST_UNSUPPORTED(__inst_arm(0xe16f02a1) " @ smultb pc, r1, r2") TEST_RR( "smulbt r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "smulbtge r7, r",8, VAL3,", r",9, VAL1,"") TEST_R( "smulbt lr, r",1, VAL2,", r13") - TEST_UNSUPPORTED(".word 0xe16f02c1 @ smultb pc, r1, r2") + TEST_UNSUPPORTED(__inst_arm(0xe16f02c1) " @ smultb pc, r1, r2") TEST_RR( "smultt r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "smulttge r7, r",8, VAL3,", r",9, VAL1,"") TEST_R( "smultt lr, r",1, VAL2,", r13") - TEST_UNSUPPORTED(".word 0xe16f02e1 @ smultt pc, r1, r2") - TEST_UNSUPPORTED(".word 0xe16002ef @ smultt r0, pc, r2") - TEST_UNSUPPORTED(".word 0xe1600fe1 @ smultt r0, r1, pc") + TEST_UNSUPPORTED(__inst_arm(0xe16f02e1) " @ smultt pc, r1, r2") + TEST_UNSUPPORTED(__inst_arm(0xe16002ef) " @ smultt r0, pc, r2") + TEST_UNSUPPORTED(__inst_arm(0xe1600fe1) " @ smultt r0, r1, pc") TEST_GROUP("Multiply and multiply-accumulate") TEST_RR( "mul r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "mulls r7, r",8, VAL2,", r",9, VAL2,"") TEST_R( "mul lr, r",4, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe00f0291 @ mul pc, r1, r2") - TEST_UNSUPPORTED(".word 0xe000029f @ mul r0, pc, r2") - TEST_UNSUPPORTED(".word 0xe0000f91 @ mul r0, r1, pc") + TEST_UNSUPPORTED(__inst_arm(0xe00f0291) " @ mul pc, r1, r2") + TEST_UNSUPPORTED(__inst_arm(0xe000029f) " @ mul r0, pc, r2") + TEST_UNSUPPORTED(__inst_arm(0xe0000f91) " @ mul r0, r1, pc") TEST_RR( "muls r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "mullss r7, r",8, VAL2,", r",9, VAL2,"") TEST_R( "muls lr, r",4, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe01f0291 @ muls pc, r1, r2") + TEST_UNSUPPORTED(__inst_arm(0xe01f0291) " @ muls pc, r1, r2") TEST_RRR( "mla r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "mlahi r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") TEST_RR( "mla lr, r",1, VAL2,", r",2, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe02f3291 @ mla pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe02f3291) " @ mla pc, r1, r2, r3") TEST_RRR( "mlas r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "mlahis r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") TEST_RR( "mlas lr, r",1, VAL2,", r",2, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe03f3291 @ mlas pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe03f3291) " @ mlas pc, r1, r2, r3") #if __LINUX_ARM_ARCH__ >= 6 TEST_RR( "umaal r0, r1, r",2, VAL1,", r",3, VAL2,"") TEST_RR( "umaalls r7, r8, r",9, VAL2,", r",10, VAL1,"") TEST_R( "umaal lr, r12, r",11,VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe041f392 @ umaal pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe04f0392 @ umaal r0, pc, r2, r3") - TEST_UNSUPPORTED(".word 0xe0500090 @ undef") - TEST_UNSUPPORTED(".word 0xe05fff9f @ undef") + TEST_UNSUPPORTED(__inst_arm(0xe041f392) " @ umaal pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe04f0392) " @ umaal r0, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0500090) " @ undef") + TEST_UNSUPPORTED(__inst_arm(0xe05fff9f) " @ undef") #endif #if __LINUX_ARM_ARCH__ >= 7 TEST_RRR( "mls r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "mlshi r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") TEST_RR( "mls lr, r",1, VAL2,", r",2, VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe06f3291 @ mls pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe060329f @ mls r0, pc, r2, r3") - TEST_UNSUPPORTED(".word 0xe0603f91 @ mls r0, r1, pc, r3") - TEST_UNSUPPORTED(".word 0xe060f291 @ mls r0, r1, r2, pc") + TEST_UNSUPPORTED(__inst_arm(0xe06f3291) " @ mls pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe060329f) " @ mls r0, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0603f91) " @ mls r0, r1, pc, r3") + TEST_UNSUPPORTED(__inst_arm(0xe060f291) " @ mls r0, r1, r2, pc") #endif - TEST_UNSUPPORTED(".word 0xe0700090 @ undef") - TEST_UNSUPPORTED(".word 0xe07fff9f @ undef") + TEST_UNSUPPORTED(__inst_arm(0xe0700090) " @ undef") + TEST_UNSUPPORTED(__inst_arm(0xe07fff9f) " @ undef") TEST_RR( "umull r0, r1, r",2, VAL1,", r",3, VAL2,"") TEST_RR( "umullls r7, r8, r",9, VAL2,", r",10, VAL1,"") TEST_R( "umull lr, r12, r",11,VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe081f392 @ umull pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe08f1392 @ umull r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe081f392) " @ umull pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe08f1392) " @ umull r1, pc, r2, r3") TEST_RR( "umulls r0, r1, r",2, VAL1,", r",3, VAL2,"") TEST_RR( "umulllss r7, r8, r",9, VAL2,", r",10, VAL1,"") TEST_R( "umulls lr, r12, r",11,VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe091f392 @ umulls pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe09f1392 @ umulls r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe091f392) " @ umulls pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe09f1392) " @ umulls r1, pc, r2, r3") TEST_RRRR( "umlal r",0, VAL1,", r",1, VAL2,", r",2, VAL3,", r",3, VAL4) TEST_RRRR( "umlalle r",8, VAL4,", r",9, VAL1,", r",10,VAL2,", r",11,VAL3) TEST_RRR( "umlal r",14,VAL3,", r",7, VAL4,", r",5, VAL1,", r13") - TEST_UNSUPPORTED(".word 0xe0af1392 @ umlal pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe0a1f392 @ umlal r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0af1392) " @ umlal pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0a1f392) " @ umlal r1, pc, r2, r3") TEST_RRRR( "umlals r",0, VAL1,", r",1, VAL2,", r",2, VAL3,", r",3, VAL4) TEST_RRRR( "umlalles r",8, VAL4,", r",9, VAL1,", r",10,VAL2,", r",11,VAL3) TEST_RRR( "umlals r",14,VAL3,", r",7, VAL4,", r",5, VAL1,", r13") - TEST_UNSUPPORTED(".word 0xe0bf1392 @ umlals pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe0b1f392 @ umlals r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0bf1392) " @ umlals pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0b1f392) " @ umlals r1, pc, r2, r3") TEST_RR( "smull r0, r1, r",2, VAL1,", r",3, VAL2,"") TEST_RR( "smullls r7, r8, r",9, VAL2,", r",10, VAL1,"") TEST_R( "smull lr, r12, r",11,VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe0c1f392 @ smull pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe0cf1392 @ smull r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0c1f392) " @ smull pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0cf1392) " @ smull r1, pc, r2, r3") TEST_RR( "smulls r0, r1, r",2, VAL1,", r",3, VAL2,"") TEST_RR( "smulllss r7, r8, r",9, VAL2,", r",10, VAL1,"") TEST_R( "smulls lr, r12, r",11,VAL3,", r13") - TEST_UNSUPPORTED(".word 0xe0d1f392 @ smulls pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe0df1392 @ smulls r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0d1f392) " @ smulls pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0df1392) " @ smulls r1, pc, r2, r3") TEST_RRRR( "smlal r",0, VAL1,", r",1, VAL2,", r",2, VAL3,", r",3, VAL4) TEST_RRRR( "smlalle r",8, VAL4,", r",9, VAL1,", r",10,VAL2,", r",11,VAL3) TEST_RRR( "smlal r",14,VAL3,", r",7, VAL4,", r",5, VAL1,", r13") - TEST_UNSUPPORTED(".word 0xe0ef1392 @ smlal pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe0e1f392 @ smlal r1, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0ef1392) " @ smlal pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0e1f392) " @ smlal r1, pc, r2, r3") TEST_RRRR( "smlals r",0, VAL1,", r",1, VAL2,", r",2, VAL3,", r",3, VAL4) TEST_RRRR( "smlalles r",8, VAL4,", r",9, VAL1,", r",10,VAL2,", r",11,VAL3) TEST_RRR( "smlals r",14,VAL3,", r",7, VAL4,", r",5, VAL1,", r13") - TEST_UNSUPPORTED(".word 0xe0ff1392 @ smlals pc, r1, r2, r3") - TEST_UNSUPPORTED(".word 0xe0f0f392 @ smlals r0, pc, r2, r3") - TEST_UNSUPPORTED(".word 0xe0f0139f @ smlals r0, r1, pc, r3") - TEST_UNSUPPORTED(".word 0xe0f01f92 @ smlals r0, r1, r2, pc") + TEST_UNSUPPORTED(__inst_arm(0xe0ff1392) " @ smlals pc, r1, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0f0f392) " @ smlals r0, pc, r2, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0f0139f) " @ smlals r0, r1, pc, r3") + TEST_UNSUPPORTED(__inst_arm(0xe0f01f92) " @ smlals r0, r1, r2, pc") TEST_GROUP("Synchronization primitives") @@ -434,28 +436,28 @@ void kprobe_arm_test_cases(void) TEST_R( "swpvs r0, r",1,VAL1,", [sp]") TEST_RP("swp sp, r",14,VAL2,", [r",12,13*4,"]") #else - TEST_UNSUPPORTED(".word 0xe108e097 @ swp lr, r7, [r8]") - TEST_UNSUPPORTED(".word 0x610d0091 @ swpvs r0, r1, [sp]") - TEST_UNSUPPORTED(".word 0xe10cd09e @ swp sp, r14 [r12]") + TEST_UNSUPPORTED(__inst_arm(0xe108e097) " @ swp lr, r7, [r8]") + TEST_UNSUPPORTED(__inst_arm(0x610d0091) " @ swpvs r0, r1, [sp]") + TEST_UNSUPPORTED(__inst_arm(0xe10cd09e) " @ swp sp, r14 [r12]") #endif - TEST_UNSUPPORTED(".word 0xe102f091 @ swp pc, r1, [r2]") - TEST_UNSUPPORTED(".word 0xe102009f @ swp r0, pc, [r2]") - TEST_UNSUPPORTED(".word 0xe10f0091 @ swp r0, r1, [pc]") + TEST_UNSUPPORTED(__inst_arm(0xe102f091) " @ swp pc, r1, [r2]") + TEST_UNSUPPORTED(__inst_arm(0xe102009f) " @ swp r0, pc, [r2]") + TEST_UNSUPPORTED(__inst_arm(0xe10f0091) " @ swp r0, r1, [pc]") #if __LINUX_ARM_ARCH__ < 6 TEST_RP("swpb lr, r",7,VAL2,", [r",8,0,"]") TEST_R( "swpvsb r0, r",1,VAL1,", [sp]") #else - TEST_UNSUPPORTED(".word 0xe148e097 @ swpb lr, r7, [r8]") - TEST_UNSUPPORTED(".word 0x614d0091 @ swpvsb r0, r1, [sp]") + TEST_UNSUPPORTED(__inst_arm(0xe148e097) " @ swpb lr, r7, [r8]") + TEST_UNSUPPORTED(__inst_arm(0x614d0091) " @ swpvsb r0, r1, [sp]") #endif - TEST_UNSUPPORTED(".word 0xe142f091 @ swpb pc, r1, [r2]") - - TEST_UNSUPPORTED(".word 0xe1100090") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe1200090") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe1300090") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe1500090") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe1600090") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe1700090") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe142f091) " @ swpb pc, r1, [r2]") + + TEST_UNSUPPORTED(__inst_arm(0xe1100090)) /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe1200090)) /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe1300090)) /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe1500090)) /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe1600090)) /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe1700090)) /* Unallocated space */ #if __LINUX_ARM_ARCH__ >= 6 TEST_UNSUPPORTED("ldrex r2, [sp]") #endif @@ -475,9 +477,9 @@ void kprobe_arm_test_cases(void) TEST_RPR( "strneh r",12,VAL2,", [r",11,48,", -r",10,24,"]!") TEST_RPR( "strh r",2, VAL1,", [r",3, 24,"], r",4, 48,"") TEST_RPR( "strh r",10,VAL2,", [r",9, 48,"], -r",11,24,"") - TEST_UNSUPPORTED(".word 0xe1afc0ba @ strh r12, [pc, r10]!") - TEST_UNSUPPORTED(".word 0xe089f0bb @ strh pc, [r9], r11") - TEST_UNSUPPORTED(".word 0xe089a0bf @ strh r10, [r9], pc") + TEST_UNSUPPORTED(__inst_arm(0xe1afc0ba) " @ strh r12, [pc, r10]!") + TEST_UNSUPPORTED(__inst_arm(0xe089f0bb) " @ strh pc, [r9], r11") + TEST_UNSUPPORTED(__inst_arm(0xe089a0bf) " @ strh r10, [r9], pc") TEST_PR( "ldrh r0, [r",0, 48,", -r",2, 24,"]") TEST_PR( "ldrcsh r14, [r",13,0, ", r",12, 48,"]") @@ -485,9 +487,9 @@ void kprobe_arm_test_cases(void) TEST_PR( "ldrcch r12, [r",11,48,", -r",10,24,"]!") TEST_PR( "ldrh r2, [r",3, 24,"], r",4, 48,"") TEST_PR( "ldrh r10, [r",9, 48,"], -r",11,24,"") - TEST_UNSUPPORTED(".word 0xe1bfc0ba @ ldrh r12, [pc, r10]!") - TEST_UNSUPPORTED(".word 0xe099f0bb @ ldrh pc, [r9], r11") - TEST_UNSUPPORTED(".word 0xe099a0bf @ ldrh r10, [r9], pc") + TEST_UNSUPPORTED(__inst_arm(0xe1bfc0ba) " @ ldrh r12, [pc, r10]!") + TEST_UNSUPPORTED(__inst_arm(0xe099f0bb) " @ ldrh pc, [r9], r11") + TEST_UNSUPPORTED(__inst_arm(0xe099a0bf) " @ ldrh r10, [r9], pc") TEST_RP( "strh r",0, VAL1,", [r",1, 24,", #-2]") TEST_RP( "strmih r",14,VAL2,", [r",13,0, ", #2]") @@ -495,8 +497,8 @@ void kprobe_arm_test_cases(void) TEST_RP( "strplh r",12,VAL2,", [r",11,24,", #-4]!") TEST_RP( "strh r",2, VAL1,", [r",3, 24,"], #48") TEST_RP( "strh r",10,VAL2,", [r",9, 64,"], #-48") - TEST_UNSUPPORTED(".word 0xe1efc3b0 @ strh r12, [pc, #48]!") - TEST_UNSUPPORTED(".word 0xe0c9f3b0 @ strh pc, [r9], #48") + TEST_UNSUPPORTED(__inst_arm(0xe1efc3b0) " @ strh r12, [pc, #48]!") + TEST_UNSUPPORTED(__inst_arm(0xe0c9f3b0) " @ strh pc, [r9], #48") TEST_P( "ldrh r0, [r",0, 24,", #-2]") TEST_P( "ldrvsh r14, [r",13,0, ", #2]") @@ -505,8 +507,8 @@ void kprobe_arm_test_cases(void) TEST_P( "ldrh r2, [r",3, 24,"], #48") TEST_P( "ldrh r10, [r",9, 64,"], #-48") TEST( "ldrh r0, [pc, #0]") - TEST_UNSUPPORTED(".word 0xe1ffc3b0 @ ldrh r12, [pc, #48]!") - TEST_UNSUPPORTED(".word 0xe0d9f3b0 @ ldrh pc, [r9], #48") + TEST_UNSUPPORTED(__inst_arm(0xe1ffc3b0) " @ ldrh r12, [pc, #48]!") + TEST_UNSUPPORTED(__inst_arm(0xe0d9f3b0) " @ ldrh pc, [r9], #48") TEST_PR( "ldrsb r0, [r",0, 48,", -r",2, 24,"]") TEST_PR( "ldrhisb r14, [r",13,0,", r",12, 48,"]") @@ -514,8 +516,8 @@ void kprobe_arm_test_cases(void) TEST_PR( "ldrlssb r12, [r",11,48,", -r",10,24,"]!") TEST_PR( "ldrsb r2, [r",3, 24,"], r",4, 48,"") TEST_PR( "ldrsb r10, [r",9, 48,"], -r",11,24,"") - TEST_UNSUPPORTED(".word 0xe1bfc0da @ ldrsb r12, [pc, r10]!") - TEST_UNSUPPORTED(".word 0xe099f0db @ ldrsb pc, [r9], r11") + TEST_UNSUPPORTED(__inst_arm(0xe1bfc0da) " @ ldrsb r12, [pc, r10]!") + TEST_UNSUPPORTED(__inst_arm(0xe099f0db) " @ ldrsb pc, [r9], r11") TEST_P( "ldrsb r0, [r",0, 24,", #-1]") TEST_P( "ldrgesb r14, [r",13,0, ", #1]") @@ -524,8 +526,8 @@ void kprobe_arm_test_cases(void) TEST_P( "ldrsb r2, [r",3, 24,"], #48") TEST_P( "ldrsb r10, [r",9, 64,"], #-48") TEST( "ldrsb r0, [pc, #0]") - TEST_UNSUPPORTED(".word 0xe1ffc3d0 @ ldrsb r12, [pc, #48]!") - TEST_UNSUPPORTED(".word 0xe0d9f3d0 @ ldrsb pc, [r9], #48") + TEST_UNSUPPORTED(__inst_arm(0xe1ffc3d0) " @ ldrsb r12, [pc, #48]!") + TEST_UNSUPPORTED(__inst_arm(0xe0d9f3d0) " @ ldrsb pc, [r9], #48") TEST_PR( "ldrsh r0, [r",0, 48,", -r",2, 24,"]") TEST_PR( "ldrgtsh r14, [r",13,0, ", r",12, 48,"]") @@ -533,8 +535,8 @@ void kprobe_arm_test_cases(void) TEST_PR( "ldrlesh r12, [r",11,48,", -r",10,24,"]!") TEST_PR( "ldrsh r2, [r",3, 24,"], r",4, 48,"") TEST_PR( "ldrsh r10, [r",9, 48,"], -r",11,24,"") - TEST_UNSUPPORTED(".word 0xe1bfc0fa @ ldrsh r12, [pc, r10]!") - TEST_UNSUPPORTED(".word 0xe099f0fb @ ldrsh pc, [r9], r11") + TEST_UNSUPPORTED(__inst_arm(0xe1bfc0fa) " @ ldrsh r12, [pc, r10]!") + TEST_UNSUPPORTED(__inst_arm(0xe099f0fb) " @ ldrsh pc, [r9], r11") TEST_P( "ldrsh r0, [r",0, 24,", #-1]") TEST_P( "ldreqsh r14, [r",13,0 ,", #1]") @@ -543,8 +545,8 @@ void kprobe_arm_test_cases(void) TEST_P( "ldrsh r2, [r",3, 24,"], #48") TEST_P( "ldrsh r10, [r",9, 64,"], #-48") TEST( "ldrsh r0, [pc, #0]") - TEST_UNSUPPORTED(".word 0xe1ffc3f0 @ ldrsh r12, [pc, #48]!") - TEST_UNSUPPORTED(".word 0xe0d9f3f0 @ ldrsh pc, [r9], #48") + TEST_UNSUPPORTED(__inst_arm(0xe1ffc3f0) " @ ldrsh r12, [pc, #48]!") + TEST_UNSUPPORTED(__inst_arm(0xe0d9f3f0) " @ ldrsh pc, [r9], #48") #if __LINUX_ARM_ARCH__ >= 7 TEST_UNSUPPORTED("strht r1, [r2], r3") @@ -563,7 +565,7 @@ void kprobe_arm_test_cases(void) TEST_RPR( "strcsd r",12,VAL2,", [r",11,48,", -r",10,24,"]!") TEST_RPR( "strd r",2, VAL1,", [r",5, 24,"], r",4,48,"") TEST_RPR( "strd r",10,VAL2,", [r",9, 48,"], -r",7,24,"") - TEST_UNSUPPORTED(".word 0xe1afc0fa @ strd r12, [pc, r10]!") + TEST_UNSUPPORTED(__inst_arm(0xe1afc0fa) " @ strd r12, [pc, r10]!") TEST_PR( "ldrd r0, [r",0, 48,", -r",2,24,"]") TEST_PR( "ldrmid r8, [r",13,0, ", r",12,48,"]") @@ -571,10 +573,10 @@ void kprobe_arm_test_cases(void) TEST_PR( "ldrpld r6, [r",11,48,", -r",10,24,"]!") TEST_PR( "ldrd r2, [r",5, 24,"], r",4,48,"") TEST_PR( "ldrd r10, [r",9,48,"], -r",7,24,"") - TEST_UNSUPPORTED(".word 0xe1afc0da @ ldrd r12, [pc, r10]!") - TEST_UNSUPPORTED(".word 0xe089f0db @ ldrd pc, [r9], r11") - TEST_UNSUPPORTED(".word 0xe089e0db @ ldrd lr, [r9], r11") - TEST_UNSUPPORTED(".word 0xe089c0df @ ldrd r12, [r9], pc") + TEST_UNSUPPORTED(__inst_arm(0xe1afc0da) " @ ldrd r12, [pc, r10]!") + TEST_UNSUPPORTED(__inst_arm(0xe089f0db) " @ ldrd pc, [r9], r11") + TEST_UNSUPPORTED(__inst_arm(0xe089e0db) " @ ldrd lr, [r9], r11") + TEST_UNSUPPORTED(__inst_arm(0xe089c0df) " @ ldrd r12, [r9], pc") TEST_RP( "strd r",0, VAL1,", [r",1, 24,", #-8]") TEST_RP( "strvsd r",8, VAL2,", [r",13,0, ", #8]") @@ -582,7 +584,7 @@ void kprobe_arm_test_cases(void) TEST_RP( "strvcd r",12,VAL2,", [r",11,24,", #-16]!") TEST_RP( "strd r",2, VAL1,", [r",4, 24,"], #48") TEST_RP( "strd r",10,VAL2,", [r",9, 64,"], #-48") - TEST_UNSUPPORTED(".word 0xe1efc3f0 @ strd r12, [pc, #48]!") + TEST_UNSUPPORTED(__inst_arm(0xe1efc3f0) " @ strd r12, [pc, #48]!") TEST_P( "ldrd r0, [r",0, 24,", #-8]") TEST_P( "ldrhid r8, [r",13,0, ", #8]") @@ -590,9 +592,9 @@ void kprobe_arm_test_cases(void) TEST_P( "ldrlsd r6, [r",11,24,", #-16]!") TEST_P( "ldrd r2, [r",5, 24,"], #48") TEST_P( "ldrd r10, [r",9,6,"], #-48") - TEST_UNSUPPORTED(".word 0xe1efc3d0 @ ldrd r12, [pc, #48]!") - TEST_UNSUPPORTED(".word 0xe0c9f3d0 @ ldrd pc, [r9], #48") - TEST_UNSUPPORTED(".word 0xe0c9e3d0 @ ldrd lr, [r9], #48") + TEST_UNSUPPORTED(__inst_arm(0xe1efc3d0) " @ ldrd r12, [pc, #48]!") + TEST_UNSUPPORTED(__inst_arm(0xe0c9f3d0) " @ ldrd pc, [r9], #48") + TEST_UNSUPPORTED(__inst_arm(0xe0c9e3d0) " @ ldrd lr, [r9], #48") TEST_GROUP("Miscellaneous") @@ -600,11 +602,11 @@ void kprobe_arm_test_cases(void) TEST("movw r0, #0") TEST("movw r0, #0xffff") TEST("movw lr, #0xffff") - TEST_UNSUPPORTED(".word 0xe300f000 @ movw pc, #0") + TEST_UNSUPPORTED(__inst_arm(0xe300f000) " @ movw pc, #0") TEST_R("movt r",0, VAL1,", #0") TEST_R("movt r",0, VAL2,", #0xffff") TEST_R("movt r",14,VAL1,", #0xffff") - TEST_UNSUPPORTED(".word 0xe340f000 @ movt pc, #0") + TEST_UNSUPPORTED(__inst_arm(0xe340f000) " @ movt pc, #0") #endif TEST_UNSUPPORTED("msr cpsr, 0x13") @@ -672,20 +674,20 @@ void kprobe_arm_test_cases(void) #ifdef CONFIG_THUMB2_KERNEL TEST_ARM_TO_THUMB_INTERWORK_P("ldr pc, [r",0,0,", #15*4]") #endif - TEST_UNSUPPORTED(".word 0xe5af6008 @ str r6, [pc, #8]!") - TEST_UNSUPPORTED(".word 0xe7af6008 @ str r6, [pc, r8]!") - TEST_UNSUPPORTED(".word 0xe5bf6008 @ ldr r6, [pc, #8]!") - TEST_UNSUPPORTED(".word 0xe7bf6008 @ ldr r6, [pc, r8]!") - TEST_UNSUPPORTED(".word 0xe788600f @ str r6, [r8, pc]") - TEST_UNSUPPORTED(".word 0xe798600f @ ldr r6, [r8, pc]") + TEST_UNSUPPORTED(__inst_arm(0xe5af6008) " @ str r6, [pc, #8]!") + TEST_UNSUPPORTED(__inst_arm(0xe7af6008) " @ str r6, [pc, r8]!") + TEST_UNSUPPORTED(__inst_arm(0xe5bf6008) " @ ldr r6, [pc, #8]!") + TEST_UNSUPPORTED(__inst_arm(0xe7bf6008) " @ ldr r6, [pc, r8]!") + TEST_UNSUPPORTED(__inst_arm(0xe788600f) " @ str r6, [r8, pc]") + TEST_UNSUPPORTED(__inst_arm(0xe798600f) " @ ldr r6, [r8, pc]") LOAD_STORE("b") - TEST_UNSUPPORTED(".word 0xe5f7f008 @ ldrb pc, [r7, #8]!") - TEST_UNSUPPORTED(".word 0xe7f7f008 @ ldrb pc, [r7, r8]!") - TEST_UNSUPPORTED(".word 0xe5ef6008 @ strb r6, [pc, #8]!") - TEST_UNSUPPORTED(".word 0xe7ef6008 @ strb r6, [pc, r3]!") - TEST_UNSUPPORTED(".word 0xe5ff6008 @ ldrb r6, [pc, #8]!") - TEST_UNSUPPORTED(".word 0xe7ff6008 @ ldrb r6, [pc, r3]!") + TEST_UNSUPPORTED(__inst_arm(0xe5f7f008) " @ ldrb pc, [r7, #8]!") + TEST_UNSUPPORTED(__inst_arm(0xe7f7f008) " @ ldrb pc, [r7, r8]!") + TEST_UNSUPPORTED(__inst_arm(0xe5ef6008) " @ strb r6, [pc, #8]!") + TEST_UNSUPPORTED(__inst_arm(0xe7ef6008) " @ strb r6, [pc, r3]!") + TEST_UNSUPPORTED(__inst_arm(0xe5ff6008) " @ ldrb r6, [pc, #8]!") + TEST_UNSUPPORTED(__inst_arm(0xe7ff6008) " @ ldrb r6, [pc, r3]!") TEST_UNSUPPORTED("ldrt r0, [r1], #4") TEST_UNSUPPORTED("ldrt r1, [r2], r3") @@ -699,153 +701,153 @@ void kprobe_arm_test_cases(void) #if __LINUX_ARM_ARCH__ >= 7 TEST_GROUP("Parallel addition and subtraction, signed") - TEST_UNSUPPORTED(".word 0xe6000010") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe60fffff") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe6000010) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe60fffff) "") /* Unallocated space */ TEST_RR( "sadd16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "sadd16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe61cff1a @ sadd16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe61cff1a) " @ sadd16 pc, r12, r10") TEST_RR( "sasx r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "sasx r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe61cff3a @ sasx pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe61cff3a) " @ sasx pc, r12, r10") TEST_RR( "ssax r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "ssax r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe61cff5a @ ssax pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe61cff5a) " @ ssax pc, r12, r10") TEST_RR( "ssub16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "ssub16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe61cff7a @ ssub16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe61cff7a) " @ ssub16 pc, r12, r10") TEST_RR( "sadd8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "sadd8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe61cff9a @ sadd8 pc, r12, r10") - TEST_UNSUPPORTED(".word 0xe61000b0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe61fffbf") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe61000d0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe61fffdf") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe61cff9a) " @ sadd8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe61000b0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe61fffbf) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe61000d0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe61fffdf) "") /* Unallocated space */ TEST_RR( "ssub8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "ssub8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe61cfffa @ ssub8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe61cfffa) " @ ssub8 pc, r12, r10") TEST_RR( "qadd16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "qadd16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe62cff1a @ qadd16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe62cff1a) " @ qadd16 pc, r12, r10") TEST_RR( "qasx r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "qasx r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe62cff3a @ qasx pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe62cff3a) " @ qasx pc, r12, r10") TEST_RR( "qsax r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "qsax r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe62cff5a @ qsax pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe62cff5a) " @ qsax pc, r12, r10") TEST_RR( "qsub16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "qsub16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe62cff7a @ qsub16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe62cff7a) " @ qsub16 pc, r12, r10") TEST_RR( "qadd8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "qadd8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe62cff9a @ qadd8 pc, r12, r10") - TEST_UNSUPPORTED(".word 0xe62000b0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe62fffbf") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe62000d0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe62fffdf") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe62cff9a) " @ qadd8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe62000b0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe62fffbf) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe62000d0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe62fffdf) "") /* Unallocated space */ TEST_RR( "qsub8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "qsub8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe62cfffa @ qsub8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe62cfffa) " @ qsub8 pc, r12, r10") TEST_RR( "shadd16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "shadd16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe63cff1a @ shadd16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe63cff1a) " @ shadd16 pc, r12, r10") TEST_RR( "shasx r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "shasx r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe63cff3a @ shasx pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe63cff3a) " @ shasx pc, r12, r10") TEST_RR( "shsax r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "shsax r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe63cff5a @ shsax pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe63cff5a) " @ shsax pc, r12, r10") TEST_RR( "shsub16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "shsub16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe63cff7a @ shsub16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe63cff7a) " @ shsub16 pc, r12, r10") TEST_RR( "shadd8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "shadd8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe63cff9a @ shadd8 pc, r12, r10") - TEST_UNSUPPORTED(".word 0xe63000b0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe63fffbf") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe63000d0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe63fffdf") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe63cff9a) " @ shadd8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe63000b0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe63fffbf) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe63000d0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe63fffdf) "") /* Unallocated space */ TEST_RR( "shsub8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "shsub8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe63cfffa @ shsub8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe63cfffa) " @ shsub8 pc, r12, r10") TEST_GROUP("Parallel addition and subtraction, unsigned") - TEST_UNSUPPORTED(".word 0xe6400010") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe64fffff") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe6400010) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe64fffff) "") /* Unallocated space */ TEST_RR( "uadd16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uadd16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe65cff1a @ uadd16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe65cff1a) " @ uadd16 pc, r12, r10") TEST_RR( "uasx r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uasx r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe65cff3a @ uasx pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe65cff3a) " @ uasx pc, r12, r10") TEST_RR( "usax r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "usax r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe65cff5a @ usax pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe65cff5a) " @ usax pc, r12, r10") TEST_RR( "usub16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "usub16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe65cff7a @ usub16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe65cff7a) " @ usub16 pc, r12, r10") TEST_RR( "uadd8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uadd8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe65cff9a @ uadd8 pc, r12, r10") - TEST_UNSUPPORTED(".word 0xe65000b0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe65fffbf") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe65000d0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe65fffdf") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe65cff9a) " @ uadd8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe65000b0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe65fffbf) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe65000d0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe65fffdf) "") /* Unallocated space */ TEST_RR( "usub8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "usub8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe65cfffa @ usub8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe65cfffa) " @ usub8 pc, r12, r10") TEST_RR( "uqadd16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uqadd16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe66cff1a @ uqadd16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe66cff1a) " @ uqadd16 pc, r12, r10") TEST_RR( "uqasx r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uqasx r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe66cff3a @ uqasx pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe66cff3a) " @ uqasx pc, r12, r10") TEST_RR( "uqsax r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uqsax r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe66cff5a @ uqsax pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe66cff5a) " @ uqsax pc, r12, r10") TEST_RR( "uqsub16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uqsub16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe66cff7a @ uqsub16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe66cff7a) " @ uqsub16 pc, r12, r10") TEST_RR( "uqadd8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uqadd8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe66cff9a @ uqadd8 pc, r12, r10") - TEST_UNSUPPORTED(".word 0xe66000b0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe66fffbf") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe66000d0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe66fffdf") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe66cff9a) " @ uqadd8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe66000b0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe66fffbf) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe66000d0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe66fffdf) "") /* Unallocated space */ TEST_RR( "uqsub8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uqsub8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe66cfffa @ uqsub8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe66cfffa) " @ uqsub8 pc, r12, r10") TEST_RR( "uhadd16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uhadd16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe67cff1a @ uhadd16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe67cff1a) " @ uhadd16 pc, r12, r10") TEST_RR( "uhasx r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uhasx r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe67cff3a @ uhasx pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe67cff3a) " @ uhasx pc, r12, r10") TEST_RR( "uhsax r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uhsax r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe67cff5a @ uhsax pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe67cff5a) " @ uhsax pc, r12, r10") TEST_RR( "uhsub16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uhsub16 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe67cff7a @ uhsub16 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe67cff7a) " @ uhsub16 pc, r12, r10") TEST_RR( "uhadd8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uhadd8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe67cff9a @ uhadd8 pc, r12, r10") - TEST_UNSUPPORTED(".word 0xe67000b0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe67fffbf") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe67000d0") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe67fffdf") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe67cff9a) " @ uhadd8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe67000b0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe67fffbf) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe67000d0) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe67fffdf) "") /* Unallocated space */ TEST_RR( "uhsub8 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uhsub8 r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe67cfffa @ uhsub8 pc, r12, r10") - TEST_UNSUPPORTED(".word 0xe67feffa @ uhsub8 r14, pc, r10") - TEST_UNSUPPORTED(".word 0xe67cefff @ uhsub8 r14, r12, pc") + TEST_UNSUPPORTED(__inst_arm(0xe67cfffa) " @ uhsub8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe67feffa) " @ uhsub8 r14, pc, r10") + TEST_UNSUPPORTED(__inst_arm(0xe67cefff) " @ uhsub8 r14, r12, pc") #endif /* __LINUX_ARM_ARCH__ >= 7 */ #if __LINUX_ARM_ARCH__ >= 6 @@ -853,99 +855,99 @@ void kprobe_arm_test_cases(void) TEST_RR( "pkhbt r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "pkhbt r14,r",12, HH1,", r",10,HH2,", lsl #2") - TEST_UNSUPPORTED(".word 0xe68cf11a @ pkhbt pc, r12, r10, lsl #2") + TEST_UNSUPPORTED(__inst_arm(0xe68cf11a) " @ pkhbt pc, r12, r10, lsl #2") TEST_RR( "pkhtb r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "pkhtb r14,r",12, HH1,", r",10,HH2,", asr #2") - TEST_UNSUPPORTED(".word 0xe68cf15a @ pkhtb pc, r12, r10, asr #2") - TEST_UNSUPPORTED(".word 0xe68fe15a @ pkhtb r14, pc, r10, asr #2") - TEST_UNSUPPORTED(".word 0xe68ce15f @ pkhtb r14, r12, pc, asr #2") - TEST_UNSUPPORTED(".word 0xe6900010") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe69fffdf") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe68cf15a) " @ pkhtb pc, r12, r10, asr #2") + TEST_UNSUPPORTED(__inst_arm(0xe68fe15a) " @ pkhtb r14, pc, r10, asr #2") + TEST_UNSUPPORTED(__inst_arm(0xe68ce15f) " @ pkhtb r14, r12, pc, asr #2") + TEST_UNSUPPORTED(__inst_arm(0xe6900010) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe69fffdf) "") /* Unallocated space */ TEST_R( "ssat r0, #24, r",0, VAL1,"") TEST_R( "ssat r14, #24, r",12, VAL2,"") TEST_R( "ssat r0, #24, r",0, VAL1,", lsl #8") TEST_R( "ssat r14, #24, r",12, VAL2,", asr #8") - TEST_UNSUPPORTED(".word 0xe6b7f01c @ ssat pc, #24, r12") + TEST_UNSUPPORTED(__inst_arm(0xe6b7f01c) " @ ssat pc, #24, r12") TEST_R( "usat r0, #24, r",0, VAL1,"") TEST_R( "usat r14, #24, r",12, VAL2,"") TEST_R( "usat r0, #24, r",0, VAL1,", lsl #8") TEST_R( "usat r14, #24, r",12, VAL2,", asr #8") - TEST_UNSUPPORTED(".word 0xe6f7f01c @ usat pc, #24, r12") + TEST_UNSUPPORTED(__inst_arm(0xe6f7f01c) " @ usat pc, #24, r12") TEST_RR( "sxtab16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "sxtab16 r14,r",12, HH2,", r",10,HH1,", ror #8") TEST_R( "sxtb16 r8, r",7, HH1,"") - TEST_UNSUPPORTED(".word 0xe68cf47a @ sxtab16 pc,r12, r10, ror #8") + TEST_UNSUPPORTED(__inst_arm(0xe68cf47a) " @ sxtab16 pc,r12, r10, ror #8") TEST_RR( "sel r0, r",0, VAL1,", r",1, VAL2,"") TEST_RR( "sel r14, r",12,VAL1,", r",10, VAL2,"") - TEST_UNSUPPORTED(".word 0xe68cffba @ sel pc, r12, r10") - TEST_UNSUPPORTED(".word 0xe68fefba @ sel r14, pc, r10") - TEST_UNSUPPORTED(".word 0xe68cefbf @ sel r14, r12, pc") + TEST_UNSUPPORTED(__inst_arm(0xe68cffba) " @ sel pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe68fefba) " @ sel r14, pc, r10") + TEST_UNSUPPORTED(__inst_arm(0xe68cefbf) " @ sel r14, r12, pc") TEST_R( "ssat16 r0, #12, r",0, HH1,"") TEST_R( "ssat16 r14, #12, r",12, HH2,"") - TEST_UNSUPPORTED(".word 0xe6abff3c @ ssat16 pc, #12, r12") + TEST_UNSUPPORTED(__inst_arm(0xe6abff3c) " @ ssat16 pc, #12, r12") TEST_RR( "sxtab r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "sxtab r14,r",12, HH2,", r",10,HH1,", ror #8") TEST_R( "sxtb r8, r",7, HH1,"") - TEST_UNSUPPORTED(".word 0xe6acf47a @ sxtab pc,r12, r10, ror #8") + TEST_UNSUPPORTED(__inst_arm(0xe6acf47a) " @ sxtab pc,r12, r10, ror #8") TEST_R( "rev r0, r",0, VAL1,"") TEST_R( "rev r14, r",12, VAL2,"") - TEST_UNSUPPORTED(".word 0xe6bfff3c @ rev pc, r12") + TEST_UNSUPPORTED(__inst_arm(0xe6bfff3c) " @ rev pc, r12") TEST_RR( "sxtah r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "sxtah r14,r",12, HH2,", r",10,HH1,", ror #8") TEST_R( "sxth r8, r",7, HH1,"") - TEST_UNSUPPORTED(".word 0xe6bcf47a @ sxtah pc,r12, r10, ror #8") + TEST_UNSUPPORTED(__inst_arm(0xe6bcf47a) " @ sxtah pc,r12, r10, ror #8") TEST_R( "rev16 r0, r",0, VAL1,"") TEST_R( "rev16 r14, r",12, VAL2,"") - TEST_UNSUPPORTED(".word 0xe6bfffbc @ rev16 pc, r12") + TEST_UNSUPPORTED(__inst_arm(0xe6bfffbc) " @ rev16 pc, r12") TEST_RR( "uxtab16 r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uxtab16 r14,r",12, HH2,", r",10,HH1,", ror #8") TEST_R( "uxtb16 r8, r",7, HH1,"") - TEST_UNSUPPORTED(".word 0xe6ccf47a @ uxtab16 pc,r12, r10, ror #8") + TEST_UNSUPPORTED(__inst_arm(0xe6ccf47a) " @ uxtab16 pc,r12, r10, ror #8") TEST_R( "usat16 r0, #12, r",0, HH1,"") TEST_R( "usat16 r14, #12, r",12, HH2,"") - TEST_UNSUPPORTED(".word 0xe6ecff3c @ usat16 pc, #12, r12") - TEST_UNSUPPORTED(".word 0xe6ecef3f @ usat16 r14, #12, pc") + TEST_UNSUPPORTED(__inst_arm(0xe6ecff3c) " @ usat16 pc, #12, r12") + TEST_UNSUPPORTED(__inst_arm(0xe6ecef3f) " @ usat16 r14, #12, pc") TEST_RR( "uxtab r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uxtab r14,r",12, HH2,", r",10,HH1,", ror #8") TEST_R( "uxtb r8, r",7, HH1,"") - TEST_UNSUPPORTED(".word 0xe6ecf47a @ uxtab pc,r12, r10, ror #8") + TEST_UNSUPPORTED(__inst_arm(0xe6ecf47a) " @ uxtab pc,r12, r10, ror #8") #if __LINUX_ARM_ARCH__ >= 7 TEST_R( "rbit r0, r",0, VAL1,"") TEST_R( "rbit r14, r",12, VAL2,"") - TEST_UNSUPPORTED(".word 0xe6ffff3c @ rbit pc, r12") + TEST_UNSUPPORTED(__inst_arm(0xe6ffff3c) " @ rbit pc, r12") #endif TEST_RR( "uxtah r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uxtah r14,r",12, HH2,", r",10,HH1,", ror #8") TEST_R( "uxth r8, r",7, HH1,"") - TEST_UNSUPPORTED(".word 0xe6fff077 @ uxth pc, r7") - TEST_UNSUPPORTED(".word 0xe6ff807f @ uxth r8, pc") - TEST_UNSUPPORTED(".word 0xe6fcf47a @ uxtah pc, r12, r10, ror #8") - TEST_UNSUPPORTED(".word 0xe6fce47f @ uxtah r14, r12, pc, ror #8") + TEST_UNSUPPORTED(__inst_arm(0xe6fff077) " @ uxth pc, r7") + TEST_UNSUPPORTED(__inst_arm(0xe6ff807f) " @ uxth r8, pc") + TEST_UNSUPPORTED(__inst_arm(0xe6fcf47a) " @ uxtah pc, r12, r10, ror #8") + TEST_UNSUPPORTED(__inst_arm(0xe6fce47f) " @ uxtah r14, r12, pc, ror #8") TEST_R( "revsh r0, r",0, VAL1,"") TEST_R( "revsh r14, r",12, VAL2,"") - TEST_UNSUPPORTED(".word 0xe6ffff3c @ revsh pc, r12") - TEST_UNSUPPORTED(".word 0xe6ffef3f @ revsh r14, pc") + TEST_UNSUPPORTED(__inst_arm(0xe6ffff3c) " @ revsh pc, r12") + TEST_UNSUPPORTED(__inst_arm(0xe6ffef3f) " @ revsh r14, pc") - TEST_UNSUPPORTED(".word 0xe6900070") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe69fff7f") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe6900070) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe69fff7f) "") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe6d00070") /* Unallocated space */ - TEST_UNSUPPORTED(".word 0xe6dfff7f") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe6d00070) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_arm(0xe6dfff7f) "") /* Unallocated space */ #endif /* __LINUX_ARM_ARCH__ >= 6 */ #if __LINUX_ARM_ARCH__ >= 6 @@ -953,79 +955,79 @@ void kprobe_arm_test_cases(void) TEST_RRR( "smlad r0, r",0, HH1,", r",1, HH2,", r",2, VAL1,"") TEST_RRR( "smlad r14, r",12,HH2,", r",10,HH1,", r",8, VAL2,"") - TEST_UNSUPPORTED(".word 0xe70f8a1c @ smlad pc, r12, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe70f8a1c) " @ smlad pc, r12, r10, r8") TEST_RRR( "smladx r0, r",0, HH1,", r",1, HH2,", r",2, VAL1,"") TEST_RRR( "smladx r14, r",12,HH2,", r",10,HH1,", r",8, VAL2,"") - TEST_UNSUPPORTED(".word 0xe70f8a3c @ smladx pc, r12, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe70f8a3c) " @ smladx pc, r12, r10, r8") TEST_RR( "smuad r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "smuad r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe70ffa1c @ smuad pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe70ffa1c) " @ smuad pc, r12, r10") TEST_RR( "smuadx r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "smuadx r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe70ffa3c @ smuadx pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe70ffa3c) " @ smuadx pc, r12, r10") TEST_RRR( "smlsd r0, r",0, HH1,", r",1, HH2,", r",2, VAL1,"") TEST_RRR( "smlsd r14, r",12,HH2,", r",10,HH1,", r",8, VAL2,"") - TEST_UNSUPPORTED(".word 0xe70f8a5c @ smlsd pc, r12, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe70f8a5c) " @ smlsd pc, r12, r10, r8") TEST_RRR( "smlsdx r0, r",0, HH1,", r",1, HH2,", r",2, VAL1,"") TEST_RRR( "smlsdx r14, r",12,HH2,", r",10,HH1,", r",8, VAL2,"") - TEST_UNSUPPORTED(".word 0xe70f8a7c @ smlsdx pc, r12, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe70f8a7c) " @ smlsdx pc, r12, r10, r8") TEST_RR( "smusd r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "smusd r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe70ffa5c @ smusd pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe70ffa5c) " @ smusd pc, r12, r10") TEST_RR( "smusdx r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "smusdx r14, r",12,HH2,", r",10,HH1,"") - TEST_UNSUPPORTED(".word 0xe70ffa7c @ smusdx pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe70ffa7c) " @ smusdx pc, r12, r10") TEST_RRRR( "smlald r",0, VAL1,", r",1, VAL2, ", r",0, HH1,", r",1, HH2) TEST_RRRR( "smlald r",11,VAL2,", r",10,VAL1, ", r",9, HH2,", r",8, HH1) - TEST_UNSUPPORTED(".word 0xe74af819 @ smlald pc, r10, r9, r8") - TEST_UNSUPPORTED(".word 0xe74fb819 @ smlald r11, pc, r9, r8") - TEST_UNSUPPORTED(".word 0xe74ab81f @ smlald r11, r10, pc, r8") - TEST_UNSUPPORTED(".word 0xe74abf19 @ smlald r11, r10, r9, pc") + TEST_UNSUPPORTED(__inst_arm(0xe74af819) " @ smlald pc, r10, r9, r8") + TEST_UNSUPPORTED(__inst_arm(0xe74fb819) " @ smlald r11, pc, r9, r8") + TEST_UNSUPPORTED(__inst_arm(0xe74ab81f) " @ smlald r11, r10, pc, r8") + TEST_UNSUPPORTED(__inst_arm(0xe74abf19) " @ smlald r11, r10, r9, pc") TEST_RRRR( "smlaldx r",0, VAL1,", r",1, VAL2, ", r",0, HH1,", r",1, HH2) TEST_RRRR( "smlaldx r",11,VAL2,", r",10,VAL1, ", r",9, HH2,", r",8, HH1) - TEST_UNSUPPORTED(".word 0xe74af839 @ smlaldx pc, r10, r9, r8") - TEST_UNSUPPORTED(".word 0xe74fb839 @ smlaldx r11, pc, r9, r8") + TEST_UNSUPPORTED(__inst_arm(0xe74af839) " @ smlaldx pc, r10, r9, r8") + TEST_UNSUPPORTED(__inst_arm(0xe74fb839) " @ smlaldx r11, pc, r9, r8") TEST_RRR( "smmla r0, r",0, VAL1,", r",1, VAL2,", r",2, VAL1,"") TEST_RRR( "smmla r14, r",12,VAL2,", r",10,VAL1,", r",8, VAL2,"") - TEST_UNSUPPORTED(".word 0xe75f8a1c @ smmla pc, r12, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe75f8a1c) " @ smmla pc, r12, r10, r8") TEST_RRR( "smmlar r0, r",0, VAL1,", r",1, VAL2,", r",2, VAL1,"") TEST_RRR( "smmlar r14, r",12,VAL2,", r",10,VAL1,", r",8, VAL2,"") - TEST_UNSUPPORTED(".word 0xe75f8a3c @ smmlar pc, r12, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe75f8a3c) " @ smmlar pc, r12, r10, r8") TEST_RR( "smmul r0, r",0, VAL1,", r",1, VAL2,"") TEST_RR( "smmul r14, r",12,VAL2,", r",10,VAL1,"") - TEST_UNSUPPORTED(".word 0xe75ffa1c @ smmul pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe75ffa1c) " @ smmul pc, r12, r10") TEST_RR( "smmulr r0, r",0, VAL1,", r",1, VAL2,"") TEST_RR( "smmulr r14, r",12,VAL2,", r",10,VAL1,"") - TEST_UNSUPPORTED(".word 0xe75ffa3c @ smmulr pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe75ffa3c) " @ smmulr pc, r12, r10") TEST_RRR( "smmls r0, r",0, VAL1,", r",1, VAL2,", r",2, VAL1,"") TEST_RRR( "smmls r14, r",12,VAL2,", r",10,VAL1,", r",8, VAL2,"") - TEST_UNSUPPORTED(".word 0xe75f8adc @ smmls pc, r12, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe75f8adc) " @ smmls pc, r12, r10, r8") TEST_RRR( "smmlsr r0, r",0, VAL1,", r",1, VAL2,", r",2, VAL1,"") TEST_RRR( "smmlsr r14, r",12,VAL2,", r",10,VAL1,", r",8, VAL2,"") - TEST_UNSUPPORTED(".word 0xe75f8afc @ smmlsr pc, r12, r10, r8") - TEST_UNSUPPORTED(".word 0xe75e8aff @ smmlsr r14, pc, r10, r8") - TEST_UNSUPPORTED(".word 0xe75e8ffc @ smmlsr r14, r12, pc, r8") - TEST_UNSUPPORTED(".word 0xe75efafc @ smmlsr r14, r12, r10, pc") + TEST_UNSUPPORTED(__inst_arm(0xe75f8afc) " @ smmlsr pc, r12, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe75e8aff) " @ smmlsr r14, pc, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe75e8ffc) " @ smmlsr r14, r12, pc, r8") + TEST_UNSUPPORTED(__inst_arm(0xe75efafc) " @ smmlsr r14, r12, r10, pc") TEST_RR( "usad8 r0, r",0, VAL1,", r",1, VAL2,"") TEST_RR( "usad8 r14, r",12,VAL2,", r",10,VAL1,"") - TEST_UNSUPPORTED(".word 0xe75ffa1c @ usad8 pc, r12, r10") - TEST_UNSUPPORTED(".word 0xe75efa1f @ usad8 r14, pc, r10") - TEST_UNSUPPORTED(".word 0xe75eff1c @ usad8 r14, r12, pc") + TEST_UNSUPPORTED(__inst_arm(0xe75ffa1c) " @ usad8 pc, r12, r10") + TEST_UNSUPPORTED(__inst_arm(0xe75efa1f) " @ usad8 r14, pc, r10") + TEST_UNSUPPORTED(__inst_arm(0xe75eff1c) " @ usad8 r14, r12, pc") TEST_RRR( "usada8 r0, r",0, VAL1,", r",1, VAL2,", r",2, VAL3,"") TEST_RRR( "usada8 r14, r",12,VAL2,", r",10,VAL1,", r",8, VAL3,"") - TEST_UNSUPPORTED(".word 0xe78f8a1c @ usada8 pc, r12, r10, r8") - TEST_UNSUPPORTED(".word 0xe78e8a1f @ usada8 r14, pc, r10, r8") - TEST_UNSUPPORTED(".word 0xe78e8f1c @ usada8 r14, r12, pc, r8") + TEST_UNSUPPORTED(__inst_arm(0xe78f8a1c) " @ usada8 pc, r12, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe78e8a1f) " @ usada8 r14, pc, r10, r8") + TEST_UNSUPPORTED(__inst_arm(0xe78e8f1c) " @ usada8 r14, r12, pc, r8") #endif /* __LINUX_ARM_ARCH__ >= 6 */ #if __LINUX_ARM_ARCH__ >= 7 @@ -1034,26 +1036,26 @@ void kprobe_arm_test_cases(void) TEST_R( "sbfx r0, r",0 , VAL1,", #0, #31") TEST_R( "sbfxeq r14, r",12, VAL2,", #8, #16") TEST_R( "sbfx r4, r",10, VAL1,", #16, #15") - TEST_UNSUPPORTED(".word 0xe7aff45c @ sbfx pc, r12, #8, #16") + TEST_UNSUPPORTED(__inst_arm(0xe7aff45c) " @ sbfx pc, r12, #8, #16") TEST_R( "ubfx r0, r",0 , VAL1,", #0, #31") TEST_R( "ubfxcs r14, r",12, VAL2,", #8, #16") TEST_R( "ubfx r4, r",10, VAL1,", #16, #15") - TEST_UNSUPPORTED(".word 0xe7eff45c @ ubfx pc, r12, #8, #16") - TEST_UNSUPPORTED(".word 0xe7efc45f @ ubfx r12, pc, #8, #16") + TEST_UNSUPPORTED(__inst_arm(0xe7eff45c) " @ ubfx pc, r12, #8, #16") + TEST_UNSUPPORTED(__inst_arm(0xe7efc45f) " @ ubfx r12, pc, #8, #16") TEST_R( "bfc r",0, VAL1,", #4, #20") TEST_R( "bfcvs r",14,VAL2,", #4, #20") TEST_R( "bfc r",7, VAL1,", #0, #31") TEST_R( "bfc r",8, VAL2,", #0, #31") - TEST_UNSUPPORTED(".word 0xe7def01f @ bfc pc, #0, #31"); + TEST_UNSUPPORTED(__inst_arm(0xe7def01f) " @ bfc pc, #0, #31"); TEST_RR( "bfi r",0, VAL1,", r",0 , VAL2,", #0, #31") TEST_RR( "bfipl r",12,VAL1,", r",14 , VAL2,", #4, #20") - TEST_UNSUPPORTED(".word 0xe7d7f21e @ bfi pc, r14, #4, #20") + TEST_UNSUPPORTED(__inst_arm(0xe7d7f21e) " @ bfi pc, r14, #4, #20") - TEST_UNSUPPORTED(".word 0x07f000f0") /* Permanently UNDEFINED */ - TEST_UNSUPPORTED(".word 0x07ffffff") /* Permanently UNDEFINED */ + TEST_UNSUPPORTED(__inst_arm(0x07f000f0) "") /* Permanently UNDEFINED */ + TEST_UNSUPPORTED(__inst_arm(0x07ffffff) "") /* Permanently UNDEFINED */ #endif /* __LINUX_ARM_ARCH__ >= 6 */ TEST_GROUP("Branch, branch with link, and block data transfer") @@ -1180,43 +1182,43 @@ void kprobe_arm_test_cases(void) \ TEST_COPROCESSOR( "stc"two" 0, cr0, [r15, #4]") \ TEST_COPROCESSOR( "stc"two" 0, cr0, [r15, #-4]") \ - TEST_UNSUPPORTED(".word 0x"cc"daf0001 @ stc"two" 0, cr0, [r15, #4]!") \ - TEST_UNSUPPORTED(".word 0x"cc"d2f0001 @ stc"two" 0, cr0, [r15, #-4]!") \ - TEST_UNSUPPORTED(".word 0x"cc"caf0001 @ stc"two" 0, cr0, [r15], #4") \ - TEST_UNSUPPORTED(".word 0x"cc"c2f0001 @ stc"two" 0, cr0, [r15], #-4") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##daf0001) " @ stc"two" 0, cr0, [r15, #4]!") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##d2f0001) " @ stc"two" 0, cr0, [r15, #-4]!") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##caf0001) " @ stc"two" 0, cr0, [r15], #4") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##c2f0001) " @ stc"two" 0, cr0, [r15], #-4") \ TEST_COPROCESSOR( "stc"two" 0, cr0, [r15], {1}") \ TEST_COPROCESSOR( "stc"two"l 0, cr0, [r15, #4]") \ TEST_COPROCESSOR( "stc"two"l 0, cr0, [r15, #-4]") \ - TEST_UNSUPPORTED(".word 0x"cc"def0001 @ stc"two"l 0, cr0, [r15, #4]!") \ - TEST_UNSUPPORTED(".word 0x"cc"d6f0001 @ stc"two"l 0, cr0, [r15, #-4]!") \ - TEST_UNSUPPORTED(".word 0x"cc"cef0001 @ stc"two"l 0, cr0, [r15], #4") \ - TEST_UNSUPPORTED(".word 0x"cc"c6f0001 @ stc"two"l 0, cr0, [r15], #-4") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##def0001) " @ stc"two"l 0, cr0, [r15, #4]!") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##d6f0001) " @ stc"two"l 0, cr0, [r15, #-4]!") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##cef0001) " @ stc"two"l 0, cr0, [r15], #4") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##c6f0001) " @ stc"two"l 0, cr0, [r15], #-4") \ TEST_COPROCESSOR( "stc"two"l 0, cr0, [r15], {1}") \ TEST_COPROCESSOR( "ldc"two" 0, cr0, [r15, #4]") \ TEST_COPROCESSOR( "ldc"two" 0, cr0, [r15, #-4]") \ - TEST_UNSUPPORTED(".word 0x"cc"dbf0001 @ ldc"two" 0, cr0, [r15, #4]!") \ - TEST_UNSUPPORTED(".word 0x"cc"d3f0001 @ ldc"two" 0, cr0, [r15, #-4]!") \ - TEST_UNSUPPORTED(".word 0x"cc"cbf0001 @ ldc"two" 0, cr0, [r15], #4") \ - TEST_UNSUPPORTED(".word 0x"cc"c3f0001 @ ldc"two" 0, cr0, [r15], #-4") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##dbf0001) " @ ldc"two" 0, cr0, [r15, #4]!") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##d3f0001) " @ ldc"two" 0, cr0, [r15, #-4]!") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##cbf0001) " @ ldc"two" 0, cr0, [r15], #4") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##c3f0001) " @ ldc"two" 0, cr0, [r15], #-4") \ TEST_COPROCESSOR( "ldc"two" 0, cr0, [r15], {1}") \ TEST_COPROCESSOR( "ldc"two"l 0, cr0, [r15, #4]") \ TEST_COPROCESSOR( "ldc"two"l 0, cr0, [r15, #-4]") \ - TEST_UNSUPPORTED(".word 0x"cc"dff0001 @ ldc"two"l 0, cr0, [r15, #4]!") \ - TEST_UNSUPPORTED(".word 0x"cc"d7f0001 @ ldc"two"l 0, cr0, [r15, #-4]!") \ - TEST_UNSUPPORTED(".word 0x"cc"cff0001 @ ldc"two"l 0, cr0, [r15], #4") \ - TEST_UNSUPPORTED(".word 0x"cc"c7f0001 @ ldc"two"l 0, cr0, [r15], #-4") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##dff0001) " @ ldc"two"l 0, cr0, [r15, #4]!") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##d7f0001) " @ ldc"two"l 0, cr0, [r15, #-4]!") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##cff0001) " @ ldc"two"l 0, cr0, [r15], #4") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##c7f0001) " @ ldc"two"l 0, cr0, [r15], #-4") \ TEST_COPROCESSOR( "ldc"two"l 0, cr0, [r15], {1}") #define COPROCESSOR_INSTRUCTIONS_MC_MR(two,cc) \ \ TEST_COPROCESSOR( "mcrr"two" 0, 15, r0, r14, cr0") \ TEST_COPROCESSOR( "mcrr"two" 15, 0, r14, r0, cr15") \ - TEST_UNSUPPORTED(".word 0x"cc"c4f00f0 @ mcrr"two" 0, 15, r0, r15, cr0") \ - TEST_UNSUPPORTED(".word 0x"cc"c40ff0f @ mcrr"two" 15, 0, r15, r0, cr15") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##c4f00f0) " @ mcrr"two" 0, 15, r0, r15, cr0") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##c40ff0f) " @ mcrr"two" 15, 0, r15, r0, cr15") \ TEST_COPROCESSOR( "mrrc"two" 0, 15, r0, r14, cr0") \ TEST_COPROCESSOR( "mrrc"two" 15, 0, r14, r0, cr15") \ - TEST_UNSUPPORTED(".word 0x"cc"c5f00f0 @ mrrc"two" 0, 15, r0, r15, cr0") \ - TEST_UNSUPPORTED(".word 0x"cc"c50ff0f @ mrrc"two" 15, 0, r15, r0, cr15") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##c5f00f0) " @ mrrc"two" 0, 15, r0, r15, cr0") \ + TEST_UNSUPPORTED(__inst_arm(0x##cc##c50ff0f) " @ mrrc"two" 15, 0, r15, r0, cr15") \ TEST_COPROCESSOR( "cdp"two" 15, 15, cr15, cr15, cr15, 7") \ TEST_COPROCESSOR( "cdp"two" 0, 0, cr0, cr0, cr0, 0") \ TEST_COPROCESSOR( "mcr"two" 15, 7, r15, cr15, cr15, 7") \ @@ -1224,8 +1226,8 @@ void kprobe_arm_test_cases(void) TEST_COPROCESSOR( "mrc"two" 15, 7, r15, cr15, cr15, 7") \ TEST_COPROCESSOR( "mrc"two" 0, 0, r0, cr0, cr0, 0") - COPROCESSOR_INSTRUCTIONS_ST_LD("","e") - COPROCESSOR_INSTRUCTIONS_MC_MR("","e") + COPROCESSOR_INSTRUCTIONS_ST_LD("",e) + COPROCESSOR_INSTRUCTIONS_MC_MR("",e) TEST_UNSUPPORTED("svc 0") TEST_UNSUPPORTED("svc 0xffffff") @@ -1251,14 +1253,14 @@ void kprobe_arm_test_cases(void) TEST_UNSUPPORTED("rfedb sp!") TEST_UNSUPPORTED("rfeia sp!") TEST_UNSUPPORTED("rfeib sp!") - TEST_UNSUPPORTED(".word 0xf81d0a00 @ rfeda pc") - TEST_UNSUPPORTED(".word 0xf91d0a00 @ rfedb pc") - TEST_UNSUPPORTED(".word 0xf89d0a00 @ rfeia pc") - TEST_UNSUPPORTED(".word 0xf99d0a00 @ rfeib pc") - TEST_UNSUPPORTED(".word 0xf83d0a00 @ rfeda pc!") - TEST_UNSUPPORTED(".word 0xf93d0a00 @ rfedb pc!") - TEST_UNSUPPORTED(".word 0xf8bd0a00 @ rfeia pc!") - TEST_UNSUPPORTED(".word 0xf9bd0a00 @ rfeib pc!") + TEST_UNSUPPORTED(__inst_arm(0xf81d0a00) " @ rfeda pc") + TEST_UNSUPPORTED(__inst_arm(0xf91d0a00) " @ rfedb pc") + TEST_UNSUPPORTED(__inst_arm(0xf89d0a00) " @ rfeia pc") + TEST_UNSUPPORTED(__inst_arm(0xf99d0a00) " @ rfeib pc") + TEST_UNSUPPORTED(__inst_arm(0xf83d0a00) " @ rfeda pc!") + TEST_UNSUPPORTED(__inst_arm(0xf93d0a00) " @ rfedb pc!") + TEST_UNSUPPORTED(__inst_arm(0xf8bd0a00) " @ rfeia pc!") + TEST_UNSUPPORTED(__inst_arm(0xf9bd0a00) " @ rfeib pc!") #endif /* __LINUX_ARM_ARCH__ >= 6 */ #if __LINUX_ARM_ARCH__ >= 6 @@ -1285,9 +1287,9 @@ void kprobe_arm_test_cases(void) TEST( "blx __dummy_thumb_subroutine_odd") #endif /* __LINUX_ARM_ARCH__ >= 6 */ - COPROCESSOR_INSTRUCTIONS_ST_LD("2","f") + COPROCESSOR_INSTRUCTIONS_ST_LD("2",f) #if __LINUX_ARM_ARCH__ >= 6 - COPROCESSOR_INSTRUCTIONS_MC_MR("2","f") + COPROCESSOR_INSTRUCTIONS_MC_MR("2",f) #endif TEST_GROUP("Miscellaneous instructions, memory hints, and Advanced SIMD instructions") @@ -1317,9 +1319,9 @@ void kprobe_arm_test_cases(void) #endif #if __LINUX_ARM_ARCH__ >= 7 - TEST_SUPPORTED( ".word 0xf590f000 @ pldw [r0, #0]") - TEST_SUPPORTED( ".word 0xf797f000 @ pldw [r7, r0]") - TEST_SUPPORTED( ".word 0xf798f18c @ pldw [r8, r12, lsl #3]"); + TEST_SUPPORTED( __inst_arm(0xf590f000) " @ pldw [r0, #0]") + TEST_SUPPORTED( __inst_arm(0xf797f000) " @ pldw [r7, r0]") + TEST_SUPPORTED( __inst_arm(0xf798f18c) " @ pldw [r8, r12, lsl #3]"); #endif #if __LINUX_ARM_ARCH__ >= 7 diff --git a/arch/arm/kernel/kprobes-test-thumb.c b/arch/arm/kernel/kprobes-test-thumb.c index 5d8b857922220b..844dd10d859396 100644 --- a/arch/arm/kernel/kprobes-test-thumb.c +++ b/arch/arm/kernel/kprobes-test-thumb.c @@ -10,6 +10,7 @@ #include #include +#include #include "kprobes-test.h" @@ -119,7 +120,7 @@ void kprobe_thumb16_test_cases(void) TEST_R( "add sp" ", r",8,-8, "") TEST_R( "add r",14,VAL1,", pc") TEST_BF_R("add pc" ", r",0,2f-1f-8,"") - TEST_UNSUPPORTED(".short 0x44ff @ add pc, pc") + TEST_UNSUPPORTED(__inst_thumb16(0x44ff) " @ add pc, pc") TEST_RR( "cmp r",3,VAL1,", r",8,VAL2,"") TEST_RR( "cmp r",8,VAL2,", r",0,VAL1,"") @@ -150,7 +151,7 @@ void kprobe_thumb16_test_cases(void) TEST_BF_R("blx r",0, 2f+1,"") TEST_BB_R("blx r",14,2f+1,"") - TEST_UNSUPPORTED(".short 0x47f8 @ blx pc") + TEST_UNSUPPORTED(__inst_thumb16(0x47f8) " @ blx pc") TEST_GROUP("Load from Literal Pool") @@ -237,8 +238,8 @@ DONT_TEST_IN_ITBLOCK( TEST_R("rev r7, r",0, VAL2,"") TEST_R("rev16 r0, r",7, VAL1,"") TEST_R("rev16 r7, r",0, VAL2,"") - TEST_UNSUPPORTED(".short 0xba80") - TEST_UNSUPPORTED(".short 0xbabf") + TEST_UNSUPPORTED(__inst_thumb16(0xba80) "") + TEST_UNSUPPORTED(__inst_thumb16(0xbabf) "") TEST_R("revsh r0, r",7, VAL1,"") TEST_R("revsh r7, r",0, VAL2,"") @@ -272,8 +273,8 @@ DONT_TEST_IN_ITBLOCK( TEST("nop") TEST("wfi") TEST_SUPPORTED("wfe") - TEST_UNSUPPORTED(".short 0xbf50") /* Unassigned hints */ - TEST_UNSUPPORTED(".short 0xbff0") /* Unassigned hints */ + TEST_UNSUPPORTED(__inst_thumb16(0xbf50) "") /* Unassigned hints */ + TEST_UNSUPPORTED(__inst_thumb16(0xbff0) "") /* Unassigned hints */ #define TEST_IT(code, code2) \ TESTCASE_START(code) \ @@ -310,8 +311,8 @@ CONDITION_INSTRUCTIONS(8, TEST_BF("bgt 2f") TEST_BB("blt 2b") ) - TEST_UNSUPPORTED(".short 0xde00") - TEST_UNSUPPORTED(".short 0xdeff") + TEST_UNSUPPORTED(__inst_thumb16(0xde00) "") + TEST_UNSUPPORTED(__inst_thumb16(0xdeff) "") TEST_UNSUPPORTED("svc #0x00") TEST_UNSUPPORTED("svc #0xff") @@ -380,13 +381,13 @@ void kprobe_thumb32_test_cases(void) TEST_THUMB_TO_ARM_INTERWORK_P("ldmia r",0,14*4,", {r12,pc}") TEST_THUMB_TO_ARM_INTERWORK_P("ldmia r",13,2*4,", {r0-r12,pc}") - TEST_UNSUPPORTED(".short 0xe88f,0x0101 @ stmia pc, {r0,r8}") - TEST_UNSUPPORTED(".short 0xe92f,0x5f00 @ stmdb pc!, {r8-r12,r14}") - TEST_UNSUPPORTED(".short 0xe8bd,0xc000 @ ldmia r13!, {r14,pc}") - TEST_UNSUPPORTED(".short 0xe93e,0xc000 @ ldmdb r14!, {r14,pc}") - TEST_UNSUPPORTED(".short 0xe8a7,0x3f00 @ stmia r7!, {r8-r12,sp}") - TEST_UNSUPPORTED(".short 0xe8a7,0x9f00 @ stmia r7!, {r8-r12,pc}") - TEST_UNSUPPORTED(".short 0xe93e,0x2010 @ ldmdb r14!, {r4,sp}") + TEST_UNSUPPORTED(__inst_thumb32(0xe88f0101) " @ stmia pc, {r0,r8}") + TEST_UNSUPPORTED(__inst_thumb32(0xe92f5f00) " @ stmdb pc!, {r8-r12,r14}") + TEST_UNSUPPORTED(__inst_thumb32(0xe8bdc000) " @ ldmia r13!, {r14,pc}") + TEST_UNSUPPORTED(__inst_thumb32(0xe93ec000) " @ ldmdb r14!, {r14,pc}") + TEST_UNSUPPORTED(__inst_thumb32(0xe8a73f00) " @ stmia r7!, {r8-r12,sp}") + TEST_UNSUPPORTED(__inst_thumb32(0xe8a79f00) " @ stmia r7!, {r8-r12,pc}") + TEST_UNSUPPORTED(__inst_thumb32(0xe93e2010) " @ ldmdb r14!, {r4,sp}") TEST_GROUP("Load/store double or exclusive, table branch") @@ -402,12 +403,12 @@ void kprobe_thumb32_test_cases(void) "3: .word "__stringify(VAL1)" \n\t" " .word "__stringify(VAL2)) - TEST_UNSUPPORTED(".short 0xe9ff,0xec04 @ ldrd r14, r12, [pc, #16]!") - TEST_UNSUPPORTED(".short 0xe8ff,0xec04 @ ldrd r14, r12, [pc], #16") - TEST_UNSUPPORTED(".short 0xe9d4,0xd800 @ ldrd sp, r8, [r4]") - TEST_UNSUPPORTED(".short 0xe9d4,0xf800 @ ldrd pc, r8, [r4]") - TEST_UNSUPPORTED(".short 0xe9d4,0x7d00 @ ldrd r7, sp, [r4]") - TEST_UNSUPPORTED(".short 0xe9d4,0x7f00 @ ldrd r7, pc, [r4]") + TEST_UNSUPPORTED(__inst_thumb32(0xe9ffec04) " @ ldrd r14, r12, [pc, #16]!") + TEST_UNSUPPORTED(__inst_thumb32(0xe8ffec04) " @ ldrd r14, r12, [pc], #16") + TEST_UNSUPPORTED(__inst_thumb32(0xe9d4d800) " @ ldrd sp, r8, [r4]") + TEST_UNSUPPORTED(__inst_thumb32(0xe9d4f800) " @ ldrd pc, r8, [r4]") + TEST_UNSUPPORTED(__inst_thumb32(0xe9d47d00) " @ ldrd r7, sp, [r4]") + TEST_UNSUPPORTED(__inst_thumb32(0xe9d47f00) " @ ldrd r7, pc, [r4]") TEST_RRP("strd r",0, VAL1,", r",1, VAL2,", [r",1, 24,", #-16]") TEST_RR( "strd r",12,VAL2,", r",14,VAL1,", [sp, #16]") @@ -415,8 +416,8 @@ void kprobe_thumb32_test_cases(void) TEST_RR( "strd r",14,VAL2,", r",12,VAL1,", [sp, #16]!") TEST_RRP("strd r",1, VAL1,", r",0, VAL2,", [r",7, 24,"], #16") TEST_RR( "strd r",7, VAL2,", r",8, VAL1,", [sp], #-16") - TEST_UNSUPPORTED(".short 0xe9ef,0xec04 @ strd r14, r12, [pc, #16]!") - TEST_UNSUPPORTED(".short 0xe8ef,0xec04 @ strd r14, r12, [pc], #16") + TEST_UNSUPPORTED(__inst_thumb32(0xe9efec04) " @ strd r14, r12, [pc, #16]!") + TEST_UNSUPPORTED(__inst_thumb32(0xe8efec04) " @ strd r14, r12, [pc], #16") TEST_RX("tbb [pc, r",0, (9f-(1f+4)),"]", "9: \n\t" @@ -460,9 +461,9 @@ void kprobe_thumb32_test_cases(void) "3: mvn r0, r0 \n\t" "2: nop \n\t") - TEST_UNSUPPORTED(".short 0xe8d1,0xf01f @ tbh [r1, pc]") - TEST_UNSUPPORTED(".short 0xe8d1,0xf01d @ tbh [r1, sp]") - TEST_UNSUPPORTED(".short 0xe8dd,0xf012 @ tbh [sp, r2]") + TEST_UNSUPPORTED(__inst_thumb32(0xe8d1f01f) " @ tbh [r1, pc]") + TEST_UNSUPPORTED(__inst_thumb32(0xe8d1f01d) " @ tbh [r1, sp]") + TEST_UNSUPPORTED(__inst_thumb32(0xe8ddf012) " @ tbh [sp, r2]") TEST_UNSUPPORTED("strexb r0, r1, [r2]") TEST_UNSUPPORTED("strexh r0, r1, [r2]") @@ -540,40 +541,40 @@ void kprobe_thumb32_test_cases(void) TEST_RR("pkhtb r0, r",0, HH1,", r",1, HH2,"") TEST_RR("pkhtb r14,r",12, HH1,", r",10,HH2,", asr #2") - TEST_UNSUPPORTED(".short 0xea17,0x0f0d @ tst.w r7, sp") - TEST_UNSUPPORTED(".short 0xea17,0x0f0f @ tst.w r7, pc") - TEST_UNSUPPORTED(".short 0xea1d,0x0f07 @ tst.w sp, r7") - TEST_UNSUPPORTED(".short 0xea1f,0x0f07 @ tst.w pc, r7") - TEST_UNSUPPORTED(".short 0xf01d,0x1f08 @ tst sp, #0x00080008") - TEST_UNSUPPORTED(".short 0xf01f,0x1f08 @ tst pc, #0x00080008") - - TEST_UNSUPPORTED(".short 0xea97,0x0f0d @ teq.w r7, sp") - TEST_UNSUPPORTED(".short 0xea97,0x0f0f @ teq.w r7, pc") - TEST_UNSUPPORTED(".short 0xea9d,0x0f07 @ teq.w sp, r7") - TEST_UNSUPPORTED(".short 0xea9f,0x0f07 @ teq.w pc, r7") - TEST_UNSUPPORTED(".short 0xf09d,0x1f08 @ tst sp, #0x00080008") - TEST_UNSUPPORTED(".short 0xf09f,0x1f08 @ tst pc, #0x00080008") - - TEST_UNSUPPORTED(".short 0xeb17,0x0f0d @ cmn.w r7, sp") - TEST_UNSUPPORTED(".short 0xeb17,0x0f0f @ cmn.w r7, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xea170f0d) " @ tst.w r7, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xea170f0f) " @ tst.w r7, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xea1d0f07) " @ tst.w sp, r7") + TEST_UNSUPPORTED(__inst_thumb32(0xea1f0f07) " @ tst.w pc, r7") + TEST_UNSUPPORTED(__inst_thumb32(0xf01d1f08) " @ tst sp, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf01f1f08) " @ tst pc, #0x00080008") + + TEST_UNSUPPORTED(__inst_thumb32(0xea970f0d) " @ teq.w r7, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xea970f0f) " @ teq.w r7, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xea9d0f07) " @ teq.w sp, r7") + TEST_UNSUPPORTED(__inst_thumb32(0xea9f0f07) " @ teq.w pc, r7") + TEST_UNSUPPORTED(__inst_thumb32(0xf09d1f08) " @ tst sp, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf09f1f08) " @ tst pc, #0x00080008") + + TEST_UNSUPPORTED(__inst_thumb32(0xeb170f0d) " @ cmn.w r7, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xeb170f0f) " @ cmn.w r7, pc") TEST_P("cmn.w sp, r",7,0,"") - TEST_UNSUPPORTED(".short 0xeb1f,0x0f07 @ cmn.w pc, r7") + TEST_UNSUPPORTED(__inst_thumb32(0xeb1f0f07) " @ cmn.w pc, r7") TEST( "cmn sp, #0x00080008") - TEST_UNSUPPORTED(".short 0xf11f,0x1f08 @ cmn pc, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf11f1f08) " @ cmn pc, #0x00080008") - TEST_UNSUPPORTED(".short 0xebb7,0x0f0d @ cmp.w r7, sp") - TEST_UNSUPPORTED(".short 0xebb7,0x0f0f @ cmp.w r7, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xebb70f0d) " @ cmp.w r7, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xebb70f0f) " @ cmp.w r7, pc") TEST_P("cmp.w sp, r",7,0,"") - TEST_UNSUPPORTED(".short 0xebbf,0x0f07 @ cmp.w pc, r7") + TEST_UNSUPPORTED(__inst_thumb32(0xebbf0f07) " @ cmp.w pc, r7") TEST( "cmp sp, #0x00080008") - TEST_UNSUPPORTED(".short 0xf1bf,0x1f08 @ cmp pc, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf1bf1f08) " @ cmp pc, #0x00080008") - TEST_UNSUPPORTED(".short 0xea5f,0x070d @ movs.w r7, sp") - TEST_UNSUPPORTED(".short 0xea5f,0x070f @ movs.w r7, pc") - TEST_UNSUPPORTED(".short 0xea5f,0x0d07 @ movs.w sp, r7") - TEST_UNSUPPORTED(".short 0xea4f,0x0f07 @ mov.w pc, r7") - TEST_UNSUPPORTED(".short 0xf04f,0x1d08 @ mov sp, #0x00080008") - TEST_UNSUPPORTED(".short 0xf04f,0x1f08 @ mov pc, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xea5f070d) " @ movs.w r7, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xea5f070f) " @ movs.w r7, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xea5f0d07) " @ movs.w sp, r7") + TEST_UNSUPPORTED(__inst_thumb32(0xea4f0f07) " @ mov.w pc, r7") + TEST_UNSUPPORTED(__inst_thumb32(0xf04f1d08) " @ mov sp, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf04f1f08) " @ mov pc, #0x00080008") TEST_R("add.w r0, sp, r",1, 4,"") TEST_R("adds r0, sp, r",1, 4,", asl #3") @@ -581,15 +582,15 @@ void kprobe_thumb32_test_cases(void) TEST_R("add r0, sp, r",1, 16,", ror #1") TEST_R("add.w sp, sp, r",1, 4,"") TEST_R("add sp, sp, r",1, 4,", asl #3") - TEST_UNSUPPORTED(".short 0xeb0d,0x1d01 @ add sp, sp, r1, asl #4") - TEST_UNSUPPORTED(".short 0xeb0d,0x0d71 @ add sp, sp, r1, ror #1") + TEST_UNSUPPORTED(__inst_thumb32(0xeb0d1d01) " @ add sp, sp, r1, asl #4") + TEST_UNSUPPORTED(__inst_thumb32(0xeb0d0d71) " @ add sp, sp, r1, ror #1") TEST( "add.w r0, sp, #24") TEST( "add.w sp, sp, #24") - TEST_UNSUPPORTED(".short 0xeb0d,0x0f01 @ add pc, sp, r1") - TEST_UNSUPPORTED(".short 0xeb0d,0x000f @ add r0, sp, pc") - TEST_UNSUPPORTED(".short 0xeb0d,0x000d @ add r0, sp, sp") - TEST_UNSUPPORTED(".short 0xeb0d,0x0d0f @ add sp, sp, pc") - TEST_UNSUPPORTED(".short 0xeb0d,0x0d0d @ add sp, sp, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xeb0d0f01) " @ add pc, sp, r1") + TEST_UNSUPPORTED(__inst_thumb32(0xeb0d000f) " @ add r0, sp, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xeb0d000d) " @ add r0, sp, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xeb0d0d0f) " @ add sp, sp, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xeb0d0d0d) " @ add sp, sp, sp") TEST_R("sub.w r0, sp, r",1, 4,"") TEST_R("subs r0, sp, r",1, 4,", asl #3") @@ -597,54 +598,54 @@ void kprobe_thumb32_test_cases(void) TEST_R("sub r0, sp, r",1, 16,", ror #1") TEST_R("sub.w sp, sp, r",1, 4,"") TEST_R("sub sp, sp, r",1, 4,", asl #3") - TEST_UNSUPPORTED(".short 0xebad,0x1d01 @ sub sp, sp, r1, asl #4") - TEST_UNSUPPORTED(".short 0xebad,0x0d71 @ sub sp, sp, r1, ror #1") - TEST_UNSUPPORTED(".short 0xebad,0x0f01 @ sub pc, sp, r1") + TEST_UNSUPPORTED(__inst_thumb32(0xebad1d01) " @ sub sp, sp, r1, asl #4") + TEST_UNSUPPORTED(__inst_thumb32(0xebad0d71) " @ sub sp, sp, r1, ror #1") + TEST_UNSUPPORTED(__inst_thumb32(0xebad0f01) " @ sub pc, sp, r1") TEST( "sub.w r0, sp, #24") TEST( "sub.w sp, sp, #24") - TEST_UNSUPPORTED(".short 0xea02,0x010f @ and r1, r2, pc") - TEST_UNSUPPORTED(".short 0xea0f,0x0103 @ and r1, pc, r3") - TEST_UNSUPPORTED(".short 0xea02,0x0f03 @ and pc, r2, r3") - TEST_UNSUPPORTED(".short 0xea02,0x010d @ and r1, r2, sp") - TEST_UNSUPPORTED(".short 0xea0d,0x0103 @ and r1, sp, r3") - TEST_UNSUPPORTED(".short 0xea02,0x0d03 @ and sp, r2, r3") - TEST_UNSUPPORTED(".short 0xf00d,0x1108 @ and r1, sp, #0x00080008") - TEST_UNSUPPORTED(".short 0xf00f,0x1108 @ and r1, pc, #0x00080008") - TEST_UNSUPPORTED(".short 0xf002,0x1d08 @ and sp, r8, #0x00080008") - TEST_UNSUPPORTED(".short 0xf002,0x1f08 @ and pc, r8, #0x00080008") - - TEST_UNSUPPORTED(".short 0xeb02,0x010f @ add r1, r2, pc") - TEST_UNSUPPORTED(".short 0xeb0f,0x0103 @ add r1, pc, r3") - TEST_UNSUPPORTED(".short 0xeb02,0x0f03 @ add pc, r2, r3") - TEST_UNSUPPORTED(".short 0xeb02,0x010d @ add r1, r2, sp") - TEST_SUPPORTED( ".short 0xeb0d,0x0103 @ add r1, sp, r3") - TEST_UNSUPPORTED(".short 0xeb02,0x0d03 @ add sp, r2, r3") - TEST_SUPPORTED( ".short 0xf10d,0x1108 @ add r1, sp, #0x00080008") - TEST_UNSUPPORTED(".short 0xf10d,0x1f08 @ add pc, sp, #0x00080008") - TEST_UNSUPPORTED(".short 0xf10f,0x1108 @ add r1, pc, #0x00080008") - TEST_UNSUPPORTED(".short 0xf102,0x1d08 @ add sp, r8, #0x00080008") - TEST_UNSUPPORTED(".short 0xf102,0x1f08 @ add pc, r8, #0x00080008") - - TEST_UNSUPPORTED(".short 0xeaa0,0x0000") - TEST_UNSUPPORTED(".short 0xeaf0,0x0000") - TEST_UNSUPPORTED(".short 0xeb20,0x0000") - TEST_UNSUPPORTED(".short 0xeb80,0x0000") - TEST_UNSUPPORTED(".short 0xebe0,0x0000") - - TEST_UNSUPPORTED(".short 0xf0a0,0x0000") - TEST_UNSUPPORTED(".short 0xf0c0,0x0000") - TEST_UNSUPPORTED(".short 0xf0f0,0x0000") - TEST_UNSUPPORTED(".short 0xf120,0x0000") - TEST_UNSUPPORTED(".short 0xf180,0x0000") - TEST_UNSUPPORTED(".short 0xf1e0,0x0000") + TEST_UNSUPPORTED(__inst_thumb32(0xea02010f) " @ and r1, r2, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xea0f0103) " @ and r1, pc, r3") + TEST_UNSUPPORTED(__inst_thumb32(0xea020f03) " @ and pc, r2, r3") + TEST_UNSUPPORTED(__inst_thumb32(0xea02010d) " @ and r1, r2, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xea0d0103) " @ and r1, sp, r3") + TEST_UNSUPPORTED(__inst_thumb32(0xea020d03) " @ and sp, r2, r3") + TEST_UNSUPPORTED(__inst_thumb32(0xf00d1108) " @ and r1, sp, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf00f1108) " @ and r1, pc, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf0021d08) " @ and sp, r8, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf0021f08) " @ and pc, r8, #0x00080008") + + TEST_UNSUPPORTED(__inst_thumb32(0xeb02010f) " @ add r1, r2, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xeb0f0103) " @ add r1, pc, r3") + TEST_UNSUPPORTED(__inst_thumb32(0xeb020f03) " @ add pc, r2, r3") + TEST_UNSUPPORTED(__inst_thumb32(0xeb02010d) " @ add r1, r2, sp") + TEST_SUPPORTED( __inst_thumb32(0xeb0d0103) " @ add r1, sp, r3") + TEST_UNSUPPORTED(__inst_thumb32(0xeb020d03) " @ add sp, r2, r3") + TEST_SUPPORTED( __inst_thumb32(0xf10d1108) " @ add r1, sp, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf10d1f08) " @ add pc, sp, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf10f1108) " @ add r1, pc, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf1021d08) " @ add sp, r8, #0x00080008") + TEST_UNSUPPORTED(__inst_thumb32(0xf1021f08) " @ add pc, r8, #0x00080008") + + TEST_UNSUPPORTED(__inst_thumb32(0xeaa00000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xeaf00000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xeb200000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xeb800000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xebe00000) "") + + TEST_UNSUPPORTED(__inst_thumb32(0xf0a00000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xf0c00000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xf0f00000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xf1200000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xf1800000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xf1e00000) "") TEST_GROUP("Coprocessor instructions") - TEST_UNSUPPORTED(".short 0xec00,0x0000") - TEST_UNSUPPORTED(".short 0xeff0,0x0000") - TEST_UNSUPPORTED(".short 0xfc00,0x0000") - TEST_UNSUPPORTED(".short 0xfff0,0x0000") + TEST_UNSUPPORTED(__inst_thumb32(0xec000000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xeff00000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xfc000000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xfff00000) "") TEST_GROUP("Data-processing (plain binary immediate)") @@ -652,92 +653,92 @@ void kprobe_thumb32_test_cases(void) TEST( "addw r14, sp, #0xf5a") TEST( "addw sp, sp, #0x20") TEST( "addw r7, pc, #0x888") - TEST_UNSUPPORTED(".short 0xf20f,0x1f20 @ addw pc, pc, #0x120") - TEST_UNSUPPORTED(".short 0xf20d,0x1f20 @ addw pc, sp, #0x120") - TEST_UNSUPPORTED(".short 0xf20f,0x1d20 @ addw sp, pc, #0x120") - TEST_UNSUPPORTED(".short 0xf200,0x1d20 @ addw sp, r0, #0x120") + TEST_UNSUPPORTED(__inst_thumb32(0xf20f1f20) " @ addw pc, pc, #0x120") + TEST_UNSUPPORTED(__inst_thumb32(0xf20d1f20) " @ addw pc, sp, #0x120") + TEST_UNSUPPORTED(__inst_thumb32(0xf20f1d20) " @ addw sp, pc, #0x120") + TEST_UNSUPPORTED(__inst_thumb32(0xf2001d20) " @ addw sp, r0, #0x120") TEST_R("subw r0, r",1, VAL1,", #0x123") TEST( "subw r14, sp, #0xf5a") TEST( "subw sp, sp, #0x20") TEST( "subw r7, pc, #0x888") - TEST_UNSUPPORTED(".short 0xf2af,0x1f20 @ subw pc, pc, #0x120") - TEST_UNSUPPORTED(".short 0xf2ad,0x1f20 @ subw pc, sp, #0x120") - TEST_UNSUPPORTED(".short 0xf2af,0x1d20 @ subw sp, pc, #0x120") - TEST_UNSUPPORTED(".short 0xf2a0,0x1d20 @ subw sp, r0, #0x120") + TEST_UNSUPPORTED(__inst_thumb32(0xf2af1f20) " @ subw pc, pc, #0x120") + TEST_UNSUPPORTED(__inst_thumb32(0xf2ad1f20) " @ subw pc, sp, #0x120") + TEST_UNSUPPORTED(__inst_thumb32(0xf2af1d20) " @ subw sp, pc, #0x120") + TEST_UNSUPPORTED(__inst_thumb32(0xf2a01d20) " @ subw sp, r0, #0x120") TEST("movw r0, #0") TEST("movw r0, #0xffff") TEST("movw lr, #0xffff") - TEST_UNSUPPORTED(".short 0xf240,0x0d00 @ movw sp, #0") - TEST_UNSUPPORTED(".short 0xf240,0x0f00 @ movw pc, #0") + TEST_UNSUPPORTED(__inst_thumb32(0xf2400d00) " @ movw sp, #0") + TEST_UNSUPPORTED(__inst_thumb32(0xf2400f00) " @ movw pc, #0") TEST_R("movt r",0, VAL1,", #0") TEST_R("movt r",0, VAL2,", #0xffff") TEST_R("movt r",14,VAL1,", #0xffff") - TEST_UNSUPPORTED(".short 0xf2c0,0x0d00 @ movt sp, #0") - TEST_UNSUPPORTED(".short 0xf2c0,0x0f00 @ movt pc, #0") + TEST_UNSUPPORTED(__inst_thumb32(0xf2c00d00) " @ movt sp, #0") + TEST_UNSUPPORTED(__inst_thumb32(0xf2c00f00) " @ movt pc, #0") TEST_R( "ssat r0, #24, r",0, VAL1,"") TEST_R( "ssat r14, #24, r",12, VAL2,"") TEST_R( "ssat r0, #24, r",0, VAL1,", lsl #8") TEST_R( "ssat r14, #24, r",12, VAL2,", asr #8") - TEST_UNSUPPORTED(".short 0xf30c,0x0d17 @ ssat sp, #24, r12") - TEST_UNSUPPORTED(".short 0xf30c,0x0f17 @ ssat pc, #24, r12") - TEST_UNSUPPORTED(".short 0xf30d,0x0c17 @ ssat r12, #24, sp") - TEST_UNSUPPORTED(".short 0xf30f,0x0c17 @ ssat r12, #24, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xf30c0d17) " @ ssat sp, #24, r12") + TEST_UNSUPPORTED(__inst_thumb32(0xf30c0f17) " @ ssat pc, #24, r12") + TEST_UNSUPPORTED(__inst_thumb32(0xf30d0c17) " @ ssat r12, #24, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xf30f0c17) " @ ssat r12, #24, pc") TEST_R( "usat r0, #24, r",0, VAL1,"") TEST_R( "usat r14, #24, r",12, VAL2,"") TEST_R( "usat r0, #24, r",0, VAL1,", lsl #8") TEST_R( "usat r14, #24, r",12, VAL2,", asr #8") - TEST_UNSUPPORTED(".short 0xf38c,0x0d17 @ usat sp, #24, r12") - TEST_UNSUPPORTED(".short 0xf38c,0x0f17 @ usat pc, #24, r12") - TEST_UNSUPPORTED(".short 0xf38d,0x0c17 @ usat r12, #24, sp") - TEST_UNSUPPORTED(".short 0xf38f,0x0c17 @ usat r12, #24, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xf38c0d17) " @ usat sp, #24, r12") + TEST_UNSUPPORTED(__inst_thumb32(0xf38c0f17) " @ usat pc, #24, r12") + TEST_UNSUPPORTED(__inst_thumb32(0xf38d0c17) " @ usat r12, #24, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xf38f0c17) " @ usat r12, #24, pc") TEST_R( "ssat16 r0, #12, r",0, HH1,"") TEST_R( "ssat16 r14, #12, r",12, HH2,"") - TEST_UNSUPPORTED(".short 0xf32c,0x0d0b @ ssat16 sp, #12, r12") - TEST_UNSUPPORTED(".short 0xf32c,0x0f0b @ ssat16 pc, #12, r12") - TEST_UNSUPPORTED(".short 0xf32d,0x0c0b @ ssat16 r12, #12, sp") - TEST_UNSUPPORTED(".short 0xf32f,0x0c0b @ ssat16 r12, #12, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xf32c0d0b) " @ ssat16 sp, #12, r12") + TEST_UNSUPPORTED(__inst_thumb32(0xf32c0f0b) " @ ssat16 pc, #12, r12") + TEST_UNSUPPORTED(__inst_thumb32(0xf32d0c0b) " @ ssat16 r12, #12, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xf32f0c0b) " @ ssat16 r12, #12, pc") TEST_R( "usat16 r0, #12, r",0, HH1,"") TEST_R( "usat16 r14, #12, r",12, HH2,"") - TEST_UNSUPPORTED(".short 0xf3ac,0x0d0b @ usat16 sp, #12, r12") - TEST_UNSUPPORTED(".short 0xf3ac,0x0f0b @ usat16 pc, #12, r12") - TEST_UNSUPPORTED(".short 0xf3ad,0x0c0b @ usat16 r12, #12, sp") - TEST_UNSUPPORTED(".short 0xf3af,0x0c0b @ usat16 r12, #12, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xf3ac0d0b) " @ usat16 sp, #12, r12") + TEST_UNSUPPORTED(__inst_thumb32(0xf3ac0f0b) " @ usat16 pc, #12, r12") + TEST_UNSUPPORTED(__inst_thumb32(0xf3ad0c0b) " @ usat16 r12, #12, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xf3af0c0b) " @ usat16 r12, #12, pc") TEST_R( "sbfx r0, r",0 , VAL1,", #0, #31") TEST_R( "sbfx r14, r",12, VAL2,", #8, #16") TEST_R( "sbfx r4, r",10, VAL1,", #16, #15") - TEST_UNSUPPORTED(".short 0xf34c,0x2d0f @ sbfx sp, r12, #8, #16") - TEST_UNSUPPORTED(".short 0xf34c,0x2f0f @ sbfx pc, r12, #8, #16") - TEST_UNSUPPORTED(".short 0xf34d,0x2c0f @ sbfx r12, sp, #8, #16") - TEST_UNSUPPORTED(".short 0xf34f,0x2c0f @ sbfx r12, pc, #8, #16") + TEST_UNSUPPORTED(__inst_thumb32(0xf34c2d0f) " @ sbfx sp, r12, #8, #16") + TEST_UNSUPPORTED(__inst_thumb32(0xf34c2f0f) " @ sbfx pc, r12, #8, #16") + TEST_UNSUPPORTED(__inst_thumb32(0xf34d2c0f) " @ sbfx r12, sp, #8, #16") + TEST_UNSUPPORTED(__inst_thumb32(0xf34f2c0f) " @ sbfx r12, pc, #8, #16") TEST_R( "ubfx r0, r",0 , VAL1,", #0, #31") TEST_R( "ubfx r14, r",12, VAL2,", #8, #16") TEST_R( "ubfx r4, r",10, VAL1,", #16, #15") - TEST_UNSUPPORTED(".short 0xf3cc,0x2d0f @ ubfx sp, r12, #8, #16") - TEST_UNSUPPORTED(".short 0xf3cc,0x2f0f @ ubfx pc, r12, #8, #16") - TEST_UNSUPPORTED(".short 0xf3cd,0x2c0f @ ubfx r12, sp, #8, #16") - TEST_UNSUPPORTED(".short 0xf3cf,0x2c0f @ ubfx r12, pc, #8, #16") + TEST_UNSUPPORTED(__inst_thumb32(0xf3cc2d0f) " @ ubfx sp, r12, #8, #16") + TEST_UNSUPPORTED(__inst_thumb32(0xf3cc2f0f) " @ ubfx pc, r12, #8, #16") + TEST_UNSUPPORTED(__inst_thumb32(0xf3cd2c0f) " @ ubfx r12, sp, #8, #16") + TEST_UNSUPPORTED(__inst_thumb32(0xf3cf2c0f) " @ ubfx r12, pc, #8, #16") TEST_R( "bfc r",0, VAL1,", #4, #20") TEST_R( "bfc r",14,VAL2,", #4, #20") TEST_R( "bfc r",7, VAL1,", #0, #31") TEST_R( "bfc r",8, VAL2,", #0, #31") - TEST_UNSUPPORTED(".short 0xf36f,0x0d1e @ bfc sp, #0, #31") - TEST_UNSUPPORTED(".short 0xf36f,0x0f1e @ bfc pc, #0, #31") + TEST_UNSUPPORTED(__inst_thumb32(0xf36f0d1e) " @ bfc sp, #0, #31") + TEST_UNSUPPORTED(__inst_thumb32(0xf36f0f1e) " @ bfc pc, #0, #31") TEST_RR( "bfi r",0, VAL1,", r",0 , VAL2,", #0, #31") TEST_RR( "bfi r",12,VAL1,", r",14 , VAL2,", #4, #20") - TEST_UNSUPPORTED(".short 0xf36e,0x1d17 @ bfi sp, r14, #4, #20") - TEST_UNSUPPORTED(".short 0xf36e,0x1f17 @ bfi pc, r14, #4, #20") - TEST_UNSUPPORTED(".short 0xf36d,0x1e17 @ bfi r14, sp, #4, #20") + TEST_UNSUPPORTED(__inst_thumb32(0xf36e1d17) " @ bfi sp, r14, #4, #20") + TEST_UNSUPPORTED(__inst_thumb32(0xf36e1f17) " @ bfi pc, r14, #4, #20") + TEST_UNSUPPORTED(__inst_thumb32(0xf36d1e17) " @ bfi r14, sp, #4, #20") TEST_GROUP("Branches and miscellaneous control") @@ -775,14 +776,14 @@ CONDITION_INSTRUCTIONS(22, TEST("mrs r0, cpsr") TEST("mrs r14, cpsr") - TEST_UNSUPPORTED(".short 0xf3ef,0x8d00 @ mrs sp, spsr") - TEST_UNSUPPORTED(".short 0xf3ef,0x8f00 @ mrs pc, spsr") + TEST_UNSUPPORTED(__inst_thumb32(0xf3ef8d00) " @ mrs sp, spsr") + TEST_UNSUPPORTED(__inst_thumb32(0xf3ef8f00) " @ mrs pc, spsr") TEST_UNSUPPORTED("mrs r0, spsr") TEST_UNSUPPORTED("mrs lr, spsr") - TEST_UNSUPPORTED(".short 0xf7f0,0x8000 @ smc #0") + TEST_UNSUPPORTED(__inst_thumb32(0xf7f08000) " @ smc #0") - TEST_UNSUPPORTED(".short 0xf7f0,0xa000 @ undefeined") + TEST_UNSUPPORTED(__inst_thumb32(0xf7f0a000) " @ undefeined") TEST_BF( "b.w 2f") TEST_BB( "b.w 2b") @@ -829,15 +830,15 @@ CONDITION_INSTRUCTIONS(22, SINGLE_STORE("") TEST("str sp, [sp]") - TEST_UNSUPPORTED(".short 0xf8cf,0xe000 @ str r14, [pc]") - TEST_UNSUPPORTED(".short 0xf8ce,0xf000 @ str pc, [r14]") + TEST_UNSUPPORTED(__inst_thumb32(0xf8cfe000) " @ str r14, [pc]") + TEST_UNSUPPORTED(__inst_thumb32(0xf8cef000) " @ str pc, [r14]") TEST_GROUP("Advanced SIMD element or structure load/store instructions") - TEST_UNSUPPORTED(".short 0xf900,0x0000") - TEST_UNSUPPORTED(".short 0xf92f,0xffff") - TEST_UNSUPPORTED(".short 0xf980,0x0000") - TEST_UNSUPPORTED(".short 0xf9ef,0xffff") + TEST_UNSUPPORTED(__inst_thumb32(0xf9000000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xf92fffff) "") + TEST_UNSUPPORTED(__inst_thumb32(0xf9800000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xf9efffff) "") TEST_GROUP("Load single data item and memory hints") @@ -881,20 +882,20 @@ CONDITION_INSTRUCTIONS(22, TEST_SUPPORTED("ldr sp, 99f") TEST_SUPPORTED("ldr pc, 99f") - TEST_UNSUPPORTED(".short 0xf854,0x700d @ ldr r7, [r4, sp]") - TEST_UNSUPPORTED(".short 0xf854,0x700f @ ldr r7, [r4, pc]") - TEST_UNSUPPORTED(".short 0xf814,0x700d @ ldrb r7, [r4, sp]") - TEST_UNSUPPORTED(".short 0xf814,0x700f @ ldrb r7, [r4, pc]") - TEST_UNSUPPORTED(".short 0xf89f,0xd004 @ ldrb sp, 99f") - TEST_UNSUPPORTED(".short 0xf814,0xd008 @ ldrb sp, [r4, r8]") - TEST_UNSUPPORTED(".short 0xf894,0xd000 @ ldrb sp, [r4]") - - TEST_UNSUPPORTED(".short 0xf860,0x0000") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xf9ff,0xffff") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xf950,0x0000") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xf95f,0xffff") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xf800,0x0800") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xf97f,0xfaff") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xf854700d) " @ ldr r7, [r4, sp]") + TEST_UNSUPPORTED(__inst_thumb32(0xf854700f) " @ ldr r7, [r4, pc]") + TEST_UNSUPPORTED(__inst_thumb32(0xf814700d) " @ ldrb r7, [r4, sp]") + TEST_UNSUPPORTED(__inst_thumb32(0xf814700f) " @ ldrb r7, [r4, pc]") + TEST_UNSUPPORTED(__inst_thumb32(0xf89fd004) " @ ldrb sp, 99f") + TEST_UNSUPPORTED(__inst_thumb32(0xf814d008) " @ ldrb sp, [r4, r8]") + TEST_UNSUPPORTED(__inst_thumb32(0xf894d000) " @ ldrb sp, [r4]") + + TEST_UNSUPPORTED(__inst_thumb32(0xf8600000) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xf9ffffff) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xf9500000) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xf95fffff) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xf8000800) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xf97ffaff) "") /* Unallocated space */ TEST( "pli [pc, #4]") TEST( "pli [pc, #-4]") @@ -902,22 +903,22 @@ CONDITION_INSTRUCTIONS(22, TEST( "pld [pc, #-4]") TEST_P( "pld [r",0,-1024,", #1024]") - TEST( ".short 0xf8b0,0xf400 @ pldw [r0, #1024]") + TEST( __inst_thumb32(0xf8b0f400) " @ pldw [r0, #1024]") TEST_P( "pli [r",4, 0b,", #1024]") TEST_P( "pld [r",7, 120,", #-120]") - TEST( ".short 0xf837,0xfc78 @ pldw [r7, #-120]") + TEST( __inst_thumb32(0xf837fc78) " @ pldw [r7, #-120]") TEST_P( "pli [r",11,120,", #-120]") TEST( "pld [sp, #0]") TEST_PR("pld [r",7, 24, ", r",0, 16,"]") TEST_PR("pld [r",8, 24, ", r",12,16,", lsl #3]") - TEST_SUPPORTED(".short 0xf837,0xf000 @ pldw [r7, r0]") - TEST_SUPPORTED(".short 0xf838,0xf03c @ pldw [r8, r12, lsl #3]"); + TEST_SUPPORTED(__inst_thumb32(0xf837f000) " @ pldw [r7, r0]") + TEST_SUPPORTED(__inst_thumb32(0xf838f03c) " @ pldw [r8, r12, lsl #3]"); TEST_RR("pli [r",12,0b,", r",0, 16,"]") TEST_RR("pli [r",0, 0b,", r",12,16,", lsl #3]") TEST_R( "pld [sp, r",1, 16,"]") - TEST_UNSUPPORTED(".short 0xf817,0xf00d @pld [r7, sp]") - TEST_UNSUPPORTED(".short 0xf817,0xf00f @pld [r7, pc]") + TEST_UNSUPPORTED(__inst_thumb32(0xf817f00d) " @pld [r7, sp]") + TEST_UNSUPPORTED(__inst_thumb32(0xf817f00f) " @pld [r7, pc]") TEST_GROUP("Data-processing (register)") @@ -934,21 +935,21 @@ CONDITION_INSTRUCTIONS(22, SHIFTS32("ror") SHIFTS32("rors") - TEST_UNSUPPORTED(".short 0xfa01,0xff02 @ lsl pc, r1, r2") - TEST_UNSUPPORTED(".short 0xfa01,0xfd02 @ lsl sp, r1, r2") - TEST_UNSUPPORTED(".short 0xfa0f,0xf002 @ lsl r0, pc, r2") - TEST_UNSUPPORTED(".short 0xfa0d,0xf002 @ lsl r0, sp, r2") - TEST_UNSUPPORTED(".short 0xfa01,0xf00f @ lsl r0, r1, pc") - TEST_UNSUPPORTED(".short 0xfa01,0xf00d @ lsl r0, r1, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xfa01ff02) " @ lsl pc, r1, r2") + TEST_UNSUPPORTED(__inst_thumb32(0xfa01fd02) " @ lsl sp, r1, r2") + TEST_UNSUPPORTED(__inst_thumb32(0xfa0ff002) " @ lsl r0, pc, r2") + TEST_UNSUPPORTED(__inst_thumb32(0xfa0df002) " @ lsl r0, sp, r2") + TEST_UNSUPPORTED(__inst_thumb32(0xfa01f00f) " @ lsl r0, r1, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xfa01f00d) " @ lsl r0, r1, sp") TEST_RR( "sxtah r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "sxtah r14,r",12, HH2,", r",10,HH1,", ror #8") TEST_R( "sxth r8, r",7, HH1,"") - TEST_UNSUPPORTED(".short 0xfa0f,0xff87 @ sxth pc, r7"); - TEST_UNSUPPORTED(".short 0xfa0f,0xfd87 @ sxth sp, r7"); - TEST_UNSUPPORTED(".short 0xfa0f,0xf88f @ sxth r8, pc"); - TEST_UNSUPPORTED(".short 0xfa0f,0xf88d @ sxth r8, sp"); + TEST_UNSUPPORTED(__inst_thumb32(0xfa0fff87) " @ sxth pc, r7"); + TEST_UNSUPPORTED(__inst_thumb32(0xfa0ffd87) " @ sxth sp, r7"); + TEST_UNSUPPORTED(__inst_thumb32(0xfa0ff88f) " @ sxth r8, pc"); + TEST_UNSUPPORTED(__inst_thumb32(0xfa0ff88d) " @ sxth r8, sp"); TEST_RR( "uxtah r0, r",0, HH1,", r",1, HH2,"") TEST_RR( "uxtah r14,r",12, HH2,", r",10,HH1,", ror #8") @@ -970,8 +971,8 @@ CONDITION_INSTRUCTIONS(22, TEST_RR( "uxtab r14,r",12, HH2,", r",10,HH1,", ror #8") TEST_R( "uxtb r8, r",7, HH1,"") - TEST_UNSUPPORTED(".short 0xfa60,0x00f0") - TEST_UNSUPPORTED(".short 0xfa7f,0xffff") + TEST_UNSUPPORTED(__inst_thumb32(0xfa6000f0) "") + TEST_UNSUPPORTED(__inst_thumb32(0xfa7fffff) "") #define PARALLEL_ADD_SUB(op) \ TEST_RR( op"add16 r0, r",0, HH1,", r",1, HH2,"") \ @@ -1019,10 +1020,10 @@ CONDITION_INSTRUCTIONS(22, TEST_R("revsh.w r0, r",0, VAL1,"") TEST_R("revsh r14, r",12, VAL2,"") - TEST_UNSUPPORTED(".short 0xfa9c,0xff8c @ rev pc, r12"); - TEST_UNSUPPORTED(".short 0xfa9c,0xfd8c @ rev sp, r12"); - TEST_UNSUPPORTED(".short 0xfa9f,0xfe8f @ rev r14, pc"); - TEST_UNSUPPORTED(".short 0xfa9d,0xfe8d @ rev r14, sp"); + TEST_UNSUPPORTED(__inst_thumb32(0xfa9cff8c) " @ rev pc, r12"); + TEST_UNSUPPORTED(__inst_thumb32(0xfa9cfd8c) " @ rev sp, r12"); + TEST_UNSUPPORTED(__inst_thumb32(0xfa9ffe8f) " @ rev r14, pc"); + TEST_UNSUPPORTED(__inst_thumb32(0xfa9dfe8d) " @ rev r14, sp"); TEST_RR("sel r0, r",0, VAL1,", r",1, VAL2,"") TEST_RR("sel r14, r",12,VAL1,", r",10, VAL2,"") @@ -1031,31 +1032,31 @@ CONDITION_INSTRUCTIONS(22, TEST_R("clz r7, r",14,0x1,"") TEST_R("clz lr, r",7, 0xffffffff,"") - TEST_UNSUPPORTED(".short 0xfa80,0xf030") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xfaff,0xff7f") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xfab0,0xf000") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xfaff,0xff7f") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfa80f030) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfaffff7f) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfab0f000) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfaffff7f) "") /* Unallocated space */ TEST_GROUP("Multiply, multiply accumulate, and absolute difference operations") TEST_RR( "mul r0, r",1, VAL1,", r",2, VAL2,"") TEST_RR( "mul r7, r",8, VAL2,", r",9, VAL2,"") - TEST_UNSUPPORTED(".short 0xfb08,0xff09 @ mul pc, r8, r9") - TEST_UNSUPPORTED(".short 0xfb08,0xfd09 @ mul sp, r8, r9") - TEST_UNSUPPORTED(".short 0xfb0f,0xf709 @ mul r7, pc, r9") - TEST_UNSUPPORTED(".short 0xfb0d,0xf709 @ mul r7, sp, r9") - TEST_UNSUPPORTED(".short 0xfb08,0xf70f @ mul r7, r8, pc") - TEST_UNSUPPORTED(".short 0xfb08,0xf70d @ mul r7, r8, sp") + TEST_UNSUPPORTED(__inst_thumb32(0xfb08ff09) " @ mul pc, r8, r9") + TEST_UNSUPPORTED(__inst_thumb32(0xfb08fd09) " @ mul sp, r8, r9") + TEST_UNSUPPORTED(__inst_thumb32(0xfb0ff709) " @ mul r7, pc, r9") + TEST_UNSUPPORTED(__inst_thumb32(0xfb0df709) " @ mul r7, sp, r9") + TEST_UNSUPPORTED(__inst_thumb32(0xfb08f70f) " @ mul r7, r8, pc") + TEST_UNSUPPORTED(__inst_thumb32(0xfb08f70d) " @ mul r7, r8, sp") TEST_RRR( "mla r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "mla r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") - TEST_UNSUPPORTED(".short 0xfb08,0xaf09 @ mla pc, r8, r9, r10"); - TEST_UNSUPPORTED(".short 0xfb08,0xad09 @ mla sp, r8, r9, r10"); - TEST_UNSUPPORTED(".short 0xfb0f,0xa709 @ mla r7, pc, r9, r10"); - TEST_UNSUPPORTED(".short 0xfb0d,0xa709 @ mla r7, sp, r9, r10"); - TEST_UNSUPPORTED(".short 0xfb08,0xa70f @ mla r7, r8, pc, r10"); - TEST_UNSUPPORTED(".short 0xfb08,0xa70d @ mla r7, r8, sp, r10"); - TEST_UNSUPPORTED(".short 0xfb08,0xd709 @ mla r7, r8, r9, sp"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb08af09) " @ mla pc, r8, r9, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb08ad09) " @ mla sp, r8, r9, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb0fa709) " @ mla r7, pc, r9, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb0da709) " @ mla r7, sp, r9, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb08a70f) " @ mla r7, r8, pc, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb08a70d) " @ mla r7, r8, sp, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb08d709) " @ mla r7, r8, r9, sp"); TEST_RRR( "mls r0, r",1, VAL1,", r",2, VAL2,", r",3, VAL3,"") TEST_RRR( "mls r7, r",8, VAL3,", r",9, VAL1,", r",10, VAL2,"") @@ -1123,25 +1124,25 @@ CONDITION_INSTRUCTIONS(22, TEST_RR( "usad8 r0, r",0, VAL1,", r",1, VAL2,"") TEST_RR( "usad8 r14, r",12,VAL2,", r",10,VAL1,"") - TEST_UNSUPPORTED(".short 0xfb00,0xf010") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xfb0f,0xff1f") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xfb70,0xf010") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xfb7f,0xff1f") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xfb70,0x0010") /* Unallocated space */ - TEST_UNSUPPORTED(".short 0xfb7f,0xff1f") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfb00f010) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfb0fff1f) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfb70f010) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfb7fff1f) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfb700010) "") /* Unallocated space */ + TEST_UNSUPPORTED(__inst_thumb32(0xfb7fff1f) "") /* Unallocated space */ TEST_GROUP("Long multiply, long multiply accumulate, and divide") TEST_RR( "smull r0, r1, r",2, VAL1,", r",3, VAL2,"") TEST_RR( "smull r7, r8, r",9, VAL2,", r",10, VAL1,"") - TEST_UNSUPPORTED(".short 0xfb89,0xf80a @ smull pc, r8, r9, r10"); - TEST_UNSUPPORTED(".short 0xfb89,0xd80a @ smull sp, r8, r9, r10"); - TEST_UNSUPPORTED(".short 0xfb89,0x7f0a @ smull r7, pc, r9, r10"); - TEST_UNSUPPORTED(".short 0xfb89,0x7d0a @ smull r7, sp, r9, r10"); - TEST_UNSUPPORTED(".short 0xfb8f,0x780a @ smull r7, r8, pc, r10"); - TEST_UNSUPPORTED(".short 0xfb8d,0x780a @ smull r7, r8, sp, r10"); - TEST_UNSUPPORTED(".short 0xfb89,0x780f @ smull r7, r8, r9, pc"); - TEST_UNSUPPORTED(".short 0xfb89,0x780d @ smull r7, r8, r9, sp"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb89f80a) " @ smull pc, r8, r9, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb89d80a) " @ smull sp, r8, r9, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb897f0a) " @ smull r7, pc, r9, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb897d0a) " @ smull r7, sp, r9, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb8f780a) " @ smull r7, r8, pc, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb8d780a) " @ smull r7, r8, sp, r10"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb89780f) " @ smull r7, r8, r9, pc"); + TEST_UNSUPPORTED(__inst_thumb32(0xfb89780d) " @ smull r7, r8, r9, sp"); TEST_RR( "umull r0, r1, r",2, VAL1,", r",3, VAL2,"") TEST_RR( "umull r7, r8, r",9, VAL2,", r",10, VAL1,"") @@ -1175,8 +1176,8 @@ CONDITION_INSTRUCTIONS(22, TEST_GROUP("Coprocessor instructions") - TEST_UNSUPPORTED(".short 0xfc00,0x0000") - TEST_UNSUPPORTED(".short 0xffff,0xffff") + TEST_UNSUPPORTED(__inst_thumb32(0xfc000000) "") + TEST_UNSUPPORTED(__inst_thumb32(0xffffffff) "") TEST_GROUP("Testing instructions in IT blocks") diff --git a/arch/arm/kernel/kprobes-test.c b/arch/arm/kernel/kprobes-test.c index 0cd63d080c7bf2..379639998d5a80 100644 --- a/arch/arm/kernel/kprobes-test.c +++ b/arch/arm/kernel/kprobes-test.c @@ -113,7 +113,7 @@ * @ start of inline data... * .ascii "mov r0, r7" @ text title for test case * .byte 0 - * .align 2 + * .align 2, 0 * * @ TEST_ARG_REG * .byte ARG_TYPE_REG @@ -201,10 +201,14 @@ #include #include #include - +#include +#include +#include #include #include "kprobes.h" +#include "probes-arm.h" +#include "probes-thumb.h" #include "kprobes-test.h" @@ -1329,7 +1333,8 @@ static void test_case_failed(const char *message) static unsigned long next_instruction(unsigned long pc) { #ifdef CONFIG_THUMB2_KERNEL - if ((pc & 1) && !is_wide_instruction(*(u16 *)(pc - 1))) + if ((pc & 1) && + !is_wide_instruction(__mem_to_opcode_thumb16(*(u16 *)(pc - 1)))) return pc + 2; else #endif @@ -1374,13 +1379,13 @@ static uintptr_t __used kprobes_test_case_start(const char *title, void *stack) if (test_case_is_thumb) { u16 *p = (u16 *)(test_code & ~1); - current_instruction = p[0]; + current_instruction = __mem_to_opcode_thumb16(p[0]); if (is_wide_instruction(current_instruction)) { - current_instruction <<= 16; - current_instruction |= p[1]; + u16 instr2 = __mem_to_opcode_thumb16(p[1]); + current_instruction = __opcode_thumb32_compose(current_instruction, instr2); } } else { - current_instruction = *(u32 *)test_code; + current_instruction = __mem_to_opcode_arm(*(u32 *)test_code); } if (current_title[0] == '.') @@ -1608,7 +1613,7 @@ static int __init run_all_tests(void) goto out; pr_info("ARM instruction simulation\n"); - ret = run_test_cases(kprobe_arm_test_cases, kprobe_decode_arm_table); + ret = run_test_cases(kprobe_arm_test_cases, probes_decode_arm_table); if (ret) goto out; @@ -1631,13 +1636,13 @@ static int __init run_all_tests(void) pr_info("16-bit Thumb instruction simulation\n"); ret = run_test_cases(kprobe_thumb16_test_cases, - kprobe_decode_thumb16_table); + probes_decode_thumb16_table); if (ret) goto out; pr_info("32-bit Thumb instruction simulation\n"); ret = run_test_cases(kprobe_thumb32_test_cases, - kprobe_decode_thumb32_table); + probes_decode_thumb32_table); if (ret) goto out; #endif diff --git a/arch/arm/kernel/kprobes-test.h b/arch/arm/kernel/kprobes-test.h index e28a869b1ae4b7..eecc90a0fd912e 100644 --- a/arch/arm/kernel/kprobes-test.h +++ b/arch/arm/kernel/kprobes-test.h @@ -115,7 +115,7 @@ struct test_arg_end { /* multiple strings to be concatenated. */ \ ".ascii "#title" \n\t" \ ".byte 0 \n\t" \ - ".align 2 \n\t" + ".align 2, 0 \n\t" #define TEST_ARG_REG(reg, val) \ ".byte "__stringify(ARG_TYPE_REG)" \n\t" \ diff --git a/arch/arm/kernel/kprobes-thumb.c b/arch/arm/kernel/kprobes-thumb.c index 6123daf397a7bb..9495d7f3516fca 100644 --- a/arch/arm/kernel/kprobes-thumb.c +++ b/arch/arm/kernel/kprobes-thumb.c @@ -8,41 +8,25 @@ * published by the Free Software Foundation. */ +#include #include +#include #include -#include #include "kprobes.h" +#include "probes-thumb.h" +/* These emulation encodings are functionally equivalent... */ +#define t32_emulate_rd8rn16rm0ra12_noflags \ + t32_emulate_rdlo12rdhi8rn16rm0_noflags -/* - * True if current instruction is in an IT block. - */ -#define in_it_block(cpsr) ((cpsr & 0x06000c00) != 0x00000000) - -/* - * Return the condition code to check for the currently executing instruction. - * This is in ITSTATE<7:4> which is in CPSR<15:12> but is only valid if - * in_it_block returns true. - */ -#define current_cond(cpsr) ((cpsr >> 12) & 0xf) - -/* - * Return the PC value for a probe in thumb code. - * This is the address of the probed instruction plus 4. - * We subtract one because the address will have bit zero set to indicate - * a pointer to thumb code. - */ -static inline unsigned long __kprobes thumb_probe_pc(struct kprobe *p) -{ - return (unsigned long)p->addr - 1 + 4; -} +/* t32 thumb actions */ static void __kprobes -t32_simulate_table_branch(struct kprobe *p, struct pt_regs *regs) +t32_simulate_table_branch(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = thumb_probe_pc(p); + unsigned long pc = regs->ARM_pc; int rn = (insn >> 16) & 0xf; int rm = insn & 0xf; @@ -59,19 +43,19 @@ t32_simulate_table_branch(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t32_simulate_mrs(struct kprobe *p, struct pt_regs *regs) +t32_simulate_mrs(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rd = (insn >> 8) & 0xf; unsigned long mask = 0xf8ff03df; /* Mask out execution state */ regs->uregs[rd] = regs->ARM_cpsr & mask; } static void __kprobes -t32_simulate_cond_branch(struct kprobe *p, struct pt_regs *regs) +t32_simulate_cond_branch(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = thumb_probe_pc(p); + unsigned long pc = regs->ARM_pc; long offset = insn & 0x7ff; /* imm11 */ offset += (insn & 0x003f0000) >> 5; /* imm6 */ @@ -82,20 +66,21 @@ t32_simulate_cond_branch(struct kprobe *p, struct pt_regs *regs) regs->ARM_pc = pc + (offset * 2); } -static enum kprobe_insn __kprobes -t32_decode_cond_branch(kprobe_opcode_t insn, struct arch_specific_insn *asi) +static enum probes_insn __kprobes +t32_decode_cond_branch(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d) { int cc = (insn >> 22) & 0xf; - asi->insn_check_cc = kprobe_condition_checks[cc]; + asi->insn_check_cc = probes_condition_checks[cc]; asi->insn_handler = t32_simulate_cond_branch; return INSN_GOOD_NO_SLOT; } static void __kprobes -t32_simulate_branch(struct kprobe *p, struct pt_regs *regs) +t32_simulate_branch(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = thumb_probe_pc(p); + unsigned long pc = regs->ARM_pc; long offset = insn & 0x7ff; /* imm11 */ offset += (insn & 0x03ff0000) >> 5; /* imm10 */ @@ -108,7 +93,7 @@ t32_simulate_branch(struct kprobe *p, struct pt_regs *regs) if (insn & (1 << 14)) { /* BL or BLX */ - regs->ARM_lr = (unsigned long)p->addr + 4; + regs->ARM_lr = regs->ARM_pc | 1; if (!(insn & (1 << 12))) { /* BLX so switch to ARM mode */ regs->ARM_cpsr &= ~PSR_T_BIT; @@ -120,10 +105,10 @@ t32_simulate_branch(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t32_simulate_ldr_literal(struct kprobe *p, struct pt_regs *regs) +t32_simulate_ldr_literal(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long addr = thumb_probe_pc(p) & ~3; + unsigned long addr = regs->ARM_pc & ~3; int rt = (insn >> 12) & 0xf; unsigned long rtv; @@ -157,24 +142,25 @@ t32_simulate_ldr_literal(struct kprobe *p, struct pt_regs *regs) regs->uregs[rt] = rtv; } -static enum kprobe_insn __kprobes -t32_decode_ldmstm(kprobe_opcode_t insn, struct arch_specific_insn *asi) +static enum probes_insn __kprobes +t32_decode_ldmstm(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d) { - enum kprobe_insn ret = kprobe_decode_ldmstm(insn, asi); + enum probes_insn ret = kprobe_decode_ldmstm(insn, asi, d); /* Fixup modified instruction to have halfwords in correct order...*/ - insn = asi->insn[0]; - ((u16 *)asi->insn)[0] = insn >> 16; - ((u16 *)asi->insn)[1] = insn & 0xffff; + insn = __mem_to_opcode_arm(asi->insn[0]); + ((u16 *)asi->insn)[0] = __opcode_to_mem_thumb16(insn >> 16); + ((u16 *)asi->insn)[1] = __opcode_to_mem_thumb16(insn & 0xffff); return ret; } static void __kprobes -t32_emulate_ldrdstrd(struct kprobe *p, struct pt_regs *regs) +t32_emulate_ldrdstrd(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = thumb_probe_pc(p) & ~3; + unsigned long pc = regs->ARM_pc & ~3; int rt1 = (insn >> 12) & 0xf; int rt2 = (insn >> 8) & 0xf; int rn = (insn >> 16) & 0xf; @@ -187,7 +173,7 @@ t32_emulate_ldrdstrd(struct kprobe *p, struct pt_regs *regs) __asm__ __volatile__ ( "blx %[fn]" : "=r" (rt1v), "=r" (rt2v), "=r" (rnv) - : "0" (rt1v), "1" (rt2v), "2" (rnv), [fn] "r" (p->ainsn.insn_fn) + : "0" (rt1v), "1" (rt2v), "2" (rnv), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -198,9 +184,9 @@ t32_emulate_ldrdstrd(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t32_emulate_ldrstr(struct kprobe *p, struct pt_regs *regs) +t32_emulate_ldrstr(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rt = (insn >> 12) & 0xf; int rn = (insn >> 16) & 0xf; int rm = insn & 0xf; @@ -212,7 +198,7 @@ t32_emulate_ldrstr(struct kprobe *p, struct pt_regs *regs) __asm__ __volatile__ ( "blx %[fn]" : "=r" (rtv), "=r" (rnv) - : "0" (rtv), "1" (rnv), "r" (rmv), [fn] "r" (p->ainsn.insn_fn) + : "0" (rtv), "1" (rnv), "r" (rmv), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -224,9 +210,9 @@ t32_emulate_ldrstr(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t32_emulate_rd8rn16rm0_rwflags(struct kprobe *p, struct pt_regs *regs) +t32_emulate_rd8rn16rm0_rwflags(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rd = (insn >> 8) & 0xf; int rn = (insn >> 16) & 0xf; int rm = insn & 0xf; @@ -242,7 +228,7 @@ t32_emulate_rd8rn16rm0_rwflags(struct kprobe *p, struct pt_regs *regs) "mrs %[cpsr], cpsr \n\t" : "=r" (rdv), [cpsr] "=r" (cpsr) : "0" (rdv), "r" (rnv), "r" (rmv), - "1" (cpsr), [fn] "r" (p->ainsn.insn_fn) + "1" (cpsr), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -251,10 +237,10 @@ t32_emulate_rd8rn16rm0_rwflags(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t32_emulate_rd8pc16_noflags(struct kprobe *p, struct pt_regs *regs) +t32_emulate_rd8pc16_noflags(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = thumb_probe_pc(p); + unsigned long pc = regs->ARM_pc; int rd = (insn >> 8) & 0xf; register unsigned long rdv asm("r1") = regs->uregs[rd]; @@ -263,7 +249,7 @@ t32_emulate_rd8pc16_noflags(struct kprobe *p, struct pt_regs *regs) __asm__ __volatile__ ( "blx %[fn]" : "=r" (rdv) - : "0" (rdv), "r" (rnv), [fn] "r" (p->ainsn.insn_fn) + : "0" (rdv), "r" (rnv), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -271,9 +257,9 @@ t32_emulate_rd8pc16_noflags(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t32_emulate_rd8rn16_noflags(struct kprobe *p, struct pt_regs *regs) +t32_emulate_rd8rn16_noflags(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rd = (insn >> 8) & 0xf; int rn = (insn >> 16) & 0xf; @@ -283,7 +269,7 @@ t32_emulate_rd8rn16_noflags(struct kprobe *p, struct pt_regs *regs) __asm__ __volatile__ ( "blx %[fn]" : "=r" (rdv) - : "0" (rdv), "r" (rnv), [fn] "r" (p->ainsn.insn_fn) + : "0" (rdv), "r" (rnv), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -291,9 +277,10 @@ t32_emulate_rd8rn16_noflags(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t32_emulate_rdlo12rdhi8rn16rm0_noflags(struct kprobe *p, struct pt_regs *regs) +t32_emulate_rdlo12rdhi8rn16rm0_noflags(probes_opcode_t insn, + struct arch_probes_insn *asi, + struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rdlo = (insn >> 12) & 0xf; int rdhi = (insn >> 8) & 0xf; int rn = (insn >> 16) & 0xf; @@ -308,674 +295,43 @@ t32_emulate_rdlo12rdhi8rn16rm0_noflags(struct kprobe *p, struct pt_regs *regs) "blx %[fn]" : "=r" (rdlov), "=r" (rdhiv) : "0" (rdlov), "1" (rdhiv), "r" (rnv), "r" (rmv), - [fn] "r" (p->ainsn.insn_fn) + [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); regs->uregs[rdlo] = rdlov; regs->uregs[rdhi] = rdhiv; } - -/* These emulation encodings are functionally equivalent... */ -#define t32_emulate_rd8rn16rm0ra12_noflags \ - t32_emulate_rdlo12rdhi8rn16rm0_noflags - -static const union decode_item t32_table_1110_100x_x0xx[] = { - /* Load/store multiple instructions */ - - /* Rn is PC 1110 100x x0xx 1111 xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xfe4f0000, 0xe80f0000), - - /* SRS 1110 1000 00x0 xxxx xxxx xxxx xxxx xxxx */ - /* RFE 1110 1000 00x1 xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xffc00000, 0xe8000000), - /* SRS 1110 1001 10x0 xxxx xxxx xxxx xxxx xxxx */ - /* RFE 1110 1001 10x1 xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xffc00000, 0xe9800000), - - /* STM Rn, {...pc} 1110 100x x0x0 xxxx 1xxx xxxx xxxx xxxx */ - DECODE_REJECT (0xfe508000, 0xe8008000), - /* LDM Rn, {...lr,pc} 1110 100x x0x1 xxxx 11xx xxxx xxxx xxxx */ - DECODE_REJECT (0xfe50c000, 0xe810c000), - /* LDM/STM Rn, {...sp} 1110 100x x0xx xxxx xx1x xxxx xxxx xxxx */ - DECODE_REJECT (0xfe402000, 0xe8002000), - - /* STMIA 1110 1000 10x0 xxxx xxxx xxxx xxxx xxxx */ - /* LDMIA 1110 1000 10x1 xxxx xxxx xxxx xxxx xxxx */ - /* STMDB 1110 1001 00x0 xxxx xxxx xxxx xxxx xxxx */ - /* LDMDB 1110 1001 00x1 xxxx xxxx xxxx xxxx xxxx */ - DECODE_CUSTOM (0xfe400000, 0xe8000000, t32_decode_ldmstm), - - DECODE_END -}; - -static const union decode_item t32_table_1110_100x_x1xx[] = { - /* Load/store dual, load/store exclusive, table branch */ - - /* STRD (immediate) 1110 1000 x110 xxxx xxxx xxxx xxxx xxxx */ - /* LDRD (immediate) 1110 1000 x111 xxxx xxxx xxxx xxxx xxxx */ - DECODE_OR (0xff600000, 0xe8600000), - /* STRD (immediate) 1110 1001 x1x0 xxxx xxxx xxxx xxxx xxxx */ - /* LDRD (immediate) 1110 1001 x1x1 xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xff400000, 0xe9400000, t32_emulate_ldrdstrd, - REGS(NOPCWB, NOSPPC, NOSPPC, 0, 0)), - - /* TBB 1110 1000 1101 xxxx xxxx xxxx 0000 xxxx */ - /* TBH 1110 1000 1101 xxxx xxxx xxxx 0001 xxxx */ - DECODE_SIMULATEX(0xfff000e0, 0xe8d00000, t32_simulate_table_branch, - REGS(NOSP, 0, 0, 0, NOSPPC)), - - /* STREX 1110 1000 0100 xxxx xxxx xxxx xxxx xxxx */ - /* LDREX 1110 1000 0101 xxxx xxxx xxxx xxxx xxxx */ - /* STREXB 1110 1000 1100 xxxx xxxx xxxx 0100 xxxx */ - /* STREXH 1110 1000 1100 xxxx xxxx xxxx 0101 xxxx */ - /* STREXD 1110 1000 1100 xxxx xxxx xxxx 0111 xxxx */ - /* LDREXB 1110 1000 1101 xxxx xxxx xxxx 0100 xxxx */ - /* LDREXH 1110 1000 1101 xxxx xxxx xxxx 0101 xxxx */ - /* LDREXD 1110 1000 1101 xxxx xxxx xxxx 0111 xxxx */ - /* And unallocated instructions... */ - DECODE_END -}; - -static const union decode_item t32_table_1110_101x[] = { - /* Data-processing (shifted register) */ - - /* TST 1110 1010 0001 xxxx xxxx 1111 xxxx xxxx */ - /* TEQ 1110 1010 1001 xxxx xxxx 1111 xxxx xxxx */ - DECODE_EMULATEX (0xff700f00, 0xea100f00, t32_emulate_rd8rn16rm0_rwflags, - REGS(NOSPPC, 0, 0, 0, NOSPPC)), - - /* CMN 1110 1011 0001 xxxx xxxx 1111 xxxx xxxx */ - DECODE_OR (0xfff00f00, 0xeb100f00), - /* CMP 1110 1011 1011 xxxx xxxx 1111 xxxx xxxx */ - DECODE_EMULATEX (0xfff00f00, 0xebb00f00, t32_emulate_rd8rn16rm0_rwflags, - REGS(NOPC, 0, 0, 0, NOSPPC)), - - /* MOV 1110 1010 010x 1111 xxxx xxxx xxxx xxxx */ - /* MVN 1110 1010 011x 1111 xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xffcf0000, 0xea4f0000, t32_emulate_rd8rn16rm0_rwflags, - REGS(0, 0, NOSPPC, 0, NOSPPC)), - - /* ??? 1110 1010 101x xxxx xxxx xxxx xxxx xxxx */ - /* ??? 1110 1010 111x xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xffa00000, 0xeaa00000), - /* ??? 1110 1011 001x xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xffe00000, 0xeb200000), - /* ??? 1110 1011 100x xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xffe00000, 0xeb800000), - /* ??? 1110 1011 111x xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xffe00000, 0xebe00000), - - /* ADD/SUB SP, SP, Rm, LSL #0..3 */ - /* 1110 1011 x0xx 1101 x000 1101 xx00 xxxx */ - DECODE_EMULATEX (0xff4f7f30, 0xeb0d0d00, t32_emulate_rd8rn16rm0_rwflags, - REGS(SP, 0, SP, 0, NOSPPC)), - - /* ADD/SUB SP, SP, Rm, shift */ - /* 1110 1011 x0xx 1101 xxxx 1101 xxxx xxxx */ - DECODE_REJECT (0xff4f0f00, 0xeb0d0d00), - - /* ADD/SUB Rd, SP, Rm, shift */ - /* 1110 1011 x0xx 1101 xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xff4f0000, 0xeb0d0000, t32_emulate_rd8rn16rm0_rwflags, - REGS(SP, 0, NOPC, 0, NOSPPC)), - - /* AND 1110 1010 000x xxxx xxxx xxxx xxxx xxxx */ - /* BIC 1110 1010 001x xxxx xxxx xxxx xxxx xxxx */ - /* ORR 1110 1010 010x xxxx xxxx xxxx xxxx xxxx */ - /* ORN 1110 1010 011x xxxx xxxx xxxx xxxx xxxx */ - /* EOR 1110 1010 100x xxxx xxxx xxxx xxxx xxxx */ - /* PKH 1110 1010 110x xxxx xxxx xxxx xxxx xxxx */ - /* ADD 1110 1011 000x xxxx xxxx xxxx xxxx xxxx */ - /* ADC 1110 1011 010x xxxx xxxx xxxx xxxx xxxx */ - /* SBC 1110 1011 011x xxxx xxxx xxxx xxxx xxxx */ - /* SUB 1110 1011 101x xxxx xxxx xxxx xxxx xxxx */ - /* RSB 1110 1011 110x xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfe000000, 0xea000000, t32_emulate_rd8rn16rm0_rwflags, - REGS(NOSPPC, 0, NOSPPC, 0, NOSPPC)), - - DECODE_END -}; - -static const union decode_item t32_table_1111_0x0x___0[] = { - /* Data-processing (modified immediate) */ - - /* TST 1111 0x00 0001 xxxx 0xxx 1111 xxxx xxxx */ - /* TEQ 1111 0x00 1001 xxxx 0xxx 1111 xxxx xxxx */ - DECODE_EMULATEX (0xfb708f00, 0xf0100f00, t32_emulate_rd8rn16rm0_rwflags, - REGS(NOSPPC, 0, 0, 0, 0)), - - /* CMN 1111 0x01 0001 xxxx 0xxx 1111 xxxx xxxx */ - DECODE_OR (0xfbf08f00, 0xf1100f00), - /* CMP 1111 0x01 1011 xxxx 0xxx 1111 xxxx xxxx */ - DECODE_EMULATEX (0xfbf08f00, 0xf1b00f00, t32_emulate_rd8rn16rm0_rwflags, - REGS(NOPC, 0, 0, 0, 0)), - - /* MOV 1111 0x00 010x 1111 0xxx xxxx xxxx xxxx */ - /* MVN 1111 0x00 011x 1111 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfbcf8000, 0xf04f0000, t32_emulate_rd8rn16rm0_rwflags, - REGS(0, 0, NOSPPC, 0, 0)), - - /* ??? 1111 0x00 101x xxxx 0xxx xxxx xxxx xxxx */ - DECODE_REJECT (0xfbe08000, 0xf0a00000), - /* ??? 1111 0x00 110x xxxx 0xxx xxxx xxxx xxxx */ - /* ??? 1111 0x00 111x xxxx 0xxx xxxx xxxx xxxx */ - DECODE_REJECT (0xfbc08000, 0xf0c00000), - /* ??? 1111 0x01 001x xxxx 0xxx xxxx xxxx xxxx */ - DECODE_REJECT (0xfbe08000, 0xf1200000), - /* ??? 1111 0x01 100x xxxx 0xxx xxxx xxxx xxxx */ - DECODE_REJECT (0xfbe08000, 0xf1800000), - /* ??? 1111 0x01 111x xxxx 0xxx xxxx xxxx xxxx */ - DECODE_REJECT (0xfbe08000, 0xf1e00000), - - /* ADD Rd, SP, #imm 1111 0x01 000x 1101 0xxx xxxx xxxx xxxx */ - /* SUB Rd, SP, #imm 1111 0x01 101x 1101 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfb4f8000, 0xf10d0000, t32_emulate_rd8rn16rm0_rwflags, - REGS(SP, 0, NOPC, 0, 0)), - - /* AND 1111 0x00 000x xxxx 0xxx xxxx xxxx xxxx */ - /* BIC 1111 0x00 001x xxxx 0xxx xxxx xxxx xxxx */ - /* ORR 1111 0x00 010x xxxx 0xxx xxxx xxxx xxxx */ - /* ORN 1111 0x00 011x xxxx 0xxx xxxx xxxx xxxx */ - /* EOR 1111 0x00 100x xxxx 0xxx xxxx xxxx xxxx */ - /* ADD 1111 0x01 000x xxxx 0xxx xxxx xxxx xxxx */ - /* ADC 1111 0x01 010x xxxx 0xxx xxxx xxxx xxxx */ - /* SBC 1111 0x01 011x xxxx 0xxx xxxx xxxx xxxx */ - /* SUB 1111 0x01 101x xxxx 0xxx xxxx xxxx xxxx */ - /* RSB 1111 0x01 110x xxxx 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfa008000, 0xf0000000, t32_emulate_rd8rn16rm0_rwflags, - REGS(NOSPPC, 0, NOSPPC, 0, 0)), - - DECODE_END -}; - -static const union decode_item t32_table_1111_0x1x___0[] = { - /* Data-processing (plain binary immediate) */ - - /* ADDW Rd, PC, #imm 1111 0x10 0000 1111 0xxx xxxx xxxx xxxx */ - DECODE_OR (0xfbff8000, 0xf20f0000), - /* SUBW Rd, PC, #imm 1111 0x10 1010 1111 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfbff8000, 0xf2af0000, t32_emulate_rd8pc16_noflags, - REGS(PC, 0, NOSPPC, 0, 0)), - - /* ADDW SP, SP, #imm 1111 0x10 0000 1101 0xxx 1101 xxxx xxxx */ - DECODE_OR (0xfbff8f00, 0xf20d0d00), - /* SUBW SP, SP, #imm 1111 0x10 1010 1101 0xxx 1101 xxxx xxxx */ - DECODE_EMULATEX (0xfbff8f00, 0xf2ad0d00, t32_emulate_rd8rn16_noflags, - REGS(SP, 0, SP, 0, 0)), - - /* ADDW 1111 0x10 0000 xxxx 0xxx xxxx xxxx xxxx */ - DECODE_OR (0xfbf08000, 0xf2000000), - /* SUBW 1111 0x10 1010 xxxx 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfbf08000, 0xf2a00000, t32_emulate_rd8rn16_noflags, - REGS(NOPCX, 0, NOSPPC, 0, 0)), - - /* MOVW 1111 0x10 0100 xxxx 0xxx xxxx xxxx xxxx */ - /* MOVT 1111 0x10 1100 xxxx 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfb708000, 0xf2400000, t32_emulate_rd8rn16_noflags, - REGS(0, 0, NOSPPC, 0, 0)), - - /* SSAT16 1111 0x11 0010 xxxx 0000 xxxx 00xx xxxx */ - /* SSAT 1111 0x11 00x0 xxxx 0xxx xxxx xxxx xxxx */ - /* USAT16 1111 0x11 1010 xxxx 0000 xxxx 00xx xxxx */ - /* USAT 1111 0x11 10x0 xxxx 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfb508000, 0xf3000000, t32_emulate_rd8rn16rm0_rwflags, - REGS(NOSPPC, 0, NOSPPC, 0, 0)), - - /* SFBX 1111 0x11 0100 xxxx 0xxx xxxx xxxx xxxx */ - /* UFBX 1111 0x11 1100 xxxx 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfb708000, 0xf3400000, t32_emulate_rd8rn16_noflags, - REGS(NOSPPC, 0, NOSPPC, 0, 0)), - - /* BFC 1111 0x11 0110 1111 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfbff8000, 0xf36f0000, t32_emulate_rd8rn16_noflags, - REGS(0, 0, NOSPPC, 0, 0)), - - /* BFI 1111 0x11 0110 xxxx 0xxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfbf08000, 0xf3600000, t32_emulate_rd8rn16_noflags, - REGS(NOSPPCX, 0, NOSPPC, 0, 0)), - - DECODE_END -}; - -static const union decode_item t32_table_1111_0xxx___1[] = { - /* Branches and miscellaneous control */ - - /* YIELD 1111 0011 1010 xxxx 10x0 x000 0000 0001 */ - DECODE_OR (0xfff0d7ff, 0xf3a08001), - /* SEV 1111 0011 1010 xxxx 10x0 x000 0000 0100 */ - DECODE_EMULATE (0xfff0d7ff, 0xf3a08004, kprobe_emulate_none), - /* NOP 1111 0011 1010 xxxx 10x0 x000 0000 0000 */ - /* WFE 1111 0011 1010 xxxx 10x0 x000 0000 0010 */ - /* WFI 1111 0011 1010 xxxx 10x0 x000 0000 0011 */ - DECODE_SIMULATE (0xfff0d7fc, 0xf3a08000, kprobe_simulate_nop), - - /* MRS Rd, CPSR 1111 0011 1110 xxxx 10x0 xxxx xxxx xxxx */ - DECODE_SIMULATEX(0xfff0d000, 0xf3e08000, t32_simulate_mrs, - REGS(0, 0, NOSPPC, 0, 0)), - - /* - * Unsupported instructions - * 1111 0x11 1xxx xxxx 10x0 xxxx xxxx xxxx - * - * MSR 1111 0011 100x xxxx 10x0 xxxx xxxx xxxx - * DBG hint 1111 0011 1010 xxxx 10x0 x000 1111 xxxx - * Unallocated hints 1111 0011 1010 xxxx 10x0 x000 xxxx xxxx - * CPS 1111 0011 1010 xxxx 10x0 xxxx xxxx xxxx - * CLREX/DSB/DMB/ISB 1111 0011 1011 xxxx 10x0 xxxx xxxx xxxx - * BXJ 1111 0011 1100 xxxx 10x0 xxxx xxxx xxxx - * SUBS PC,LR,# 1111 0011 1101 xxxx 10x0 xxxx xxxx xxxx - * MRS Rd, SPSR 1111 0011 1111 xxxx 10x0 xxxx xxxx xxxx - * SMC 1111 0111 1111 xxxx 1000 xxxx xxxx xxxx - * UNDEFINED 1111 0111 1111 xxxx 1010 xxxx xxxx xxxx - * ??? 1111 0111 1xxx xxxx 1010 xxxx xxxx xxxx - */ - DECODE_REJECT (0xfb80d000, 0xf3808000), - - /* Bcc 1111 0xxx xxxx xxxx 10x0 xxxx xxxx xxxx */ - DECODE_CUSTOM (0xf800d000, 0xf0008000, t32_decode_cond_branch), - - /* BLX 1111 0xxx xxxx xxxx 11x0 xxxx xxxx xxx0 */ - DECODE_OR (0xf800d001, 0xf000c000), - /* B 1111 0xxx xxxx xxxx 10x1 xxxx xxxx xxxx */ - /* BL 1111 0xxx xxxx xxxx 11x1 xxxx xxxx xxxx */ - DECODE_SIMULATE (0xf8009000, 0xf0009000, t32_simulate_branch), - - DECODE_END -}; - -static const union decode_item t32_table_1111_100x_x0x1__1111[] = { - /* Memory hints */ - - /* PLD (literal) 1111 1000 x001 1111 1111 xxxx xxxx xxxx */ - /* PLI (literal) 1111 1001 x001 1111 1111 xxxx xxxx xxxx */ - DECODE_SIMULATE (0xfe7ff000, 0xf81ff000, kprobe_simulate_nop), - - /* PLD{W} (immediate) 1111 1000 10x1 xxxx 1111 xxxx xxxx xxxx */ - DECODE_OR (0xffd0f000, 0xf890f000), - /* PLD{W} (immediate) 1111 1000 00x1 xxxx 1111 1100 xxxx xxxx */ - DECODE_OR (0xffd0ff00, 0xf810fc00), - /* PLI (immediate) 1111 1001 1001 xxxx 1111 xxxx xxxx xxxx */ - DECODE_OR (0xfff0f000, 0xf990f000), - /* PLI (immediate) 1111 1001 0001 xxxx 1111 1100 xxxx xxxx */ - DECODE_SIMULATEX(0xfff0ff00, 0xf910fc00, kprobe_simulate_nop, - REGS(NOPCX, 0, 0, 0, 0)), - - /* PLD{W} (register) 1111 1000 00x1 xxxx 1111 0000 00xx xxxx */ - DECODE_OR (0xffd0ffc0, 0xf810f000), - /* PLI (register) 1111 1001 0001 xxxx 1111 0000 00xx xxxx */ - DECODE_SIMULATEX(0xfff0ffc0, 0xf910f000, kprobe_simulate_nop, - REGS(NOPCX, 0, 0, 0, NOSPPC)), - - /* Other unallocated instructions... */ - DECODE_END -}; - -static const union decode_item t32_table_1111_100x[] = { - /* Store/Load single data item */ - - /* ??? 1111 100x x11x xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xfe600000, 0xf8600000), - - /* ??? 1111 1001 0101 xxxx xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xfff00000, 0xf9500000), - - /* ??? 1111 100x 0xxx xxxx xxxx 10x0 xxxx xxxx */ - DECODE_REJECT (0xfe800d00, 0xf8000800), - - /* STRBT 1111 1000 0000 xxxx xxxx 1110 xxxx xxxx */ - /* STRHT 1111 1000 0010 xxxx xxxx 1110 xxxx xxxx */ - /* STRT 1111 1000 0100 xxxx xxxx 1110 xxxx xxxx */ - /* LDRBT 1111 1000 0001 xxxx xxxx 1110 xxxx xxxx */ - /* LDRSBT 1111 1001 0001 xxxx xxxx 1110 xxxx xxxx */ - /* LDRHT 1111 1000 0011 xxxx xxxx 1110 xxxx xxxx */ - /* LDRSHT 1111 1001 0011 xxxx xxxx 1110 xxxx xxxx */ - /* LDRT 1111 1000 0101 xxxx xxxx 1110 xxxx xxxx */ - DECODE_REJECT (0xfe800f00, 0xf8000e00), - - /* STR{,B,H} Rn,[PC...] 1111 1000 xxx0 1111 xxxx xxxx xxxx xxxx */ - DECODE_REJECT (0xff1f0000, 0xf80f0000), - - /* STR{,B,H} PC,[Rn...] 1111 1000 xxx0 xxxx 1111 xxxx xxxx xxxx */ - DECODE_REJECT (0xff10f000, 0xf800f000), - - /* LDR (literal) 1111 1000 x101 1111 xxxx xxxx xxxx xxxx */ - DECODE_SIMULATEX(0xff7f0000, 0xf85f0000, t32_simulate_ldr_literal, - REGS(PC, ANY, 0, 0, 0)), - - /* STR (immediate) 1111 1000 0100 xxxx xxxx 1xxx xxxx xxxx */ - /* LDR (immediate) 1111 1000 0101 xxxx xxxx 1xxx xxxx xxxx */ - DECODE_OR (0xffe00800, 0xf8400800), - /* STR (immediate) 1111 1000 1100 xxxx xxxx xxxx xxxx xxxx */ - /* LDR (immediate) 1111 1000 1101 xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xffe00000, 0xf8c00000, t32_emulate_ldrstr, - REGS(NOPCX, ANY, 0, 0, 0)), - - /* STR (register) 1111 1000 0100 xxxx xxxx 0000 00xx xxxx */ - /* LDR (register) 1111 1000 0101 xxxx xxxx 0000 00xx xxxx */ - DECODE_EMULATEX (0xffe00fc0, 0xf8400000, t32_emulate_ldrstr, - REGS(NOPCX, ANY, 0, 0, NOSPPC)), - - /* LDRB (literal) 1111 1000 x001 1111 xxxx xxxx xxxx xxxx */ - /* LDRSB (literal) 1111 1001 x001 1111 xxxx xxxx xxxx xxxx */ - /* LDRH (literal) 1111 1000 x011 1111 xxxx xxxx xxxx xxxx */ - /* LDRSH (literal) 1111 1001 x011 1111 xxxx xxxx xxxx xxxx */ - DECODE_SIMULATEX(0xfe5f0000, 0xf81f0000, t32_simulate_ldr_literal, - REGS(PC, NOSPPCX, 0, 0, 0)), - - /* STRB (immediate) 1111 1000 0000 xxxx xxxx 1xxx xxxx xxxx */ - /* STRH (immediate) 1111 1000 0010 xxxx xxxx 1xxx xxxx xxxx */ - /* LDRB (immediate) 1111 1000 0001 xxxx xxxx 1xxx xxxx xxxx */ - /* LDRSB (immediate) 1111 1001 0001 xxxx xxxx 1xxx xxxx xxxx */ - /* LDRH (immediate) 1111 1000 0011 xxxx xxxx 1xxx xxxx xxxx */ - /* LDRSH (immediate) 1111 1001 0011 xxxx xxxx 1xxx xxxx xxxx */ - DECODE_OR (0xfec00800, 0xf8000800), - /* STRB (immediate) 1111 1000 1000 xxxx xxxx xxxx xxxx xxxx */ - /* STRH (immediate) 1111 1000 1010 xxxx xxxx xxxx xxxx xxxx */ - /* LDRB (immediate) 1111 1000 1001 xxxx xxxx xxxx xxxx xxxx */ - /* LDRSB (immediate) 1111 1001 1001 xxxx xxxx xxxx xxxx xxxx */ - /* LDRH (immediate) 1111 1000 1011 xxxx xxxx xxxx xxxx xxxx */ - /* LDRSH (immediate) 1111 1001 1011 xxxx xxxx xxxx xxxx xxxx */ - DECODE_EMULATEX (0xfec00000, 0xf8800000, t32_emulate_ldrstr, - REGS(NOPCX, NOSPPCX, 0, 0, 0)), - - /* STRB (register) 1111 1000 0000 xxxx xxxx 0000 00xx xxxx */ - /* STRH (register) 1111 1000 0010 xxxx xxxx 0000 00xx xxxx */ - /* LDRB (register) 1111 1000 0001 xxxx xxxx 0000 00xx xxxx */ - /* LDRSB (register) 1111 1001 0001 xxxx xxxx 0000 00xx xxxx */ - /* LDRH (register) 1111 1000 0011 xxxx xxxx 0000 00xx xxxx */ - /* LDRSH (register) 1111 1001 0011 xxxx xxxx 0000 00xx xxxx */ - DECODE_EMULATEX (0xfe800fc0, 0xf8000000, t32_emulate_ldrstr, - REGS(NOPCX, NOSPPCX, 0, 0, NOSPPC)), - - /* Other unallocated instructions... */ - DECODE_END -}; - -static const union decode_item t32_table_1111_1010___1111[] = { - /* Data-processing (register) */ - - /* ??? 1111 1010 011x xxxx 1111 xxxx 1xxx xxxx */ - DECODE_REJECT (0xffe0f080, 0xfa60f080), - - /* SXTH 1111 1010 0000 1111 1111 xxxx 1xxx xxxx */ - /* UXTH 1111 1010 0001 1111 1111 xxxx 1xxx xxxx */ - /* SXTB16 1111 1010 0010 1111 1111 xxxx 1xxx xxxx */ - /* UXTB16 1111 1010 0011 1111 1111 xxxx 1xxx xxxx */ - /* SXTB 1111 1010 0100 1111 1111 xxxx 1xxx xxxx */ - /* UXTB 1111 1010 0101 1111 1111 xxxx 1xxx xxxx */ - DECODE_EMULATEX (0xff8ff080, 0xfa0ff080, t32_emulate_rd8rn16rm0_rwflags, - REGS(0, 0, NOSPPC, 0, NOSPPC)), - - - /* ??? 1111 1010 1xxx xxxx 1111 xxxx 0x11 xxxx */ - DECODE_REJECT (0xff80f0b0, 0xfa80f030), - /* ??? 1111 1010 1x11 xxxx 1111 xxxx 0xxx xxxx */ - DECODE_REJECT (0xffb0f080, 0xfab0f000), - - /* SADD16 1111 1010 1001 xxxx 1111 xxxx 0000 xxxx */ - /* SASX 1111 1010 1010 xxxx 1111 xxxx 0000 xxxx */ - /* SSAX 1111 1010 1110 xxxx 1111 xxxx 0000 xxxx */ - /* SSUB16 1111 1010 1101 xxxx 1111 xxxx 0000 xxxx */ - /* SADD8 1111 1010 1000 xxxx 1111 xxxx 0000 xxxx */ - /* SSUB8 1111 1010 1100 xxxx 1111 xxxx 0000 xxxx */ - - /* QADD16 1111 1010 1001 xxxx 1111 xxxx 0001 xxxx */ - /* QASX 1111 1010 1010 xxxx 1111 xxxx 0001 xxxx */ - /* QSAX 1111 1010 1110 xxxx 1111 xxxx 0001 xxxx */ - /* QSUB16 1111 1010 1101 xxxx 1111 xxxx 0001 xxxx */ - /* QADD8 1111 1010 1000 xxxx 1111 xxxx 0001 xxxx */ - /* QSUB8 1111 1010 1100 xxxx 1111 xxxx 0001 xxxx */ - - /* SHADD16 1111 1010 1001 xxxx 1111 xxxx 0010 xxxx */ - /* SHASX 1111 1010 1010 xxxx 1111 xxxx 0010 xxxx */ - /* SHSAX 1111 1010 1110 xxxx 1111 xxxx 0010 xxxx */ - /* SHSUB16 1111 1010 1101 xxxx 1111 xxxx 0010 xxxx */ - /* SHADD8 1111 1010 1000 xxxx 1111 xxxx 0010 xxxx */ - /* SHSUB8 1111 1010 1100 xxxx 1111 xxxx 0010 xxxx */ - - /* UADD16 1111 1010 1001 xxxx 1111 xxxx 0100 xxxx */ - /* UASX 1111 1010 1010 xxxx 1111 xxxx 0100 xxxx */ - /* USAX 1111 1010 1110 xxxx 1111 xxxx 0100 xxxx */ - /* USUB16 1111 1010 1101 xxxx 1111 xxxx 0100 xxxx */ - /* UADD8 1111 1010 1000 xxxx 1111 xxxx 0100 xxxx */ - /* USUB8 1111 1010 1100 xxxx 1111 xxxx 0100 xxxx */ - - /* UQADD16 1111 1010 1001 xxxx 1111 xxxx 0101 xxxx */ - /* UQASX 1111 1010 1010 xxxx 1111 xxxx 0101 xxxx */ - /* UQSAX 1111 1010 1110 xxxx 1111 xxxx 0101 xxxx */ - /* UQSUB16 1111 1010 1101 xxxx 1111 xxxx 0101 xxxx */ - /* UQADD8 1111 1010 1000 xxxx 1111 xxxx 0101 xxxx */ - /* UQSUB8 1111 1010 1100 xxxx 1111 xxxx 0101 xxxx */ - - /* UHADD16 1111 1010 1001 xxxx 1111 xxxx 0110 xxxx */ - /* UHASX 1111 1010 1010 xxxx 1111 xxxx 0110 xxxx */ - /* UHSAX 1111 1010 1110 xxxx 1111 xxxx 0110 xxxx */ - /* UHSUB16 1111 1010 1101 xxxx 1111 xxxx 0110 xxxx */ - /* UHADD8 1111 1010 1000 xxxx 1111 xxxx 0110 xxxx */ - /* UHSUB8 1111 1010 1100 xxxx 1111 xxxx 0110 xxxx */ - DECODE_OR (0xff80f080, 0xfa80f000), - - /* SXTAH 1111 1010 0000 xxxx 1111 xxxx 1xxx xxxx */ - /* UXTAH 1111 1010 0001 xxxx 1111 xxxx 1xxx xxxx */ - /* SXTAB16 1111 1010 0010 xxxx 1111 xxxx 1xxx xxxx */ - /* UXTAB16 1111 1010 0011 xxxx 1111 xxxx 1xxx xxxx */ - /* SXTAB 1111 1010 0100 xxxx 1111 xxxx 1xxx xxxx */ - /* UXTAB 1111 1010 0101 xxxx 1111 xxxx 1xxx xxxx */ - DECODE_OR (0xff80f080, 0xfa00f080), - - /* QADD 1111 1010 1000 xxxx 1111 xxxx 1000 xxxx */ - /* QDADD 1111 1010 1000 xxxx 1111 xxxx 1001 xxxx */ - /* QSUB 1111 1010 1000 xxxx 1111 xxxx 1010 xxxx */ - /* QDSUB 1111 1010 1000 xxxx 1111 xxxx 1011 xxxx */ - DECODE_OR (0xfff0f0c0, 0xfa80f080), - - /* SEL 1111 1010 1010 xxxx 1111 xxxx 1000 xxxx */ - DECODE_OR (0xfff0f0f0, 0xfaa0f080), - - /* LSL 1111 1010 000x xxxx 1111 xxxx 0000 xxxx */ - /* LSR 1111 1010 001x xxxx 1111 xxxx 0000 xxxx */ - /* ASR 1111 1010 010x xxxx 1111 xxxx 0000 xxxx */ - /* ROR 1111 1010 011x xxxx 1111 xxxx 0000 xxxx */ - DECODE_EMULATEX (0xff80f0f0, 0xfa00f000, t32_emulate_rd8rn16rm0_rwflags, - REGS(NOSPPC, 0, NOSPPC, 0, NOSPPC)), - - /* CLZ 1111 1010 1010 xxxx 1111 xxxx 1000 xxxx */ - DECODE_OR (0xfff0f0f0, 0xfab0f080), - - /* REV 1111 1010 1001 xxxx 1111 xxxx 1000 xxxx */ - /* REV16 1111 1010 1001 xxxx 1111 xxxx 1001 xxxx */ - /* RBIT 1111 1010 1001 xxxx 1111 xxxx 1010 xxxx */ - /* REVSH 1111 1010 1001 xxxx 1111 xxxx 1011 xxxx */ - DECODE_EMULATEX (0xfff0f0c0, 0xfa90f080, t32_emulate_rd8rn16_noflags, - REGS(NOSPPC, 0, NOSPPC, 0, SAMEAS16)), - - /* Other unallocated instructions... */ - DECODE_END -}; - -static const union decode_item t32_table_1111_1011_0[] = { - /* Multiply, multiply accumulate, and absolute difference */ - - /* ??? 1111 1011 0000 xxxx 1111 xxxx 0001 xxxx */ - DECODE_REJECT (0xfff0f0f0, 0xfb00f010), - /* ??? 1111 1011 0111 xxxx 1111 xxxx 0001 xxxx */ - DECODE_REJECT (0xfff0f0f0, 0xfb70f010), - - /* SMULxy 1111 1011 0001 xxxx 1111 xxxx 00xx xxxx */ - DECODE_OR (0xfff0f0c0, 0xfb10f000), - /* MUL 1111 1011 0000 xxxx 1111 xxxx 0000 xxxx */ - /* SMUAD{X} 1111 1011 0010 xxxx 1111 xxxx 000x xxxx */ - /* SMULWy 1111 1011 0011 xxxx 1111 xxxx 000x xxxx */ - /* SMUSD{X} 1111 1011 0100 xxxx 1111 xxxx 000x xxxx */ - /* SMMUL{R} 1111 1011 0101 xxxx 1111 xxxx 000x xxxx */ - /* USAD8 1111 1011 0111 xxxx 1111 xxxx 0000 xxxx */ - DECODE_EMULATEX (0xff80f0e0, 0xfb00f000, t32_emulate_rd8rn16rm0_rwflags, - REGS(NOSPPC, 0, NOSPPC, 0, NOSPPC)), - - /* ??? 1111 1011 0111 xxxx xxxx xxxx 0001 xxxx */ - DECODE_REJECT (0xfff000f0, 0xfb700010), - - /* SMLAxy 1111 1011 0001 xxxx xxxx xxxx 00xx xxxx */ - DECODE_OR (0xfff000c0, 0xfb100000), - /* MLA 1111 1011 0000 xxxx xxxx xxxx 0000 xxxx */ - /* MLS 1111 1011 0000 xxxx xxxx xxxx 0001 xxxx */ - /* SMLAD{X} 1111 1011 0010 xxxx xxxx xxxx 000x xxxx */ - /* SMLAWy 1111 1011 0011 xxxx xxxx xxxx 000x xxxx */ - /* SMLSD{X} 1111 1011 0100 xxxx xxxx xxxx 000x xxxx */ - /* SMMLA{R} 1111 1011 0101 xxxx xxxx xxxx 000x xxxx */ - /* SMMLS{R} 1111 1011 0110 xxxx xxxx xxxx 000x xxxx */ - /* USADA8 1111 1011 0111 xxxx xxxx xxxx 0000 xxxx */ - DECODE_EMULATEX (0xff8000c0, 0xfb000000, t32_emulate_rd8rn16rm0ra12_noflags, - REGS(NOSPPC, NOSPPCX, NOSPPC, 0, NOSPPC)), - - /* Other unallocated instructions... */ - DECODE_END -}; - -static const union decode_item t32_table_1111_1011_1[] = { - /* Long multiply, long multiply accumulate, and divide */ - - /* UMAAL 1111 1011 1110 xxxx xxxx xxxx 0110 xxxx */ - DECODE_OR (0xfff000f0, 0xfbe00060), - /* SMLALxy 1111 1011 1100 xxxx xxxx xxxx 10xx xxxx */ - DECODE_OR (0xfff000c0, 0xfbc00080), - /* SMLALD{X} 1111 1011 1100 xxxx xxxx xxxx 110x xxxx */ - /* SMLSLD{X} 1111 1011 1101 xxxx xxxx xxxx 110x xxxx */ - DECODE_OR (0xffe000e0, 0xfbc000c0), - /* SMULL 1111 1011 1000 xxxx xxxx xxxx 0000 xxxx */ - /* UMULL 1111 1011 1010 xxxx xxxx xxxx 0000 xxxx */ - /* SMLAL 1111 1011 1100 xxxx xxxx xxxx 0000 xxxx */ - /* UMLAL 1111 1011 1110 xxxx xxxx xxxx 0000 xxxx */ - DECODE_EMULATEX (0xff9000f0, 0xfb800000, t32_emulate_rdlo12rdhi8rn16rm0_noflags, - REGS(NOSPPC, NOSPPC, NOSPPC, 0, NOSPPC)), - - /* SDIV 1111 1011 1001 xxxx xxxx xxxx 1111 xxxx */ - /* UDIV 1111 1011 1011 xxxx xxxx xxxx 1111 xxxx */ - /* Other unallocated instructions... */ - DECODE_END -}; - -const union decode_item kprobe_decode_thumb32_table[] = { - - /* - * Load/store multiple instructions - * 1110 100x x0xx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xfe400000, 0xe8000000, t32_table_1110_100x_x0xx), - - /* - * Load/store dual, load/store exclusive, table branch - * 1110 100x x1xx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xfe400000, 0xe8400000, t32_table_1110_100x_x1xx), - - /* - * Data-processing (shifted register) - * 1110 101x xxxx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xfe000000, 0xea000000, t32_table_1110_101x), - - /* - * Coprocessor instructions - * 1110 11xx xxxx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_REJECT (0xfc000000, 0xec000000), - - /* - * Data-processing (modified immediate) - * 1111 0x0x xxxx xxxx 0xxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xfa008000, 0xf0000000, t32_table_1111_0x0x___0), - - /* - * Data-processing (plain binary immediate) - * 1111 0x1x xxxx xxxx 0xxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xfa008000, 0xf2000000, t32_table_1111_0x1x___0), - - /* - * Branches and miscellaneous control - * 1111 0xxx xxxx xxxx 1xxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xf8008000, 0xf0008000, t32_table_1111_0xxx___1), - - /* - * Advanced SIMD element or structure load/store instructions - * 1111 1001 xxx0 xxxx xxxx xxxx xxxx xxxx - */ - DECODE_REJECT (0xff100000, 0xf9000000), - - /* - * Memory hints - * 1111 100x x0x1 xxxx 1111 xxxx xxxx xxxx - */ - DECODE_TABLE (0xfe50f000, 0xf810f000, t32_table_1111_100x_x0x1__1111), - - /* - * Store single data item - * 1111 1000 xxx0 xxxx xxxx xxxx xxxx xxxx - * Load single data items - * 1111 100x xxx1 xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xfe000000, 0xf8000000, t32_table_1111_100x), - - /* - * Data-processing (register) - * 1111 1010 xxxx xxxx 1111 xxxx xxxx xxxx - */ - DECODE_TABLE (0xff00f000, 0xfa00f000, t32_table_1111_1010___1111), - - /* - * Multiply, multiply accumulate, and absolute difference - * 1111 1011 0xxx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xff800000, 0xfb000000, t32_table_1111_1011_0), - - /* - * Long multiply, long multiply accumulate, and divide - * 1111 1011 1xxx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_TABLE (0xff800000, 0xfb800000, t32_table_1111_1011_1), - - /* - * Coprocessor instructions - * 1111 11xx xxxx xxxx xxxx xxxx xxxx xxxx - */ - DECODE_END -}; -#ifdef CONFIG_ARM_KPROBES_TEST_MODULE -EXPORT_SYMBOL_GPL(kprobe_decode_thumb32_table); -#endif +/* t16 thumb actions */ static void __kprobes -t16_simulate_bxblx(struct kprobe *p, struct pt_regs *regs) +t16_simulate_bxblx(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = thumb_probe_pc(p); + unsigned long pc = regs->ARM_pc + 2; int rm = (insn >> 3) & 0xf; unsigned long rmv = (rm == 15) ? pc : regs->uregs[rm]; if (insn & (1 << 7)) /* BLX ? */ - regs->ARM_lr = (unsigned long)p->addr + 2; + regs->ARM_lr = regs->ARM_pc | 1; bx_write_pc(rmv, regs); } static void __kprobes -t16_simulate_ldr_literal(struct kprobe *p, struct pt_regs *regs) +t16_simulate_ldr_literal(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long* base = (unsigned long *)(thumb_probe_pc(p) & ~3); + unsigned long *base = (unsigned long *)((regs->ARM_pc + 2) & ~3); long index = insn & 0xff; int rt = (insn >> 8) & 0x7; regs->uregs[rt] = base[index]; } static void __kprobes -t16_simulate_ldrstr_sp_relative(struct kprobe *p, struct pt_regs *regs) +t16_simulate_ldrstr_sp_relative(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; unsigned long* base = (unsigned long *)regs->ARM_sp; long index = insn & 0xff; int rt = (insn >> 8) & 0x7; @@ -986,20 +342,20 @@ t16_simulate_ldrstr_sp_relative(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t16_simulate_reladr(struct kprobe *p, struct pt_regs *regs) +t16_simulate_reladr(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; unsigned long base = (insn & 0x800) ? regs->ARM_sp - : (thumb_probe_pc(p) & ~3); + : ((regs->ARM_pc + 2) & ~3); long offset = insn & 0xff; int rt = (insn >> 8) & 0x7; regs->uregs[rt] = base + offset * 4; } static void __kprobes -t16_simulate_add_sp_imm(struct kprobe *p, struct pt_regs *regs) +t16_simulate_add_sp_imm(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; long imm = insn & 0x7f; if (insn & 0x80) /* SUB */ regs->ARM_sp -= imm * 4; @@ -1008,21 +364,22 @@ t16_simulate_add_sp_imm(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t16_simulate_cbz(struct kprobe *p, struct pt_regs *regs) +t16_simulate_cbz(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; int rn = insn & 0x7; - kprobe_opcode_t nonzero = regs->uregs[rn] ? insn : ~insn; + probes_opcode_t nonzero = regs->uregs[rn] ? insn : ~insn; if (nonzero & 0x800) { long i = insn & 0x200; long imm5 = insn & 0xf8; - unsigned long pc = thumb_probe_pc(p); + unsigned long pc = regs->ARM_pc + 2; regs->ARM_pc = pc + (i >> 3) + (imm5 >> 2); } } static void __kprobes -t16_simulate_it(struct kprobe *p, struct pt_regs *regs) +t16_simulate_it(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { /* * The 8 IT state bits are split into two parts in CPSR: @@ -1030,7 +387,6 @@ t16_simulate_it(struct kprobe *p, struct pt_regs *regs) * ITSTATE<7:2> are in CPSR<15:10> * The new IT state is in the lower byte of insn. */ - kprobe_opcode_t insn = p->opcode; unsigned long cpsr = regs->ARM_cpsr; cpsr &= ~PSR_IT_MASK; cpsr |= (insn & 0xfc) << 8; @@ -1039,50 +395,54 @@ t16_simulate_it(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t16_singlestep_it(struct kprobe *p, struct pt_regs *regs) +t16_singlestep_it(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { regs->ARM_pc += 2; - t16_simulate_it(p, regs); + t16_simulate_it(insn, asi, regs); } -static enum kprobe_insn __kprobes -t16_decode_it(kprobe_opcode_t insn, struct arch_specific_insn *asi) +static enum probes_insn __kprobes +t16_decode_it(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d) { asi->insn_singlestep = t16_singlestep_it; return INSN_GOOD_NO_SLOT; } static void __kprobes -t16_simulate_cond_branch(struct kprobe *p, struct pt_regs *regs) +t16_simulate_cond_branch(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = thumb_probe_pc(p); + unsigned long pc = regs->ARM_pc + 2; long offset = insn & 0x7f; offset -= insn & 0x80; /* Apply sign bit */ regs->ARM_pc = pc + (offset * 2); } -static enum kprobe_insn __kprobes -t16_decode_cond_branch(kprobe_opcode_t insn, struct arch_specific_insn *asi) +static enum probes_insn __kprobes +t16_decode_cond_branch(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d) { int cc = (insn >> 8) & 0xf; - asi->insn_check_cc = kprobe_condition_checks[cc]; + asi->insn_check_cc = probes_condition_checks[cc]; asi->insn_handler = t16_simulate_cond_branch; return INSN_GOOD_NO_SLOT; } static void __kprobes -t16_simulate_branch(struct kprobe *p, struct pt_regs *regs) +t16_simulate_branch(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = thumb_probe_pc(p); + unsigned long pc = regs->ARM_pc + 2; long offset = insn & 0x3ff; offset -= insn & 0x400; /* Apply sign bit */ regs->ARM_pc = pc + (offset * 2); } static unsigned long __kprobes -t16_emulate_loregs(struct kprobe *p, struct pt_regs *regs) +t16_emulate_loregs(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { unsigned long oldcpsr = regs->ARM_cpsr; unsigned long newcpsr; @@ -1095,7 +455,7 @@ t16_emulate_loregs(struct kprobe *p, struct pt_regs *regs) "mrs %[newcpsr], cpsr \n\t" : [newcpsr] "=r" (newcpsr) : [oldcpsr] "r" (oldcpsr), [regs] "r" (regs), - [fn] "r" (p->ainsn.insn_fn) + [fn] "r" (asi->insn_fn) : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "lr", "memory", "cc" ); @@ -1104,24 +464,26 @@ t16_emulate_loregs(struct kprobe *p, struct pt_regs *regs) } static void __kprobes -t16_emulate_loregs_rwflags(struct kprobe *p, struct pt_regs *regs) +t16_emulate_loregs_rwflags(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - regs->ARM_cpsr = t16_emulate_loregs(p, regs); + regs->ARM_cpsr = t16_emulate_loregs(insn, asi, regs); } static void __kprobes -t16_emulate_loregs_noitrwflags(struct kprobe *p, struct pt_regs *regs) +t16_emulate_loregs_noitrwflags(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - unsigned long cpsr = t16_emulate_loregs(p, regs); + unsigned long cpsr = t16_emulate_loregs(insn, asi, regs); if (!in_it_block(cpsr)) regs->ARM_cpsr = cpsr; } static void __kprobes -t16_emulate_hiregs(struct kprobe *p, struct pt_regs *regs) +t16_emulate_hiregs(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { - kprobe_opcode_t insn = p->opcode; - unsigned long pc = thumb_probe_pc(p); + unsigned long pc = regs->ARM_pc + 2; int rdn = (insn & 0x7) | ((insn & 0x80) >> 4); int rm = (insn >> 3) & 0xf; @@ -1137,7 +499,7 @@ t16_emulate_hiregs(struct kprobe *p, struct pt_regs *regs) "blx %[fn] \n\t" "mrs %[cpsr], cpsr \n\t" : "=r" (rdnv), [cpsr] "=r" (cpsr) - : "0" (rdnv), "r" (rmv), "1" (cpsr), [fn] "r" (p->ainsn.insn_fn) + : "0" (rdnv), "r" (rmv), "1" (cpsr), [fn] "r" (asi->insn_fn) : "lr", "memory", "cc" ); @@ -1148,18 +510,20 @@ t16_emulate_hiregs(struct kprobe *p, struct pt_regs *regs) regs->ARM_cpsr = (regs->ARM_cpsr & ~APSR_MASK) | (cpsr & APSR_MASK); } -static enum kprobe_insn __kprobes -t16_decode_hiregs(kprobe_opcode_t insn, struct arch_specific_insn *asi) +static enum probes_insn __kprobes +t16_decode_hiregs(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d) { insn &= ~0x00ff; insn |= 0x001; /* Set Rdn = R1 and Rm = R0 */ - ((u16 *)asi->insn)[0] = insn; + ((u16 *)asi->insn)[0] = __opcode_to_mem_thumb16(insn); asi->insn_handler = t16_emulate_hiregs; return INSN_GOOD; } static void __kprobes -t16_emulate_push(struct kprobe *p, struct pt_regs *regs) +t16_emulate_push(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { __asm__ __volatile__ ( "ldr r9, [%[regs], #13*4] \n\t" @@ -1168,28 +532,32 @@ t16_emulate_push(struct kprobe *p, struct pt_regs *regs) "blx %[fn] \n\t" "str r9, [%[regs], #13*4] \n\t" : - : [regs] "r" (regs), [fn] "r" (p->ainsn.insn_fn) + : [regs] "r" (regs), [fn] "r" (asi->insn_fn) : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "lr", "memory", "cc" ); } -static enum kprobe_insn __kprobes -t16_decode_push(kprobe_opcode_t insn, struct arch_specific_insn *asi) +static enum probes_insn __kprobes +t16_decode_push(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d) { /* * To simulate a PUSH we use a Thumb-2 "STMDB R9!, {registers}" * and call it with R9=SP and LR in the register list represented * by R8. */ - ((u16 *)asi->insn)[0] = 0xe929; /* 1st half STMDB R9!,{} */ - ((u16 *)asi->insn)[1] = insn & 0x1ff; /* 2nd half (register list) */ + /* 1st half STMDB R9!,{} */ + ((u16 *)asi->insn)[0] = __opcode_to_mem_thumb16(0xe929); + /* 2nd half (register list) */ + ((u16 *)asi->insn)[1] = __opcode_to_mem_thumb16(insn & 0x1ff); asi->insn_handler = t16_emulate_push; return INSN_GOOD; } static void __kprobes -t16_emulate_pop_nopc(struct kprobe *p, struct pt_regs *regs) +t16_emulate_pop_nopc(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { __asm__ __volatile__ ( "ldr r9, [%[regs], #13*4] \n\t" @@ -1198,14 +566,15 @@ t16_emulate_pop_nopc(struct kprobe *p, struct pt_regs *regs) "stmia %[regs], {r0-r7} \n\t" "str r9, [%[regs], #13*4] \n\t" : - : [regs] "r" (regs), [fn] "r" (p->ainsn.insn_fn) + : [regs] "r" (regs), [fn] "r" (asi->insn_fn) : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r9", "lr", "memory", "cc" ); } static void __kprobes -t16_emulate_pop_pc(struct kprobe *p, struct pt_regs *regs) +t16_emulate_pop_pc(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) { register unsigned long pc asm("r8"); @@ -1216,7 +585,7 @@ t16_emulate_pop_pc(struct kprobe *p, struct pt_regs *regs) "stmia %[regs], {r0-r7} \n\t" "str r9, [%[regs], #13*4] \n\t" : "=r" (pc) - : [regs] "r" (regs), [fn] "r" (p->ainsn.insn_fn) + : [regs] "r" (regs), [fn] "r" (asi->insn_fn) : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r9", "lr", "memory", "cc" ); @@ -1224,246 +593,74 @@ t16_emulate_pop_pc(struct kprobe *p, struct pt_regs *regs) bx_write_pc(pc, regs); } -static enum kprobe_insn __kprobes -t16_decode_pop(kprobe_opcode_t insn, struct arch_specific_insn *asi) +static enum probes_insn __kprobes +t16_decode_pop(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d) { /* * To simulate a POP we use a Thumb-2 "LDMDB R9!, {registers}" * and call it with R9=SP and PC in the register list represented * by R8. */ - ((u16 *)asi->insn)[0] = 0xe8b9; /* 1st half LDMIA R9!,{} */ - ((u16 *)asi->insn)[1] = insn & 0x1ff; /* 2nd half (register list) */ + /* 1st half LDMIA R9!,{} */ + ((u16 *)asi->insn)[0] = __opcode_to_mem_thumb16(0xe8b9); + /* 2nd half (register list) */ + ((u16 *)asi->insn)[1] = __opcode_to_mem_thumb16(insn & 0x1ff); asi->insn_handler = insn & 0x100 ? t16_emulate_pop_pc : t16_emulate_pop_nopc; return INSN_GOOD; } -static const union decode_item t16_table_1011[] = { - /* Miscellaneous 16-bit instructions */ - - /* ADD (SP plus immediate) 1011 0000 0xxx xxxx */ - /* SUB (SP minus immediate) 1011 0000 1xxx xxxx */ - DECODE_SIMULATE (0xff00, 0xb000, t16_simulate_add_sp_imm), - - /* CBZ 1011 00x1 xxxx xxxx */ - /* CBNZ 1011 10x1 xxxx xxxx */ - DECODE_SIMULATE (0xf500, 0xb100, t16_simulate_cbz), - - /* SXTH 1011 0010 00xx xxxx */ - /* SXTB 1011 0010 01xx xxxx */ - /* UXTH 1011 0010 10xx xxxx */ - /* UXTB 1011 0010 11xx xxxx */ - /* REV 1011 1010 00xx xxxx */ - /* REV16 1011 1010 01xx xxxx */ - /* ??? 1011 1010 10xx xxxx */ - /* REVSH 1011 1010 11xx xxxx */ - DECODE_REJECT (0xffc0, 0xba80), - DECODE_EMULATE (0xf500, 0xb000, t16_emulate_loregs_rwflags), - - /* PUSH 1011 010x xxxx xxxx */ - DECODE_CUSTOM (0xfe00, 0xb400, t16_decode_push), - /* POP 1011 110x xxxx xxxx */ - DECODE_CUSTOM (0xfe00, 0xbc00, t16_decode_pop), - - /* - * If-Then, and hints - * 1011 1111 xxxx xxxx - */ - - /* YIELD 1011 1111 0001 0000 */ - DECODE_OR (0xffff, 0xbf10), - /* SEV 1011 1111 0100 0000 */ - DECODE_EMULATE (0xffff, 0xbf40, kprobe_emulate_none), - /* NOP 1011 1111 0000 0000 */ - /* WFE 1011 1111 0010 0000 */ - /* WFI 1011 1111 0011 0000 */ - DECODE_SIMULATE (0xffcf, 0xbf00, kprobe_simulate_nop), - /* Unassigned hints 1011 1111 xxxx 0000 */ - DECODE_REJECT (0xff0f, 0xbf00), - /* IT 1011 1111 xxxx xxxx */ - DECODE_CUSTOM (0xff00, 0xbf00, t16_decode_it), - - /* SETEND 1011 0110 010x xxxx */ - /* CPS 1011 0110 011x xxxx */ - /* BKPT 1011 1110 xxxx xxxx */ - /* And unallocated instructions... */ - DECODE_END +const union decode_action kprobes_t16_actions[NUM_PROBES_T16_ACTIONS] = { + [PROBES_T16_ADD_SP] = {.handler = t16_simulate_add_sp_imm}, + [PROBES_T16_CBZ] = {.handler = t16_simulate_cbz}, + [PROBES_T16_SIGN_EXTEND] = {.handler = t16_emulate_loregs_rwflags}, + [PROBES_T16_PUSH] = {.decoder = t16_decode_push}, + [PROBES_T16_POP] = {.decoder = t16_decode_pop}, + [PROBES_T16_SEV] = {.handler = probes_emulate_none}, + [PROBES_T16_WFE] = {.handler = probes_simulate_nop}, + [PROBES_T16_IT] = {.decoder = t16_decode_it}, + [PROBES_T16_CMP] = {.handler = t16_emulate_loregs_rwflags}, + [PROBES_T16_ADDSUB] = {.handler = t16_emulate_loregs_noitrwflags}, + [PROBES_T16_LOGICAL] = {.handler = t16_emulate_loregs_noitrwflags}, + [PROBES_T16_LDR_LIT] = {.handler = t16_simulate_ldr_literal}, + [PROBES_T16_BLX] = {.handler = t16_simulate_bxblx}, + [PROBES_T16_HIREGOPS] = {.decoder = t16_decode_hiregs}, + [PROBES_T16_LDRHSTRH] = {.handler = t16_emulate_loregs_rwflags}, + [PROBES_T16_LDRSTR] = {.handler = t16_simulate_ldrstr_sp_relative}, + [PROBES_T16_ADR] = {.handler = t16_simulate_reladr}, + [PROBES_T16_LDMSTM] = {.handler = t16_emulate_loregs_rwflags}, + [PROBES_T16_BRANCH_COND] = {.decoder = t16_decode_cond_branch}, + [PROBES_T16_BRANCH] = {.handler = t16_simulate_branch}, }; -const union decode_item kprobe_decode_thumb16_table[] = { - - /* - * Shift (immediate), add, subtract, move, and compare - * 00xx xxxx xxxx xxxx - */ - - /* CMP (immediate) 0010 1xxx xxxx xxxx */ - DECODE_EMULATE (0xf800, 0x2800, t16_emulate_loregs_rwflags), - - /* ADD (register) 0001 100x xxxx xxxx */ - /* SUB (register) 0001 101x xxxx xxxx */ - /* LSL (immediate) 0000 0xxx xxxx xxxx */ - /* LSR (immediate) 0000 1xxx xxxx xxxx */ - /* ASR (immediate) 0001 0xxx xxxx xxxx */ - /* ADD (immediate, Thumb) 0001 110x xxxx xxxx */ - /* SUB (immediate, Thumb) 0001 111x xxxx xxxx */ - /* MOV (immediate) 0010 0xxx xxxx xxxx */ - /* ADD (immediate, Thumb) 0011 0xxx xxxx xxxx */ - /* SUB (immediate, Thumb) 0011 1xxx xxxx xxxx */ - DECODE_EMULATE (0xc000, 0x0000, t16_emulate_loregs_noitrwflags), - - /* - * 16-bit Thumb data-processing instructions - * 0100 00xx xxxx xxxx - */ - - /* TST (register) 0100 0010 00xx xxxx */ - DECODE_EMULATE (0xffc0, 0x4200, t16_emulate_loregs_rwflags), - /* CMP (register) 0100 0010 10xx xxxx */ - /* CMN (register) 0100 0010 11xx xxxx */ - DECODE_EMULATE (0xff80, 0x4280, t16_emulate_loregs_rwflags), - /* AND (register) 0100 0000 00xx xxxx */ - /* EOR (register) 0100 0000 01xx xxxx */ - /* LSL (register) 0100 0000 10xx xxxx */ - /* LSR (register) 0100 0000 11xx xxxx */ - /* ASR (register) 0100 0001 00xx xxxx */ - /* ADC (register) 0100 0001 01xx xxxx */ - /* SBC (register) 0100 0001 10xx xxxx */ - /* ROR (register) 0100 0001 11xx xxxx */ - /* RSB (immediate) 0100 0010 01xx xxxx */ - /* ORR (register) 0100 0011 00xx xxxx */ - /* MUL 0100 0011 00xx xxxx */ - /* BIC (register) 0100 0011 10xx xxxx */ - /* MVN (register) 0100 0011 10xx xxxx */ - DECODE_EMULATE (0xfc00, 0x4000, t16_emulate_loregs_noitrwflags), - - /* - * Special data instructions and branch and exchange - * 0100 01xx xxxx xxxx - */ - - /* BLX pc 0100 0111 1111 1xxx */ - DECODE_REJECT (0xfff8, 0x47f8), - - /* BX (register) 0100 0111 0xxx xxxx */ - /* BLX (register) 0100 0111 1xxx xxxx */ - DECODE_SIMULATE (0xff00, 0x4700, t16_simulate_bxblx), - - /* ADD pc, pc 0100 0100 1111 1111 */ - DECODE_REJECT (0xffff, 0x44ff), - - /* ADD (register) 0100 0100 xxxx xxxx */ - /* CMP (register) 0100 0101 xxxx xxxx */ - /* MOV (register) 0100 0110 xxxx xxxx */ - DECODE_CUSTOM (0xfc00, 0x4400, t16_decode_hiregs), - - /* - * Load from Literal Pool - * LDR (literal) 0100 1xxx xxxx xxxx - */ - DECODE_SIMULATE (0xf800, 0x4800, t16_simulate_ldr_literal), - - /* - * 16-bit Thumb Load/store instructions - * 0101 xxxx xxxx xxxx - * 011x xxxx xxxx xxxx - * 100x xxxx xxxx xxxx - */ - - /* STR (register) 0101 000x xxxx xxxx */ - /* STRH (register) 0101 001x xxxx xxxx */ - /* STRB (register) 0101 010x xxxx xxxx */ - /* LDRSB (register) 0101 011x xxxx xxxx */ - /* LDR (register) 0101 100x xxxx xxxx */ - /* LDRH (register) 0101 101x xxxx xxxx */ - /* LDRB (register) 0101 110x xxxx xxxx */ - /* LDRSH (register) 0101 111x xxxx xxxx */ - /* STR (immediate, Thumb) 0110 0xxx xxxx xxxx */ - /* LDR (immediate, Thumb) 0110 1xxx xxxx xxxx */ - /* STRB (immediate, Thumb) 0111 0xxx xxxx xxxx */ - /* LDRB (immediate, Thumb) 0111 1xxx xxxx xxxx */ - DECODE_EMULATE (0xc000, 0x4000, t16_emulate_loregs_rwflags), - /* STRH (immediate, Thumb) 1000 0xxx xxxx xxxx */ - /* LDRH (immediate, Thumb) 1000 1xxx xxxx xxxx */ - DECODE_EMULATE (0xf000, 0x8000, t16_emulate_loregs_rwflags), - /* STR (immediate, Thumb) 1001 0xxx xxxx xxxx */ - /* LDR (immediate, Thumb) 1001 1xxx xxxx xxxx */ - DECODE_SIMULATE (0xf000, 0x9000, t16_simulate_ldrstr_sp_relative), - - /* - * Generate PC-/SP-relative address - * ADR (literal) 1010 0xxx xxxx xxxx - * ADD (SP plus immediate) 1010 1xxx xxxx xxxx - */ - DECODE_SIMULATE (0xf000, 0xa000, t16_simulate_reladr), - - /* - * Miscellaneous 16-bit instructions - * 1011 xxxx xxxx xxxx - */ - DECODE_TABLE (0xf000, 0xb000, t16_table_1011), - - /* STM 1100 0xxx xxxx xxxx */ - /* LDM 1100 1xxx xxxx xxxx */ - DECODE_EMULATE (0xf000, 0xc000, t16_emulate_loregs_rwflags), - - /* - * Conditional branch, and Supervisor Call - */ - - /* Permanently UNDEFINED 1101 1110 xxxx xxxx */ - /* SVC 1101 1111 xxxx xxxx */ - DECODE_REJECT (0xfe00, 0xde00), - - /* Conditional branch 1101 xxxx xxxx xxxx */ - DECODE_CUSTOM (0xf000, 0xd000, t16_decode_cond_branch), - - /* - * Unconditional branch - * B 1110 0xxx xxxx xxxx - */ - DECODE_SIMULATE (0xf800, 0xe000, t16_simulate_branch), - - DECODE_END +const union decode_action kprobes_t32_actions[NUM_PROBES_T32_ACTIONS] = { + [PROBES_T32_LDMSTM] = {.decoder = t32_decode_ldmstm}, + [PROBES_T32_LDRDSTRD] = {.handler = t32_emulate_ldrdstrd}, + [PROBES_T32_TABLE_BRANCH] = {.handler = t32_simulate_table_branch}, + [PROBES_T32_TST] = {.handler = t32_emulate_rd8rn16rm0_rwflags}, + [PROBES_T32_MOV] = {.handler = t32_emulate_rd8rn16rm0_rwflags}, + [PROBES_T32_ADDSUB] = {.handler = t32_emulate_rd8rn16rm0_rwflags}, + [PROBES_T32_LOGICAL] = {.handler = t32_emulate_rd8rn16rm0_rwflags}, + [PROBES_T32_CMP] = {.handler = t32_emulate_rd8rn16rm0_rwflags}, + [PROBES_T32_ADDWSUBW_PC] = {.handler = t32_emulate_rd8pc16_noflags,}, + [PROBES_T32_ADDWSUBW] = {.handler = t32_emulate_rd8rn16_noflags}, + [PROBES_T32_MOVW] = {.handler = t32_emulate_rd8rn16_noflags}, + [PROBES_T32_SAT] = {.handler = t32_emulate_rd8rn16rm0_rwflags}, + [PROBES_T32_BITFIELD] = {.handler = t32_emulate_rd8rn16_noflags}, + [PROBES_T32_SEV] = {.handler = probes_emulate_none}, + [PROBES_T32_WFE] = {.handler = probes_simulate_nop}, + [PROBES_T32_MRS] = {.handler = t32_simulate_mrs}, + [PROBES_T32_BRANCH_COND] = {.decoder = t32_decode_cond_branch}, + [PROBES_T32_BRANCH] = {.handler = t32_simulate_branch}, + [PROBES_T32_PLDI] = {.handler = probes_simulate_nop}, + [PROBES_T32_LDR_LIT] = {.handler = t32_simulate_ldr_literal}, + [PROBES_T32_LDRSTR] = {.handler = t32_emulate_ldrstr}, + [PROBES_T32_SIGN_EXTEND] = {.handler = t32_emulate_rd8rn16rm0_rwflags}, + [PROBES_T32_MEDIA] = {.handler = t32_emulate_rd8rn16rm0_rwflags}, + [PROBES_T32_REVERSE] = {.handler = t32_emulate_rd8rn16_noflags}, + [PROBES_T32_MUL_ADD] = {.handler = t32_emulate_rd8rn16rm0_rwflags}, + [PROBES_T32_MUL_ADD2] = {.handler = t32_emulate_rd8rn16rm0ra12_noflags}, + [PROBES_T32_MUL_ADD_LONG] = { + .handler = t32_emulate_rdlo12rdhi8rn16rm0_noflags}, }; -#ifdef CONFIG_ARM_KPROBES_TEST_MODULE -EXPORT_SYMBOL_GPL(kprobe_decode_thumb16_table); -#endif - -static unsigned long __kprobes thumb_check_cc(unsigned long cpsr) -{ - if (unlikely(in_it_block(cpsr))) - return kprobe_condition_checks[current_cond(cpsr)](cpsr); - return true; -} - -static void __kprobes thumb16_singlestep(struct kprobe *p, struct pt_regs *regs) -{ - regs->ARM_pc += 2; - p->ainsn.insn_handler(p, regs); - regs->ARM_cpsr = it_advance(regs->ARM_cpsr); -} - -static void __kprobes thumb32_singlestep(struct kprobe *p, struct pt_regs *regs) -{ - regs->ARM_pc += 4; - p->ainsn.insn_handler(p, regs); - regs->ARM_cpsr = it_advance(regs->ARM_cpsr); -} - -enum kprobe_insn __kprobes -thumb16_kprobe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi) -{ - asi->insn_singlestep = thumb16_singlestep; - asi->insn_check_cc = thumb_check_cc; - return kprobe_decode_insn(insn, asi, kprobe_decode_thumb16_table, true); -} - -enum kprobe_insn __kprobes -thumb32_kprobe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi) -{ - asi->insn_singlestep = thumb32_singlestep; - asi->insn_check_cc = thumb_check_cc; - return kprobe_decode_insn(insn, asi, kprobe_decode_thumb32_table, true); -} diff --git a/arch/arm/kernel/kprobes.c b/arch/arm/kernel/kprobes.c index a7b621ece23d3c..6d644202c8dcb1 100644 --- a/arch/arm/kernel/kprobes.c +++ b/arch/arm/kernel/kprobes.c @@ -26,9 +26,14 @@ #include #include #include +#include #include +#include +#include #include "kprobes.h" +#include "probes-arm.h" +#include "probes-thumb.h" #include "patch.h" #define MIN_STACK_SIZE(addr) \ @@ -54,6 +59,7 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p) unsigned long addr = (unsigned long)p->addr; bool thumb; kprobe_decode_insn_t *decode_insn; + const union decode_action *actions; int is; if (in_exception_text(addr)) @@ -62,25 +68,29 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p) #ifdef CONFIG_THUMB2_KERNEL thumb = true; addr &= ~1; /* Bit 0 would normally be set to indicate Thumb code */ - insn = ((u16 *)addr)[0]; + insn = __mem_to_opcode_thumb16(((u16 *)addr)[0]); if (is_wide_instruction(insn)) { - insn <<= 16; - insn |= ((u16 *)addr)[1]; - decode_insn = thumb32_kprobe_decode_insn; - } else - decode_insn = thumb16_kprobe_decode_insn; + u16 inst2 = __mem_to_opcode_thumb16(((u16 *)addr)[1]); + insn = __opcode_thumb32_compose(insn, inst2); + decode_insn = thumb32_probes_decode_insn; + actions = kprobes_t32_actions; + } else { + decode_insn = thumb16_probes_decode_insn; + actions = kprobes_t16_actions; + } #else /* !CONFIG_THUMB2_KERNEL */ thumb = false; if (addr & 0x3) return -EINVAL; - insn = *p->addr; - decode_insn = arm_kprobe_decode_insn; + insn = __mem_to_opcode_arm(*p->addr); + decode_insn = arm_probes_decode_insn; + actions = kprobes_arm_actions; #endif p->opcode = insn; p->ainsn.insn = tmp_insn; - switch ((*decode_insn)(insn, &p->ainsn)) { + switch ((*decode_insn)(insn, &p->ainsn, true, actions)) { case INSN_REJECTED: /* not supported */ return -EINVAL; @@ -92,7 +102,7 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p) p->ainsn.insn[is] = tmp_insn[is]; flush_insns(p->ainsn.insn, sizeof(p->ainsn.insn[0]) * MAX_INSN_SIZE); - p->ainsn.insn_fn = (kprobe_insn_fn_t *) + p->ainsn.insn_fn = (probes_insn_fn_t *) ((uintptr_t)p->ainsn.insn | thumb); break; @@ -197,7 +207,7 @@ singlestep_skip(struct kprobe *p, struct pt_regs *regs) static inline void __kprobes singlestep(struct kprobe *p, struct pt_regs *regs, struct kprobe_ctlblk *kcb) { - p->ainsn.insn_singlestep(p, regs); + p->ainsn.insn_singlestep(p->opcode, &p->ainsn, regs); } /* @@ -607,7 +617,7 @@ static struct undef_hook kprobes_arm_break_hook = { int __init arch_init_kprobes() { - arm_kprobe_decode_init(); + arm_probes_decode_init(); #ifdef CONFIG_THUMB2_KERNEL register_undef_hook(&kprobes_thumb16_break_hook); register_undef_hook(&kprobes_thumb32_break_hook); diff --git a/arch/arm/kernel/kprobes.h b/arch/arm/kernel/kprobes.h index 38945f78f9f1ce..9a2712ecefc32d 100644 --- a/arch/arm/kernel/kprobes.h +++ b/arch/arm/kernel/kprobes.h @@ -19,6 +19,8 @@ #ifndef _ARM_KERNEL_KPROBES_H #define _ARM_KERNEL_KPROBES_H +#include "probes.h" + /* * These undefined instructions must be unique and * reserved solely for kprobes' use. @@ -27,402 +29,24 @@ #define KPROBE_THUMB16_BREAKPOINT_INSTRUCTION 0xde18 #define KPROBE_THUMB32_BREAKPOINT_INSTRUCTION 0xf7f0a018 +enum probes_insn __kprobes +kprobe_decode_ldmstm(kprobe_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *h); -enum kprobe_insn { - INSN_REJECTED, - INSN_GOOD, - INSN_GOOD_NO_SLOT -}; - -typedef enum kprobe_insn (kprobe_decode_insn_t)(kprobe_opcode_t, - struct arch_specific_insn *); +typedef enum probes_insn (kprobe_decode_insn_t)(probes_opcode_t, + struct arch_probes_insn *, + bool, + const union decode_action *); #ifdef CONFIG_THUMB2_KERNEL -enum kprobe_insn thumb16_kprobe_decode_insn(kprobe_opcode_t, - struct arch_specific_insn *); -enum kprobe_insn thumb32_kprobe_decode_insn(kprobe_opcode_t, - struct arch_specific_insn *); +extern const union decode_action kprobes_t32_actions[]; +extern const union decode_action kprobes_t16_actions[]; #else /* !CONFIG_THUMB2_KERNEL */ -enum kprobe_insn arm_kprobe_decode_insn(kprobe_opcode_t, - struct arch_specific_insn *); -#endif - -void __init arm_kprobe_decode_init(void); - -extern kprobe_check_cc * const kprobe_condition_checks[16]; - - -#if __LINUX_ARM_ARCH__ >= 7 - -/* str_pc_offset is architecturally defined from ARMv7 onwards */ -#define str_pc_offset 8 -#define find_str_pc_offset() - -#else /* __LINUX_ARM_ARCH__ < 7 */ - -/* We need a run-time check to determine str_pc_offset */ -extern int str_pc_offset; -void __init find_str_pc_offset(void); +extern const union decode_action kprobes_arm_actions[]; #endif - -/* - * Update ITSTATE after normal execution of an IT block instruction. - * - * The 8 IT state bits are split into two parts in CPSR: - * ITSTATE<1:0> are in CPSR<26:25> - * ITSTATE<7:2> are in CPSR<15:10> - */ -static inline unsigned long it_advance(unsigned long cpsr) - { - if ((cpsr & 0x06000400) == 0) { - /* ITSTATE<2:0> == 0 means end of IT block, so clear IT state */ - cpsr &= ~PSR_IT_MASK; - } else { - /* We need to shift left ITSTATE<4:0> */ - const unsigned long mask = 0x06001c00; /* Mask ITSTATE<4:0> */ - unsigned long it = cpsr & mask; - it <<= 1; - it |= it >> (27 - 10); /* Carry ITSTATE<2> to correct place */ - it &= mask; - cpsr &= ~mask; - cpsr |= it; - } - return cpsr; -} - -static inline void __kprobes bx_write_pc(long pcv, struct pt_regs *regs) -{ - long cpsr = regs->ARM_cpsr; - if (pcv & 0x1) { - cpsr |= PSR_T_BIT; - pcv &= ~0x1; - } else { - cpsr &= ~PSR_T_BIT; - pcv &= ~0x2; /* Avoid UNPREDICTABLE address allignment */ - } - regs->ARM_cpsr = cpsr; - regs->ARM_pc = pcv; -} - - -#if __LINUX_ARM_ARCH__ >= 6 - -/* Kernels built for >= ARMv6 should never run on <= ARMv5 hardware, so... */ -#define load_write_pc_interworks true -#define test_load_write_pc_interworking() - -#else /* __LINUX_ARM_ARCH__ < 6 */ - -/* We need run-time testing to determine if load_write_pc() should interwork. */ -extern bool load_write_pc_interworks; -void __init test_load_write_pc_interworking(void); - -#endif - -static inline void __kprobes load_write_pc(long pcv, struct pt_regs *regs) -{ - if (load_write_pc_interworks) - bx_write_pc(pcv, regs); - else - regs->ARM_pc = pcv; -} - - -#if __LINUX_ARM_ARCH__ >= 7 - -#define alu_write_pc_interworks true -#define test_alu_write_pc_interworking() - -#elif __LINUX_ARM_ARCH__ <= 5 - -/* Kernels built for <= ARMv5 should never run on >= ARMv6 hardware, so... */ -#define alu_write_pc_interworks false -#define test_alu_write_pc_interworking() - -#else /* __LINUX_ARM_ARCH__ == 6 */ - -/* We could be an ARMv6 binary on ARMv7 hardware so we need a run-time check. */ -extern bool alu_write_pc_interworks; -void __init test_alu_write_pc_interworking(void); - -#endif /* __LINUX_ARM_ARCH__ == 6 */ - -static inline void __kprobes alu_write_pc(long pcv, struct pt_regs *regs) -{ - if (alu_write_pc_interworks) - bx_write_pc(pcv, regs); - else - regs->ARM_pc = pcv; -} - - -void __kprobes kprobe_simulate_nop(struct kprobe *p, struct pt_regs *regs); -void __kprobes kprobe_emulate_none(struct kprobe *p, struct pt_regs *regs); - -enum kprobe_insn __kprobes -kprobe_decode_ldmstm(kprobe_opcode_t insn, struct arch_specific_insn *asi); - -/* - * Test if load/store instructions writeback the address register. - * if P (bit 24) == 0 or W (bit 21) == 1 - */ -#define is_writeback(insn) ((insn ^ 0x01000000) & 0x01200000) - -/* - * The following definitions and macros are used to build instruction - * decoding tables for use by kprobe_decode_insn. - * - * These tables are a concatenation of entries each of which consist of one of - * the decode_* structs. All of the fields in every type of decode structure - * are of the union type decode_item, therefore the entire decode table can be - * viewed as an array of these and declared like: - * - * static const union decode_item table_name[] = {}; - * - * In order to construct each entry in the table, macros are used to - * initialise a number of sequential decode_item values in a layout which - * matches the relevant struct. E.g. DECODE_SIMULATE initialise a struct - * decode_simulate by initialising four decode_item objects like this... - * - * {.bits = _type}, - * {.bits = _mask}, - * {.bits = _value}, - * {.handler = _handler}, - * - * Initialising a specified member of the union means that the compiler - * will produce a warning if the argument is of an incorrect type. - * - * Below is a list of each of the macros used to initialise entries and a - * description of the action performed when that entry is matched to an - * instruction. A match is found when (instruction & mask) == value. - * - * DECODE_TABLE(mask, value, table) - * Instruction decoding jumps to parsing the new sub-table 'table'. - * - * DECODE_CUSTOM(mask, value, decoder) - * The custom function 'decoder' is called to the complete decoding - * of an instruction. - * - * DECODE_SIMULATE(mask, value, handler) - * Set the probes instruction handler to 'handler', this will be used - * to simulate the instruction when the probe is hit. Decoding returns - * with INSN_GOOD_NO_SLOT. - * - * DECODE_EMULATE(mask, value, handler) - * Set the probes instruction handler to 'handler', this will be used - * to emulate the instruction when the probe is hit. The modified - * instruction (see below) is placed in the probes instruction slot so it - * may be called by the emulation code. Decoding returns with INSN_GOOD. - * - * DECODE_REJECT(mask, value) - * Instruction decoding fails with INSN_REJECTED - * - * DECODE_OR(mask, value) - * This allows the mask/value test of multiple table entries to be - * logically ORed. Once an 'or' entry is matched the decoding action to - * be performed is that of the next entry which isn't an 'or'. E.g. - * - * DECODE_OR (mask1, value1) - * DECODE_OR (mask2, value2) - * DECODE_SIMULATE (mask3, value3, simulation_handler) - * - * This means that if any of the three mask/value pairs match the - * instruction being decoded, then 'simulation_handler' will be used - * for it. - * - * Both the SIMULATE and EMULATE macros have a second form which take an - * additional 'regs' argument. - * - * DECODE_SIMULATEX(mask, value, handler, regs) - * DECODE_EMULATEX (mask, value, handler, regs) - * - * These are used to specify what kind of CPU register is encoded in each of the - * least significant 5 nibbles of the instruction being decoded. The regs value - * is specified using the REGS macro, this takes any of the REG_TYPE_* values - * from enum decode_reg_type as arguments; only the '*' part of the name is - * given. E.g. - * - * REGS(0, ANY, NOPC, 0, ANY) - * - * This indicates an instruction is encoded like: - * - * bits 19..16 ignore - * bits 15..12 any register allowed here - * bits 11.. 8 any register except PC allowed here - * bits 7.. 4 ignore - * bits 3.. 0 any register allowed here - * - * This register specification is checked after a decode table entry is found to - * match an instruction (through the mask/value test). Any invalid register then - * found in the instruction will cause decoding to fail with INSN_REJECTED. In - * the above example this would happen if bits 11..8 of the instruction were - * 1111, indicating R15 or PC. - * - * As well as checking for legal combinations of registers, this data is also - * used to modify the registers encoded in the instructions so that an - * emulation routines can use it. (See decode_regs() and INSN_NEW_BITS.) - * - * Here is a real example which matches ARM instructions of the form - * "AND ,,, " - * - * DECODE_EMULATEX (0x0e000090, 0x00000010, emulate_rd12rn16rm0rs8_rwflags, - * REGS(ANY, ANY, NOPC, 0, ANY)), - * ^ ^ ^ ^ - * Rn Rd Rs Rm - * - * Decoding the instruction "AND R4, R5, R6, ASL R15" will be rejected because - * Rs == R15 - * - * Decoding the instruction "AND R4, R5, R6, ASL R7" will be accepted and the - * instruction will be modified to "AND R0, R2, R3, ASL R1" and then placed into - * the kprobes instruction slot. This can then be called later by the handler - * function emulate_rd12rn16rm0rs8_rwflags in order to simulate the instruction. - */ - -enum decode_type { - DECODE_TYPE_END, - DECODE_TYPE_TABLE, - DECODE_TYPE_CUSTOM, - DECODE_TYPE_SIMULATE, - DECODE_TYPE_EMULATE, - DECODE_TYPE_OR, - DECODE_TYPE_REJECT, - NUM_DECODE_TYPES /* Must be last enum */ -}; - -#define DECODE_TYPE_BITS 4 -#define DECODE_TYPE_MASK ((1 << DECODE_TYPE_BITS) - 1) - -enum decode_reg_type { - REG_TYPE_NONE = 0, /* Not a register, ignore */ - REG_TYPE_ANY, /* Any register allowed */ - REG_TYPE_SAMEAS16, /* Register should be same as that at bits 19..16 */ - REG_TYPE_SP, /* Register must be SP */ - REG_TYPE_PC, /* Register must be PC */ - REG_TYPE_NOSP, /* Register must not be SP */ - REG_TYPE_NOSPPC, /* Register must not be SP or PC */ - REG_TYPE_NOPC, /* Register must not be PC */ - REG_TYPE_NOPCWB, /* No PC if load/store write-back flag also set */ - - /* The following types are used when the encoding for PC indicates - * another instruction form. This distiction only matters for test - * case coverage checks. - */ - REG_TYPE_NOPCX, /* Register must not be PC */ - REG_TYPE_NOSPPCX, /* Register must not be SP or PC */ - - /* Alias to allow '0' arg to be used in REGS macro. */ - REG_TYPE_0 = REG_TYPE_NONE -}; - -#define REGS(r16, r12, r8, r4, r0) \ - ((REG_TYPE_##r16) << 16) + \ - ((REG_TYPE_##r12) << 12) + \ - ((REG_TYPE_##r8) << 8) + \ - ((REG_TYPE_##r4) << 4) + \ - (REG_TYPE_##r0) - -union decode_item { - u32 bits; - const union decode_item *table; - kprobe_insn_handler_t *handler; - kprobe_decode_insn_t *decoder; -}; - - -#define DECODE_END \ - {.bits = DECODE_TYPE_END} - - -struct decode_header { - union decode_item type_regs; - union decode_item mask; - union decode_item value; -}; - -#define DECODE_HEADER(_type, _mask, _value, _regs) \ - {.bits = (_type) | ((_regs) << DECODE_TYPE_BITS)}, \ - {.bits = (_mask)}, \ - {.bits = (_value)} - - -struct decode_table { - struct decode_header header; - union decode_item table; -}; - -#define DECODE_TABLE(_mask, _value, _table) \ - DECODE_HEADER(DECODE_TYPE_TABLE, _mask, _value, 0), \ - {.table = (_table)} - - -struct decode_custom { - struct decode_header header; - union decode_item decoder; -}; - -#define DECODE_CUSTOM(_mask, _value, _decoder) \ - DECODE_HEADER(DECODE_TYPE_CUSTOM, _mask, _value, 0), \ - {.decoder = (_decoder)} - - -struct decode_simulate { - struct decode_header header; - union decode_item handler; -}; - -#define DECODE_SIMULATEX(_mask, _value, _handler, _regs) \ - DECODE_HEADER(DECODE_TYPE_SIMULATE, _mask, _value, _regs), \ - {.handler = (_handler)} - -#define DECODE_SIMULATE(_mask, _value, _handler) \ - DECODE_SIMULATEX(_mask, _value, _handler, 0) - - -struct decode_emulate { - struct decode_header header; - union decode_item handler; -}; - -#define DECODE_EMULATEX(_mask, _value, _handler, _regs) \ - DECODE_HEADER(DECODE_TYPE_EMULATE, _mask, _value, _regs), \ - {.handler = (_handler)} - -#define DECODE_EMULATE(_mask, _value, _handler) \ - DECODE_EMULATEX(_mask, _value, _handler, 0) - - -struct decode_or { - struct decode_header header; -}; - -#define DECODE_OR(_mask, _value) \ - DECODE_HEADER(DECODE_TYPE_OR, _mask, _value, 0) - - -struct decode_reject { - struct decode_header header; -}; - -#define DECODE_REJECT(_mask, _value) \ - DECODE_HEADER(DECODE_TYPE_REJECT, _mask, _value, 0) - - -#ifdef CONFIG_THUMB2_KERNEL -extern const union decode_item kprobe_decode_thumb16_table[]; -extern const union decode_item kprobe_decode_thumb32_table[]; -#else -extern const union decode_item kprobe_decode_arm_table[]; -#endif - - -int kprobe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi, - const union decode_item *table, bool thumb16); - - #endif /* _ARM_KERNEL_KPROBES_H */ diff --git a/arch/arm/kernel/perf_event.c b/arch/arm/kernel/perf_event.c index 789d846a918453..42059a54a9c016 100644 --- a/arch/arm/kernel/perf_event.c +++ b/arch/arm/kernel/perf_event.c @@ -12,6 +12,7 @@ */ #define pr_fmt(fmt) "hw perfevents: " fmt +#include #include #include #include @@ -86,6 +87,9 @@ armpmu_map_event(struct perf_event *event, return armpmu_map_cache_event(cache_map, config); case PERF_TYPE_RAW: return armpmu_map_raw_event(raw_event_mask, config); + default: + if (event->attr.type >= PERF_TYPE_MAX) + return armpmu_map_raw_event(raw_event_mask, config); } return -ENOENT; @@ -159,6 +163,8 @@ armpmu_stop(struct perf_event *event, int flags) struct arm_pmu *armpmu = to_arm_pmu(event->pmu); struct hw_perf_event *hwc = &event->hw; + if (!cpumask_test_cpu(smp_processor_id(), &armpmu->valid_cpus)) + return; /* * ARM pmu always has to update the counter, so ignore * PERF_EF_UPDATE, see comments in armpmu_start(). @@ -175,6 +181,8 @@ static void armpmu_start(struct perf_event *event, int flags) struct arm_pmu *armpmu = to_arm_pmu(event->pmu); struct hw_perf_event *hwc = &event->hw; + if (!cpumask_test_cpu(smp_processor_id(), &armpmu->valid_cpus)) + return; /* * ARM pmu always has to reprogram the period, so ignore * PERF_EF_RELOAD, see the comment below. @@ -202,6 +210,9 @@ armpmu_del(struct perf_event *event, int flags) struct hw_perf_event *hwc = &event->hw; int idx = hwc->idx; + if (!cpumask_test_cpu(smp_processor_id(), &armpmu->valid_cpus)) + return; + armpmu_stop(event, PERF_EF_UPDATE); hw_events->events[idx] = NULL; clear_bit(idx, hw_events->used_mask); @@ -218,6 +229,10 @@ armpmu_add(struct perf_event *event, int flags) int idx; int err = 0; + /* An event following a process won't be stopped earlier */ + if (!cpumask_test_cpu(smp_processor_id(), &armpmu->valid_cpus)) + return 0; + perf_pmu_disable(event->pmu); /* If we don't have a space for the counter then finish early. */ @@ -419,6 +434,10 @@ static int armpmu_event_init(struct perf_event *event) int err = 0; atomic_t *active_events = &armpmu->active_events; + if (event->cpu != -1 && + !cpumask_test_cpu(event->cpu, &armpmu->valid_cpus)) + return -ENOENT; + /* does not support taken branch sampling */ if (has_branch_stack(event)) return -EOPNOTSUPP; diff --git a/arch/arm/kernel/perf_event_cpu.c b/arch/arm/kernel/perf_event_cpu.c index 20d553c9f5e292..31c5cdefae3ff2 100644 --- a/arch/arm/kernel/perf_event_cpu.c +++ b/arch/arm/kernel/perf_event_cpu.c @@ -19,6 +19,7 @@ #define pr_fmt(fmt) "CPU PMU: " fmt #include +#include #include #include #include @@ -31,33 +32,36 @@ #include /* Set at runtime when we know what CPU type we are. */ -static struct arm_pmu *cpu_pmu; +static DEFINE_PER_CPU(struct arm_pmu *, cpu_pmu); static DEFINE_PER_CPU(struct perf_event * [ARMPMU_MAX_HWEVENTS], hw_events); static DEFINE_PER_CPU(unsigned long [BITS_TO_LONGS(ARMPMU_MAX_HWEVENTS)], used_mask); static DEFINE_PER_CPU(struct pmu_hw_events, cpu_hw_events); +static DEFINE_PER_CPU(struct cpupmu_regs, cpu_pmu_regs); + /* * Despite the names, these two functions are CPU-specific and are used * by the OProfile/perf code. */ const char *perf_pmu_name(void) { - if (!cpu_pmu) + struct arm_pmu *pmu = per_cpu(cpu_pmu, 0); + if (!pmu) return NULL; - return cpu_pmu->name; + return pmu->name; } EXPORT_SYMBOL_GPL(perf_pmu_name); int perf_num_counters(void) { - int max_events = 0; + struct arm_pmu *pmu = per_cpu(cpu_pmu, 0); - if (cpu_pmu != NULL) - max_events = cpu_pmu->num_events; + if (!pmu) + return 0; - return max_events; + return pmu->num_events; } EXPORT_SYMBOL_GPL(perf_num_counters); @@ -75,11 +79,13 @@ static void cpu_pmu_free_irq(struct arm_pmu *cpu_pmu) { int i, irq, irqs; struct platform_device *pmu_device = cpu_pmu->plat_device; + int cpu = -1; irqs = min(pmu_device->num_resources, num_possible_cpus()); for (i = 0; i < irqs; ++i) { - if (!cpumask_test_and_clear_cpu(i, &cpu_pmu->active_irqs)) + cpu = cpumask_next(cpu, &cpu_pmu->valid_cpus); + if (!cpumask_test_and_clear_cpu(cpu, &cpu_pmu->active_irqs)) continue; irq = platform_get_irq(pmu_device, i); if (irq >= 0) @@ -91,6 +97,7 @@ static int cpu_pmu_request_irq(struct arm_pmu *cpu_pmu, irq_handler_t handler) { int i, err, irq, irqs; struct platform_device *pmu_device = cpu_pmu->plat_device; + int cpu = -1; if (!pmu_device) return -ENODEV; @@ -103,6 +110,7 @@ static int cpu_pmu_request_irq(struct arm_pmu *cpu_pmu, irq_handler_t handler) for (i = 0; i < irqs; ++i) { err = 0; + cpu = cpumask_next(cpu, &cpu_pmu->valid_cpus); irq = platform_get_irq(pmu_device, i); if (irq < 0) continue; @@ -112,7 +120,7 @@ static int cpu_pmu_request_irq(struct arm_pmu *cpu_pmu, irq_handler_t handler) * assume that we're running on a uniprocessor machine and * continue. Otherwise, continue without this interrupt. */ - if (irq_set_affinity(irq, cpumask_of(i)) && irqs > 1) { + if (irq_set_affinity(irq, cpumask_of(cpu)) && irqs > 1) { pr_warning("unable to set irq affinity (irq=%d, cpu=%u)\n", irq, i); continue; @@ -127,7 +135,7 @@ static int cpu_pmu_request_irq(struct arm_pmu *cpu_pmu, irq_handler_t handler) return err; } - cpumask_set_cpu(i, &cpu_pmu->active_irqs); + cpumask_set_cpu(cpu, &cpu_pmu->active_irqs); } return 0; @@ -136,7 +144,7 @@ static int cpu_pmu_request_irq(struct arm_pmu *cpu_pmu, irq_handler_t handler) static void cpu_pmu_init(struct arm_pmu *cpu_pmu) { int cpu; - for_each_possible_cpu(cpu) { + for_each_cpu_mask(cpu, cpu_pmu->valid_cpus) { struct pmu_hw_events *events = &per_cpu(cpu_hw_events, cpu); events->events = per_cpu(hw_events, cpu); events->used_mask = per_cpu(used_mask, cpu); @@ -149,7 +157,7 @@ static void cpu_pmu_init(struct arm_pmu *cpu_pmu) /* Ensure the PMU has sane values out of reset. */ if (cpu_pmu->reset) - on_each_cpu(cpu_pmu->reset, cpu_pmu, 1); + on_each_cpu_mask(&cpu_pmu->valid_cpus, cpu_pmu->reset, cpu_pmu, 1); } /* @@ -161,21 +169,46 @@ static void cpu_pmu_init(struct arm_pmu *cpu_pmu) static int cpu_pmu_notify(struct notifier_block *b, unsigned long action, void *hcpu) { + struct arm_pmu *pmu = per_cpu(cpu_pmu, (long)hcpu); + if ((action & ~CPU_TASKS_FROZEN) != CPU_STARTING) return NOTIFY_DONE; - if (cpu_pmu && cpu_pmu->reset) - cpu_pmu->reset(cpu_pmu); + if (pmu && pmu->reset) + pmu->reset(pmu); else return NOTIFY_DONE; return NOTIFY_OK; } +static int cpu_pmu_pm_notify(struct notifier_block *b, + unsigned long action, void *hcpu) +{ + int cpu = smp_processor_id(); + struct arm_pmu *pmu = per_cpu(cpu_pmu, cpu); + struct cpupmu_regs *pmuregs = &per_cpu(cpu_pmu_regs, cpu); + + if (!pmu) + return NOTIFY_DONE; + + if (action == CPU_PM_ENTER && pmu->save_regs) { + pmu->save_regs(pmu, pmuregs); + } else if (action == CPU_PM_EXIT && pmu->restore_regs) { + pmu->restore_regs(pmu, pmuregs); + } + + return NOTIFY_OK; +} + static struct notifier_block cpu_pmu_hotplug_notifier = { .notifier_call = cpu_pmu_notify, }; +static struct notifier_block cpu_pmu_pm_notifier = { + .notifier_call = cpu_pmu_pm_notify, +}; + /* * PMU platform driver and devicetree bindings. */ @@ -247,6 +280,9 @@ static int probe_current_pmu(struct arm_pmu *pmu) } } + /* assume PMU support all the CPUs in this case */ + cpumask_setall(&pmu->valid_cpus); + put_cpu(); return ret; } @@ -254,15 +290,10 @@ static int probe_current_pmu(struct arm_pmu *pmu) static int cpu_pmu_device_probe(struct platform_device *pdev) { const struct of_device_id *of_id; - const int (*init_fn)(struct arm_pmu *); struct device_node *node = pdev->dev.of_node; struct arm_pmu *pmu; - int ret = -ENODEV; - - if (cpu_pmu) { - pr_info("attempt to register multiple PMU devices!"); - return -ENOSPC; - } + int ret = 0; + int cpu; pmu = kzalloc(sizeof(struct arm_pmu), GFP_KERNEL); if (!pmu) { @@ -271,8 +302,28 @@ static int cpu_pmu_device_probe(struct platform_device *pdev) } if (node && (of_id = of_match_node(cpu_pmu_of_device_ids, pdev->dev.of_node))) { - init_fn = of_id->data; - ret = init_fn(pmu); + smp_call_func_t init_fn = (smp_call_func_t)of_id->data; + struct device_node *ncluster; + int cluster = -1; + cpumask_t sibling_mask; + + ncluster = of_parse_phandle(node, "cluster", 0); + if (ncluster) { + int len; + const u32 *hwid; + hwid = of_get_property(ncluster, "reg", &len); + if (hwid && len == 4) + cluster = be32_to_cpup(hwid); + } + /* set sibling mask to all cpu mask if socket is not specified */ + if (cluster == -1 || + cluster_to_logical_mask(cluster, &sibling_mask)) + cpumask_setall(&sibling_mask); + + smp_call_function_any(&sibling_mask, init_fn, pmu, 1); + + /* now set the valid_cpus after init */ + cpumask_copy(&pmu->valid_cpus, &sibling_mask); } else { ret = probe_current_pmu(pmu); } @@ -282,10 +333,12 @@ static int cpu_pmu_device_probe(struct platform_device *pdev) goto out_free; } - cpu_pmu = pmu; - cpu_pmu->plat_device = pdev; - cpu_pmu_init(cpu_pmu); - ret = armpmu_register(cpu_pmu, PERF_TYPE_RAW); + for_each_cpu_mask(cpu, pmu->valid_cpus) + per_cpu(cpu_pmu, cpu) = pmu; + + pmu->plat_device = pdev; + cpu_pmu_init(pmu); + ret = armpmu_register(pmu, -1); if (!ret) return 0; @@ -314,9 +367,17 @@ static int __init register_pmu_driver(void) if (err) return err; + err = cpu_pm_register_notifier(&cpu_pmu_pm_notifier); + if (err) { + unregister_cpu_notifier(&cpu_pmu_hotplug_notifier); + return err; + } + err = platform_driver_register(&cpu_pmu_driver); - if (err) + if (err) { + cpu_pm_unregister_notifier(&cpu_pmu_pm_notifier); unregister_cpu_notifier(&cpu_pmu_hotplug_notifier); + } return err; } diff --git a/arch/arm/kernel/perf_event_v7.c b/arch/arm/kernel/perf_event_v7.c index 039cffb053a7ec..654db5030c3186 100644 --- a/arch/arm/kernel/perf_event_v7.c +++ b/arch/arm/kernel/perf_event_v7.c @@ -950,6 +950,51 @@ static void armv7_pmnc_dump_regs(struct arm_pmu *cpu_pmu) } #endif +static void armv7pmu_save_regs(struct arm_pmu *cpu_pmu, + struct cpupmu_regs *regs) +{ + unsigned int cnt; + asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (regs->pmc)); + if (!(regs->pmc & ARMV7_PMNC_E)) + return; + + asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r" (regs->pmcntenset)); + asm volatile("mrc p15, 0, %0, c9, c14, 0" : "=r" (regs->pmuseren)); + asm volatile("mrc p15, 0, %0, c9, c14, 1" : "=r" (regs->pmintenset)); + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r" (regs->pmxevtcnt[0])); + for (cnt = ARMV7_IDX_COUNTER0; + cnt <= ARMV7_IDX_COUNTER_LAST(cpu_pmu); cnt++) { + armv7_pmnc_select_counter(cnt); + asm volatile("mrc p15, 0, %0, c9, c13, 1" + : "=r"(regs->pmxevttype[cnt])); + asm volatile("mrc p15, 0, %0, c9, c13, 2" + : "=r"(regs->pmxevtcnt[cnt])); + } + return; +} + +static void armv7pmu_restore_regs(struct arm_pmu *cpu_pmu, + struct cpupmu_regs *regs) +{ + unsigned int cnt; + if (!(regs->pmc & ARMV7_PMNC_E)) + return; + + asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (regs->pmcntenset)); + asm volatile("mcr p15, 0, %0, c9, c14, 0" : : "r" (regs->pmuseren)); + asm volatile("mcr p15, 0, %0, c9, c14, 1" : : "r" (regs->pmintenset)); + asm volatile("mcr p15, 0, %0, c9, c13, 0" : : "r" (regs->pmxevtcnt[0])); + for (cnt = ARMV7_IDX_COUNTER0; + cnt <= ARMV7_IDX_COUNTER_LAST(cpu_pmu); cnt++) { + armv7_pmnc_select_counter(cnt); + asm volatile("mcr p15, 0, %0, c9, c13, 1" + : : "r"(regs->pmxevttype[cnt])); + asm volatile("mcr p15, 0, %0, c9, c13, 2" + : : "r"(regs->pmxevtcnt[cnt])); + } + asm volatile("mcr p15, 0, %0, c9, c12, 0" : : "r" (regs->pmc)); +} + static void armv7pmu_enable_event(struct perf_event *event) { unsigned long flags; @@ -1223,6 +1268,8 @@ static void armv7pmu_init(struct arm_pmu *cpu_pmu) cpu_pmu->start = armv7pmu_start; cpu_pmu->stop = armv7pmu_stop; cpu_pmu->reset = armv7pmu_reset; + cpu_pmu->save_regs = armv7pmu_save_regs; + cpu_pmu->restore_regs = armv7pmu_restore_regs; cpu_pmu->max_period = (1LLU << 32) - 1; }; @@ -1240,7 +1287,7 @@ static u32 armv7_read_num_pmnc_events(void) static int armv7_a8_pmu_init(struct arm_pmu *cpu_pmu) { armv7pmu_init(cpu_pmu); - cpu_pmu->name = "ARMv7 Cortex-A8"; + cpu_pmu->name = "ARMv7_Cortex_A8"; cpu_pmu->map_event = armv7_a8_map_event; cpu_pmu->num_events = armv7_read_num_pmnc_events(); return 0; @@ -1249,7 +1296,7 @@ static int armv7_a8_pmu_init(struct arm_pmu *cpu_pmu) static int armv7_a9_pmu_init(struct arm_pmu *cpu_pmu) { armv7pmu_init(cpu_pmu); - cpu_pmu->name = "ARMv7 Cortex-A9"; + cpu_pmu->name = "ARMv7_Cortex_A9"; cpu_pmu->map_event = armv7_a9_map_event; cpu_pmu->num_events = armv7_read_num_pmnc_events(); return 0; @@ -1258,7 +1305,7 @@ static int armv7_a9_pmu_init(struct arm_pmu *cpu_pmu) static int armv7_a5_pmu_init(struct arm_pmu *cpu_pmu) { armv7pmu_init(cpu_pmu); - cpu_pmu->name = "ARMv7 Cortex-A5"; + cpu_pmu->name = "ARMv7_Cortex_A5"; cpu_pmu->map_event = armv7_a5_map_event; cpu_pmu->num_events = armv7_read_num_pmnc_events(); return 0; @@ -1267,7 +1314,7 @@ static int armv7_a5_pmu_init(struct arm_pmu *cpu_pmu) static int armv7_a15_pmu_init(struct arm_pmu *cpu_pmu) { armv7pmu_init(cpu_pmu); - cpu_pmu->name = "ARMv7 Cortex-A15"; + cpu_pmu->name = "ARMv7_Cortex_A15"; cpu_pmu->map_event = armv7_a15_map_event; cpu_pmu->num_events = armv7_read_num_pmnc_events(); cpu_pmu->set_event_filter = armv7pmu_set_event_filter; @@ -1277,7 +1324,7 @@ static int armv7_a15_pmu_init(struct arm_pmu *cpu_pmu) static int armv7_a7_pmu_init(struct arm_pmu *cpu_pmu) { armv7pmu_init(cpu_pmu); - cpu_pmu->name = "ARMv7 Cortex-A7"; + cpu_pmu->name = "ARMv7_Cortex_A7"; cpu_pmu->map_event = armv7_a7_map_event; cpu_pmu->num_events = armv7_read_num_pmnc_events(); cpu_pmu->set_event_filter = armv7pmu_set_event_filter; diff --git a/arch/arm/kernel/probes-arm.c b/arch/arm/kernel/probes-arm.c new file mode 100644 index 00000000000000..51a13a02798949 --- /dev/null +++ b/arch/arm/kernel/probes-arm.c @@ -0,0 +1,734 @@ +/* + * arch/arm/kernel/probes-arm.c + * + * Some code moved here from arch/arm/kernel/kprobes-arm.c + * + * Copyright (C) 2006, 2007 Motorola Inc. + * + * 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. + */ + +#include +#include +#include +#include + +#include "probes.h" +#include "probes-arm.h" + +#define sign_extend(x, signbit) ((x) | (0 - ((x) & (1 << (signbit))))) + +#define branch_displacement(insn) sign_extend(((insn) & 0xffffff) << 2, 25) + +/* + * To avoid the complications of mimicing single-stepping on a + * processor without a Next-PC or a single-step mode, and to + * avoid having to deal with the side-effects of boosting, we + * simulate or emulate (almost) all ARM instructions. + * + * "Simulation" is where the instruction's behavior is duplicated in + * C code. "Emulation" is where the original instruction is rewritten + * and executed, often by altering its registers. + * + * By having all behavior of the kprobe'd instruction completed before + * returning from the kprobe_handler(), all locks (scheduler and + * interrupt) can safely be released. There is no need for secondary + * breakpoints, no race with MP or preemptable kernels, nor having to + * clean up resources counts at a later time impacting overall system + * performance. By rewriting the instruction, only the minimum registers + * need to be loaded and saved back optimizing performance. + * + * Calling the insnslot_*_rwflags version of a function doesn't hurt + * anything even when the CPSR flags aren't updated by the + * instruction. It's just a little slower in return for saving + * a little space by not having a duplicate function that doesn't + * update the flags. (The same optimization can be said for + * instructions that do or don't perform register writeback) + * Also, instructions can either read the flags, only write the + * flags, or read and write the flags. To save combinations + * rather than for sheer performance, flag functions just assume + * read and write of flags. + */ + +void __kprobes simulate_bbl(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) +{ + long iaddr = (long) regs->ARM_pc - 4; + int disp = branch_displacement(insn); + + if (insn & (1 << 24)) + regs->ARM_lr = iaddr + 4; + + regs->ARM_pc = iaddr + 8 + disp; +} + +void __kprobes simulate_blx1(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) +{ + long iaddr = (long) regs->ARM_pc - 4; + int disp = branch_displacement(insn); + + regs->ARM_lr = iaddr + 4; + regs->ARM_pc = iaddr + 8 + disp + ((insn >> 23) & 0x2); + regs->ARM_cpsr |= PSR_T_BIT; +} + +void __kprobes simulate_blx2bx(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) +{ + int rm = insn & 0xf; + long rmv = regs->uregs[rm]; + + if (insn & (1 << 5)) + regs->ARM_lr = (long) regs->ARM_pc; + + regs->ARM_pc = rmv & ~0x1; + regs->ARM_cpsr &= ~PSR_T_BIT; + if (rmv & 0x1) + regs->ARM_cpsr |= PSR_T_BIT; +} + +void __kprobes simulate_mrs(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) +{ + int rd = (insn >> 12) & 0xf; + unsigned long mask = 0xf8ff03df; /* Mask out execution state */ + regs->uregs[rd] = regs->ARM_cpsr & mask; +} + +void __kprobes simulate_mov_ipsp(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) +{ + regs->uregs[12] = regs->uregs[13]; +} + +/* + * For the instruction masking and comparisons in all the "space_*" + * functions below, Do _not_ rearrange the order of tests unless + * you're very, very sure of what you are doing. For the sake of + * efficiency, the masks for some tests sometimes assume other test + * have been done prior to them so the number of patterns to test + * for an instruction set can be as broad as possible to reduce the + * number of tests needed. + */ + +static const union decode_item arm_1111_table[] = { + /* Unconditional instructions */ + + /* memory hint 1111 0100 x001 xxxx xxxx xxxx xxxx xxxx */ + /* PLDI (immediate) 1111 0100 x101 xxxx xxxx xxxx xxxx xxxx */ + /* PLDW (immediate) 1111 0101 x001 xxxx xxxx xxxx xxxx xxxx */ + /* PLD (immediate) 1111 0101 x101 xxxx xxxx xxxx xxxx xxxx */ + DECODE_SIMULATE (0xfe300000, 0xf4100000, PROBES_PRELOAD_IMM), + + /* memory hint 1111 0110 x001 xxxx xxxx xxxx xxx0 xxxx */ + /* PLDI (register) 1111 0110 x101 xxxx xxxx xxxx xxx0 xxxx */ + /* PLDW (register) 1111 0111 x001 xxxx xxxx xxxx xxx0 xxxx */ + /* PLD (register) 1111 0111 x101 xxxx xxxx xxxx xxx0 xxxx */ + DECODE_SIMULATE (0xfe300010, 0xf6100000, PROBES_PRELOAD_REG), + + /* BLX (immediate) 1111 101x xxxx xxxx xxxx xxxx xxxx xxxx */ + DECODE_SIMULATE (0xfe000000, 0xfa000000, PROBES_BRANCH_IMM), + + /* CPS 1111 0001 0000 xxx0 xxxx xxxx xx0x xxxx */ + /* SETEND 1111 0001 0000 0001 xxxx xxxx 0000 xxxx */ + /* SRS 1111 100x x1x0 xxxx xxxx xxxx xxxx xxxx */ + /* RFE 1111 100x x0x1 xxxx xxxx xxxx xxxx xxxx */ + + /* Coprocessor instructions... */ + /* MCRR2 1111 1100 0100 xxxx xxxx xxxx xxxx xxxx */ + /* MRRC2 1111 1100 0101 xxxx xxxx xxxx xxxx xxxx */ + /* LDC2 1111 110x xxx1 xxxx xxxx xxxx xxxx xxxx */ + /* STC2 1111 110x xxx0 xxxx xxxx xxxx xxxx xxxx */ + /* CDP2 1111 1110 xxxx xxxx xxxx xxxx xxx0 xxxx */ + /* MCR2 1111 1110 xxx0 xxxx xxxx xxxx xxx1 xxxx */ + /* MRC2 1111 1110 xxx1 xxxx xxxx xxxx xxx1 xxxx */ + + /* Other unallocated instructions... */ + DECODE_END +}; + +static const union decode_item arm_cccc_0001_0xx0____0xxx_table[] = { + /* Miscellaneous instructions */ + + /* MRS cpsr cccc 0001 0000 xxxx xxxx xxxx 0000 xxxx */ + DECODE_SIMULATEX(0x0ff000f0, 0x01000000, PROBES_MRS, + REGS(0, NOPC, 0, 0, 0)), + + /* BX cccc 0001 0010 xxxx xxxx xxxx 0001 xxxx */ + DECODE_SIMULATE (0x0ff000f0, 0x01200010, PROBES_BRANCH_REG), + + /* BLX (register) cccc 0001 0010 xxxx xxxx xxxx 0011 xxxx */ + DECODE_SIMULATEX(0x0ff000f0, 0x01200030, PROBES_BRANCH_REG, + REGS(0, 0, 0, 0, NOPC)), + + /* CLZ cccc 0001 0110 xxxx xxxx xxxx 0001 xxxx */ + DECODE_EMULATEX (0x0ff000f0, 0x01600010, PROBES_CLZ, + REGS(0, NOPC, 0, 0, NOPC)), + + /* QADD cccc 0001 0000 xxxx xxxx xxxx 0101 xxxx */ + /* QSUB cccc 0001 0010 xxxx xxxx xxxx 0101 xxxx */ + /* QDADD cccc 0001 0100 xxxx xxxx xxxx 0101 xxxx */ + /* QDSUB cccc 0001 0110 xxxx xxxx xxxx 0101 xxxx */ + DECODE_EMULATEX (0x0f9000f0, 0x01000050, PROBES_SATURATING_ARITHMETIC, + REGS(NOPC, NOPC, 0, 0, NOPC)), + + /* BXJ cccc 0001 0010 xxxx xxxx xxxx 0010 xxxx */ + /* MSR cccc 0001 0x10 xxxx xxxx xxxx 0000 xxxx */ + /* MRS spsr cccc 0001 0100 xxxx xxxx xxxx 0000 xxxx */ + /* BKPT 1110 0001 0010 xxxx xxxx xxxx 0111 xxxx */ + /* SMC cccc 0001 0110 xxxx xxxx xxxx 0111 xxxx */ + /* And unallocated instructions... */ + DECODE_END +}; + +static const union decode_item arm_cccc_0001_0xx0____1xx0_table[] = { + /* Halfword multiply and multiply-accumulate */ + + /* SMLALxy cccc 0001 0100 xxxx xxxx xxxx 1xx0 xxxx */ + DECODE_EMULATEX (0x0ff00090, 0x01400080, PROBES_MUL1, + REGS(NOPC, NOPC, NOPC, 0, NOPC)), + + /* SMULWy cccc 0001 0010 xxxx xxxx xxxx 1x10 xxxx */ + DECODE_OR (0x0ff000b0, 0x012000a0), + /* SMULxy cccc 0001 0110 xxxx xxxx xxxx 1xx0 xxxx */ + DECODE_EMULATEX (0x0ff00090, 0x01600080, PROBES_MUL2, + REGS(NOPC, 0, NOPC, 0, NOPC)), + + /* SMLAxy cccc 0001 0000 xxxx xxxx xxxx 1xx0 xxxx */ + DECODE_OR (0x0ff00090, 0x01000080), + /* SMLAWy cccc 0001 0010 xxxx xxxx xxxx 1x00 xxxx */ + DECODE_EMULATEX (0x0ff000b0, 0x01200080, PROBES_MUL2, + REGS(NOPC, NOPC, NOPC, 0, NOPC)), + + DECODE_END +}; + +static const union decode_item arm_cccc_0000_____1001_table[] = { + /* Multiply and multiply-accumulate */ + + /* MUL cccc 0000 0000 xxxx xxxx xxxx 1001 xxxx */ + /* MULS cccc 0000 0001 xxxx xxxx xxxx 1001 xxxx */ + DECODE_EMULATEX (0x0fe000f0, 0x00000090, PROBES_MUL2, + REGS(NOPC, 0, NOPC, 0, NOPC)), + + /* MLA cccc 0000 0010 xxxx xxxx xxxx 1001 xxxx */ + /* MLAS cccc 0000 0011 xxxx xxxx xxxx 1001 xxxx */ + DECODE_OR (0x0fe000f0, 0x00200090), + /* MLS cccc 0000 0110 xxxx xxxx xxxx 1001 xxxx */ + DECODE_EMULATEX (0x0ff000f0, 0x00600090, PROBES_MUL2, + REGS(NOPC, NOPC, NOPC, 0, NOPC)), + + /* UMAAL cccc 0000 0100 xxxx xxxx xxxx 1001 xxxx */ + DECODE_OR (0x0ff000f0, 0x00400090), + /* UMULL cccc 0000 1000 xxxx xxxx xxxx 1001 xxxx */ + /* UMULLS cccc 0000 1001 xxxx xxxx xxxx 1001 xxxx */ + /* UMLAL cccc 0000 1010 xxxx xxxx xxxx 1001 xxxx */ + /* UMLALS cccc 0000 1011 xxxx xxxx xxxx 1001 xxxx */ + /* SMULL cccc 0000 1100 xxxx xxxx xxxx 1001 xxxx */ + /* SMULLS cccc 0000 1101 xxxx xxxx xxxx 1001 xxxx */ + /* SMLAL cccc 0000 1110 xxxx xxxx xxxx 1001 xxxx */ + /* SMLALS cccc 0000 1111 xxxx xxxx xxxx 1001 xxxx */ + DECODE_EMULATEX (0x0f8000f0, 0x00800090, PROBES_MUL1, + REGS(NOPC, NOPC, NOPC, 0, NOPC)), + + DECODE_END +}; + +static const union decode_item arm_cccc_0001_____1001_table[] = { + /* Synchronization primitives */ + +#if __LINUX_ARM_ARCH__ < 6 + /* Deprecated on ARMv6 and may be UNDEFINED on v7 */ + /* SMP/SWPB cccc 0001 0x00 xxxx xxxx xxxx 1001 xxxx */ + DECODE_EMULATEX (0x0fb000f0, 0x01000090, PROBES_SWP, + REGS(NOPC, NOPC, 0, 0, NOPC)), +#endif + /* LDREX/STREX{,D,B,H} cccc 0001 1xxx xxxx xxxx xxxx 1001 xxxx */ + /* And unallocated instructions... */ + DECODE_END +}; + +static const union decode_item arm_cccc_000x_____1xx1_table[] = { + /* Extra load/store instructions */ + + /* STRHT cccc 0000 xx10 xxxx xxxx xxxx 1011 xxxx */ + /* ??? cccc 0000 xx10 xxxx xxxx xxxx 11x1 xxxx */ + /* LDRHT cccc 0000 xx11 xxxx xxxx xxxx 1011 xxxx */ + /* LDRSBT cccc 0000 xx11 xxxx xxxx xxxx 1101 xxxx */ + /* LDRSHT cccc 0000 xx11 xxxx xxxx xxxx 1111 xxxx */ + DECODE_REJECT (0x0f200090, 0x00200090), + + /* LDRD/STRD lr,pc,{... cccc 000x x0x0 xxxx 111x xxxx 1101 xxxx */ + DECODE_REJECT (0x0e10e0d0, 0x0000e0d0), + + /* LDRD (register) cccc 000x x0x0 xxxx xxxx xxxx 1101 xxxx */ + /* STRD (register) cccc 000x x0x0 xxxx xxxx xxxx 1111 xxxx */ + DECODE_EMULATEX (0x0e5000d0, 0x000000d0, PROBES_LDRSTRD, + REGS(NOPCWB, NOPCX, 0, 0, NOPC)), + + /* LDRD (immediate) cccc 000x x1x0 xxxx xxxx xxxx 1101 xxxx */ + /* STRD (immediate) cccc 000x x1x0 xxxx xxxx xxxx 1111 xxxx */ + DECODE_EMULATEX (0x0e5000d0, 0x004000d0, PROBES_LDRSTRD, + REGS(NOPCWB, NOPCX, 0, 0, 0)), + + /* STRH (register) cccc 000x x0x0 xxxx xxxx xxxx 1011 xxxx */ + DECODE_EMULATEX (0x0e5000f0, 0x000000b0, PROBES_STORE_EXTRA, + REGS(NOPCWB, NOPC, 0, 0, NOPC)), + + /* LDRH (register) cccc 000x x0x1 xxxx xxxx xxxx 1011 xxxx */ + /* LDRSB (register) cccc 000x x0x1 xxxx xxxx xxxx 1101 xxxx */ + /* LDRSH (register) cccc 000x x0x1 xxxx xxxx xxxx 1111 xxxx */ + DECODE_EMULATEX (0x0e500090, 0x00100090, PROBES_LOAD_EXTRA, + REGS(NOPCWB, NOPC, 0, 0, NOPC)), + + /* STRH (immediate) cccc 000x x1x0 xxxx xxxx xxxx 1011 xxxx */ + DECODE_EMULATEX (0x0e5000f0, 0x004000b0, PROBES_STORE_EXTRA, + REGS(NOPCWB, NOPC, 0, 0, 0)), + + /* LDRH (immediate) cccc 000x x1x1 xxxx xxxx xxxx 1011 xxxx */ + /* LDRSB (immediate) cccc 000x x1x1 xxxx xxxx xxxx 1101 xxxx */ + /* LDRSH (immediate) cccc 000x x1x1 xxxx xxxx xxxx 1111 xxxx */ + DECODE_EMULATEX (0x0e500090, 0x00500090, PROBES_LOAD_EXTRA, + REGS(NOPCWB, NOPC, 0, 0, 0)), + + DECODE_END +}; + +static const union decode_item arm_cccc_000x_table[] = { + /* Data-processing (register) */ + + /* S PC, ... cccc 000x xxx1 xxxx 1111 xxxx xxxx xxxx */ + DECODE_REJECT (0x0e10f000, 0x0010f000), + + /* MOV IP, SP 1110 0001 1010 0000 1100 0000 0000 1101 */ + DECODE_SIMULATE (0xffffffff, 0xe1a0c00d, PROBES_MOV_IP_SP), + + /* TST (register) cccc 0001 0001 xxxx xxxx xxxx xxx0 xxxx */ + /* TEQ (register) cccc 0001 0011 xxxx xxxx xxxx xxx0 xxxx */ + /* CMP (register) cccc 0001 0101 xxxx xxxx xxxx xxx0 xxxx */ + /* CMN (register) cccc 0001 0111 xxxx xxxx xxxx xxx0 xxxx */ + DECODE_EMULATEX (0x0f900010, 0x01100000, PROBES_DATA_PROCESSING_REG, + REGS(ANY, 0, 0, 0, ANY)), + + /* MOV (register) cccc 0001 101x xxxx xxxx xxxx xxx0 xxxx */ + /* MVN (register) cccc 0001 111x xxxx xxxx xxxx xxx0 xxxx */ + DECODE_EMULATEX (0x0fa00010, 0x01a00000, PROBES_DATA_PROCESSING_REG, + REGS(0, ANY, 0, 0, ANY)), + + /* AND (register) cccc 0000 000x xxxx xxxx xxxx xxx0 xxxx */ + /* EOR (register) cccc 0000 001x xxxx xxxx xxxx xxx0 xxxx */ + /* SUB (register) cccc 0000 010x xxxx xxxx xxxx xxx0 xxxx */ + /* RSB (register) cccc 0000 011x xxxx xxxx xxxx xxx0 xxxx */ + /* ADD (register) cccc 0000 100x xxxx xxxx xxxx xxx0 xxxx */ + /* ADC (register) cccc 0000 101x xxxx xxxx xxxx xxx0 xxxx */ + /* SBC (register) cccc 0000 110x xxxx xxxx xxxx xxx0 xxxx */ + /* RSC (register) cccc 0000 111x xxxx xxxx xxxx xxx0 xxxx */ + /* ORR (register) cccc 0001 100x xxxx xxxx xxxx xxx0 xxxx */ + /* BIC (register) cccc 0001 110x xxxx xxxx xxxx xxx0 xxxx */ + DECODE_EMULATEX (0x0e000010, 0x00000000, PROBES_DATA_PROCESSING_REG, + REGS(ANY, ANY, 0, 0, ANY)), + + /* TST (reg-shift reg) cccc 0001 0001 xxxx xxxx xxxx 0xx1 xxxx */ + /* TEQ (reg-shift reg) cccc 0001 0011 xxxx xxxx xxxx 0xx1 xxxx */ + /* CMP (reg-shift reg) cccc 0001 0101 xxxx xxxx xxxx 0xx1 xxxx */ + /* CMN (reg-shift reg) cccc 0001 0111 xxxx xxxx xxxx 0xx1 xxxx */ + DECODE_EMULATEX (0x0f900090, 0x01100010, PROBES_DATA_PROCESSING_REG, + REGS(ANY, 0, NOPC, 0, ANY)), + + /* MOV (reg-shift reg) cccc 0001 101x xxxx xxxx xxxx 0xx1 xxxx */ + /* MVN (reg-shift reg) cccc 0001 111x xxxx xxxx xxxx 0xx1 xxxx */ + DECODE_EMULATEX (0x0fa00090, 0x01a00010, PROBES_DATA_PROCESSING_REG, + REGS(0, ANY, NOPC, 0, ANY)), + + /* AND (reg-shift reg) cccc 0000 000x xxxx xxxx xxxx 0xx1 xxxx */ + /* EOR (reg-shift reg) cccc 0000 001x xxxx xxxx xxxx 0xx1 xxxx */ + /* SUB (reg-shift reg) cccc 0000 010x xxxx xxxx xxxx 0xx1 xxxx */ + /* RSB (reg-shift reg) cccc 0000 011x xxxx xxxx xxxx 0xx1 xxxx */ + /* ADD (reg-shift reg) cccc 0000 100x xxxx xxxx xxxx 0xx1 xxxx */ + /* ADC (reg-shift reg) cccc 0000 101x xxxx xxxx xxxx 0xx1 xxxx */ + /* SBC (reg-shift reg) cccc 0000 110x xxxx xxxx xxxx 0xx1 xxxx */ + /* RSC (reg-shift reg) cccc 0000 111x xxxx xxxx xxxx 0xx1 xxxx */ + /* ORR (reg-shift reg) cccc 0001 100x xxxx xxxx xxxx 0xx1 xxxx */ + /* BIC (reg-shift reg) cccc 0001 110x xxxx xxxx xxxx 0xx1 xxxx */ + DECODE_EMULATEX (0x0e000090, 0x00000010, PROBES_DATA_PROCESSING_REG, + REGS(ANY, ANY, NOPC, 0, ANY)), + + DECODE_END +}; + +static const union decode_item arm_cccc_001x_table[] = { + /* Data-processing (immediate) */ + + /* MOVW cccc 0011 0000 xxxx xxxx xxxx xxxx xxxx */ + /* MOVT cccc 0011 0100 xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0x0fb00000, 0x03000000, PROBES_DATA_PROCESSING_IMM, + REGS(0, NOPC, 0, 0, 0)), + + /* YIELD cccc 0011 0010 0000 xxxx xxxx 0000 0001 */ + DECODE_OR (0x0fff00ff, 0x03200001), + /* SEV cccc 0011 0010 0000 xxxx xxxx 0000 0100 */ + DECODE_EMULATE (0x0fff00ff, 0x03200004, PROBES_EMULATE_NONE), + /* NOP cccc 0011 0010 0000 xxxx xxxx 0000 0000 */ + /* WFE cccc 0011 0010 0000 xxxx xxxx 0000 0010 */ + /* WFI cccc 0011 0010 0000 xxxx xxxx 0000 0011 */ + DECODE_SIMULATE (0x0fff00fc, 0x03200000, PROBES_SIMULATE_NOP), + /* DBG cccc 0011 0010 0000 xxxx xxxx ffff xxxx */ + /* unallocated hints cccc 0011 0010 0000 xxxx xxxx xxxx xxxx */ + /* MSR (immediate) cccc 0011 0x10 xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0x0fb00000, 0x03200000), + + /* S PC, ... cccc 001x xxx1 xxxx 1111 xxxx xxxx xxxx */ + DECODE_REJECT (0x0e10f000, 0x0210f000), + + /* TST (immediate) cccc 0011 0001 xxxx xxxx xxxx xxxx xxxx */ + /* TEQ (immediate) cccc 0011 0011 xxxx xxxx xxxx xxxx xxxx */ + /* CMP (immediate) cccc 0011 0101 xxxx xxxx xxxx xxxx xxxx */ + /* CMN (immediate) cccc 0011 0111 xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0x0f900000, 0x03100000, PROBES_DATA_PROCESSING_IMM, + REGS(ANY, 0, 0, 0, 0)), + + /* MOV (immediate) cccc 0011 101x xxxx xxxx xxxx xxxx xxxx */ + /* MVN (immediate) cccc 0011 111x xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0x0fa00000, 0x03a00000, PROBES_DATA_PROCESSING_IMM, + REGS(0, ANY, 0, 0, 0)), + + /* AND (immediate) cccc 0010 000x xxxx xxxx xxxx xxxx xxxx */ + /* EOR (immediate) cccc 0010 001x xxxx xxxx xxxx xxxx xxxx */ + /* SUB (immediate) cccc 0010 010x xxxx xxxx xxxx xxxx xxxx */ + /* RSB (immediate) cccc 0010 011x xxxx xxxx xxxx xxxx xxxx */ + /* ADD (immediate) cccc 0010 100x xxxx xxxx xxxx xxxx xxxx */ + /* ADC (immediate) cccc 0010 101x xxxx xxxx xxxx xxxx xxxx */ + /* SBC (immediate) cccc 0010 110x xxxx xxxx xxxx xxxx xxxx */ + /* RSC (immediate) cccc 0010 111x xxxx xxxx xxxx xxxx xxxx */ + /* ORR (immediate) cccc 0011 100x xxxx xxxx xxxx xxxx xxxx */ + /* BIC (immediate) cccc 0011 110x xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0x0e000000, 0x02000000, PROBES_DATA_PROCESSING_IMM, + REGS(ANY, ANY, 0, 0, 0)), + + DECODE_END +}; + +static const union decode_item arm_cccc_0110_____xxx1_table[] = { + /* Media instructions */ + + /* SEL cccc 0110 1000 xxxx xxxx xxxx 1011 xxxx */ + DECODE_EMULATEX (0x0ff000f0, 0x068000b0, PROBES_SATURATE, + REGS(NOPC, NOPC, 0, 0, NOPC)), + + /* SSAT cccc 0110 101x xxxx xxxx xxxx xx01 xxxx */ + /* USAT cccc 0110 111x xxxx xxxx xxxx xx01 xxxx */ + DECODE_OR(0x0fa00030, 0x06a00010), + /* SSAT16 cccc 0110 1010 xxxx xxxx xxxx 0011 xxxx */ + /* USAT16 cccc 0110 1110 xxxx xxxx xxxx 0011 xxxx */ + DECODE_EMULATEX (0x0fb000f0, 0x06a00030, PROBES_SATURATE, + REGS(0, NOPC, 0, 0, NOPC)), + + /* REV cccc 0110 1011 xxxx xxxx xxxx 0011 xxxx */ + /* REV16 cccc 0110 1011 xxxx xxxx xxxx 1011 xxxx */ + /* RBIT cccc 0110 1111 xxxx xxxx xxxx 0011 xxxx */ + /* REVSH cccc 0110 1111 xxxx xxxx xxxx 1011 xxxx */ + DECODE_EMULATEX (0x0fb00070, 0x06b00030, PROBES_REV, + REGS(0, NOPC, 0, 0, NOPC)), + + /* ??? cccc 0110 0x00 xxxx xxxx xxxx xxx1 xxxx */ + DECODE_REJECT (0x0fb00010, 0x06000010), + /* ??? cccc 0110 0xxx xxxx xxxx xxxx 1011 xxxx */ + DECODE_REJECT (0x0f8000f0, 0x060000b0), + /* ??? cccc 0110 0xxx xxxx xxxx xxxx 1101 xxxx */ + DECODE_REJECT (0x0f8000f0, 0x060000d0), + /* SADD16 cccc 0110 0001 xxxx xxxx xxxx 0001 xxxx */ + /* SADDSUBX cccc 0110 0001 xxxx xxxx xxxx 0011 xxxx */ + /* SSUBADDX cccc 0110 0001 xxxx xxxx xxxx 0101 xxxx */ + /* SSUB16 cccc 0110 0001 xxxx xxxx xxxx 0111 xxxx */ + /* SADD8 cccc 0110 0001 xxxx xxxx xxxx 1001 xxxx */ + /* SSUB8 cccc 0110 0001 xxxx xxxx xxxx 1111 xxxx */ + /* QADD16 cccc 0110 0010 xxxx xxxx xxxx 0001 xxxx */ + /* QADDSUBX cccc 0110 0010 xxxx xxxx xxxx 0011 xxxx */ + /* QSUBADDX cccc 0110 0010 xxxx xxxx xxxx 0101 xxxx */ + /* QSUB16 cccc 0110 0010 xxxx xxxx xxxx 0111 xxxx */ + /* QADD8 cccc 0110 0010 xxxx xxxx xxxx 1001 xxxx */ + /* QSUB8 cccc 0110 0010 xxxx xxxx xxxx 1111 xxxx */ + /* SHADD16 cccc 0110 0011 xxxx xxxx xxxx 0001 xxxx */ + /* SHADDSUBX cccc 0110 0011 xxxx xxxx xxxx 0011 xxxx */ + /* SHSUBADDX cccc 0110 0011 xxxx xxxx xxxx 0101 xxxx */ + /* SHSUB16 cccc 0110 0011 xxxx xxxx xxxx 0111 xxxx */ + /* SHADD8 cccc 0110 0011 xxxx xxxx xxxx 1001 xxxx */ + /* SHSUB8 cccc 0110 0011 xxxx xxxx xxxx 1111 xxxx */ + /* UADD16 cccc 0110 0101 xxxx xxxx xxxx 0001 xxxx */ + /* UADDSUBX cccc 0110 0101 xxxx xxxx xxxx 0011 xxxx */ + /* USUBADDX cccc 0110 0101 xxxx xxxx xxxx 0101 xxxx */ + /* USUB16 cccc 0110 0101 xxxx xxxx xxxx 0111 xxxx */ + /* UADD8 cccc 0110 0101 xxxx xxxx xxxx 1001 xxxx */ + /* USUB8 cccc 0110 0101 xxxx xxxx xxxx 1111 xxxx */ + /* UQADD16 cccc 0110 0110 xxxx xxxx xxxx 0001 xxxx */ + /* UQADDSUBX cccc 0110 0110 xxxx xxxx xxxx 0011 xxxx */ + /* UQSUBADDX cccc 0110 0110 xxxx xxxx xxxx 0101 xxxx */ + /* UQSUB16 cccc 0110 0110 xxxx xxxx xxxx 0111 xxxx */ + /* UQADD8 cccc 0110 0110 xxxx xxxx xxxx 1001 xxxx */ + /* UQSUB8 cccc 0110 0110 xxxx xxxx xxxx 1111 xxxx */ + /* UHADD16 cccc 0110 0111 xxxx xxxx xxxx 0001 xxxx */ + /* UHADDSUBX cccc 0110 0111 xxxx xxxx xxxx 0011 xxxx */ + /* UHSUBADDX cccc 0110 0111 xxxx xxxx xxxx 0101 xxxx */ + /* UHSUB16 cccc 0110 0111 xxxx xxxx xxxx 0111 xxxx */ + /* UHADD8 cccc 0110 0111 xxxx xxxx xxxx 1001 xxxx */ + /* UHSUB8 cccc 0110 0111 xxxx xxxx xxxx 1111 xxxx */ + DECODE_EMULATEX (0x0f800010, 0x06000010, PROBES_MMI, + REGS(NOPC, NOPC, 0, 0, NOPC)), + + /* PKHBT cccc 0110 1000 xxxx xxxx xxxx x001 xxxx */ + /* PKHTB cccc 0110 1000 xxxx xxxx xxxx x101 xxxx */ + DECODE_EMULATEX (0x0ff00030, 0x06800010, PROBES_PACK, + REGS(NOPC, NOPC, 0, 0, NOPC)), + + /* ??? cccc 0110 1001 xxxx xxxx xxxx 0111 xxxx */ + /* ??? cccc 0110 1101 xxxx xxxx xxxx 0111 xxxx */ + DECODE_REJECT (0x0fb000f0, 0x06900070), + + /* SXTB16 cccc 0110 1000 1111 xxxx xxxx 0111 xxxx */ + /* SXTB cccc 0110 1010 1111 xxxx xxxx 0111 xxxx */ + /* SXTH cccc 0110 1011 1111 xxxx xxxx 0111 xxxx */ + /* UXTB16 cccc 0110 1100 1111 xxxx xxxx 0111 xxxx */ + /* UXTB cccc 0110 1110 1111 xxxx xxxx 0111 xxxx */ + /* UXTH cccc 0110 1111 1111 xxxx xxxx 0111 xxxx */ + DECODE_EMULATEX (0x0f8f00f0, 0x068f0070, PROBES_EXTEND, + REGS(0, NOPC, 0, 0, NOPC)), + + /* SXTAB16 cccc 0110 1000 xxxx xxxx xxxx 0111 xxxx */ + /* SXTAB cccc 0110 1010 xxxx xxxx xxxx 0111 xxxx */ + /* SXTAH cccc 0110 1011 xxxx xxxx xxxx 0111 xxxx */ + /* UXTAB16 cccc 0110 1100 xxxx xxxx xxxx 0111 xxxx */ + /* UXTAB cccc 0110 1110 xxxx xxxx xxxx 0111 xxxx */ + /* UXTAH cccc 0110 1111 xxxx xxxx xxxx 0111 xxxx */ + DECODE_EMULATEX (0x0f8000f0, 0x06800070, PROBES_EXTEND_ADD, + REGS(NOPCX, NOPC, 0, 0, NOPC)), + + DECODE_END +}; + +static const union decode_item arm_cccc_0111_____xxx1_table[] = { + /* Media instructions */ + + /* UNDEFINED cccc 0111 1111 xxxx xxxx xxxx 1111 xxxx */ + DECODE_REJECT (0x0ff000f0, 0x07f000f0), + + /* SMLALD cccc 0111 0100 xxxx xxxx xxxx 00x1 xxxx */ + /* SMLSLD cccc 0111 0100 xxxx xxxx xxxx 01x1 xxxx */ + DECODE_EMULATEX (0x0ff00090, 0x07400010, PROBES_MUL_ADD_LONG, + REGS(NOPC, NOPC, NOPC, 0, NOPC)), + + /* SMUAD cccc 0111 0000 xxxx 1111 xxxx 00x1 xxxx */ + /* SMUSD cccc 0111 0000 xxxx 1111 xxxx 01x1 xxxx */ + DECODE_OR (0x0ff0f090, 0x0700f010), + /* SMMUL cccc 0111 0101 xxxx 1111 xxxx 00x1 xxxx */ + DECODE_OR (0x0ff0f0d0, 0x0750f010), + /* USAD8 cccc 0111 1000 xxxx 1111 xxxx 0001 xxxx */ + DECODE_EMULATEX (0x0ff0f0f0, 0x0780f010, PROBES_MUL_ADD, + REGS(NOPC, 0, NOPC, 0, NOPC)), + + /* SMLAD cccc 0111 0000 xxxx xxxx xxxx 00x1 xxxx */ + /* SMLSD cccc 0111 0000 xxxx xxxx xxxx 01x1 xxxx */ + DECODE_OR (0x0ff00090, 0x07000010), + /* SMMLA cccc 0111 0101 xxxx xxxx xxxx 00x1 xxxx */ + DECODE_OR (0x0ff000d0, 0x07500010), + /* USADA8 cccc 0111 1000 xxxx xxxx xxxx 0001 xxxx */ + DECODE_EMULATEX (0x0ff000f0, 0x07800010, PROBES_MUL_ADD, + REGS(NOPC, NOPCX, NOPC, 0, NOPC)), + + /* SMMLS cccc 0111 0101 xxxx xxxx xxxx 11x1 xxxx */ + DECODE_EMULATEX (0x0ff000d0, 0x075000d0, PROBES_MUL_ADD, + REGS(NOPC, NOPC, NOPC, 0, NOPC)), + + /* SBFX cccc 0111 101x xxxx xxxx xxxx x101 xxxx */ + /* UBFX cccc 0111 111x xxxx xxxx xxxx x101 xxxx */ + DECODE_EMULATEX (0x0fa00070, 0x07a00050, PROBES_BITFIELD, + REGS(0, NOPC, 0, 0, NOPC)), + + /* BFC cccc 0111 110x xxxx xxxx xxxx x001 1111 */ + DECODE_EMULATEX (0x0fe0007f, 0x07c0001f, PROBES_BITFIELD, + REGS(0, NOPC, 0, 0, 0)), + + /* BFI cccc 0111 110x xxxx xxxx xxxx x001 xxxx */ + DECODE_EMULATEX (0x0fe00070, 0x07c00010, PROBES_BITFIELD, + REGS(0, NOPC, 0, 0, NOPCX)), + + DECODE_END +}; + +static const union decode_item arm_cccc_01xx_table[] = { + /* Load/store word and unsigned byte */ + + /* LDRB/STRB pc,[...] cccc 01xx x0xx xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0x0c40f000, 0x0440f000), + + /* STRT cccc 01x0 x010 xxxx xxxx xxxx xxxx xxxx */ + /* LDRT cccc 01x0 x011 xxxx xxxx xxxx xxxx xxxx */ + /* STRBT cccc 01x0 x110 xxxx xxxx xxxx xxxx xxxx */ + /* LDRBT cccc 01x0 x111 xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0x0d200000, 0x04200000), + + /* STR (immediate) cccc 010x x0x0 xxxx xxxx xxxx xxxx xxxx */ + /* STRB (immediate) cccc 010x x1x0 xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0x0e100000, 0x04000000, PROBES_STORE, + REGS(NOPCWB, ANY, 0, 0, 0)), + + /* LDR (immediate) cccc 010x x0x1 xxxx xxxx xxxx xxxx xxxx */ + /* LDRB (immediate) cccc 010x x1x1 xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0x0e100000, 0x04100000, PROBES_LOAD, + REGS(NOPCWB, ANY, 0, 0, 0)), + + /* STR (register) cccc 011x x0x0 xxxx xxxx xxxx xxxx xxxx */ + /* STRB (register) cccc 011x x1x0 xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0x0e100000, 0x06000000, PROBES_STORE, + REGS(NOPCWB, ANY, 0, 0, NOPC)), + + /* LDR (register) cccc 011x x0x1 xxxx xxxx xxxx xxxx xxxx */ + /* LDRB (register) cccc 011x x1x1 xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0x0e100000, 0x06100000, PROBES_LOAD, + REGS(NOPCWB, ANY, 0, 0, NOPC)), + + DECODE_END +}; + +static const union decode_item arm_cccc_100x_table[] = { + /* Block data transfer instructions */ + + /* LDM cccc 100x x0x1 xxxx xxxx xxxx xxxx xxxx */ + /* STM cccc 100x x0x0 xxxx xxxx xxxx xxxx xxxx */ + DECODE_CUSTOM (0x0e400000, 0x08000000, PROBES_LDMSTM), + + /* STM (user registers) cccc 100x x1x0 xxxx xxxx xxxx xxxx xxxx */ + /* LDM (user registers) cccc 100x x1x1 xxxx 0xxx xxxx xxxx xxxx */ + /* LDM (exception ret) cccc 100x x1x1 xxxx 1xxx xxxx xxxx xxxx */ + DECODE_END +}; + +const union decode_item probes_decode_arm_table[] = { + /* + * Unconditional instructions + * 1111 xxxx xxxx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xf0000000, 0xf0000000, arm_1111_table), + + /* + * Miscellaneous instructions + * cccc 0001 0xx0 xxxx xxxx xxxx 0xxx xxxx + */ + DECODE_TABLE (0x0f900080, 0x01000000, arm_cccc_0001_0xx0____0xxx_table), + + /* + * Halfword multiply and multiply-accumulate + * cccc 0001 0xx0 xxxx xxxx xxxx 1xx0 xxxx + */ + DECODE_TABLE (0x0f900090, 0x01000080, arm_cccc_0001_0xx0____1xx0_table), + + /* + * Multiply and multiply-accumulate + * cccc 0000 xxxx xxxx xxxx xxxx 1001 xxxx + */ + DECODE_TABLE (0x0f0000f0, 0x00000090, arm_cccc_0000_____1001_table), + + /* + * Synchronization primitives + * cccc 0001 xxxx xxxx xxxx xxxx 1001 xxxx + */ + DECODE_TABLE (0x0f0000f0, 0x01000090, arm_cccc_0001_____1001_table), + + /* + * Extra load/store instructions + * cccc 000x xxxx xxxx xxxx xxxx 1xx1 xxxx + */ + DECODE_TABLE (0x0e000090, 0x00000090, arm_cccc_000x_____1xx1_table), + + /* + * Data-processing (register) + * cccc 000x xxxx xxxx xxxx xxxx xxx0 xxxx + * Data-processing (register-shifted register) + * cccc 000x xxxx xxxx xxxx xxxx 0xx1 xxxx + */ + DECODE_TABLE (0x0e000000, 0x00000000, arm_cccc_000x_table), + + /* + * Data-processing (immediate) + * cccc 001x xxxx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0x0e000000, 0x02000000, arm_cccc_001x_table), + + /* + * Media instructions + * cccc 011x xxxx xxxx xxxx xxxx xxx1 xxxx + */ + DECODE_TABLE (0x0f000010, 0x06000010, arm_cccc_0110_____xxx1_table), + DECODE_TABLE (0x0f000010, 0x07000010, arm_cccc_0111_____xxx1_table), + + /* + * Load/store word and unsigned byte + * cccc 01xx xxxx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0x0c000000, 0x04000000, arm_cccc_01xx_table), + + /* + * Block data transfer instructions + * cccc 100x xxxx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0x0e000000, 0x08000000, arm_cccc_100x_table), + + /* B cccc 1010 xxxx xxxx xxxx xxxx xxxx xxxx */ + /* BL cccc 1011 xxxx xxxx xxxx xxxx xxxx xxxx */ + DECODE_SIMULATE (0x0e000000, 0x0a000000, PROBES_BRANCH), + + /* + * Supervisor Call, and coprocessor instructions + */ + + /* MCRR cccc 1100 0100 xxxx xxxx xxxx xxxx xxxx */ + /* MRRC cccc 1100 0101 xxxx xxxx xxxx xxxx xxxx */ + /* LDC cccc 110x xxx1 xxxx xxxx xxxx xxxx xxxx */ + /* STC cccc 110x xxx0 xxxx xxxx xxxx xxxx xxxx */ + /* CDP cccc 1110 xxxx xxxx xxxx xxxx xxx0 xxxx */ + /* MCR cccc 1110 xxx0 xxxx xxxx xxxx xxx1 xxxx */ + /* MRC cccc 1110 xxx1 xxxx xxxx xxxx xxx1 xxxx */ + /* SVC cccc 1111 xxxx xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0x0c000000, 0x0c000000), + + DECODE_END +}; +#ifdef CONFIG_ARM_KPROBES_TEST_MODULE +EXPORT_SYMBOL_GPL(probes_decode_arm_table); +#endif + +static void __kprobes arm_singlestep(probes_opcode_t insn, + struct arch_probes_insn *asi, struct pt_regs *regs) +{ + regs->ARM_pc += 4; + asi->insn_handler(insn, asi, regs); +} + +/* Return: + * INSN_REJECTED If instruction is one not allowed to kprobe, + * INSN_GOOD If instruction is supported and uses instruction slot, + * INSN_GOOD_NO_SLOT If instruction is supported but doesn't use its slot. + * + * For instructions we don't want to kprobe (INSN_REJECTED return result): + * These are generally ones that modify the processor state making + * them "hard" to simulate such as switches processor modes or + * make accesses in alternate modes. Any of these could be simulated + * if the work was put into it, but low return considering they + * should also be very rare. + */ +enum probes_insn __kprobes +arm_probes_decode_insn(probes_opcode_t insn, struct arch_probes_insn *asi, + bool emulate, const union decode_action *actions) +{ + asi->insn_singlestep = arm_singlestep; + asi->insn_check_cc = probes_condition_checks[insn>>28]; + return probes_decode_insn(insn, asi, probes_decode_arm_table, false, + emulate, actions); +} diff --git a/arch/arm/kernel/probes-arm.h b/arch/arm/kernel/probes-arm.h new file mode 100644 index 00000000000000..ace6572f6e26a2 --- /dev/null +++ b/arch/arm/kernel/probes-arm.h @@ -0,0 +1,73 @@ +/* + * arch/arm/kernel/probes-arm.h + * + * Copyright 2013 Linaro Ltd. + * Written by: David A. Long + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _ARM_KERNEL_PROBES_ARM_H +#define _ARM_KERNEL_PROBES_ARM_H + +enum probes_arm_action { + PROBES_EMULATE_NONE, + PROBES_SIMULATE_NOP, + PROBES_PRELOAD_IMM, + PROBES_PRELOAD_REG, + PROBES_BRANCH_IMM, + PROBES_BRANCH_REG, + PROBES_MRS, + PROBES_CLZ, + PROBES_SATURATING_ARITHMETIC, + PROBES_MUL1, + PROBES_MUL2, + PROBES_SWP, + PROBES_LDRSTRD, + PROBES_LOAD, + PROBES_STORE, + PROBES_LOAD_EXTRA, + PROBES_STORE_EXTRA, + PROBES_MOV_IP_SP, + PROBES_DATA_PROCESSING_REG, + PROBES_DATA_PROCESSING_IMM, + PROBES_MOV_HALFWORD, + PROBES_SEV, + PROBES_WFE, + PROBES_SATURATE, + PROBES_REV, + PROBES_MMI, + PROBES_PACK, + PROBES_EXTEND, + PROBES_EXTEND_ADD, + PROBES_MUL_ADD_LONG, + PROBES_MUL_ADD, + PROBES_BITFIELD, + PROBES_BRANCH, + PROBES_LDMSTM, + NUM_PROBES_ARM_ACTIONS +}; + +void __kprobes simulate_bbl(probes_opcode_t opcode, + struct arch_probes_insn *asi, struct pt_regs *regs); +void __kprobes simulate_blx1(probes_opcode_t opcode, + struct arch_probes_insn *asi, struct pt_regs *regs); +void __kprobes simulate_blx2bx(probes_opcode_t opcode, + struct arch_probes_insn *asi, struct pt_regs *regs); +void __kprobes simulate_mrs(probes_opcode_t opcode, + struct arch_probes_insn *asi, struct pt_regs *regs); +void __kprobes simulate_mov_ipsp(probes_opcode_t opcode, + struct arch_probes_insn *asi, struct pt_regs *regs); + +extern const union decode_item probes_decode_arm_table[]; + +enum probes_insn arm_probes_decode_insn(probes_opcode_t, + struct arch_probes_insn *, bool emulate, + const union decode_action *actions); + +#endif diff --git a/arch/arm/kernel/probes-thumb.c b/arch/arm/kernel/probes-thumb.c new file mode 100644 index 00000000000000..4131351e812f47 --- /dev/null +++ b/arch/arm/kernel/probes-thumb.c @@ -0,0 +1,882 @@ +/* + * arch/arm/kernel/probes-thumb.c + * + * Copyright (C) 2011 Jon Medhurst . + * + * 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 +#include +#include + +#include "probes.h" +#include "probes-thumb.h" + + +static const union decode_item t32_table_1110_100x_x0xx[] = { + /* Load/store multiple instructions */ + + /* Rn is PC 1110 100x x0xx 1111 xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xfe4f0000, 0xe80f0000), + + /* SRS 1110 1000 00x0 xxxx xxxx xxxx xxxx xxxx */ + /* RFE 1110 1000 00x1 xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xffc00000, 0xe8000000), + /* SRS 1110 1001 10x0 xxxx xxxx xxxx xxxx xxxx */ + /* RFE 1110 1001 10x1 xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xffc00000, 0xe9800000), + + /* STM Rn, {...pc} 1110 100x x0x0 xxxx 1xxx xxxx xxxx xxxx */ + DECODE_REJECT (0xfe508000, 0xe8008000), + /* LDM Rn, {...lr,pc} 1110 100x x0x1 xxxx 11xx xxxx xxxx xxxx */ + DECODE_REJECT (0xfe50c000, 0xe810c000), + /* LDM/STM Rn, {...sp} 1110 100x x0xx xxxx xx1x xxxx xxxx xxxx */ + DECODE_REJECT (0xfe402000, 0xe8002000), + + /* STMIA 1110 1000 10x0 xxxx xxxx xxxx xxxx xxxx */ + /* LDMIA 1110 1000 10x1 xxxx xxxx xxxx xxxx xxxx */ + /* STMDB 1110 1001 00x0 xxxx xxxx xxxx xxxx xxxx */ + /* LDMDB 1110 1001 00x1 xxxx xxxx xxxx xxxx xxxx */ + DECODE_CUSTOM (0xfe400000, 0xe8000000, PROBES_T32_LDMSTM), + + DECODE_END +}; + +static const union decode_item t32_table_1110_100x_x1xx[] = { + /* Load/store dual, load/store exclusive, table branch */ + + /* STRD (immediate) 1110 1000 x110 xxxx xxxx xxxx xxxx xxxx */ + /* LDRD (immediate) 1110 1000 x111 xxxx xxxx xxxx xxxx xxxx */ + DECODE_OR (0xff600000, 0xe8600000), + /* STRD (immediate) 1110 1001 x1x0 xxxx xxxx xxxx xxxx xxxx */ + /* LDRD (immediate) 1110 1001 x1x1 xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xff400000, 0xe9400000, PROBES_T32_LDRDSTRD, + REGS(NOPCWB, NOSPPC, NOSPPC, 0, 0)), + + /* TBB 1110 1000 1101 xxxx xxxx xxxx 0000 xxxx */ + /* TBH 1110 1000 1101 xxxx xxxx xxxx 0001 xxxx */ + DECODE_SIMULATEX(0xfff000e0, 0xe8d00000, PROBES_T32_TABLE_BRANCH, + REGS(NOSP, 0, 0, 0, NOSPPC)), + + /* STREX 1110 1000 0100 xxxx xxxx xxxx xxxx xxxx */ + /* LDREX 1110 1000 0101 xxxx xxxx xxxx xxxx xxxx */ + /* STREXB 1110 1000 1100 xxxx xxxx xxxx 0100 xxxx */ + /* STREXH 1110 1000 1100 xxxx xxxx xxxx 0101 xxxx */ + /* STREXD 1110 1000 1100 xxxx xxxx xxxx 0111 xxxx */ + /* LDREXB 1110 1000 1101 xxxx xxxx xxxx 0100 xxxx */ + /* LDREXH 1110 1000 1101 xxxx xxxx xxxx 0101 xxxx */ + /* LDREXD 1110 1000 1101 xxxx xxxx xxxx 0111 xxxx */ + /* And unallocated instructions... */ + DECODE_END +}; + +static const union decode_item t32_table_1110_101x[] = { + /* Data-processing (shifted register) */ + + /* TST 1110 1010 0001 xxxx xxxx 1111 xxxx xxxx */ + /* TEQ 1110 1010 1001 xxxx xxxx 1111 xxxx xxxx */ + DECODE_EMULATEX (0xff700f00, 0xea100f00, PROBES_T32_TST, + REGS(NOSPPC, 0, 0, 0, NOSPPC)), + + /* CMN 1110 1011 0001 xxxx xxxx 1111 xxxx xxxx */ + DECODE_OR (0xfff00f00, 0xeb100f00), + /* CMP 1110 1011 1011 xxxx xxxx 1111 xxxx xxxx */ + DECODE_EMULATEX (0xfff00f00, 0xebb00f00, PROBES_T32_TST, + REGS(NOPC, 0, 0, 0, NOSPPC)), + + /* MOV 1110 1010 010x 1111 xxxx xxxx xxxx xxxx */ + /* MVN 1110 1010 011x 1111 xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xffcf0000, 0xea4f0000, PROBES_T32_MOV, + REGS(0, 0, NOSPPC, 0, NOSPPC)), + + /* ??? 1110 1010 101x xxxx xxxx xxxx xxxx xxxx */ + /* ??? 1110 1010 111x xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xffa00000, 0xeaa00000), + /* ??? 1110 1011 001x xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xffe00000, 0xeb200000), + /* ??? 1110 1011 100x xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xffe00000, 0xeb800000), + /* ??? 1110 1011 111x xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xffe00000, 0xebe00000), + + /* ADD/SUB SP, SP, Rm, LSL #0..3 */ + /* 1110 1011 x0xx 1101 x000 1101 xx00 xxxx */ + DECODE_EMULATEX (0xff4f7f30, 0xeb0d0d00, PROBES_T32_ADDSUB, + REGS(SP, 0, SP, 0, NOSPPC)), + + /* ADD/SUB SP, SP, Rm, shift */ + /* 1110 1011 x0xx 1101 xxxx 1101 xxxx xxxx */ + DECODE_REJECT (0xff4f0f00, 0xeb0d0d00), + + /* ADD/SUB Rd, SP, Rm, shift */ + /* 1110 1011 x0xx 1101 xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xff4f0000, 0xeb0d0000, PROBES_T32_ADDSUB, + REGS(SP, 0, NOPC, 0, NOSPPC)), + + /* AND 1110 1010 000x xxxx xxxx xxxx xxxx xxxx */ + /* BIC 1110 1010 001x xxxx xxxx xxxx xxxx xxxx */ + /* ORR 1110 1010 010x xxxx xxxx xxxx xxxx xxxx */ + /* ORN 1110 1010 011x xxxx xxxx xxxx xxxx xxxx */ + /* EOR 1110 1010 100x xxxx xxxx xxxx xxxx xxxx */ + /* PKH 1110 1010 110x xxxx xxxx xxxx xxxx xxxx */ + /* ADD 1110 1011 000x xxxx xxxx xxxx xxxx xxxx */ + /* ADC 1110 1011 010x xxxx xxxx xxxx xxxx xxxx */ + /* SBC 1110 1011 011x xxxx xxxx xxxx xxxx xxxx */ + /* SUB 1110 1011 101x xxxx xxxx xxxx xxxx xxxx */ + /* RSB 1110 1011 110x xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfe000000, 0xea000000, PROBES_T32_LOGICAL, + REGS(NOSPPC, 0, NOSPPC, 0, NOSPPC)), + + DECODE_END +}; + +static const union decode_item t32_table_1111_0x0x___0[] = { + /* Data-processing (modified immediate) */ + + /* TST 1111 0x00 0001 xxxx 0xxx 1111 xxxx xxxx */ + /* TEQ 1111 0x00 1001 xxxx 0xxx 1111 xxxx xxxx */ + DECODE_EMULATEX (0xfb708f00, 0xf0100f00, PROBES_T32_TST, + REGS(NOSPPC, 0, 0, 0, 0)), + + /* CMN 1111 0x01 0001 xxxx 0xxx 1111 xxxx xxxx */ + DECODE_OR (0xfbf08f00, 0xf1100f00), + /* CMP 1111 0x01 1011 xxxx 0xxx 1111 xxxx xxxx */ + DECODE_EMULATEX (0xfbf08f00, 0xf1b00f00, PROBES_T32_CMP, + REGS(NOPC, 0, 0, 0, 0)), + + /* MOV 1111 0x00 010x 1111 0xxx xxxx xxxx xxxx */ + /* MVN 1111 0x00 011x 1111 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfbcf8000, 0xf04f0000, PROBES_T32_MOV, + REGS(0, 0, NOSPPC, 0, 0)), + + /* ??? 1111 0x00 101x xxxx 0xxx xxxx xxxx xxxx */ + DECODE_REJECT (0xfbe08000, 0xf0a00000), + /* ??? 1111 0x00 110x xxxx 0xxx xxxx xxxx xxxx */ + /* ??? 1111 0x00 111x xxxx 0xxx xxxx xxxx xxxx */ + DECODE_REJECT (0xfbc08000, 0xf0c00000), + /* ??? 1111 0x01 001x xxxx 0xxx xxxx xxxx xxxx */ + DECODE_REJECT (0xfbe08000, 0xf1200000), + /* ??? 1111 0x01 100x xxxx 0xxx xxxx xxxx xxxx */ + DECODE_REJECT (0xfbe08000, 0xf1800000), + /* ??? 1111 0x01 111x xxxx 0xxx xxxx xxxx xxxx */ + DECODE_REJECT (0xfbe08000, 0xf1e00000), + + /* ADD Rd, SP, #imm 1111 0x01 000x 1101 0xxx xxxx xxxx xxxx */ + /* SUB Rd, SP, #imm 1111 0x01 101x 1101 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfb4f8000, 0xf10d0000, PROBES_T32_ADDSUB, + REGS(SP, 0, NOPC, 0, 0)), + + /* AND 1111 0x00 000x xxxx 0xxx xxxx xxxx xxxx */ + /* BIC 1111 0x00 001x xxxx 0xxx xxxx xxxx xxxx */ + /* ORR 1111 0x00 010x xxxx 0xxx xxxx xxxx xxxx */ + /* ORN 1111 0x00 011x xxxx 0xxx xxxx xxxx xxxx */ + /* EOR 1111 0x00 100x xxxx 0xxx xxxx xxxx xxxx */ + /* ADD 1111 0x01 000x xxxx 0xxx xxxx xxxx xxxx */ + /* ADC 1111 0x01 010x xxxx 0xxx xxxx xxxx xxxx */ + /* SBC 1111 0x01 011x xxxx 0xxx xxxx xxxx xxxx */ + /* SUB 1111 0x01 101x xxxx 0xxx xxxx xxxx xxxx */ + /* RSB 1111 0x01 110x xxxx 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfa008000, 0xf0000000, PROBES_T32_LOGICAL, + REGS(NOSPPC, 0, NOSPPC, 0, 0)), + + DECODE_END +}; + +static const union decode_item t32_table_1111_0x1x___0[] = { + /* Data-processing (plain binary immediate) */ + + /* ADDW Rd, PC, #imm 1111 0x10 0000 1111 0xxx xxxx xxxx xxxx */ + DECODE_OR (0xfbff8000, 0xf20f0000), + /* SUBW Rd, PC, #imm 1111 0x10 1010 1111 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfbff8000, 0xf2af0000, PROBES_T32_ADDWSUBW_PC, + REGS(PC, 0, NOSPPC, 0, 0)), + + /* ADDW SP, SP, #imm 1111 0x10 0000 1101 0xxx 1101 xxxx xxxx */ + DECODE_OR (0xfbff8f00, 0xf20d0d00), + /* SUBW SP, SP, #imm 1111 0x10 1010 1101 0xxx 1101 xxxx xxxx */ + DECODE_EMULATEX (0xfbff8f00, 0xf2ad0d00, PROBES_T32_ADDWSUBW, + REGS(SP, 0, SP, 0, 0)), + + /* ADDW 1111 0x10 0000 xxxx 0xxx xxxx xxxx xxxx */ + DECODE_OR (0xfbf08000, 0xf2000000), + /* SUBW 1111 0x10 1010 xxxx 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfbf08000, 0xf2a00000, PROBES_T32_ADDWSUBW, + REGS(NOPCX, 0, NOSPPC, 0, 0)), + + /* MOVW 1111 0x10 0100 xxxx 0xxx xxxx xxxx xxxx */ + /* MOVT 1111 0x10 1100 xxxx 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfb708000, 0xf2400000, PROBES_T32_MOVW, + REGS(0, 0, NOSPPC, 0, 0)), + + /* SSAT16 1111 0x11 0010 xxxx 0000 xxxx 00xx xxxx */ + /* SSAT 1111 0x11 00x0 xxxx 0xxx xxxx xxxx xxxx */ + /* USAT16 1111 0x11 1010 xxxx 0000 xxxx 00xx xxxx */ + /* USAT 1111 0x11 10x0 xxxx 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfb508000, 0xf3000000, PROBES_T32_SAT, + REGS(NOSPPC, 0, NOSPPC, 0, 0)), + + /* SFBX 1111 0x11 0100 xxxx 0xxx xxxx xxxx xxxx */ + /* UFBX 1111 0x11 1100 xxxx 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfb708000, 0xf3400000, PROBES_T32_BITFIELD, + REGS(NOSPPC, 0, NOSPPC, 0, 0)), + + /* BFC 1111 0x11 0110 1111 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfbff8000, 0xf36f0000, PROBES_T32_BITFIELD, + REGS(0, 0, NOSPPC, 0, 0)), + + /* BFI 1111 0x11 0110 xxxx 0xxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfbf08000, 0xf3600000, PROBES_T32_BITFIELD, + REGS(NOSPPCX, 0, NOSPPC, 0, 0)), + + DECODE_END +}; + +static const union decode_item t32_table_1111_0xxx___1[] = { + /* Branches and miscellaneous control */ + + /* YIELD 1111 0011 1010 xxxx 10x0 x000 0000 0001 */ + DECODE_OR (0xfff0d7ff, 0xf3a08001), + /* SEV 1111 0011 1010 xxxx 10x0 x000 0000 0100 */ + DECODE_EMULATE (0xfff0d7ff, 0xf3a08004, PROBES_T32_SEV), + /* NOP 1111 0011 1010 xxxx 10x0 x000 0000 0000 */ + /* WFE 1111 0011 1010 xxxx 10x0 x000 0000 0010 */ + /* WFI 1111 0011 1010 xxxx 10x0 x000 0000 0011 */ + DECODE_SIMULATE (0xfff0d7fc, 0xf3a08000, PROBES_T32_WFE), + + /* MRS Rd, CPSR 1111 0011 1110 xxxx 10x0 xxxx xxxx xxxx */ + DECODE_SIMULATEX(0xfff0d000, 0xf3e08000, PROBES_T32_MRS, + REGS(0, 0, NOSPPC, 0, 0)), + + /* + * Unsupported instructions + * 1111 0x11 1xxx xxxx 10x0 xxxx xxxx xxxx + * + * MSR 1111 0011 100x xxxx 10x0 xxxx xxxx xxxx + * DBG hint 1111 0011 1010 xxxx 10x0 x000 1111 xxxx + * Unallocated hints 1111 0011 1010 xxxx 10x0 x000 xxxx xxxx + * CPS 1111 0011 1010 xxxx 10x0 xxxx xxxx xxxx + * CLREX/DSB/DMB/ISB 1111 0011 1011 xxxx 10x0 xxxx xxxx xxxx + * BXJ 1111 0011 1100 xxxx 10x0 xxxx xxxx xxxx + * SUBS PC,LR,# 1111 0011 1101 xxxx 10x0 xxxx xxxx xxxx + * MRS Rd, SPSR 1111 0011 1111 xxxx 10x0 xxxx xxxx xxxx + * SMC 1111 0111 1111 xxxx 1000 xxxx xxxx xxxx + * UNDEFINED 1111 0111 1111 xxxx 1010 xxxx xxxx xxxx + * ??? 1111 0111 1xxx xxxx 1010 xxxx xxxx xxxx + */ + DECODE_REJECT (0xfb80d000, 0xf3808000), + + /* Bcc 1111 0xxx xxxx xxxx 10x0 xxxx xxxx xxxx */ + DECODE_CUSTOM (0xf800d000, 0xf0008000, PROBES_T32_BRANCH_COND), + + /* BLX 1111 0xxx xxxx xxxx 11x0 xxxx xxxx xxx0 */ + DECODE_OR (0xf800d001, 0xf000c000), + /* B 1111 0xxx xxxx xxxx 10x1 xxxx xxxx xxxx */ + /* BL 1111 0xxx xxxx xxxx 11x1 xxxx xxxx xxxx */ + DECODE_SIMULATE (0xf8009000, 0xf0009000, PROBES_T32_BRANCH), + + DECODE_END +}; + +static const union decode_item t32_table_1111_100x_x0x1__1111[] = { + /* Memory hints */ + + /* PLD (literal) 1111 1000 x001 1111 1111 xxxx xxxx xxxx */ + /* PLI (literal) 1111 1001 x001 1111 1111 xxxx xxxx xxxx */ + DECODE_SIMULATE (0xfe7ff000, 0xf81ff000, PROBES_T32_PLDI), + + /* PLD{W} (immediate) 1111 1000 10x1 xxxx 1111 xxxx xxxx xxxx */ + DECODE_OR (0xffd0f000, 0xf890f000), + /* PLD{W} (immediate) 1111 1000 00x1 xxxx 1111 1100 xxxx xxxx */ + DECODE_OR (0xffd0ff00, 0xf810fc00), + /* PLI (immediate) 1111 1001 1001 xxxx 1111 xxxx xxxx xxxx */ + DECODE_OR (0xfff0f000, 0xf990f000), + /* PLI (immediate) 1111 1001 0001 xxxx 1111 1100 xxxx xxxx */ + DECODE_SIMULATEX(0xfff0ff00, 0xf910fc00, PROBES_T32_PLDI, + REGS(NOPCX, 0, 0, 0, 0)), + + /* PLD{W} (register) 1111 1000 00x1 xxxx 1111 0000 00xx xxxx */ + DECODE_OR (0xffd0ffc0, 0xf810f000), + /* PLI (register) 1111 1001 0001 xxxx 1111 0000 00xx xxxx */ + DECODE_SIMULATEX(0xfff0ffc0, 0xf910f000, PROBES_T32_PLDI, + REGS(NOPCX, 0, 0, 0, NOSPPC)), + + /* Other unallocated instructions... */ + DECODE_END +}; + +static const union decode_item t32_table_1111_100x[] = { + /* Store/Load single data item */ + + /* ??? 1111 100x x11x xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xfe600000, 0xf8600000), + + /* ??? 1111 1001 0101 xxxx xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xfff00000, 0xf9500000), + + /* ??? 1111 100x 0xxx xxxx xxxx 10x0 xxxx xxxx */ + DECODE_REJECT (0xfe800d00, 0xf8000800), + + /* STRBT 1111 1000 0000 xxxx xxxx 1110 xxxx xxxx */ + /* STRHT 1111 1000 0010 xxxx xxxx 1110 xxxx xxxx */ + /* STRT 1111 1000 0100 xxxx xxxx 1110 xxxx xxxx */ + /* LDRBT 1111 1000 0001 xxxx xxxx 1110 xxxx xxxx */ + /* LDRSBT 1111 1001 0001 xxxx xxxx 1110 xxxx xxxx */ + /* LDRHT 1111 1000 0011 xxxx xxxx 1110 xxxx xxxx */ + /* LDRSHT 1111 1001 0011 xxxx xxxx 1110 xxxx xxxx */ + /* LDRT 1111 1000 0101 xxxx xxxx 1110 xxxx xxxx */ + DECODE_REJECT (0xfe800f00, 0xf8000e00), + + /* STR{,B,H} Rn,[PC...] 1111 1000 xxx0 1111 xxxx xxxx xxxx xxxx */ + DECODE_REJECT (0xff1f0000, 0xf80f0000), + + /* STR{,B,H} PC,[Rn...] 1111 1000 xxx0 xxxx 1111 xxxx xxxx xxxx */ + DECODE_REJECT (0xff10f000, 0xf800f000), + + /* LDR (literal) 1111 1000 x101 1111 xxxx xxxx xxxx xxxx */ + DECODE_SIMULATEX(0xff7f0000, 0xf85f0000, PROBES_T32_LDR_LIT, + REGS(PC, ANY, 0, 0, 0)), + + /* STR (immediate) 1111 1000 0100 xxxx xxxx 1xxx xxxx xxxx */ + /* LDR (immediate) 1111 1000 0101 xxxx xxxx 1xxx xxxx xxxx */ + DECODE_OR (0xffe00800, 0xf8400800), + /* STR (immediate) 1111 1000 1100 xxxx xxxx xxxx xxxx xxxx */ + /* LDR (immediate) 1111 1000 1101 xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xffe00000, 0xf8c00000, PROBES_T32_LDRSTR, + REGS(NOPCX, ANY, 0, 0, 0)), + + /* STR (register) 1111 1000 0100 xxxx xxxx 0000 00xx xxxx */ + /* LDR (register) 1111 1000 0101 xxxx xxxx 0000 00xx xxxx */ + DECODE_EMULATEX (0xffe00fc0, 0xf8400000, PROBES_T32_LDRSTR, + REGS(NOPCX, ANY, 0, 0, NOSPPC)), + + /* LDRB (literal) 1111 1000 x001 1111 xxxx xxxx xxxx xxxx */ + /* LDRSB (literal) 1111 1001 x001 1111 xxxx xxxx xxxx xxxx */ + /* LDRH (literal) 1111 1000 x011 1111 xxxx xxxx xxxx xxxx */ + /* LDRSH (literal) 1111 1001 x011 1111 xxxx xxxx xxxx xxxx */ + DECODE_SIMULATEX(0xfe5f0000, 0xf81f0000, PROBES_T32_LDR_LIT, + REGS(PC, NOSPPCX, 0, 0, 0)), + + /* STRB (immediate) 1111 1000 0000 xxxx xxxx 1xxx xxxx xxxx */ + /* STRH (immediate) 1111 1000 0010 xxxx xxxx 1xxx xxxx xxxx */ + /* LDRB (immediate) 1111 1000 0001 xxxx xxxx 1xxx xxxx xxxx */ + /* LDRSB (immediate) 1111 1001 0001 xxxx xxxx 1xxx xxxx xxxx */ + /* LDRH (immediate) 1111 1000 0011 xxxx xxxx 1xxx xxxx xxxx */ + /* LDRSH (immediate) 1111 1001 0011 xxxx xxxx 1xxx xxxx xxxx */ + DECODE_OR (0xfec00800, 0xf8000800), + /* STRB (immediate) 1111 1000 1000 xxxx xxxx xxxx xxxx xxxx */ + /* STRH (immediate) 1111 1000 1010 xxxx xxxx xxxx xxxx xxxx */ + /* LDRB (immediate) 1111 1000 1001 xxxx xxxx xxxx xxxx xxxx */ + /* LDRSB (immediate) 1111 1001 1001 xxxx xxxx xxxx xxxx xxxx */ + /* LDRH (immediate) 1111 1000 1011 xxxx xxxx xxxx xxxx xxxx */ + /* LDRSH (immediate) 1111 1001 1011 xxxx xxxx xxxx xxxx xxxx */ + DECODE_EMULATEX (0xfec00000, 0xf8800000, PROBES_T32_LDRSTR, + REGS(NOPCX, NOSPPCX, 0, 0, 0)), + + /* STRB (register) 1111 1000 0000 xxxx xxxx 0000 00xx xxxx */ + /* STRH (register) 1111 1000 0010 xxxx xxxx 0000 00xx xxxx */ + /* LDRB (register) 1111 1000 0001 xxxx xxxx 0000 00xx xxxx */ + /* LDRSB (register) 1111 1001 0001 xxxx xxxx 0000 00xx xxxx */ + /* LDRH (register) 1111 1000 0011 xxxx xxxx 0000 00xx xxxx */ + /* LDRSH (register) 1111 1001 0011 xxxx xxxx 0000 00xx xxxx */ + DECODE_EMULATEX (0xfe800fc0, 0xf8000000, PROBES_T32_LDRSTR, + REGS(NOPCX, NOSPPCX, 0, 0, NOSPPC)), + + /* Other unallocated instructions... */ + DECODE_END +}; + +static const union decode_item t32_table_1111_1010___1111[] = { + /* Data-processing (register) */ + + /* ??? 1111 1010 011x xxxx 1111 xxxx 1xxx xxxx */ + DECODE_REJECT (0xffe0f080, 0xfa60f080), + + /* SXTH 1111 1010 0000 1111 1111 xxxx 1xxx xxxx */ + /* UXTH 1111 1010 0001 1111 1111 xxxx 1xxx xxxx */ + /* SXTB16 1111 1010 0010 1111 1111 xxxx 1xxx xxxx */ + /* UXTB16 1111 1010 0011 1111 1111 xxxx 1xxx xxxx */ + /* SXTB 1111 1010 0100 1111 1111 xxxx 1xxx xxxx */ + /* UXTB 1111 1010 0101 1111 1111 xxxx 1xxx xxxx */ + DECODE_EMULATEX (0xff8ff080, 0xfa0ff080, PROBES_T32_SIGN_EXTEND, + REGS(0, 0, NOSPPC, 0, NOSPPC)), + + + /* ??? 1111 1010 1xxx xxxx 1111 xxxx 0x11 xxxx */ + DECODE_REJECT (0xff80f0b0, 0xfa80f030), + /* ??? 1111 1010 1x11 xxxx 1111 xxxx 0xxx xxxx */ + DECODE_REJECT (0xffb0f080, 0xfab0f000), + + /* SADD16 1111 1010 1001 xxxx 1111 xxxx 0000 xxxx */ + /* SASX 1111 1010 1010 xxxx 1111 xxxx 0000 xxxx */ + /* SSAX 1111 1010 1110 xxxx 1111 xxxx 0000 xxxx */ + /* SSUB16 1111 1010 1101 xxxx 1111 xxxx 0000 xxxx */ + /* SADD8 1111 1010 1000 xxxx 1111 xxxx 0000 xxxx */ + /* SSUB8 1111 1010 1100 xxxx 1111 xxxx 0000 xxxx */ + + /* QADD16 1111 1010 1001 xxxx 1111 xxxx 0001 xxxx */ + /* QASX 1111 1010 1010 xxxx 1111 xxxx 0001 xxxx */ + /* QSAX 1111 1010 1110 xxxx 1111 xxxx 0001 xxxx */ + /* QSUB16 1111 1010 1101 xxxx 1111 xxxx 0001 xxxx */ + /* QADD8 1111 1010 1000 xxxx 1111 xxxx 0001 xxxx */ + /* QSUB8 1111 1010 1100 xxxx 1111 xxxx 0001 xxxx */ + + /* SHADD16 1111 1010 1001 xxxx 1111 xxxx 0010 xxxx */ + /* SHASX 1111 1010 1010 xxxx 1111 xxxx 0010 xxxx */ + /* SHSAX 1111 1010 1110 xxxx 1111 xxxx 0010 xxxx */ + /* SHSUB16 1111 1010 1101 xxxx 1111 xxxx 0010 xxxx */ + /* SHADD8 1111 1010 1000 xxxx 1111 xxxx 0010 xxxx */ + /* SHSUB8 1111 1010 1100 xxxx 1111 xxxx 0010 xxxx */ + + /* UADD16 1111 1010 1001 xxxx 1111 xxxx 0100 xxxx */ + /* UASX 1111 1010 1010 xxxx 1111 xxxx 0100 xxxx */ + /* USAX 1111 1010 1110 xxxx 1111 xxxx 0100 xxxx */ + /* USUB16 1111 1010 1101 xxxx 1111 xxxx 0100 xxxx */ + /* UADD8 1111 1010 1000 xxxx 1111 xxxx 0100 xxxx */ + /* USUB8 1111 1010 1100 xxxx 1111 xxxx 0100 xxxx */ + + /* UQADD16 1111 1010 1001 xxxx 1111 xxxx 0101 xxxx */ + /* UQASX 1111 1010 1010 xxxx 1111 xxxx 0101 xxxx */ + /* UQSAX 1111 1010 1110 xxxx 1111 xxxx 0101 xxxx */ + /* UQSUB16 1111 1010 1101 xxxx 1111 xxxx 0101 xxxx */ + /* UQADD8 1111 1010 1000 xxxx 1111 xxxx 0101 xxxx */ + /* UQSUB8 1111 1010 1100 xxxx 1111 xxxx 0101 xxxx */ + + /* UHADD16 1111 1010 1001 xxxx 1111 xxxx 0110 xxxx */ + /* UHASX 1111 1010 1010 xxxx 1111 xxxx 0110 xxxx */ + /* UHSAX 1111 1010 1110 xxxx 1111 xxxx 0110 xxxx */ + /* UHSUB16 1111 1010 1101 xxxx 1111 xxxx 0110 xxxx */ + /* UHADD8 1111 1010 1000 xxxx 1111 xxxx 0110 xxxx */ + /* UHSUB8 1111 1010 1100 xxxx 1111 xxxx 0110 xxxx */ + DECODE_OR (0xff80f080, 0xfa80f000), + + /* SXTAH 1111 1010 0000 xxxx 1111 xxxx 1xxx xxxx */ + /* UXTAH 1111 1010 0001 xxxx 1111 xxxx 1xxx xxxx */ + /* SXTAB16 1111 1010 0010 xxxx 1111 xxxx 1xxx xxxx */ + /* UXTAB16 1111 1010 0011 xxxx 1111 xxxx 1xxx xxxx */ + /* SXTAB 1111 1010 0100 xxxx 1111 xxxx 1xxx xxxx */ + /* UXTAB 1111 1010 0101 xxxx 1111 xxxx 1xxx xxxx */ + DECODE_OR (0xff80f080, 0xfa00f080), + + /* QADD 1111 1010 1000 xxxx 1111 xxxx 1000 xxxx */ + /* QDADD 1111 1010 1000 xxxx 1111 xxxx 1001 xxxx */ + /* QSUB 1111 1010 1000 xxxx 1111 xxxx 1010 xxxx */ + /* QDSUB 1111 1010 1000 xxxx 1111 xxxx 1011 xxxx */ + DECODE_OR (0xfff0f0c0, 0xfa80f080), + + /* SEL 1111 1010 1010 xxxx 1111 xxxx 1000 xxxx */ + DECODE_OR (0xfff0f0f0, 0xfaa0f080), + + /* LSL 1111 1010 000x xxxx 1111 xxxx 0000 xxxx */ + /* LSR 1111 1010 001x xxxx 1111 xxxx 0000 xxxx */ + /* ASR 1111 1010 010x xxxx 1111 xxxx 0000 xxxx */ + /* ROR 1111 1010 011x xxxx 1111 xxxx 0000 xxxx */ + DECODE_EMULATEX (0xff80f0f0, 0xfa00f000, PROBES_T32_MEDIA, + REGS(NOSPPC, 0, NOSPPC, 0, NOSPPC)), + + /* CLZ 1111 1010 1010 xxxx 1111 xxxx 1000 xxxx */ + DECODE_OR (0xfff0f0f0, 0xfab0f080), + + /* REV 1111 1010 1001 xxxx 1111 xxxx 1000 xxxx */ + /* REV16 1111 1010 1001 xxxx 1111 xxxx 1001 xxxx */ + /* RBIT 1111 1010 1001 xxxx 1111 xxxx 1010 xxxx */ + /* REVSH 1111 1010 1001 xxxx 1111 xxxx 1011 xxxx */ + DECODE_EMULATEX (0xfff0f0c0, 0xfa90f080, PROBES_T32_REVERSE, + REGS(NOSPPC, 0, NOSPPC, 0, SAMEAS16)), + + /* Other unallocated instructions... */ + DECODE_END +}; + +static const union decode_item t32_table_1111_1011_0[] = { + /* Multiply, multiply accumulate, and absolute difference */ + + /* ??? 1111 1011 0000 xxxx 1111 xxxx 0001 xxxx */ + DECODE_REJECT (0xfff0f0f0, 0xfb00f010), + /* ??? 1111 1011 0111 xxxx 1111 xxxx 0001 xxxx */ + DECODE_REJECT (0xfff0f0f0, 0xfb70f010), + + /* SMULxy 1111 1011 0001 xxxx 1111 xxxx 00xx xxxx */ + DECODE_OR (0xfff0f0c0, 0xfb10f000), + /* MUL 1111 1011 0000 xxxx 1111 xxxx 0000 xxxx */ + /* SMUAD{X} 1111 1011 0010 xxxx 1111 xxxx 000x xxxx */ + /* SMULWy 1111 1011 0011 xxxx 1111 xxxx 000x xxxx */ + /* SMUSD{X} 1111 1011 0100 xxxx 1111 xxxx 000x xxxx */ + /* SMMUL{R} 1111 1011 0101 xxxx 1111 xxxx 000x xxxx */ + /* USAD8 1111 1011 0111 xxxx 1111 xxxx 0000 xxxx */ + DECODE_EMULATEX (0xff80f0e0, 0xfb00f000, PROBES_T32_MUL_ADD, + REGS(NOSPPC, 0, NOSPPC, 0, NOSPPC)), + + /* ??? 1111 1011 0111 xxxx xxxx xxxx 0001 xxxx */ + DECODE_REJECT (0xfff000f0, 0xfb700010), + + /* SMLAxy 1111 1011 0001 xxxx xxxx xxxx 00xx xxxx */ + DECODE_OR (0xfff000c0, 0xfb100000), + /* MLA 1111 1011 0000 xxxx xxxx xxxx 0000 xxxx */ + /* MLS 1111 1011 0000 xxxx xxxx xxxx 0001 xxxx */ + /* SMLAD{X} 1111 1011 0010 xxxx xxxx xxxx 000x xxxx */ + /* SMLAWy 1111 1011 0011 xxxx xxxx xxxx 000x xxxx */ + /* SMLSD{X} 1111 1011 0100 xxxx xxxx xxxx 000x xxxx */ + /* SMMLA{R} 1111 1011 0101 xxxx xxxx xxxx 000x xxxx */ + /* SMMLS{R} 1111 1011 0110 xxxx xxxx xxxx 000x xxxx */ + /* USADA8 1111 1011 0111 xxxx xxxx xxxx 0000 xxxx */ + DECODE_EMULATEX (0xff8000c0, 0xfb000000, PROBES_T32_MUL_ADD2, + REGS(NOSPPC, NOSPPCX, NOSPPC, 0, NOSPPC)), + + /* Other unallocated instructions... */ + DECODE_END +}; + +static const union decode_item t32_table_1111_1011_1[] = { + /* Long multiply, long multiply accumulate, and divide */ + + /* UMAAL 1111 1011 1110 xxxx xxxx xxxx 0110 xxxx */ + DECODE_OR (0xfff000f0, 0xfbe00060), + /* SMLALxy 1111 1011 1100 xxxx xxxx xxxx 10xx xxxx */ + DECODE_OR (0xfff000c0, 0xfbc00080), + /* SMLALD{X} 1111 1011 1100 xxxx xxxx xxxx 110x xxxx */ + /* SMLSLD{X} 1111 1011 1101 xxxx xxxx xxxx 110x xxxx */ + DECODE_OR (0xffe000e0, 0xfbc000c0), + /* SMULL 1111 1011 1000 xxxx xxxx xxxx 0000 xxxx */ + /* UMULL 1111 1011 1010 xxxx xxxx xxxx 0000 xxxx */ + /* SMLAL 1111 1011 1100 xxxx xxxx xxxx 0000 xxxx */ + /* UMLAL 1111 1011 1110 xxxx xxxx xxxx 0000 xxxx */ + DECODE_EMULATEX (0xff9000f0, 0xfb800000, PROBES_T32_MUL_ADD_LONG, + REGS(NOSPPC, NOSPPC, NOSPPC, 0, NOSPPC)), + + /* SDIV 1111 1011 1001 xxxx xxxx xxxx 1111 xxxx */ + /* UDIV 1111 1011 1011 xxxx xxxx xxxx 1111 xxxx */ + /* Other unallocated instructions... */ + DECODE_END +}; + +const union decode_item probes_decode_thumb32_table[] = { + + /* + * Load/store multiple instructions + * 1110 100x x0xx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xfe400000, 0xe8000000, t32_table_1110_100x_x0xx), + + /* + * Load/store dual, load/store exclusive, table branch + * 1110 100x x1xx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xfe400000, 0xe8400000, t32_table_1110_100x_x1xx), + + /* + * Data-processing (shifted register) + * 1110 101x xxxx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xfe000000, 0xea000000, t32_table_1110_101x), + + /* + * Coprocessor instructions + * 1110 11xx xxxx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_REJECT (0xfc000000, 0xec000000), + + /* + * Data-processing (modified immediate) + * 1111 0x0x xxxx xxxx 0xxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xfa008000, 0xf0000000, t32_table_1111_0x0x___0), + + /* + * Data-processing (plain binary immediate) + * 1111 0x1x xxxx xxxx 0xxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xfa008000, 0xf2000000, t32_table_1111_0x1x___0), + + /* + * Branches and miscellaneous control + * 1111 0xxx xxxx xxxx 1xxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xf8008000, 0xf0008000, t32_table_1111_0xxx___1), + + /* + * Advanced SIMD element or structure load/store instructions + * 1111 1001 xxx0 xxxx xxxx xxxx xxxx xxxx + */ + DECODE_REJECT (0xff100000, 0xf9000000), + + /* + * Memory hints + * 1111 100x x0x1 xxxx 1111 xxxx xxxx xxxx + */ + DECODE_TABLE (0xfe50f000, 0xf810f000, t32_table_1111_100x_x0x1__1111), + + /* + * Store single data item + * 1111 1000 xxx0 xxxx xxxx xxxx xxxx xxxx + * Load single data items + * 1111 100x xxx1 xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xfe000000, 0xf8000000, t32_table_1111_100x), + + /* + * Data-processing (register) + * 1111 1010 xxxx xxxx 1111 xxxx xxxx xxxx + */ + DECODE_TABLE (0xff00f000, 0xfa00f000, t32_table_1111_1010___1111), + + /* + * Multiply, multiply accumulate, and absolute difference + * 1111 1011 0xxx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xff800000, 0xfb000000, t32_table_1111_1011_0), + + /* + * Long multiply, long multiply accumulate, and divide + * 1111 1011 1xxx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_TABLE (0xff800000, 0xfb800000, t32_table_1111_1011_1), + + /* + * Coprocessor instructions + * 1111 11xx xxxx xxxx xxxx xxxx xxxx xxxx + */ + DECODE_END +}; +#ifdef CONFIG_ARM_KPROBES_TEST_MODULE +EXPORT_SYMBOL_GPL(probes_decode_thumb32_table); +#endif + +static const union decode_item t16_table_1011[] = { + /* Miscellaneous 16-bit instructions */ + + /* ADD (SP plus immediate) 1011 0000 0xxx xxxx */ + /* SUB (SP minus immediate) 1011 0000 1xxx xxxx */ + DECODE_SIMULATE (0xff00, 0xb000, PROBES_T16_ADD_SP), + + /* CBZ 1011 00x1 xxxx xxxx */ + /* CBNZ 1011 10x1 xxxx xxxx */ + DECODE_SIMULATE (0xf500, 0xb100, PROBES_T16_CBZ), + + /* SXTH 1011 0010 00xx xxxx */ + /* SXTB 1011 0010 01xx xxxx */ + /* UXTH 1011 0010 10xx xxxx */ + /* UXTB 1011 0010 11xx xxxx */ + /* REV 1011 1010 00xx xxxx */ + /* REV16 1011 1010 01xx xxxx */ + /* ??? 1011 1010 10xx xxxx */ + /* REVSH 1011 1010 11xx xxxx */ + DECODE_REJECT (0xffc0, 0xba80), + DECODE_EMULATE (0xf500, 0xb000, PROBES_T16_SIGN_EXTEND), + + /* PUSH 1011 010x xxxx xxxx */ + DECODE_CUSTOM (0xfe00, 0xb400, PROBES_T16_PUSH), + /* POP 1011 110x xxxx xxxx */ + DECODE_CUSTOM (0xfe00, 0xbc00, PROBES_T16_POP), + + /* + * If-Then, and hints + * 1011 1111 xxxx xxxx + */ + + /* YIELD 1011 1111 0001 0000 */ + DECODE_OR (0xffff, 0xbf10), + /* SEV 1011 1111 0100 0000 */ + DECODE_EMULATE (0xffff, 0xbf40, PROBES_T16_SEV), + /* NOP 1011 1111 0000 0000 */ + /* WFE 1011 1111 0010 0000 */ + /* WFI 1011 1111 0011 0000 */ + DECODE_SIMULATE (0xffcf, 0xbf00, PROBES_T16_WFE), + /* Unassigned hints 1011 1111 xxxx 0000 */ + DECODE_REJECT (0xff0f, 0xbf00), + /* IT 1011 1111 xxxx xxxx */ + DECODE_CUSTOM (0xff00, 0xbf00, PROBES_T16_IT), + + /* SETEND 1011 0110 010x xxxx */ + /* CPS 1011 0110 011x xxxx */ + /* BKPT 1011 1110 xxxx xxxx */ + /* And unallocated instructions... */ + DECODE_END +}; + +const union decode_item probes_decode_thumb16_table[] = { + + /* + * Shift (immediate), add, subtract, move, and compare + * 00xx xxxx xxxx xxxx + */ + + /* CMP (immediate) 0010 1xxx xxxx xxxx */ + DECODE_EMULATE (0xf800, 0x2800, PROBES_T16_CMP), + + /* ADD (register) 0001 100x xxxx xxxx */ + /* SUB (register) 0001 101x xxxx xxxx */ + /* LSL (immediate) 0000 0xxx xxxx xxxx */ + /* LSR (immediate) 0000 1xxx xxxx xxxx */ + /* ASR (immediate) 0001 0xxx xxxx xxxx */ + /* ADD (immediate, Thumb) 0001 110x xxxx xxxx */ + /* SUB (immediate, Thumb) 0001 111x xxxx xxxx */ + /* MOV (immediate) 0010 0xxx xxxx xxxx */ + /* ADD (immediate, Thumb) 0011 0xxx xxxx xxxx */ + /* SUB (immediate, Thumb) 0011 1xxx xxxx xxxx */ + DECODE_EMULATE (0xc000, 0x0000, PROBES_T16_ADDSUB), + + /* + * 16-bit Thumb data-processing instructions + * 0100 00xx xxxx xxxx + */ + + /* TST (register) 0100 0010 00xx xxxx */ + DECODE_EMULATE (0xffc0, 0x4200, PROBES_T16_CMP), + /* CMP (register) 0100 0010 10xx xxxx */ + /* CMN (register) 0100 0010 11xx xxxx */ + DECODE_EMULATE (0xff80, 0x4280, PROBES_T16_CMP), + /* AND (register) 0100 0000 00xx xxxx */ + /* EOR (register) 0100 0000 01xx xxxx */ + /* LSL (register) 0100 0000 10xx xxxx */ + /* LSR (register) 0100 0000 11xx xxxx */ + /* ASR (register) 0100 0001 00xx xxxx */ + /* ADC (register) 0100 0001 01xx xxxx */ + /* SBC (register) 0100 0001 10xx xxxx */ + /* ROR (register) 0100 0001 11xx xxxx */ + /* RSB (immediate) 0100 0010 01xx xxxx */ + /* ORR (register) 0100 0011 00xx xxxx */ + /* MUL 0100 0011 00xx xxxx */ + /* BIC (register) 0100 0011 10xx xxxx */ + /* MVN (register) 0100 0011 10xx xxxx */ + DECODE_EMULATE (0xfc00, 0x4000, PROBES_T16_LOGICAL), + + /* + * Special data instructions and branch and exchange + * 0100 01xx xxxx xxxx + */ + + /* BLX pc 0100 0111 1111 1xxx */ + DECODE_REJECT (0xfff8, 0x47f8), + + /* BX (register) 0100 0111 0xxx xxxx */ + /* BLX (register) 0100 0111 1xxx xxxx */ + DECODE_SIMULATE (0xff00, 0x4700, PROBES_T16_BLX), + + /* ADD pc, pc 0100 0100 1111 1111 */ + DECODE_REJECT (0xffff, 0x44ff), + + /* ADD (register) 0100 0100 xxxx xxxx */ + /* CMP (register) 0100 0101 xxxx xxxx */ + /* MOV (register) 0100 0110 xxxx xxxx */ + DECODE_CUSTOM (0xfc00, 0x4400, PROBES_T16_HIREGOPS), + + /* + * Load from Literal Pool + * LDR (literal) 0100 1xxx xxxx xxxx + */ + DECODE_SIMULATE (0xf800, 0x4800, PROBES_T16_LDR_LIT), + + /* + * 16-bit Thumb Load/store instructions + * 0101 xxxx xxxx xxxx + * 011x xxxx xxxx xxxx + * 100x xxxx xxxx xxxx + */ + + /* STR (register) 0101 000x xxxx xxxx */ + /* STRH (register) 0101 001x xxxx xxxx */ + /* STRB (register) 0101 010x xxxx xxxx */ + /* LDRSB (register) 0101 011x xxxx xxxx */ + /* LDR (register) 0101 100x xxxx xxxx */ + /* LDRH (register) 0101 101x xxxx xxxx */ + /* LDRB (register) 0101 110x xxxx xxxx */ + /* LDRSH (register) 0101 111x xxxx xxxx */ + /* STR (immediate, Thumb) 0110 0xxx xxxx xxxx */ + /* LDR (immediate, Thumb) 0110 1xxx xxxx xxxx */ + /* STRB (immediate, Thumb) 0111 0xxx xxxx xxxx */ + /* LDRB (immediate, Thumb) 0111 1xxx xxxx xxxx */ + DECODE_EMULATE (0xc000, 0x4000, PROBES_T16_LDRHSTRH), + /* STRH (immediate, Thumb) 1000 0xxx xxxx xxxx */ + /* LDRH (immediate, Thumb) 1000 1xxx xxxx xxxx */ + DECODE_EMULATE (0xf000, 0x8000, PROBES_T16_LDRHSTRH), + /* STR (immediate, Thumb) 1001 0xxx xxxx xxxx */ + /* LDR (immediate, Thumb) 1001 1xxx xxxx xxxx */ + DECODE_SIMULATE (0xf000, 0x9000, PROBES_T16_LDRSTR), + + /* + * Generate PC-/SP-relative address + * ADR (literal) 1010 0xxx xxxx xxxx + * ADD (SP plus immediate) 1010 1xxx xxxx xxxx + */ + DECODE_SIMULATE (0xf000, 0xa000, PROBES_T16_ADR), + + /* + * Miscellaneous 16-bit instructions + * 1011 xxxx xxxx xxxx + */ + DECODE_TABLE (0xf000, 0xb000, t16_table_1011), + + /* STM 1100 0xxx xxxx xxxx */ + /* LDM 1100 1xxx xxxx xxxx */ + DECODE_EMULATE (0xf000, 0xc000, PROBES_T16_LDMSTM), + + /* + * Conditional branch, and Supervisor Call + */ + + /* Permanently UNDEFINED 1101 1110 xxxx xxxx */ + /* SVC 1101 1111 xxxx xxxx */ + DECODE_REJECT (0xfe00, 0xde00), + + /* Conditional branch 1101 xxxx xxxx xxxx */ + DECODE_CUSTOM (0xf000, 0xd000, PROBES_T16_BRANCH_COND), + + /* + * Unconditional branch + * B 1110 0xxx xxxx xxxx + */ + DECODE_SIMULATE (0xf800, 0xe000, PROBES_T16_BRANCH), + + DECODE_END +}; +#ifdef CONFIG_ARM_KPROBES_TEST_MODULE +EXPORT_SYMBOL_GPL(probes_decode_thumb16_table); +#endif + +static unsigned long __kprobes thumb_check_cc(unsigned long cpsr) +{ + if (unlikely(in_it_block(cpsr))) + return probes_condition_checks[current_cond(cpsr)](cpsr); + return true; +} + +static void __kprobes thumb16_singlestep(probes_opcode_t opcode, + struct arch_probes_insn *asi, + struct pt_regs *regs) +{ + regs->ARM_pc += 2; + asi->insn_handler(opcode, asi, regs); + regs->ARM_cpsr = it_advance(regs->ARM_cpsr); +} + +static void __kprobes thumb32_singlestep(probes_opcode_t opcode, + struct arch_probes_insn *asi, + struct pt_regs *regs) +{ + regs->ARM_pc += 4; + asi->insn_handler(opcode, asi, regs); + regs->ARM_cpsr = it_advance(regs->ARM_cpsr); +} + +enum probes_insn __kprobes +thumb16_probes_decode_insn(probes_opcode_t insn, struct arch_probes_insn *asi, + bool emulate, const union decode_action *actions) +{ + asi->insn_singlestep = thumb16_singlestep; + asi->insn_check_cc = thumb_check_cc; + return probes_decode_insn(insn, asi, probes_decode_thumb16_table, true, + emulate, actions); +} + +enum probes_insn __kprobes +thumb32_probes_decode_insn(probes_opcode_t insn, struct arch_probes_insn *asi, + bool emulate, const union decode_action *actions) +{ + asi->insn_singlestep = thumb32_singlestep; + asi->insn_check_cc = thumb_check_cc; + return probes_decode_insn(insn, asi, probes_decode_thumb32_table, true, + emulate, actions); +} diff --git a/arch/arm/kernel/probes-thumb.h b/arch/arm/kernel/probes-thumb.h new file mode 100644 index 00000000000000..7c6f6ebe514fee --- /dev/null +++ b/arch/arm/kernel/probes-thumb.h @@ -0,0 +1,97 @@ +/* + * arch/arm/kernel/probes-thumb.h + * + * Copyright 2013 Linaro Ltd. + * Written by: David A. Long + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _ARM_KERNEL_PROBES_THUMB_H +#define _ARM_KERNEL_PROBES_THUMB_H + +/* + * True if current instruction is in an IT block. + */ +#define in_it_block(cpsr) ((cpsr & 0x06000c00) != 0x00000000) + +/* + * Return the condition code to check for the currently executing instruction. + * This is in ITSTATE<7:4> which is in CPSR<15:12> but is only valid if + * in_it_block returns true. + */ +#define current_cond(cpsr) ((cpsr >> 12) & 0xf) + +enum probes_t32_action { + PROBES_T32_EMULATE_NONE, + PROBES_T32_SIMULATE_NOP, + PROBES_T32_LDMSTM, + PROBES_T32_LDRDSTRD, + PROBES_T32_TABLE_BRANCH, + PROBES_T32_TST, + PROBES_T32_CMP, + PROBES_T32_MOV, + PROBES_T32_ADDSUB, + PROBES_T32_LOGICAL, + PROBES_T32_ADDWSUBW_PC, + PROBES_T32_ADDWSUBW, + PROBES_T32_MOVW, + PROBES_T32_SAT, + PROBES_T32_BITFIELD, + PROBES_T32_SEV, + PROBES_T32_WFE, + PROBES_T32_MRS, + PROBES_T32_BRANCH_COND, + PROBES_T32_BRANCH, + PROBES_T32_PLDI, + PROBES_T32_LDR_LIT, + PROBES_T32_LDRSTR, + PROBES_T32_SIGN_EXTEND, + PROBES_T32_MEDIA, + PROBES_T32_REVERSE, + PROBES_T32_MUL_ADD, + PROBES_T32_MUL_ADD2, + PROBES_T32_MUL_ADD_LONG, + NUM_PROBES_T32_ACTIONS +}; + +enum probes_t16_action { + PROBES_T16_ADD_SP, + PROBES_T16_CBZ, + PROBES_T16_SIGN_EXTEND, + PROBES_T16_PUSH, + PROBES_T16_POP, + PROBES_T16_SEV, + PROBES_T16_WFE, + PROBES_T16_IT, + PROBES_T16_CMP, + PROBES_T16_ADDSUB, + PROBES_T16_LOGICAL, + PROBES_T16_BLX, + PROBES_T16_HIREGOPS, + PROBES_T16_LDR_LIT, + PROBES_T16_LDRHSTRH, + PROBES_T16_LDRSTR, + PROBES_T16_ADR, + PROBES_T16_LDMSTM, + PROBES_T16_BRANCH_COND, + PROBES_T16_BRANCH, + NUM_PROBES_T16_ACTIONS +}; + +extern const union decode_item probes_decode_thumb32_table[]; +extern const union decode_item probes_decode_thumb16_table[]; + +enum probes_insn __kprobes +thumb16_probes_decode_insn(probes_opcode_t insn, struct arch_probes_insn *asi, + bool emulate, const union decode_action *actions); +enum probes_insn __kprobes +thumb32_probes_decode_insn(probes_opcode_t insn, struct arch_probes_insn *asi, + bool emulate, const union decode_action *actions); + +#endif diff --git a/arch/arm/kernel/probes.c b/arch/arm/kernel/probes.c new file mode 100644 index 00000000000000..c4408cfff0d0e0 --- /dev/null +++ b/arch/arm/kernel/probes.c @@ -0,0 +1,456 @@ +/* + * arch/arm/kernel/probes.c + * + * Copyright (C) 2011 Jon Medhurst . + * + * Some contents moved here from arch/arm/include/asm/kprobes-arm.c which is + * Copyright (C) 2006, 2007 Motorola Inc. + * + * 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 +#include +#include +#include +#include + +#include "probes.h" + + +#ifndef find_str_pc_offset + +/* + * For STR and STM instructions, an ARM core may choose to use either + * a +8 or a +12 displacement from the current instruction's address. + * Whichever value is chosen for a given core, it must be the same for + * both instructions and may not change. This function measures it. + */ + +int str_pc_offset; + +void __init find_str_pc_offset(void) +{ + int addr, scratch, ret; + + __asm__ ( + "sub %[ret], pc, #4 \n\t" + "str pc, %[addr] \n\t" + "ldr %[scr], %[addr] \n\t" + "sub %[ret], %[scr], %[ret] \n\t" + : [ret] "=r" (ret), [scr] "=r" (scratch), [addr] "+m" (addr)); + + str_pc_offset = ret; +} + +#endif /* !find_str_pc_offset */ + + +#ifndef test_load_write_pc_interworking + +bool load_write_pc_interworks; + +void __init test_load_write_pc_interworking(void) +{ + int arch = cpu_architecture(); + BUG_ON(arch == CPU_ARCH_UNKNOWN); + load_write_pc_interworks = arch >= CPU_ARCH_ARMv5T; +} + +#endif /* !test_load_write_pc_interworking */ + + +#ifndef test_alu_write_pc_interworking + +bool alu_write_pc_interworks; + +void __init test_alu_write_pc_interworking(void) +{ + int arch = cpu_architecture(); + BUG_ON(arch == CPU_ARCH_UNKNOWN); + alu_write_pc_interworks = arch >= CPU_ARCH_ARMv7; +} + +#endif /* !test_alu_write_pc_interworking */ + + +void __init arm_probes_decode_init(void) +{ + find_str_pc_offset(); + test_load_write_pc_interworking(); + test_alu_write_pc_interworking(); +} + + +static unsigned long __kprobes __check_eq(unsigned long cpsr) +{ + return cpsr & PSR_Z_BIT; +} + +static unsigned long __kprobes __check_ne(unsigned long cpsr) +{ + return (~cpsr) & PSR_Z_BIT; +} + +static unsigned long __kprobes __check_cs(unsigned long cpsr) +{ + return cpsr & PSR_C_BIT; +} + +static unsigned long __kprobes __check_cc(unsigned long cpsr) +{ + return (~cpsr) & PSR_C_BIT; +} + +static unsigned long __kprobes __check_mi(unsigned long cpsr) +{ + return cpsr & PSR_N_BIT; +} + +static unsigned long __kprobes __check_pl(unsigned long cpsr) +{ + return (~cpsr) & PSR_N_BIT; +} + +static unsigned long __kprobes __check_vs(unsigned long cpsr) +{ + return cpsr & PSR_V_BIT; +} + +static unsigned long __kprobes __check_vc(unsigned long cpsr) +{ + return (~cpsr) & PSR_V_BIT; +} + +static unsigned long __kprobes __check_hi(unsigned long cpsr) +{ + cpsr &= ~(cpsr >> 1); /* PSR_C_BIT &= ~PSR_Z_BIT */ + return cpsr & PSR_C_BIT; +} + +static unsigned long __kprobes __check_ls(unsigned long cpsr) +{ + cpsr &= ~(cpsr >> 1); /* PSR_C_BIT &= ~PSR_Z_BIT */ + return (~cpsr) & PSR_C_BIT; +} + +static unsigned long __kprobes __check_ge(unsigned long cpsr) +{ + cpsr ^= (cpsr << 3); /* PSR_N_BIT ^= PSR_V_BIT */ + return (~cpsr) & PSR_N_BIT; +} + +static unsigned long __kprobes __check_lt(unsigned long cpsr) +{ + cpsr ^= (cpsr << 3); /* PSR_N_BIT ^= PSR_V_BIT */ + return cpsr & PSR_N_BIT; +} + +static unsigned long __kprobes __check_gt(unsigned long cpsr) +{ + unsigned long temp = cpsr ^ (cpsr << 3); /* PSR_N_BIT ^= PSR_V_BIT */ + temp |= (cpsr << 1); /* PSR_N_BIT |= PSR_Z_BIT */ + return (~temp) & PSR_N_BIT; +} + +static unsigned long __kprobes __check_le(unsigned long cpsr) +{ + unsigned long temp = cpsr ^ (cpsr << 3); /* PSR_N_BIT ^= PSR_V_BIT */ + temp |= (cpsr << 1); /* PSR_N_BIT |= PSR_Z_BIT */ + return temp & PSR_N_BIT; +} + +static unsigned long __kprobes __check_al(unsigned long cpsr) +{ + return true; +} + +probes_check_cc * const probes_condition_checks[16] = { + &__check_eq, &__check_ne, &__check_cs, &__check_cc, + &__check_mi, &__check_pl, &__check_vs, &__check_vc, + &__check_hi, &__check_ls, &__check_ge, &__check_lt, + &__check_gt, &__check_le, &__check_al, &__check_al +}; + + +void __kprobes probes_simulate_nop(probes_opcode_t opcode, + struct arch_probes_insn *asi, + struct pt_regs *regs) +{ +} + +void __kprobes probes_emulate_none(probes_opcode_t opcode, + struct arch_probes_insn *asi, + struct pt_regs *regs) +{ + asi->insn_fn(); +} + +/* + * Prepare an instruction slot to receive an instruction for emulating. + * This is done by placing a subroutine return after the location where the + * instruction will be placed. We also modify ARM instructions to be + * unconditional as the condition code will already be checked before any + * emulation handler is called. + */ +static probes_opcode_t __kprobes +prepare_emulated_insn(probes_opcode_t insn, struct arch_probes_insn *asi, + bool thumb) +{ +#ifdef CONFIG_THUMB2_KERNEL + if (thumb) { + u16 *thumb_insn = (u16 *)asi->insn; + /* Thumb bx lr */ + thumb_insn[1] = __opcode_to_mem_thumb16(0x4770); + thumb_insn[2] = __opcode_to_mem_thumb16(0x4770); + return insn; + } + asi->insn[1] = __opcode_to_mem_arm(0xe12fff1e); /* ARM bx lr */ +#else + asi->insn[1] = __opcode_to_mem_arm(0xe1a0f00e); /* mov pc, lr */ +#endif + /* Make an ARM instruction unconditional */ + if (insn < 0xe0000000) + insn = (insn | 0xe0000000) & ~0x10000000; + return insn; +} + +/* + * Write a (probably modified) instruction into the slot previously prepared by + * prepare_emulated_insn + */ +static void __kprobes +set_emulated_insn(probes_opcode_t insn, struct arch_probes_insn *asi, + bool thumb) +{ +#ifdef CONFIG_THUMB2_KERNEL + if (thumb) { + u16 *ip = (u16 *)asi->insn; + if (is_wide_instruction(insn)) + *ip++ = __opcode_to_mem_thumb16(insn >> 16); + *ip++ = __opcode_to_mem_thumb16(insn); + return; + } +#endif + asi->insn[0] = __opcode_to_mem_arm(insn); +} + +/* + * When we modify the register numbers encoded in an instruction to be emulated, + * the new values come from this define. For ARM and 32-bit Thumb instructions + * this gives... + * + * bit position 16 12 8 4 0 + * ---------------+---+---+---+---+---+ + * register r2 r0 r1 -- r3 + */ +#define INSN_NEW_BITS 0x00020103 + +/* Each nibble has same value as that at INSN_NEW_BITS bit 16 */ +#define INSN_SAMEAS16_BITS 0x22222222 + +/* + * Validate and modify each of the registers encoded in an instruction. + * + * Each nibble in regs contains a value from enum decode_reg_type. For each + * non-zero value, the corresponding nibble in pinsn is validated and modified + * according to the type. + */ +static bool __kprobes decode_regs(probes_opcode_t *pinsn, u32 regs, bool modify) +{ + probes_opcode_t insn = *pinsn; + probes_opcode_t mask = 0xf; /* Start at least significant nibble */ + + for (; regs != 0; regs >>= 4, mask <<= 4) { + + probes_opcode_t new_bits = INSN_NEW_BITS; + + switch (regs & 0xf) { + + case REG_TYPE_NONE: + /* Nibble not a register, skip to next */ + continue; + + case REG_TYPE_ANY: + /* Any register is allowed */ + break; + + case REG_TYPE_SAMEAS16: + /* Replace register with same as at bit position 16 */ + new_bits = INSN_SAMEAS16_BITS; + break; + + case REG_TYPE_SP: + /* Only allow SP (R13) */ + if ((insn ^ 0xdddddddd) & mask) + goto reject; + break; + + case REG_TYPE_PC: + /* Only allow PC (R15) */ + if ((insn ^ 0xffffffff) & mask) + goto reject; + break; + + case REG_TYPE_NOSP: + /* Reject SP (R13) */ + if (((insn ^ 0xdddddddd) & mask) == 0) + goto reject; + break; + + case REG_TYPE_NOSPPC: + case REG_TYPE_NOSPPCX: + /* Reject SP and PC (R13 and R15) */ + if (((insn ^ 0xdddddddd) & 0xdddddddd & mask) == 0) + goto reject; + break; + + case REG_TYPE_NOPCWB: + if (!is_writeback(insn)) + break; /* No writeback, so any register is OK */ + /* fall through... */ + case REG_TYPE_NOPC: + case REG_TYPE_NOPCX: + /* Reject PC (R15) */ + if (((insn ^ 0xffffffff) & mask) == 0) + goto reject; + break; + } + + /* Replace value of nibble with new register number... */ + insn &= ~mask; + insn |= new_bits & mask; + } + + if (modify) + *pinsn = insn; + + return true; + +reject: + return false; +} + +static const int decode_struct_sizes[NUM_DECODE_TYPES] = { + [DECODE_TYPE_TABLE] = sizeof(struct decode_table), + [DECODE_TYPE_CUSTOM] = sizeof(struct decode_custom), + [DECODE_TYPE_SIMULATE] = sizeof(struct decode_simulate), + [DECODE_TYPE_EMULATE] = sizeof(struct decode_emulate), + [DECODE_TYPE_OR] = sizeof(struct decode_or), + [DECODE_TYPE_REJECT] = sizeof(struct decode_reject) +}; + +/* + * probes_decode_insn operates on data tables in order to decode an ARM + * architecture instruction onto which a kprobe has been placed. + * + * These instruction decoding tables are a concatenation of entries each + * of which consist of one of the following structs: + * + * decode_table + * decode_custom + * decode_simulate + * decode_emulate + * decode_or + * decode_reject + * + * Each of these starts with a struct decode_header which has the following + * fields: + * + * type_regs + * mask + * value + * + * The least significant DECODE_TYPE_BITS of type_regs contains a value + * from enum decode_type, this indicates which of the decode_* structs + * the entry contains. The value DECODE_TYPE_END indicates the end of the + * table. + * + * When the table is parsed, each entry is checked in turn to see if it + * matches the instruction to be decoded using the test: + * + * (insn & mask) == value + * + * If no match is found before the end of the table is reached then decoding + * fails with INSN_REJECTED. + * + * When a match is found, decode_regs() is called to validate and modify each + * of the registers encoded in the instruction; the data it uses to do this + * is (type_regs >> DECODE_TYPE_BITS). A validation failure will cause decoding + * to fail with INSN_REJECTED. + * + * Once the instruction has passed the above tests, further processing + * depends on the type of the table entry's decode struct. + * + */ +int __kprobes +probes_decode_insn(probes_opcode_t insn, struct arch_probes_insn *asi, + const union decode_item *table, bool thumb, + bool emulate, const union decode_action *actions) +{ + const struct decode_header *h = (struct decode_header *)table; + const struct decode_header *next; + bool matched = false; + + if (emulate) + insn = prepare_emulated_insn(insn, asi, thumb); + + for (;; h = next) { + enum decode_type type = h->type_regs.bits & DECODE_TYPE_MASK; + u32 regs = h->type_regs.bits >> DECODE_TYPE_BITS; + + if (type == DECODE_TYPE_END) + return INSN_REJECTED; + + next = (struct decode_header *) + ((uintptr_t)h + decode_struct_sizes[type]); + + if (!matched && (insn & h->mask.bits) != h->value.bits) + continue; + + if (!decode_regs(&insn, regs, emulate)) + return INSN_REJECTED; + + switch (type) { + + case DECODE_TYPE_TABLE: { + struct decode_table *d = (struct decode_table *)h; + next = (struct decode_header *)d->table.table; + break; + } + + case DECODE_TYPE_CUSTOM: { + struct decode_custom *d = (struct decode_custom *)h; + return actions[d->decoder.action].decoder(insn, asi, h); + } + + case DECODE_TYPE_SIMULATE: { + struct decode_simulate *d = (struct decode_simulate *)h; + asi->insn_handler = actions[d->handler.action].handler; + return INSN_GOOD_NO_SLOT; + } + + case DECODE_TYPE_EMULATE: { + struct decode_emulate *d = (struct decode_emulate *)h; + + if (!emulate) + return actions[d->handler.action].decoder(insn, + asi, h); + + asi->insn_handler = actions[d->handler.action].handler; + set_emulated_insn(insn, asi, thumb); + return INSN_GOOD; + } + + case DECODE_TYPE_OR: + matched = true; + break; + + case DECODE_TYPE_REJECT: + default: + return INSN_REJECTED; + } + } +} diff --git a/arch/arm/kernel/probes.h b/arch/arm/kernel/probes.h new file mode 100644 index 00000000000000..dba9f2466a93fc --- /dev/null +++ b/arch/arm/kernel/probes.h @@ -0,0 +1,407 @@ +/* + * arch/arm/kernel/probes.h + * + * Copyright (C) 2011 Jon Medhurst . + * + * Some contents moved here from arch/arm/include/asm/kprobes.h which is + * Copyright (C) 2006, 2007 Motorola Inc. + * + * 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. + */ + +#ifndef _ARM_KERNEL_PROBES_H +#define _ARM_KERNEL_PROBES_H + +#include +#include +#include + +void __init arm_probes_decode_init(void); + +extern probes_check_cc * const probes_condition_checks[16]; + +#if __LINUX_ARM_ARCH__ >= 7 + +/* str_pc_offset is architecturally defined from ARMv7 onwards */ +#define str_pc_offset 8 +#define find_str_pc_offset() + +#else /* __LINUX_ARM_ARCH__ < 7 */ + +/* We need a run-time check to determine str_pc_offset */ +extern int str_pc_offset; +void __init find_str_pc_offset(void); + +#endif + + +/* + * Update ITSTATE after normal execution of an IT block instruction. + * + * The 8 IT state bits are split into two parts in CPSR: + * ITSTATE<1:0> are in CPSR<26:25> + * ITSTATE<7:2> are in CPSR<15:10> + */ +static inline unsigned long it_advance(unsigned long cpsr) + { + if ((cpsr & 0x06000400) == 0) { + /* ITSTATE<2:0> == 0 means end of IT block, so clear IT state */ + cpsr &= ~PSR_IT_MASK; + } else { + /* We need to shift left ITSTATE<4:0> */ + const unsigned long mask = 0x06001c00; /* Mask ITSTATE<4:0> */ + unsigned long it = cpsr & mask; + it <<= 1; + it |= it >> (27 - 10); /* Carry ITSTATE<2> to correct place */ + it &= mask; + cpsr &= ~mask; + cpsr |= it; + } + return cpsr; +} + +static inline void __kprobes bx_write_pc(long pcv, struct pt_regs *regs) +{ + long cpsr = regs->ARM_cpsr; + if (pcv & 0x1) { + cpsr |= PSR_T_BIT; + pcv &= ~0x1; + } else { + cpsr &= ~PSR_T_BIT; + pcv &= ~0x2; /* Avoid UNPREDICTABLE address allignment */ + } + regs->ARM_cpsr = cpsr; + regs->ARM_pc = pcv; +} + + +#if __LINUX_ARM_ARCH__ >= 6 + +/* Kernels built for >= ARMv6 should never run on <= ARMv5 hardware, so... */ +#define load_write_pc_interworks true +#define test_load_write_pc_interworking() + +#else /* __LINUX_ARM_ARCH__ < 6 */ + +/* We need run-time testing to determine if load_write_pc() should interwork. */ +extern bool load_write_pc_interworks; +void __init test_load_write_pc_interworking(void); + +#endif + +static inline void __kprobes load_write_pc(long pcv, struct pt_regs *regs) +{ + if (load_write_pc_interworks) + bx_write_pc(pcv, regs); + else + regs->ARM_pc = pcv; +} + + +#if __LINUX_ARM_ARCH__ >= 7 + +#define alu_write_pc_interworks true +#define test_alu_write_pc_interworking() + +#elif __LINUX_ARM_ARCH__ <= 5 + +/* Kernels built for <= ARMv5 should never run on >= ARMv6 hardware, so... */ +#define alu_write_pc_interworks false +#define test_alu_write_pc_interworking() + +#else /* __LINUX_ARM_ARCH__ == 6 */ + +/* We could be an ARMv6 binary on ARMv7 hardware so we need a run-time check. */ +extern bool alu_write_pc_interworks; +void __init test_alu_write_pc_interworking(void); + +#endif /* __LINUX_ARM_ARCH__ == 6 */ + +static inline void __kprobes alu_write_pc(long pcv, struct pt_regs *regs) +{ + if (alu_write_pc_interworks) + bx_write_pc(pcv, regs); + else + regs->ARM_pc = pcv; +} + + +/* + * Test if load/store instructions writeback the address register. + * if P (bit 24) == 0 or W (bit 21) == 1 + */ +#define is_writeback(insn) ((insn ^ 0x01000000) & 0x01200000) + +/* + * The following definitions and macros are used to build instruction + * decoding tables for use by probes_decode_insn. + * + * These tables are a concatenation of entries each of which consist of one of + * the decode_* structs. All of the fields in every type of decode structure + * are of the union type decode_item, therefore the entire decode table can be + * viewed as an array of these and declared like: + * + * static const union decode_item table_name[] = {}; + * + * In order to construct each entry in the table, macros are used to + * initialise a number of sequential decode_item values in a layout which + * matches the relevant struct. E.g. DECODE_SIMULATE initialise a struct + * decode_simulate by initialising four decode_item objects like this... + * + * {.bits = _type}, + * {.bits = _mask}, + * {.bits = _value}, + * {.action = _handler}, + * + * Initialising a specified member of the union means that the compiler + * will produce a warning if the argument is of an incorrect type. + * + * Below is a list of each of the macros used to initialise entries and a + * description of the action performed when that entry is matched to an + * instruction. A match is found when (instruction & mask) == value. + * + * DECODE_TABLE(mask, value, table) + * Instruction decoding jumps to parsing the new sub-table 'table'. + * + * DECODE_CUSTOM(mask, value, decoder) + * The value of 'decoder' is used as an index into the array of + * action functions, and the retrieved decoder function is invoked + * to complete decoding of the instruction. + * + * DECODE_SIMULATE(mask, value, handler) + * The probes instruction handler is set to the value found by + * indexing into the action array using the value of 'handler'. This + * will be used to simulate the instruction when the probe is hit. + * Decoding returns with INSN_GOOD_NO_SLOT. + * + * DECODE_EMULATE(mask, value, handler) + * The probes instruction handler is set to the value found by + * indexing into the action array using the value of 'handler'. This + * will be used to emulate the instruction when the probe is hit. The + * modified instruction (see below) is placed in the probes instruction + * slot so it may be called by the emulation code. Decoding returns + * with INSN_GOOD. + * + * DECODE_REJECT(mask, value) + * Instruction decoding fails with INSN_REJECTED + * + * DECODE_OR(mask, value) + * This allows the mask/value test of multiple table entries to be + * logically ORed. Once an 'or' entry is matched the decoding action to + * be performed is that of the next entry which isn't an 'or'. E.g. + * + * DECODE_OR (mask1, value1) + * DECODE_OR (mask2, value2) + * DECODE_SIMULATE (mask3, value3, simulation_handler) + * + * This means that if any of the three mask/value pairs match the + * instruction being decoded, then 'simulation_handler' will be used + * for it. + * + * Both the SIMULATE and EMULATE macros have a second form which take an + * additional 'regs' argument. + * + * DECODE_SIMULATEX(mask, value, handler, regs) + * DECODE_EMULATEX (mask, value, handler, regs) + * + * These are used to specify what kind of CPU register is encoded in each of the + * least significant 5 nibbles of the instruction being decoded. The regs value + * is specified using the REGS macro, this takes any of the REG_TYPE_* values + * from enum decode_reg_type as arguments; only the '*' part of the name is + * given. E.g. + * + * REGS(0, ANY, NOPC, 0, ANY) + * + * This indicates an instruction is encoded like: + * + * bits 19..16 ignore + * bits 15..12 any register allowed here + * bits 11.. 8 any register except PC allowed here + * bits 7.. 4 ignore + * bits 3.. 0 any register allowed here + * + * This register specification is checked after a decode table entry is found to + * match an instruction (through the mask/value test). Any invalid register then + * found in the instruction will cause decoding to fail with INSN_REJECTED. In + * the above example this would happen if bits 11..8 of the instruction were + * 1111, indicating R15 or PC. + * + * As well as checking for legal combinations of registers, this data is also + * used to modify the registers encoded in the instructions so that an + * emulation routines can use it. (See decode_regs() and INSN_NEW_BITS.) + * + * Here is a real example which matches ARM instructions of the form + * "AND ,,, " + * + * DECODE_EMULATEX (0x0e000090, 0x00000010, PROBES_DATA_PROCESSING_REG, + * REGS(ANY, ANY, NOPC, 0, ANY)), + * ^ ^ ^ ^ + * Rn Rd Rs Rm + * + * Decoding the instruction "AND R4, R5, R6, ASL R15" will be rejected because + * Rs == R15 + * + * Decoding the instruction "AND R4, R5, R6, ASL R7" will be accepted and the + * instruction will be modified to "AND R0, R2, R3, ASL R1" and then placed into + * the kprobes instruction slot. This can then be called later by the handler + * function emulate_rd12rn16rm0rs8_rwflags (a pointer to which is retrieved from + * the indicated slot in the action array), in order to simulate the instruction. + */ + +enum decode_type { + DECODE_TYPE_END, + DECODE_TYPE_TABLE, + DECODE_TYPE_CUSTOM, + DECODE_TYPE_SIMULATE, + DECODE_TYPE_EMULATE, + DECODE_TYPE_OR, + DECODE_TYPE_REJECT, + NUM_DECODE_TYPES /* Must be last enum */ +}; + +#define DECODE_TYPE_BITS 4 +#define DECODE_TYPE_MASK ((1 << DECODE_TYPE_BITS) - 1) + +enum decode_reg_type { + REG_TYPE_NONE = 0, /* Not a register, ignore */ + REG_TYPE_ANY, /* Any register allowed */ + REG_TYPE_SAMEAS16, /* Register should be same as that at bits 19..16 */ + REG_TYPE_SP, /* Register must be SP */ + REG_TYPE_PC, /* Register must be PC */ + REG_TYPE_NOSP, /* Register must not be SP */ + REG_TYPE_NOSPPC, /* Register must not be SP or PC */ + REG_TYPE_NOPC, /* Register must not be PC */ + REG_TYPE_NOPCWB, /* No PC if load/store write-back flag also set */ + + /* The following types are used when the encoding for PC indicates + * another instruction form. This distiction only matters for test + * case coverage checks. + */ + REG_TYPE_NOPCX, /* Register must not be PC */ + REG_TYPE_NOSPPCX, /* Register must not be SP or PC */ + + /* Alias to allow '0' arg to be used in REGS macro. */ + REG_TYPE_0 = REG_TYPE_NONE +}; + +#define REGS(r16, r12, r8, r4, r0) \ + (((REG_TYPE_##r16) << 16) + \ + ((REG_TYPE_##r12) << 12) + \ + ((REG_TYPE_##r8) << 8) + \ + ((REG_TYPE_##r4) << 4) + \ + (REG_TYPE_##r0)) + +union decode_item { + u32 bits; + const union decode_item *table; + int action; +}; + +struct decode_header; +typedef enum probes_insn (probes_custom_decode_t)(probes_opcode_t, + struct arch_probes_insn *, + const struct decode_header *); + +union decode_action { + probes_insn_handler_t *handler; + probes_custom_decode_t *decoder; +}; + +#define DECODE_END \ + {.bits = DECODE_TYPE_END} + + +struct decode_header { + union decode_item type_regs; + union decode_item mask; + union decode_item value; +}; + +#define DECODE_HEADER(_type, _mask, _value, _regs) \ + {.bits = (_type) | ((_regs) << DECODE_TYPE_BITS)}, \ + {.bits = (_mask)}, \ + {.bits = (_value)} + + +struct decode_table { + struct decode_header header; + union decode_item table; +}; + +#define DECODE_TABLE(_mask, _value, _table) \ + DECODE_HEADER(DECODE_TYPE_TABLE, _mask, _value, 0), \ + {.table = (_table)} + + +struct decode_custom { + struct decode_header header; + union decode_item decoder; +}; + +#define DECODE_CUSTOM(_mask, _value, _decoder) \ + DECODE_HEADER(DECODE_TYPE_CUSTOM, _mask, _value, 0), \ + {.action = (_decoder)} + + +struct decode_simulate { + struct decode_header header; + union decode_item handler; +}; + +#define DECODE_SIMULATEX(_mask, _value, _handler, _regs) \ + DECODE_HEADER(DECODE_TYPE_SIMULATE, _mask, _value, _regs), \ + {.action = (_handler)} + +#define DECODE_SIMULATE(_mask, _value, _handler) \ + DECODE_SIMULATEX(_mask, _value, _handler, 0) + + +struct decode_emulate { + struct decode_header header; + union decode_item handler; +}; + +#define DECODE_EMULATEX(_mask, _value, _handler, _regs) \ + DECODE_HEADER(DECODE_TYPE_EMULATE, _mask, _value, _regs), \ + {.action = (_handler)} + +#define DECODE_EMULATE(_mask, _value, _handler) \ + DECODE_EMULATEX(_mask, _value, _handler, 0) + + +struct decode_or { + struct decode_header header; +}; + +#define DECODE_OR(_mask, _value) \ + DECODE_HEADER(DECODE_TYPE_OR, _mask, _value, 0) + +enum probes_insn { + INSN_REJECTED, + INSN_GOOD, + INSN_GOOD_NO_SLOT +}; + +struct decode_reject { + struct decode_header header; +}; + +#define DECODE_REJECT(_mask, _value) \ + DECODE_HEADER(DECODE_TYPE_REJECT, _mask, _value, 0) + +probes_insn_handler_t probes_simulate_nop; +probes_insn_handler_t probes_emulate_none; + +int __kprobes +probes_decode_insn(probes_opcode_t insn, struct arch_probes_insn *asi, + const union decode_item *table, bool thumb, bool emulate, + const union decode_action *actions); + +#endif diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c index 92f7b15dd22121..618117c8e06315 100644 --- a/arch/arm/kernel/process.c +++ b/arch/arm/kernel/process.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -59,9 +60,46 @@ static const char *isa_modes[] = { "ARM" , "Thumb" , "Jazelle", "ThumbEE" }; +#ifdef CONFIG_SMP +void arch_trigger_all_cpu_backtrace(void) +{ + smp_send_all_cpu_backtrace(); +} +#else +void arch_trigger_all_cpu_backtrace(void) +{ + dump_stack(); +} +#endif + extern void call_with_stack(void (*fn)(void *), void *arg, void *sp); typedef void (*phys_reset_t)(unsigned long); +#ifdef CONFIG_ARM_FLUSH_CONSOLE_ON_RESTART +void arm_machine_flush_console(void) +{ + printk("\n"); + pr_emerg("Restarting %s\n", linux_banner); + if (console_trylock()) { + console_unlock(); + return; + } + + mdelay(50); + + local_irq_disable(); + if (!console_trylock()) + pr_emerg("arm_restart: Console was locked! Busting\n"); + else + pr_emerg("arm_restart: Console was locked!\n"); + console_unlock(); +} +#else +void arm_machine_flush_console(void) +{ +} +#endif + /* * A temporary stack to use for CPU reset. This is static so that we * don't clobber it with the identity mapping. When running with this @@ -149,6 +187,7 @@ void arch_cpu_idle_prepare(void) void arch_cpu_idle_enter(void) { + idle_notifier_call_chain(IDLE_START); ledtrig_cpu(CPU_LED_IDLE_START); #ifdef CONFIG_PL310_ERRATA_769419 wmb(); @@ -158,6 +197,7 @@ void arch_cpu_idle_enter(void) void arch_cpu_idle_exit(void) { ledtrig_cpu(CPU_LED_IDLE_END); + idle_notifier_call_chain(IDLE_END); } #ifdef CONFIG_HOTPLUG_CPU @@ -187,6 +227,16 @@ void arch_cpu_idle(void) */ void machine_shutdown(void) { +#ifdef CONFIG_SMP + /* + * Disable preemption so we're guaranteed to + * run to power off or reboot and prevent + * the possibility of switching to another + * thread that might wind up blocking on + * one of the stopped CPUs. + */ + preempt_disable(); +#endif disable_nonboot_cpus(); } @@ -235,6 +285,10 @@ void machine_restart(char *cmd) local_irq_disable(); smp_send_stop(); + /* Flush the console to make sure all the relevant messages make it + * out to the console drivers */ + arm_machine_flush_console(); + arm_pm_restart(reboot_mode, cmd); /* Give a grace period for failure to restart of 1s */ @@ -246,6 +300,77 @@ void machine_restart(char *cmd) while (1); } +/* + * dump a block of kernel memory from around the given address + */ +static void show_data(unsigned long addr, int nbytes, const char *name) +{ + int i, j; + int nlines; + u32 *p; + + /* + * don't attempt to dump non-kernel addresses or + * values that are probably just small negative numbers + */ + if (addr < PAGE_OFFSET || addr > -256UL) + return; + + printk("\n%s: %#lx:\n", name, addr); + + /* + * round address down to a 32 bit boundary + * and always dump a multiple of 32 bytes + */ + p = (u32 *)(addr & ~(sizeof(u32) - 1)); + nbytes += (addr & (sizeof(u32) - 1)); + nlines = (nbytes + 31) / 32; + + + for (i = 0; i < nlines; i++) { + /* + * just display low 16 bits of address to keep + * each line of the dump < 80 characters + */ + printk("%04lx ", (unsigned long)p & 0xffff); + for (j = 0; j < 8; j++) { + u32 data; + if (probe_kernel_address(p, data)) { + printk(" ********"); + } else { + printk(" %08x", data); + } + ++p; + } + printk("\n"); + } +} + +static void show_extra_register_data(struct pt_regs *regs, int nbytes) +{ + mm_segment_t fs; + + fs = get_fs(); + set_fs(KERNEL_DS); + show_data(regs->ARM_pc - nbytes, nbytes * 2, "PC"); + show_data(regs->ARM_lr - nbytes, nbytes * 2, "LR"); + show_data(regs->ARM_sp - nbytes, nbytes * 2, "SP"); + show_data(regs->ARM_ip - nbytes, nbytes * 2, "IP"); + show_data(regs->ARM_fp - nbytes, nbytes * 2, "FP"); + show_data(regs->ARM_r0 - nbytes, nbytes * 2, "R0"); + show_data(regs->ARM_r1 - nbytes, nbytes * 2, "R1"); + show_data(regs->ARM_r2 - nbytes, nbytes * 2, "R2"); + show_data(regs->ARM_r3 - nbytes, nbytes * 2, "R3"); + show_data(regs->ARM_r4 - nbytes, nbytes * 2, "R4"); + show_data(regs->ARM_r5 - nbytes, nbytes * 2, "R5"); + show_data(regs->ARM_r6 - nbytes, nbytes * 2, "R6"); + show_data(regs->ARM_r7 - nbytes, nbytes * 2, "R7"); + show_data(regs->ARM_r8 - nbytes, nbytes * 2, "R8"); + show_data(regs->ARM_r9 - nbytes, nbytes * 2, "R9"); + show_data(regs->ARM_r10 - nbytes, nbytes * 2, "R10"); + set_fs(fs); +} + void __show_regs(struct pt_regs *regs) { unsigned long flags; @@ -302,6 +427,8 @@ void __show_regs(struct pt_regs *regs) printk("Control: %08x%s\n", ctrl, buf); } #endif + + show_extra_register_data(regs, 128); } void show_regs(struct pt_regs * regs) diff --git a/arch/arm/kernel/psci.c b/arch/arm/kernel/psci.c index 46931880093dc1..e2c3fa8632e809 100644 --- a/arch/arm/kernel/psci.c +++ b/arch/arm/kernel/psci.c @@ -42,6 +42,7 @@ static u32 psci_function_id[PSCI_FN_MAX]; #define PSCI_RET_EOPNOTSUPP -1 #define PSCI_RET_EINVAL -2 #define PSCI_RET_EPERM -3 +#define PSCI_RET_EALREADYON -4 static int psci_to_linux_errno(int errno) { @@ -54,6 +55,8 @@ static int psci_to_linux_errno(int errno) return -EINVAL; case PSCI_RET_EPERM: return -EPERM; + case PSCI_RET_EALREADYON: + return -EAGAIN; }; return -EINVAL; @@ -153,7 +156,7 @@ static int psci_migrate(unsigned long cpuid) return psci_to_linux_errno(err); } -static const struct of_device_id psci_of_match[] __initconst = { +static const struct of_device_id psci_of_match[] = { { .compatible = "arm,psci", }, {}, }; @@ -208,3 +211,16 @@ void __init psci_init(void) of_node_put(np); return; } + +int psci_probe(void) +{ + struct device_node *np; + int ret = -ENODEV; + + np = of_find_matching_node(NULL, psci_of_match); + if (np) + ret = 0; + + of_node_put(np); + return ret; +} diff --git a/arch/arm/kernel/return_address.c b/arch/arm/kernel/return_address.c index fafedd86885dde..4dc8ecb267096c 100644 --- a/arch/arm/kernel/return_address.c +++ b/arch/arm/kernel/return_address.c @@ -39,13 +39,12 @@ void *return_address(unsigned int level) { struct return_address_data data; struct stackframe frame; - register unsigned long current_sp asm ("sp"); data.level = level + 2; data.addr = NULL; frame.fp = (unsigned long)__builtin_frame_address(0); - frame.sp = current_sp; + frame.sp = __builtin_stack_pointer(); frame.lr = (unsigned long)__builtin_return_address(0); frame.pc = (unsigned long)return_address; @@ -63,11 +62,6 @@ void *return_address(unsigned int level) #warning "TODO: return_address should use unwind tables" #endif -void *return_address(unsigned int level) -{ - return NULL; -} - #endif /* if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND) / else */ EXPORT_SYMBOL_GPL(return_address); diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 1e8b030dbefd8b..4a5e9430f16b1a 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -273,6 +273,19 @@ static int cpu_has_aliasing_icache(unsigned int arch) int aliasing_icache; unsigned int id_reg, num_sets, line_size; +#ifdef CONFIG_BIG_LITTLE + /* + * We expect a combination of Cortex-A15 and Cortex-A7 cores. + * A7 = VIPT aliasing I-cache + * A15 = PIPT (non-aliasing) I-cache + * To cater for this discrepancy, let's assume aliasing I-cache + * all the time. This means unneeded extra work on the A15 but + * only ptrace is affected which is not performance critical. + */ + if ((read_cpuid_id() & 0xff0ffff0) == 0x410fc0f0) + return 1; +#endif + /* PIPT caches never alias. */ if (icache_is_pipt()) return 0; diff --git a/arch/arm/kernel/signal.c b/arch/arm/kernel/signal.c index 04d63880037f9c..bd198343720519 100644 --- a/arch/arm/kernel/signal.c +++ b/arch/arm/kernel/signal.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -590,6 +591,9 @@ do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall) return restart; } syscall = 0; + } else if (thread_flags & _TIF_UPROBE) { + clear_thread_flag(TIF_UPROBE); + uprobe_notify_resume(regs); } else { clear_thread_flag(TIF_NOTIFY_RESUME); tracehook_notify_resume(regs); diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index b7b4c86e338b02..7b1e20ec311933 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -69,6 +69,7 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_CPU_BACKTRACE, }; static DECLARE_COMPLETION(cpu_running); @@ -471,6 +472,7 @@ static const char *ipi_types[NR_IPI] = { S(IPI_CPU_STOP, "CPU stop interrupts"), S(IPI_IRQ_WORK, "IRQ work interrupts"), S(IPI_COMPLETION, "completion interrupts"), + S(IPI_CPU_BACKTRACE, "CPU backtrace"), }; void show_ipi_list(struct seq_file *p, int prec) @@ -543,6 +545,58 @@ static void ipi_complete(unsigned int cpu) complete(per_cpu(cpu_completion, cpu)); } +static cpumask_t backtrace_mask; +static DEFINE_RAW_SPINLOCK(backtrace_lock); + +/* "in progress" flag of arch_trigger_all_cpu_backtrace */ +static unsigned long backtrace_flag; + +void smp_send_all_cpu_backtrace(void) +{ + unsigned int this_cpu = smp_processor_id(); + int i; + + if (test_and_set_bit(0, &backtrace_flag)) + /* + * If there is already a trigger_all_cpu_backtrace() in progress + * (backtrace_flag == 1), don't output double cpu dump infos. + */ + return; + + cpumask_copy(&backtrace_mask, cpu_online_mask); + cpu_clear(this_cpu, backtrace_mask); + + pr_info("Backtrace for cpu %d (current):\n", this_cpu); + dump_stack(); + + pr_info("\nsending IPI to all other CPUs:\n"); + smp_cross_call(&backtrace_mask, IPI_CPU_BACKTRACE); + + /* Wait for up to 10 seconds for all other CPUs to do the backtrace */ + for (i = 0; i < 10 * 1000; i++) { + if (cpumask_empty(&backtrace_mask)) + break; + mdelay(1); + } + + clear_bit(0, &backtrace_flag); + smp_mb__after_clear_bit(); +} + +/* + * ipi_cpu_backtrace - handle IPI from smp_send_all_cpu_backtrace() + */ +static void ipi_cpu_backtrace(unsigned int cpu, struct pt_regs *regs) +{ + if (cpu_isset(cpu, backtrace_mask)) { + raw_spin_lock(&backtrace_lock); + pr_warning("IPI backtrace for cpu %d\n", cpu); + show_regs(regs); + raw_spin_unlock(&backtrace_lock); + cpu_clear(cpu, backtrace_mask); + } +} + /* * Main handler for inter-processor interrupts */ @@ -607,6 +661,10 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break; + case IPI_CPU_BACKTRACE: + ipi_cpu_backtrace(cpu, regs); + break; + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr); diff --git a/arch/arm/kernel/stacktrace.c b/arch/arm/kernel/stacktrace.c index af4e8c8a5422c4..8b83eaf7e4371c 100644 --- a/arch/arm/kernel/stacktrace.c +++ b/arch/arm/kernel/stacktrace.c @@ -109,11 +109,9 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) frame.pc = thread_saved_pc(tsk); #endif } else { - register unsigned long current_sp asm ("sp"); - data.no_sched_functions = 0; frame.fp = (unsigned long)__builtin_frame_address(0); - frame.sp = current_sp; + frame.sp = __builtin_stack_pointer(); frame.lr = (unsigned long)__builtin_return_address(0); frame.pc = (unsigned long)save_stack_trace_tsk; } diff --git a/arch/arm/kernel/topology.c b/arch/arm/kernel/topology.c index 0bc94b1fd1ae9e..d325410e02c82b 100644 --- a/arch/arm/kernel/topology.c +++ b/arch/arm/kernel/topology.c @@ -266,6 +266,33 @@ void store_cpu_topology(unsigned int cpuid) cpu_topology[cpuid].socket_id, mpidr); } +/* + * cluster_to_logical_mask - return cpu logical mask of CPUs in a cluster + * @socket_id: cluster HW identifier + * @cluster_mask: the cpumask location to be initialized, modified by the + * function only if return value == 0 + * + * Return: + * + * 0 on success + * -EINVAL if cluster_mask is NULL or there is no record matching socket_id + */ +int cluster_to_logical_mask(unsigned int socket_id, cpumask_t *cluster_mask) +{ + int cpu; + + if (!cluster_mask) + return -EINVAL; + + for_each_online_cpu(cpu) + if (socket_id == topology_physical_package_id(cpu)) { + cpumask_copy(cluster_mask, topology_core_cpumask(cpu)); + return 0; + } + + return -EINVAL; +} + /* * init_cpu_topology is called at boot when only one cpu is running * which prevent simultaneous write access to cpu_topology array diff --git a/arch/arm/kernel/unwind.c b/arch/arm/kernel/unwind.c index 00df012c46784a..d324a84a08ed25 100644 --- a/arch/arm/kernel/unwind.c +++ b/arch/arm/kernel/unwind.c @@ -31,7 +31,7 @@ #warning Your compiler does not have EABI support. #warning ARM unwind is known to compile only with EABI compilers. #warning Change compiler or disable ARM_UNWIND option. -#elif (__GNUC__ == 4 && __GNUC_MINOR__ <= 2) +#elif (__GNUC__ == 4 && __GNUC_MINOR__ <= 2) && !defined(__clang__) #warning Your compiler is too buggy; it is known to not compile ARM unwind support. #warning Change compiler or disable ARM_UNWIND option. #endif @@ -408,7 +408,6 @@ int unwind_frame(struct stackframe *frame) void unwind_backtrace(struct pt_regs *regs, struct task_struct *tsk) { struct stackframe frame; - register unsigned long current_sp asm ("sp"); pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk); @@ -424,7 +423,7 @@ void unwind_backtrace(struct pt_regs *regs, struct task_struct *tsk) ? regs->ARM_pc : regs->ARM_lr; } else if (tsk == current) { frame.fp = (unsigned long)__builtin_frame_address(0); - frame.sp = current_sp; + frame.sp = __builtin_stack_pointer(); frame.lr = (unsigned long)__builtin_return_address(0); frame.pc = (unsigned long)unwind_backtrace; } else { diff --git a/arch/arm/kernel/uprobes-arm.c b/arch/arm/kernel/uprobes-arm.c new file mode 100644 index 00000000000000..d3b655ff17da20 --- /dev/null +++ b/arch/arm/kernel/uprobes-arm.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2012 Rabin Vincent + * + * 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 +#include +#include +#include +#include +#include + +#include "probes.h" +#include "probes-arm.h" +#include "uprobes.h" + +static int uprobes_substitute_pc(unsigned long *pinsn, u32 oregs) +{ + probes_opcode_t insn = __mem_to_opcode_arm(*pinsn); + probes_opcode_t temp; + probes_opcode_t mask; + int freereg; + u32 free = 0xffff; + u32 regs; + + for (regs = oregs; regs; regs >>= 4, insn >>= 4) { + if ((regs & 0xf) == REG_TYPE_NONE) + continue; + + free &= ~(1 << (insn & 0xf)); + } + + /* No PC, no problem */ + if (free & (1 << 15)) + return 15; + + if (!free) + return -1; + + /* + * fls instead of ffs ensures that for "ldrd r0, r1, [pc]" we would + * pick LR instead of R1. + */ + freereg = free = fls(free) - 1; + + temp = __mem_to_opcode_arm(*pinsn); + insn = temp; + regs = oregs; + mask = 0xf; + + for (; regs; regs >>= 4, mask <<= 4, free <<= 4, temp >>= 4) { + if ((regs & 0xf) == REG_TYPE_NONE) + continue; + + if ((temp & 0xf) != 15) + continue; + + insn &= ~mask; + insn |= free & mask; + } + + *pinsn = __opcode_to_mem_arm(insn); + return freereg; +} + +static void uprobe_set_pc(struct arch_uprobe *auprobe, + struct arch_uprobe_task *autask, + struct pt_regs *regs) +{ + u32 pcreg = auprobe->pcreg; + + autask->backup = regs->uregs[pcreg]; + regs->uregs[pcreg] = regs->ARM_pc + 8; +} + +static void uprobe_unset_pc(struct arch_uprobe *auprobe, + struct arch_uprobe_task *autask, + struct pt_regs *regs) +{ + /* PC will be taken care of by common code */ + regs->uregs[auprobe->pcreg] = autask->backup; +} + +static void uprobe_aluwrite_pc(struct arch_uprobe *auprobe, + struct arch_uprobe_task *autask, + struct pt_regs *regs) +{ + u32 pcreg = auprobe->pcreg; + + alu_write_pc(regs->uregs[pcreg], regs); + regs->uregs[pcreg] = autask->backup; +} + +static void uprobe_write_pc(struct arch_uprobe *auprobe, + struct arch_uprobe_task *autask, + struct pt_regs *regs) +{ + u32 pcreg = auprobe->pcreg; + + load_write_pc(regs->uregs[pcreg], regs); + regs->uregs[pcreg] = autask->backup; +} + +enum probes_insn +decode_pc_ro(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d) +{ + struct arch_uprobe *auprobe = container_of(asi, struct arch_uprobe, + asi); + struct decode_emulate *decode = (struct decode_emulate *) d; + u32 regs = decode->header.type_regs.bits >> DECODE_TYPE_BITS; + int reg; + + reg = uprobes_substitute_pc(&auprobe->ixol[0], regs); + if (reg == 15) + return INSN_GOOD; + + if (reg == -1) + return INSN_REJECTED; + + auprobe->pcreg = reg; + auprobe->prehandler = uprobe_set_pc; + auprobe->posthandler = uprobe_unset_pc; + + return INSN_GOOD; +} + +enum probes_insn +decode_wb_pc(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d, bool alu) +{ + struct arch_uprobe *auprobe = container_of(asi, struct arch_uprobe, + asi); + enum probes_insn ret = decode_pc_ro(insn, asi, d); + + if (((insn >> 12) & 0xf) == 15) + auprobe->posthandler = alu ? uprobe_aluwrite_pc + : uprobe_write_pc; + + return ret; +} + +enum probes_insn +decode_rd12rn16rm0rs8_rwflags(probes_opcode_t insn, + struct arch_probes_insn *asi, + const struct decode_header *d) +{ + return decode_wb_pc(insn, asi, d, true); +} + +enum probes_insn +decode_ldr(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d) +{ + return decode_wb_pc(insn, asi, d, false); +} + +enum probes_insn +uprobe_decode_ldmstm(probes_opcode_t insn, + struct arch_probes_insn *asi, + const struct decode_header *d) +{ + struct arch_uprobe *auprobe = container_of(asi, struct arch_uprobe, + asi); + unsigned reglist = insn & 0xffff; + int rn = (insn >> 16) & 0xf; + int lbit = insn & (1 << 20); + unsigned used = reglist | (1 << rn); + + if (rn == 15) + return INSN_REJECTED; + + if (!(used & (1 << 15))) + return INSN_GOOD; + + if (used & (1 << 14)) + return INSN_REJECTED; + + /* Use LR instead of PC */ + insn ^= 0xc000; + + auprobe->pcreg = 14; + auprobe->ixol[0] = __opcode_to_mem_arm(insn); + + auprobe->prehandler = uprobe_set_pc; + if (lbit) + auprobe->posthandler = uprobe_write_pc; + else + auprobe->posthandler = uprobe_unset_pc; + + return INSN_GOOD; +} + +const union decode_action uprobes_probes_actions[] = { + [PROBES_EMULATE_NONE] = {.handler = probes_simulate_nop}, + [PROBES_SIMULATE_NOP] = {.handler = probes_simulate_nop}, + [PROBES_PRELOAD_IMM] = {.handler = probes_simulate_nop}, + [PROBES_PRELOAD_REG] = {.handler = probes_simulate_nop}, + [PROBES_BRANCH_IMM] = {.handler = simulate_blx1}, + [PROBES_MRS] = {.handler = simulate_mrs}, + [PROBES_BRANCH_REG] = {.handler = simulate_blx2bx}, + [PROBES_CLZ] = {.handler = probes_simulate_nop}, + [PROBES_SATURATING_ARITHMETIC] = {.handler = probes_simulate_nop}, + [PROBES_MUL1] = {.handler = probes_simulate_nop}, + [PROBES_MUL2] = {.handler = probes_simulate_nop}, + [PROBES_SWP] = {.handler = probes_simulate_nop}, + [PROBES_LDRSTRD] = {.decoder = decode_pc_ro}, + [PROBES_LOAD_EXTRA] = {.decoder = decode_pc_ro}, + [PROBES_LOAD] = {.decoder = decode_ldr}, + [PROBES_STORE_EXTRA] = {.decoder = decode_pc_ro}, + [PROBES_STORE] = {.decoder = decode_pc_ro}, + [PROBES_MOV_IP_SP] = {.handler = simulate_mov_ipsp}, + [PROBES_DATA_PROCESSING_REG] = { + .decoder = decode_rd12rn16rm0rs8_rwflags}, + [PROBES_DATA_PROCESSING_IMM] = { + .decoder = decode_rd12rn16rm0rs8_rwflags}, + [PROBES_MOV_HALFWORD] = {.handler = probes_simulate_nop}, + [PROBES_SEV] = {.handler = probes_simulate_nop}, + [PROBES_WFE] = {.handler = probes_simulate_nop}, + [PROBES_SATURATE] = {.handler = probes_simulate_nop}, + [PROBES_REV] = {.handler = probes_simulate_nop}, + [PROBES_MMI] = {.handler = probes_simulate_nop}, + [PROBES_PACK] = {.handler = probes_simulate_nop}, + [PROBES_EXTEND] = {.handler = probes_simulate_nop}, + [PROBES_EXTEND_ADD] = {.handler = probes_simulate_nop}, + [PROBES_MUL_ADD_LONG] = {.handler = probes_simulate_nop}, + [PROBES_MUL_ADD] = {.handler = probes_simulate_nop}, + [PROBES_BITFIELD] = {.handler = probes_simulate_nop}, + [PROBES_BRANCH] = {.handler = simulate_bbl}, + [PROBES_LDMSTM] = {.decoder = uprobe_decode_ldmstm} +}; diff --git a/arch/arm/kernel/uprobes.c b/arch/arm/kernel/uprobes.c new file mode 100644 index 00000000000000..f9bacee973bf68 --- /dev/null +++ b/arch/arm/kernel/uprobes.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2012 Rabin Vincent + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "probes.h" +#include "probes-arm.h" +#include "uprobes.h" + +#define UPROBE_TRAP_NR UINT_MAX + +bool is_swbp_insn(uprobe_opcode_t *insn) +{ + return (__mem_to_opcode_arm(*insn) & 0x0fffffff) == + (UPROBE_SWBP_ARM_INSN & 0x0fffffff); +} + +int set_swbp(struct arch_uprobe *auprobe, struct mm_struct *mm, + unsigned long vaddr) +{ + return uprobe_write_opcode(mm, vaddr, + __opcode_to_mem_arm(auprobe->bpinsn)); +} + +bool arch_uprobe_ignore(struct arch_uprobe *auprobe, struct pt_regs *regs) +{ + if (!auprobe->asi.insn_check_cc(regs->ARM_cpsr)) { + regs->ARM_pc += 4; + return true; + } + + return false; +} + +bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs) +{ + probes_opcode_t opcode; + + if (!auprobe->simulate) + return false; + + opcode = __mem_to_opcode_arm(*(unsigned int *) auprobe->insn); + + auprobe->asi.insn_singlestep(opcode, &auprobe->asi, regs); + + return true; +} + +unsigned long +arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr, + struct pt_regs *regs) +{ + unsigned long orig_ret_vaddr; + + orig_ret_vaddr = regs->ARM_lr; + /* Replace the return addr with trampoline addr */ + regs->ARM_lr = trampoline_vaddr; + return orig_ret_vaddr; +} + +int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, + unsigned long addr) +{ + unsigned int insn; + unsigned int bpinsn; + enum probes_insn ret; + + /* Thumb not yet support */ + if (addr & 0x3) + return -EINVAL; + + insn = __mem_to_opcode_arm(*(unsigned int *)auprobe->insn); + auprobe->ixol[0] = __opcode_to_mem_arm(insn); + auprobe->ixol[1] = __opcode_to_mem_arm(UPROBE_SS_ARM_INSN); + + ret = arm_probes_decode_insn(insn, &auprobe->asi, false, + uprobes_probes_actions); + switch (ret) { + case INSN_REJECTED: + return -EINVAL; + + case INSN_GOOD_NO_SLOT: + auprobe->simulate = true; + break; + + case INSN_GOOD: + default: + break; + } + + bpinsn = UPROBE_SWBP_ARM_INSN & 0x0fffffff; + if (insn >= 0xe0000000) + bpinsn |= 0xe0000000; /* Unconditional instruction */ + else + bpinsn |= insn & 0xf0000000; /* Copy condition from insn */ + + auprobe->bpinsn = bpinsn; + + return 0; +} + +int arch_uprobe_pre_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) +{ + struct uprobe_task *utask = current->utask; + + if (auprobe->prehandler) + auprobe->prehandler(auprobe, &utask->autask, regs); + + utask->autask.saved_trap_no = current->thread.trap_no; + current->thread.trap_no = UPROBE_TRAP_NR; + regs->ARM_pc = utask->xol_vaddr; + + return 0; +} + +int arch_uprobe_post_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) +{ + struct uprobe_task *utask = current->utask; + + WARN_ON_ONCE(current->thread.trap_no != UPROBE_TRAP_NR); + + current->thread.trap_no = utask->autask.saved_trap_no; + regs->ARM_pc = utask->vaddr + 4; + + if (auprobe->posthandler) + auprobe->posthandler(auprobe, &utask->autask, regs); + + return 0; +} + +bool arch_uprobe_xol_was_trapped(struct task_struct *t) +{ + if (t->thread.trap_no != UPROBE_TRAP_NR) + return true; + + return false; +} + +void arch_uprobe_abort_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) +{ + struct uprobe_task *utask = current->utask; + + current->thread.trap_no = utask->autask.saved_trap_no; + instruction_pointer_set(regs, utask->vaddr); +} + +int arch_uprobe_exception_notify(struct notifier_block *self, + unsigned long val, void *data) +{ + return NOTIFY_DONE; +} + +static int uprobe_trap_handler(struct pt_regs *regs, unsigned int instr) +{ + unsigned long flags; + + local_irq_save(flags); + instr &= 0x0fffffff; + if (instr == (UPROBE_SWBP_ARM_INSN & 0x0fffffff)) + uprobe_pre_sstep_notifier(regs); + else if (instr == (UPROBE_SS_ARM_INSN & 0x0fffffff)) + uprobe_post_sstep_notifier(regs); + local_irq_restore(flags); + + return 0; +} + +unsigned long uprobe_get_swbp_addr(struct pt_regs *regs) +{ + return instruction_pointer(regs); +} + +static struct undef_hook uprobes_arm_break_hook = { + .instr_mask = 0x0fffffff, + .instr_val = (UPROBE_SWBP_ARM_INSN & 0x0fffffff), + .cpsr_mask = MODE_MASK, + .cpsr_val = USR_MODE, + .fn = uprobe_trap_handler, +}; + +static struct undef_hook uprobes_arm_ss_hook = { + .instr_mask = 0x0fffffff, + .instr_val = (UPROBE_SS_ARM_INSN & 0x0fffffff), + .cpsr_mask = MODE_MASK, + .cpsr_val = USR_MODE, + .fn = uprobe_trap_handler, +}; + +static int arch_uprobes_init(void) +{ + register_undef_hook(&uprobes_arm_break_hook); + register_undef_hook(&uprobes_arm_ss_hook); + + return 0; +} +device_initcall(arch_uprobes_init); diff --git a/arch/arm/kernel/uprobes.h b/arch/arm/kernel/uprobes.h new file mode 100644 index 00000000000000..1d0c12dfbd03db --- /dev/null +++ b/arch/arm/kernel/uprobes.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012 Rabin Vincent + * + * 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. + */ + +#ifndef __ARM_KERNEL_UPROBES_H +#define __ARM_KERNEL_UPROBES_H + +enum probes_insn uprobe_decode_ldmstm(probes_opcode_t insn, + struct arch_probes_insn *asi, + const struct decode_header *d); + +enum probes_insn decode_ldr(probes_opcode_t insn, + struct arch_probes_insn *asi, + const struct decode_header *d); + +enum probes_insn +decode_rd12rn16rm0rs8_rwflags(probes_opcode_t insn, + struct arch_probes_insn *asi, + const struct decode_header *d); + +enum probes_insn +decode_wb_pc(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d, bool alu); + +enum probes_insn +decode_pc_ro(probes_opcode_t insn, struct arch_probes_insn *asi, + const struct decode_header *d); + +extern const union decode_action uprobes_probes_actions[]; + +#endif diff --git a/arch/arm/lib/Makefile b/arch/arm/lib/Makefile index 0573faab96ad2c..b585fcf2234a64 100644 --- a/arch/arm/lib/Makefile +++ b/arch/arm/lib/Makefile @@ -15,6 +15,10 @@ lib-y := backtrace.o changebit.o csumipv6.o csumpartial.o \ io-readsb.o io-writesb.o io-readsl.o io-writesl.o \ call_with_stack.o bswapsdi2.o +ifeq ($(COMPILER),clang) +lib-y += eabi.o +endif + mmu-y := clear_user.o copy_page.o getuser.o putuser.o # the code in uaccess.S is not preemption safe and diff --git a/arch/arm/lib/eabi.c b/arch/arm/lib/eabi.c new file mode 100644 index 00000000000000..41b27b2f49b802 --- /dev/null +++ b/arch/arm/lib/eabi.c @@ -0,0 +1,32 @@ +/* + * linux/lib/eabi.c + * + * Copyright (C) 2012 Mark Charlebois + */ + +/* + * EABI routines + */ + +#include +#include +#include +#include + +void __aeabi_memcpy(void *dest, const void *src, size_t n) +{ + (void)memcpy(dest, src, n); +} +EXPORT_SYMBOL(__aeabi_memcpy); + +void __aeabi_memmove(void *dest, const void *src, size_t n) +{ + (void)memmove(dest, src, n); +} +EXPORT_SYMBOL(__aeabi_memmove); + +void __aeabi_memset(void *s, size_t n, int c) +{ + (void)memset(s, c, n); +} +EXPORT_SYMBOL(__aeabi_memset); diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig index b1aa6a9b3bd13a..f2a2fb9909a103 100644 --- a/arch/arm/mach-bcm/Kconfig +++ b/arch/arm/mach-bcm/Kconfig @@ -19,7 +19,6 @@ config ARCH_BCM_MOBILE select CPU_V7 select CLKSRC_OF select GENERIC_CLOCKEVENTS - select GENERIC_TIME select GPIO_BCM_KONA select SPARSE_IRQ select TICK_ONESHOT @@ -32,6 +31,32 @@ config ARCH_BCM_MOBILE BCM11130, BCM11140, BCM11351, BCM28145 and BCM28155 variants. +config ARCH_BCM_5301X + bool "Broadcom BCM470X / BCM5301X ARM SoC" if ARCH_MULTI_V7 + depends on MMU + select ARM_GIC + select CACHE_L2X0 + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD if SMP + select HAVE_SMP + select COMMON_CLK + select GENERIC_CLOCKEVENTS + select ARM_GLOBAL_TIMER + select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK + select MIGHT_HAVE_PCI + help + Support for Broadcom BCM470X and BCM5301X SoCs with ARM CPU cores. + + This is a network SoC line mostly used in home routers and + wifi access points, it's internal name is Northstar. + This inclused the following SoC: BCM53010, BCM53011, BCM53012, + BCM53014, BCM53015, BCM53016, BCM53017, BCM53018, BCM4707, + BCM4708 and BCM4709. + + Do not confuse this with the BCM4760 which is a totally + different SoC or with the older BCM47XX and BCM53XX based + network SoC using a MIPS CPU, they are supported by arch/mips/bcm47xx + endmenu endif diff --git a/arch/arm/mach-bcm/Makefile b/arch/arm/mach-bcm/Makefile index c2ccd5a0f77212..4e4a2ed6851e5e 100644 --- a/arch/arm/mach-bcm/Makefile +++ b/arch/arm/mach-bcm/Makefile @@ -1,5 +1,5 @@ # -# Copyright (C) 2012-2013 Broadcom Corporation +# Copyright (C) 2012-2014 Broadcom Corporation # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -10,6 +10,9 @@ # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -obj-$(CONFIG_ARCH_BCM_MOBILE) := board_bcm281xx.o bcm_kona_smc.o bcm_kona_smc_asm.o kona.o +obj-$(CONFIG_ARCH_BCM_MOBILE) := board_bcm281xx.o board_bcm21664.o \ + bcm_kona_smc.o bcm_kona_smc_asm.o kona.o + plus_sec := $(call as-instr,.arch_extension sec,+sec) AFLAGS_bcm_kona_smc_asm.o :=-Wa,-march=armv7-a$(plus_sec) +obj-$(CONFIG_ARCH_BCM_5301X) += bcm_5301x.o diff --git a/arch/arm/mach-bcm/bcm_5301x.c b/arch/arm/mach-bcm/bcm_5301x.c new file mode 100644 index 00000000000000..edff69761e0463 --- /dev/null +++ b/arch/arm/mach-bcm/bcm_5301x.c @@ -0,0 +1,61 @@ +/* + * Broadcom BCM470X / BCM5301X ARM platform code. + * + * Copyright 2013 Hauke Mehrtens + * + * Licensed under the GNU/GPL. See COPYING for details. + */ +#include +#include + +#include +#include +#include + + +static bool first_fault = true; + +static int bcm5301x_abort_handler(unsigned long addr, unsigned int fsr, + struct pt_regs *regs) +{ + if (fsr == 0x1c06 && first_fault) { + first_fault = false; + + /* + * These faults with code 0x1c06 happens for no good reason, + * possibly left over from the CFE boot loader. + */ + pr_warn("External imprecise Data abort at addr=%#lx, fsr=%#x ignored.\n", + addr, fsr); + + /* Returning non-zero causes fault display and panic */ + return 0; + } + + /* Others should cause a fault */ + return 1; +} + +static void __init bcm5301x_init_early(void) +{ + /* Install our hook */ + hook_fault_code(16 + 6, bcm5301x_abort_handler, SIGBUS, BUS_OBJERR, + "imprecise external abort"); +} + +static void __init bcm5301x_dt_init(void) +{ + l2x0_of_init(0, ~0UL); + of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); +} + +static const char __initconst *bcm5301x_dt_compat[] = { + "brcm,bcm4708", + NULL, +}; + +DT_MACHINE_START(BCM5301X, "BCM5301X") + .init_early = bcm5301x_init_early, + .init_machine = bcm5301x_dt_init, + .dt_compat = bcm5301x_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-bcm/board_bcm21664.c b/arch/arm/mach-bcm/board_bcm21664.c new file mode 100644 index 00000000000000..acc1573fd00599 --- /dev/null +++ b/arch/arm/mach-bcm/board_bcm21664.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include + +#include "bcm_kona_smc.h" +#include "kona.h" + +#define RSTMGR_DT_STRING "brcm,bcm21664-resetmgr" + +#define RSTMGR_REG_WR_ACCESS_OFFSET 0 +#define RSTMGR_REG_CHIP_SOFT_RST_OFFSET 4 + +#define RSTMGR_WR_PASSWORD 0xa5a5 +#define RSTMGR_WR_PASSWORD_SHIFT 8 +#define RSTMGR_WR_ACCESS_ENABLE 1 + +static void bcm21664_restart(enum reboot_mode mode, const char *cmd) +{ + void __iomem *base; + struct device_node *resetmgr; + + resetmgr = of_find_compatible_node(NULL, NULL, RSTMGR_DT_STRING); + if (!resetmgr) { + pr_emerg("Couldn't find " RSTMGR_DT_STRING "\n"); + return; + } + base = of_iomap(resetmgr, 0); + if (!base) { + pr_emerg("Couldn't map " RSTMGR_DT_STRING "\n"); + return; + } + + /* + * A soft reset is triggered by writing a 0 to bit 0 of the soft reset + * register. To write to that register we must first write the password + * and the enable bit in the write access enable register. + */ + writel((RSTMGR_WR_PASSWORD << RSTMGR_WR_PASSWORD_SHIFT) | + RSTMGR_WR_ACCESS_ENABLE, + base + RSTMGR_REG_WR_ACCESS_OFFSET); + writel(0, base + RSTMGR_REG_CHIP_SOFT_RST_OFFSET); + + /* Wait for reset */ + while (1); +} + +static void __init bcm21664_init(void) +{ + of_platform_populate(NULL, of_default_bus_match_table, NULL, + &platform_bus); + kona_l2_cache_init(); +} + +static const char * const bcm21664_dt_compat[] = { + "brcm,bcm21664", + NULL, +}; + +DT_MACHINE_START(BCM21664_DT, "BCM21664 Broadcom Application Processor") + .init_machine = bcm21664_init, + .restart = bcm21664_restart, + .dt_compat = bcm21664_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-bcm/board_bcm281xx.c b/arch/arm/mach-bcm/board_bcm281xx.c index cb3dc364405c4e..6be54c10f8cb9b 100644 --- a/arch/arm/mach-bcm/board_bcm281xx.c +++ b/arch/arm/mach-bcm/board_bcm281xx.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Broadcom Corporation + * Copyright (C) 2012-2014 Broadcom Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -11,64 +11,65 @@ * GNU General Public License for more details. */ -#include -#include -#include -#include #include +#include +#include #include -#include -#include -#include "bcm_kona_smc.h" #include "kona.h" -static int __init kona_l2_cache_init(void) +#define SECWDOG_OFFSET 0x00000000 +#define SECWDOG_RESERVED_MASK 0xe2000000 +#define SECWDOG_WD_LOAD_FLAG_MASK 0x10000000 +#define SECWDOG_EN_MASK 0x08000000 +#define SECWDOG_SRSTEN_MASK 0x04000000 +#define SECWDOG_CLKS_SHIFT 20 +#define SECWDOG_COUNT_SHIFT 0 + +static void bcm281xx_restart(enum reboot_mode mode, const char *cmd) { - if (!IS_ENABLED(CONFIG_CACHE_L2X0)) - return 0; + uint32_t val; + void __iomem *base; + struct device_node *np_wdog; - if (bcm_kona_smc_init() < 0) { - pr_info("Kona secure API not available. Skipping L2 init\n"); - return 0; + np_wdog = of_find_compatible_node(NULL, NULL, "brcm,kona-wdt"); + if (!np_wdog) { + pr_emerg("Couldn't find brcm,kona-wdt\n"); + return; + } + base = of_iomap(np_wdog, 0); + if (!base) { + pr_emerg("Couldn't map brcm,kona-wdt\n"); + return; } - bcm_kona_smc(SSAPI_ENABLE_L2_CACHE, 0, 0, 0, 0); - - /* - * The aux_val and aux_mask have no effect since L2 cache is already - * enabled. Pass 0s for aux_val and 1s for aux_mask for default value. - */ - return l2x0_of_init(0, ~0); -} - -static void bcm_board_setup_restart(void) -{ - struct device_node *np; + /* Enable watchdog with short timeout (244us). */ + val = readl(base + SECWDOG_OFFSET); + val &= SECWDOG_RESERVED_MASK | SECWDOG_WD_LOAD_FLAG_MASK; + val |= SECWDOG_EN_MASK | SECWDOG_SRSTEN_MASK | + (0x15 << SECWDOG_CLKS_SHIFT) | + (0x8 << SECWDOG_COUNT_SHIFT); + writel(val, base + SECWDOG_OFFSET); - np = of_find_compatible_node(NULL, NULL, "brcm,bcm11351"); - if (np) { - if (of_device_is_available(np)) - bcm_kona_setup_restart(); - of_node_put(np); - } - /* Restart setup for other boards goes here */ + /* Wait for reset */ + while (1); } -static void __init board_init(void) +static void __init bcm281xx_init(void) { of_platform_populate(NULL, of_default_bus_match_table, NULL, &platform_bus); - - bcm_board_setup_restart(); kona_l2_cache_init(); } -static const char * const bcm11351_dt_compat[] = { "brcm,bcm11351", NULL, }; +static const char * const bcm281xx_dt_compat[] = { + "brcm,bcm11351", /* Have to use the first number upstreamed */ + NULL, +}; -DT_MACHINE_START(BCM11351_DT, "BCM281xx Broadcom Application Processor") - .init_machine = board_init, - .restart = bcm_kona_restart, - .dt_compat = bcm11351_dt_compat, +DT_MACHINE_START(BCM281XX_DT, "BCM281xx Broadcom Application Processor") + .init_machine = bcm281xx_init, + .restart = bcm281xx_restart, + .dt_compat = bcm281xx_dt_compat, MACHINE_END diff --git a/arch/arm/mach-bcm/kona.c b/arch/arm/mach-bcm/kona.c index 6939d9017f6336..768bc2837bf598 100644 --- a/arch/arm/mach-bcm/kona.c +++ b/arch/arm/mach-bcm/kona.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Broadcom Corporation + * Copyright (C) 2012-2014 Broadcom Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -11,55 +11,33 @@ * GNU General Public License for more details. */ -#include -#include +#include +#include +#include "bcm_kona_smc.h" #include "kona.h" -static void __iomem *watchdog_base; - -void bcm_kona_setup_restart(void) +void __init kona_l2_cache_init(void) { - struct device_node *np_wdog; + int ret; - /* - * The assumption is that whoever calls bcm_kona_setup_restart() - * also needs a Kona Watchdog Timer entry in Device Tree, i.e. we - * report an error if the DT entry is missing. - */ - np_wdog = of_find_compatible_node(NULL, NULL, "brcm,kona-wdt"); - if (!np_wdog) { - pr_err("brcm,kona-wdt not found in DT, reboot disabled\n"); + if (!IS_ENABLED(CONFIG_CACHE_L2X0)) return; - } - watchdog_base = of_iomap(np_wdog, 0); - WARN(!watchdog_base, "failed to map watchdog base"); - of_node_put(np_wdog); -} - -#define SECWDOG_OFFSET 0x00000000 -#define SECWDOG_RESERVED_MASK 0xE2000000 -#define SECWDOG_WD_LOAD_FLAG_MASK 0x10000000 -#define SECWDOG_EN_MASK 0x08000000 -#define SECWDOG_SRSTEN_MASK 0x04000000 -#define SECWDOG_CLKS_SHIFT 20 -#define SECWDOG_LOCK_SHIFT 0 -void bcm_kona_restart(enum reboot_mode mode, const char *cmd) -{ - uint32_t val; - - if (!watchdog_base) - panic("Watchdog not mapped. Reboot failed.\n"); + ret = bcm_kona_smc_init(); + if (ret) { + pr_info("Secure API not available (%d). Skipping L2 init.\n", + ret); + return; + } - /* Enable watchdog2 with very short timeout. */ - val = readl(watchdog_base + SECWDOG_OFFSET); - val &= SECWDOG_RESERVED_MASK | SECWDOG_WD_LOAD_FLAG_MASK; - val |= SECWDOG_EN_MASK | SECWDOG_SRSTEN_MASK | - (0x8 << SECWDOG_CLKS_SHIFT) | - (0x8 << SECWDOG_LOCK_SHIFT); - writel(val, watchdog_base + SECWDOG_OFFSET); + bcm_kona_smc(SSAPI_ENABLE_L2_CACHE, 0, 0, 0, 0); - while (1) - ; + /* + * The aux_val and aux_mask have no effect since L2 cache is already + * enabled. Pass 0s for aux_val and 1s for aux_mask for default value. + */ + ret = l2x0_of_init(0, ~0); + if (ret) + pr_err("Couldn't enable L2 cache: %d\n", ret); } diff --git a/arch/arm/mach-bcm/kona.h b/arch/arm/mach-bcm/kona.h index 291eca3e06ff1f..3a7a017c29cd04 100644 --- a/arch/arm/mach-bcm/kona.h +++ b/arch/arm/mach-bcm/kona.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Broadcom Corporation + * Copyright (C) 2012-2014 Broadcom Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -11,7 +11,4 @@ * GNU General Public License for more details. */ -#include - -void bcm_kona_setup_restart(void); -void bcm_kona_restart(enum reboot_mode mode, const char *cmd); +void __init kona_l2_cache_init(void); diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig index 4c414af75ef0ed..685f1408669705 100644 --- a/arch/arm/mach-exynos/Kconfig +++ b/arch/arm/mach-exynos/Kconfig @@ -31,12 +31,14 @@ config ARCH_EXYNOS4 config ARCH_EXYNOS5 bool "SAMSUNG EXYNOS5" + select ARCH_SUPPORTS_BIG_ENDIAN select ARM_AMBA select CLKSRC_OF select HAVE_ARM_SCU if SMP select HAVE_SMP select PINCTRL select USB_ARCH_HAS_XHCI + select ARM_DMA_USE_IOMMU help Samsung EXYNOS5 (Cortex-A15) SoC based systems @@ -91,6 +93,16 @@ config SOC_EXYNOS5250 help Enable EXYNOS5250 SoC support +config SOC_EXYNOS5410 + bool "SAMSUNG EXYNOS5410" + default y + depends on ARCH_EXYNOS5 + select PM_GENERIC_DOMAINS if PM + select S5P_PM if PM_SLEEP + select S5P_SLEEP if PM_SLEEP + help + Enable EXYNOS5410 SoC support + config SOC_EXYNOS5420 bool "SAMSUNG EXYNOS5420" default y diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index f18be40e5b2129..082c2df8e9152e 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -52,6 +52,7 @@ static const char name_exynos4210[] = "EXYNOS4210"; static const char name_exynos4212[] = "EXYNOS4212"; static const char name_exynos4412[] = "EXYNOS4412"; static const char name_exynos5250[] = "EXYNOS5250"; +static const char name_exynos5410[] = "EXYNOS5410"; static const char name_exynos5420[] = "EXYNOS5420"; static const char name_exynos5440[] = "EXYNOS5440"; @@ -84,6 +85,12 @@ static struct cpu_table cpu_ids[] __initdata = { .map_io = exynos5_map_io, .init = exynos_init, .name = name_exynos5250, + }, { + .idcode = EXYNOS5410_SOC_ID, + .idmask = EXYNOS5_SOC_MASK, + .map_io = exynos5_map_io, + .init = exynos_init, + .name = name_exynos5410, }, { .idcode = EXYNOS5420_SOC_ID, .idmask = EXYNOS5_SOC_MASK, @@ -215,6 +222,15 @@ static struct map_desc exynos4x12_iodesc[] __initdata = { }, }; +static struct map_desc exynos5410_iodesc[] __initdata = { + { + .virtual = (unsigned long)S5P_VA_SYSRAM_NS, + .pfn = __phys_to_pfn(EXYNOS5410_PA_SYSRAM_NS), + .length = SZ_4K, + .type = MT_DEVICE, + }, +}; + static struct map_desc exynos5250_iodesc[] __initdata = { { .virtual = (unsigned long)S5P_VA_SYSRAM_NS, @@ -224,6 +240,15 @@ static struct map_desc exynos5250_iodesc[] __initdata = { }, }; +static struct map_desc exynos5420_iodesc[] __initdata = { + { + .virtual = (unsigned long)S5P_VA_SYSRAM_NS, + .pfn = __phys_to_pfn(EXYNOS5420_PA_SYSRAM_NS), + .length = SZ_4K, + .type = MT_DEVICE, + }, +}; + static struct map_desc exynos5_iodesc[] __initdata = { { .virtual = (unsigned long)S3C_VA_SYS, @@ -265,7 +290,7 @@ static struct map_desc exynos5_iodesc[] __initdata = { void exynos4_restart(enum reboot_mode mode, const char *cmd) { - __raw_writel(0x1, S5P_SWRESET); + writel_relaxed(0x1, S5P_SWRESET); } void exynos5_restart(enum reboot_mode mode, const char *cmd) @@ -282,15 +307,15 @@ void exynos5_restart(enum reboot_mode mode, const char *cmd) np = of_find_compatible_node(NULL, NULL, "samsung,exynos5440-clock"); addr = of_iomap(np, 0) + 0xbc; - status = __raw_readl(addr); + status = readl_relaxed(addr); addr = of_iomap(np, 0) + 0xcc; - val = __raw_readl(addr); + val = readl_relaxed(addr); val = (val & 0xffff0000) | (status & 0xffff); } - __raw_writel(val, addr); + writel_relaxed(val, addr); } static struct platform_device exynos_cpuidle = { @@ -379,6 +404,10 @@ static void __init exynos5_map_io(void) if (soc_is_exynos5250()) iotable_init(exynos5250_iodesc, ARRAY_SIZE(exynos5250_iodesc)); + if (soc_is_exynos5410()) + iotable_init(exynos5410_iodesc, ARRAY_SIZE(exynos5410_iodesc)); + if (soc_is_exynos5420()) + iotable_init(exynos5420_iodesc, ARRAY_SIZE(exynos5420_iodesc)); } struct bus_type exynos_subsys = { diff --git a/arch/arm/mach-exynos/cpuidle.c b/arch/arm/mach-exynos/cpuidle.c index f57cb91f02aa93..45aef491c16d5a 100644 --- a/arch/arm/mach-exynos/cpuidle.c +++ b/arch/arm/mach-exynos/cpuidle.c @@ -87,7 +87,7 @@ static struct cpuidle_driver exynos4_idle_driver = { /* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */ static void exynos4_set_wakeupmask(void) { - __raw_writel(0x0000ff3e, S5P_WAKEUP_MASK); + writel_relaxed(0x0000ff3e, S5P_WAKEUP_MASK); } static unsigned int g_pwr_ctrl, g_diag_reg; @@ -127,15 +127,15 @@ static int exynos4_enter_core0_aftr(struct cpuidle_device *dev, /* Set value of power down register for aftr mode */ exynos_sys_powerdown_conf(SYS_AFTR); - __raw_writel(virt_to_phys(s3c_cpu_resume), REG_DIRECTGO_ADDR); - __raw_writel(S5P_CHECK_AFTR, REG_DIRECTGO_FLAG); + writel_relaxed(virt_to_phys(s3c_cpu_resume), REG_DIRECTGO_ADDR); + writel_relaxed(S5P_CHECK_AFTR, REG_DIRECTGO_FLAG); save_cpu_arch_register(); /* Setting Central Sequence Register for power down mode */ - tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); + tmp = readl_relaxed(S5P_CENTRAL_SEQ_CONFIGURATION); tmp &= ~S5P_CENTRAL_LOWPWR_CFG; - __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); + writel_relaxed(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); cpu_pm_enter(); cpu_suspend(0, idle_finisher); @@ -154,14 +154,14 @@ static int exynos4_enter_core0_aftr(struct cpuidle_device *dev, * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically * in this situation. */ - tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); + tmp = readl_relaxed(S5P_CENTRAL_SEQ_CONFIGURATION); if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) { tmp |= S5P_CENTRAL_LOWPWR_CFG; - __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); + writel_relaxed(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); } /* Clear wakeup state register */ - __raw_writel(0x0, S5P_WAKEUP_STAT); + writel_relaxed(0x0, S5P_WAKEUP_STAT); return index; } @@ -198,7 +198,7 @@ static void __init exynos5_core_down_clk(void) PWR_CTRL1_USE_CORE0_WFE | \ PWR_CTRL1_USE_CORE1_WFI | \ PWR_CTRL1_USE_CORE0_WFI; - __raw_writel(tmp, EXYNOS5_PWR_CTRL1); + writel_relaxed(tmp, EXYNOS5_PWR_CTRL1); /* * Enable arm clock up (on exiting idle). Set arm divider @@ -211,7 +211,7 @@ static void __init exynos5_core_down_clk(void) PWR_CTRL2_DUR_STANDBY1_VAL | \ PWR_CTRL2_CORE2_UP_RATIO | \ PWR_CTRL2_CORE1_UP_RATIO; - __raw_writel(tmp, EXYNOS5_PWR_CTRL2); + writel_relaxed(tmp, EXYNOS5_PWR_CTRL2); } static int exynos_cpuidle_probe(struct platform_device *pdev) diff --git a/arch/arm/mach-exynos/firmware.c b/arch/arm/mach-exynos/firmware.c index 932129ef26c660..e61df85d650e14 100644 --- a/arch/arm/mach-exynos/firmware.c +++ b/arch/arm/mach-exynos/firmware.c @@ -17,6 +17,7 @@ #include #include +#include #include "smc.h" @@ -34,9 +35,12 @@ static int exynos_cpu_boot(int cpu) static int exynos_set_cpu_boot_addr(int cpu, unsigned long boot_addr) { - void __iomem *boot_reg = S5P_VA_SYSRAM_NS + 0x1c + 4*cpu; + void __iomem *boot_reg = S5P_VA_SYSRAM_NS + 0x1c; - __raw_writel(boot_addr, boot_reg); + if (!soc_is_exynos5420()) + boot_reg += 4 * cpu; + + writel_relaxed(boot_addr, boot_reg); return 0; } diff --git a/arch/arm/mach-exynos/headsmp.S b/arch/arm/mach-exynos/headsmp.S index cdd9d91e993304..08d3d3f5328314 100644 --- a/arch/arm/mach-exynos/headsmp.S +++ b/arch/arm/mach-exynos/headsmp.S @@ -12,15 +12,113 @@ */ #include #include +#include /* * exynos4 specific entry point for secondary CPUs. This provides * a "holding pen" into which all secondary cores are held until we're * ready for them to initialise. */ + +.arch_extension sec +.arch_extension virt +.text + +.align 5 +/* We use the same vector table for Hyp and Monitor mode, since + * we will only use each once and they don't overlap. + */ +mon_vectors: + W(b) . /* reset */ + W(b) . /* undef */ + W(b) 2f /* smc */ + W(b) . /* pabt */ + W(b) . /* dabt */ + W(b) 1f /* hyp */ + W(b) . /* irq */ + W(b) . /* fiq */ + +/* Return directly back to the caller without leaving Hyp mode: */ +1: mrs lr, elr_hyp + mov pc, lr + +/* In monitor mode, set up HVBAR and SCR then return to caller in NS-SVC. */ +2: + mrc p15, 0, r1, c1, c1, 0 @ SCR + /* + * Set SCR.NS=1(needed for setting HVBAR and also returning to NS state) + * .IRQ,FIQ,EA=0 (don't take aborts/exceptions to Monitor mode) + * .FW,AW=1 (CPSR.A,F modifiable in NS state) + * .nET=0 (early termination OK) + * .SCD=0 (SMC in NS mode OK, so we can call secure firmware) + * .HCE=1 (HVC does Hyp call) + */ + bic r1, r1, #0x07f + ldr r2, =0x131 + orr r1, r1, r2 + mcr p15, 0, r2, c1, c1, 0 @ SCR + isb + ldr r2, =mon_vectors + + + adr r4, 1f + ldmia r4, {r5} + sub r4, r4, r5 + add r2, r2, r4 + + mcr p15, 4, r2, c12, c0, 0 @ set HVBAR + + THUMB( mrc p15, 4, r2, c1, c0, 0 ) @ ctrl register + THUMB( orr r2, r2, #1 << 30 ) @ HSCTLR.TE (Thumb exceptions) + THUMB( mcr p15, 4, r2, c1, c0, 0 ) + THUMB( isb) + + /* ...and return to calling code in NS state */ + movs pc, lr + + + .globl monitor_init +monitor_init: + ldr ip, =mon_vectors + + adr r4, 1f + ldmia r4, {r5} + sub r4, r4, r5 + add ip, ip, r4 + mcr p15, 0, ip, c12, c0, 1 + + THUMB( mrc p15, 0, r1, c1, c0, 0 ) @ ctrl register + THUMB( orr r1, r1, #1 << 30 ) @ SCTLR.TE (Thumb exceptions) + THUMB( mcr p15, 0, r1, c1, c0, 0 ) + THUMB( isb ) + + mov pc, lr + + /* Try to go into NS-SVC: void enter_ns(void); */ + .globl enter_ns +enter_ns: + smc #0 + mov pc, lr + + /* void enter_hyp(void); */ + .globl enter_hyp +enter_hyp: + /* Now we're in NS-SVC, make a Hyp call to get into Hyp mode */ + mov r0, lr + mov r1, sp + hvc #0 + /* We will end up here in NS-Hyp. */ + mov sp, r1 + mov pc, r0 + ENTRY(exynos4_secondary_startup) + /* + * ROM code operates in little endian mode, when we get control we + * need to switch it to big endian mode. + */ +ARM_BE8(setend be) mrc p15, 0, r0, c0, c0, 5 - and r0, r0, #15 + and r0, r0, #0xffffff adr r4, 1f ldmia r4, {r5, r6} sub r4, r4, r5 @@ -29,6 +127,27 @@ pen: ldr r7, [r6] cmp r7, r0 bne pen + ldr r1, =__boot_cpu_mode + add r1, r1, r4 + ldr r2, [r1] + mrs r0, cpsr + ands r0, r0, #MODE_MASK + subs r1, r0, r2 + beq 3f + subs r2, r2, #HYP_MODE + bne 3f + + /* Setting NSACR to allow coprocessor access from non-secure mode */ + mrc p15, 0, r0, c1, c1, 2 + movw r1, #0x3fff + orr r0, r0, r1 + mcr p15, 0, r0, c1, c1, 2 +5: + bl monitor_init + bl enter_ns + bl enter_hyp + +3: /* * we've been released from the holding pen: secondary_stack * should now contain the SVC stack for this core diff --git a/arch/arm/mach-exynos/hotplug.c b/arch/arm/mach-exynos/hotplug.c index 5eead530c6f8fa..5a96c1bdbb9deb 100644 --- a/arch/arm/mach-exynos/hotplug.c +++ b/arch/arm/mach-exynos/hotplug.c @@ -96,15 +96,9 @@ static inline void platform_do_lowpower(unsigned int cpu, int *spurious) /* make cpu1 to be turned off at next WFI command */ if (cpu == 1) - __raw_writel(0, S5P_ARM_CORE1_CONFIGURATION); + writel_relaxed(0, S5P_ARM_CORE1_CONFIGURATION); - /* - * here's the WFI - */ - asm(".word 0xe320f003\n" - : - : - : "memory", "cc"); + wfi(); if (pen_release == cpu_logical_map(cpu)) { /* diff --git a/arch/arm/mach-exynos/include/mach/map.h b/arch/arm/mach-exynos/include/mach/map.h index 7b046b59d9ecc8..b2f426e5b3a90d 100644 --- a/arch/arm/mach-exynos/include/mach/map.h +++ b/arch/arm/mach-exynos/include/mach/map.h @@ -29,6 +29,8 @@ #define EXYNOS4210_PA_SYSRAM_NS 0x0203F000 #define EXYNOS4x12_PA_SYSRAM_NS 0x0204F000 #define EXYNOS5250_PA_SYSRAM_NS 0x0204F000 +#define EXYNOS5410_PA_SYSRAM_NS 0x02073000 +#define EXYNOS5420_PA_SYSRAM_NS 0x02073000 #define EXYNOS_PA_CHIPID 0x10000000 @@ -60,6 +62,10 @@ #define EXYNOS4_PA_COREPERI 0x10500000 #define EXYNOS4_PA_L2CC 0x10502000 +#define EXYNOS5_PA_SATA_PHY_CTRL 0x12170000 +#define EXYNOS5_PA_SATA_PHY_I2C 0x121D0000 +#define EXYNOS5_PA_SATA_BASE 0x122F0000 + #define EXYNOS4_PA_SROMC 0x12570000 #define EXYNOS5_PA_SROMC 0x12250000 diff --git a/arch/arm/mach-exynos/include/mach/pm-core.h b/arch/arm/mach-exynos/include/mach/pm-core.h index dc0697c2fa927c..a12caa6fed4949 100644 --- a/arch/arm/mach-exynos/include/mach/pm-core.h +++ b/arch/arm/mach-exynos/include/mach/pm-core.h @@ -37,8 +37,8 @@ static inline void s3c_pm_debug_init_uart(void) static inline void s3c_pm_arch_prepare_irqs(void) { - __raw_writel(exynos_get_eint_wake_mask(), S5P_EINT_WAKEUP_MASK); - __raw_writel(s3c_irqwake_intmask & ~(1 << 31), S5P_WAKEUP_MASK); + writel_relaxed(exynos_get_eint_wake_mask(), S5P_EINT_WAKEUP_MASK); + writel_relaxed(s3c_irqwake_intmask & ~(1 << 31), S5P_WAKEUP_MASK); } static inline void s3c_pm_arch_stop_clocks(void) diff --git a/arch/arm/mach-exynos/include/mach/uncompress.h b/arch/arm/mach-exynos/include/mach/uncompress.h index 5d7ce36be46fca..7106a436883a48 100644 --- a/arch/arm/mach-exynos/include/mach/uncompress.h +++ b/arch/arm/mach-exynos/include/mach/uncompress.h @@ -17,9 +17,15 @@ #include #include +#include + static unsigned int __raw_readl(unsigned int ptr) { +#ifdef CONFIG_CPU_BIG_ENDIAN + return __swab32(*((volatile unsigned int *)ptr)); +#else return *((volatile unsigned int *)ptr); +#endif /* CONFIG_CPU_BIG_ENDIAN */ } static void arch_detect_cpu(void) diff --git a/arch/arm/mach-exynos/mach-exynos5-dt.c b/arch/arm/mach-exynos/mach-exynos5-dt.c index 37ea261f0f6ce8..d9808f273f82bf 100644 --- a/arch/arm/mach-exynos/mach-exynos5-dt.c +++ b/arch/arm/mach-exynos/mach-exynos5-dt.c @@ -51,6 +51,7 @@ static void __init exynos5_dt_machine_init(void) static char const *exynos5_dt_compat[] __initdata = { "samsung,exynos5250", + "samsung,exynos5410", "samsung,exynos5420", "samsung,exynos5440", NULL @@ -73,6 +74,7 @@ DT_MACHINE_START(EXYNOS5_DT, "SAMSUNG EXYNOS5 (Flattened Device Tree)") /* Maintainer: Kukjin Kim */ .smp = smp_ops(exynos_smp_ops), .map_io = exynos_init_io, + .init_early = exynos_firmware_init, .init_machine = exynos5_dt_machine_init, .init_late = exynos_init_late, .dt_compat = exynos5_dt_compat, diff --git a/arch/arm/mach-exynos/platsmp.c b/arch/arm/mach-exynos/platsmp.c index 8ea02f63fed956..3642a0dcb631cd 100644 --- a/arch/arm/mach-exynos/platsmp.c +++ b/arch/arm/mach-exynos/platsmp.c @@ -39,6 +39,8 @@ static inline void __iomem *cpu_boot_reg_base(void) { if (soc_is_exynos4210() && samsung_rev() == EXYNOS4210_REV_1_1) return S5P_INFORM5; + if (soc_is_exynos5410()) + return EXYNOS5410_BOOT_REG; return S5P_VA_SYSRAM; } @@ -109,14 +111,14 @@ static int exynos_boot_secondary(unsigned int cpu, struct task_struct *idle) */ write_pen_release(phys_cpu); - if (!(__raw_readl(S5P_ARM_CORE1_STATUS) & S5P_CORE_LOCAL_PWR_EN)) { - __raw_writel(S5P_CORE_LOCAL_PWR_EN, - S5P_ARM_CORE1_CONFIGURATION); + if (!(readl_relaxed(S5P_ARM_CORE1_STATUS + (0x80 * cpu) - 0x80) & S5P_CORE_LOCAL_PWR_EN)) { + writel_relaxed(S5P_CORE_LOCAL_PWR_EN, + S5P_ARM_CORE1_CONFIGURATION + (0x80 * cpu) - 0x80); timeout = 10; /* wait max 10 ms until cpu1 is on */ - while ((__raw_readl(S5P_ARM_CORE1_STATUS) + while ((readl_relaxed(S5P_ARM_CORE1_STATUS + (0x80 * cpu) - 0x80) & S5P_CORE_LOCAL_PWR_EN) != S5P_CORE_LOCAL_PWR_EN) { if (timeout-- == 0) break; @@ -149,7 +151,7 @@ static int exynos_boot_secondary(unsigned int cpu, struct task_struct *idle) * and fall back to boot register if it fails. */ if (call_firmware_op(set_cpu_boot_addr, phys_cpu, boot_addr)) - __raw_writel(boot_addr, cpu_boot_reg(phys_cpu)); + writel_relaxed(boot_addr, cpu_boot_reg(phys_cpu)); call_firmware_op(cpu_boot, phys_cpu); @@ -224,7 +226,7 @@ static void __init exynos_smp_prepare_cpus(unsigned int max_cpus) boot_addr = virt_to_phys(exynos4_secondary_startup); if (call_firmware_op(set_cpu_boot_addr, phys_cpu, boot_addr)) - __raw_writel(boot_addr, cpu_boot_reg(phys_cpu)); + writel_relaxed(boot_addr, cpu_boot_reg(phys_cpu)); } } diff --git a/arch/arm/mach-exynos/pm.c b/arch/arm/mach-exynos/pm.c index e00025bbbe89c9..f429e34e72806f 100644 --- a/arch/arm/mach-exynos/pm.c +++ b/arch/arm/mach-exynos/pm.c @@ -130,19 +130,19 @@ static void exynos_pm_prepare(void) } else { s3c_pm_do_save(exynos5_sys_save, ARRAY_SIZE(exynos5_sys_save)); /* Disable USE_RETENTION of JPEG_MEM_OPTION */ - tmp = __raw_readl(EXYNOS5_JPEG_MEM_OPTION); + tmp = readl_relaxed(EXYNOS5_JPEG_MEM_OPTION); tmp &= ~EXYNOS5_OPTION_USE_RETENTION; - __raw_writel(tmp, EXYNOS5_JPEG_MEM_OPTION); + writel_relaxed(tmp, EXYNOS5_JPEG_MEM_OPTION); } /* Set value of power down register for sleep mode */ exynos_sys_powerdown_conf(SYS_SLEEP); - __raw_writel(S5P_CHECK_SLEEP, S5P_INFORM1); + writel_relaxed(S5P_CHECK_SLEEP, S5P_INFORM1); /* ensure at least INFORM0 has the resume address */ - __raw_writel(virt_to_phys(s3c_cpu_resume), S5P_INFORM0); + writel_relaxed(virt_to_phys(s3c_cpu_resume), S5P_INFORM0); /* Before enter central sequence mode, clock src register have to set */ @@ -187,7 +187,7 @@ static void exynos4_restore_pll(void) locktime = (3000 / pll_in_rate) * p_div; lockcnt = locktime * 10000 / (10000 / pll_in_rate); - __raw_writel(lockcnt, EXYNOS4_EPLL_LOCK); + writel_relaxed(lockcnt, EXYNOS4_EPLL_LOCK); s3c_pm_do_restore_core(exynos4_epll_save, ARRAY_SIZE(exynos4_epll_save)); @@ -205,7 +205,7 @@ static void exynos4_restore_pll(void) locktime = 750; lockcnt = locktime * 10000 / (10000 / pll_in_rate); - __raw_writel(lockcnt, EXYNOS4_VPLL_LOCK); + writel_relaxed(lockcnt, EXYNOS4_VPLL_LOCK); s3c_pm_do_restore_core(exynos4_vpll_save, ARRAY_SIZE(exynos4_vpll_save)); @@ -216,13 +216,13 @@ static void exynos4_restore_pll(void) do { if (epll_wait) { - pll_con = __raw_readl(EXYNOS4_EPLL_CON0); + pll_con = readl_relaxed(EXYNOS4_EPLL_CON0); if (pll_con & (1 << EXYNOS4_EPLLCON0_LOCKED_SHIFT)) epll_wait = 0; } if (vpll_wait) { - pll_con = __raw_readl(EXYNOS4_VPLL_CON0); + pll_con = readl_relaxed(EXYNOS4_VPLL_CON0); if (pll_con & (1 << EXYNOS4_VPLLCON0_LOCKED_SHIFT)) vpll_wait = 0; } @@ -247,9 +247,9 @@ static __init int exynos_pm_drvinit(void) /* All wakeup disable */ - tmp = __raw_readl(S5P_WAKEUP_MASK); + tmp = readl_relaxed(S5P_WAKEUP_MASK); tmp |= ((0xFF << 8) | (0x1F << 1)); - __raw_writel(tmp, S5P_WAKEUP_MASK); + writel_relaxed(tmp, S5P_WAKEUP_MASK); if (!soc_is_exynos5250()) { pll_base = clk_get(NULL, "xtal"); @@ -270,14 +270,14 @@ static int exynos_pm_suspend(void) /* Setting Central Sequence Register for power down mode */ - tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); + tmp = readl_relaxed(S5P_CENTRAL_SEQ_CONFIGURATION); tmp &= ~S5P_CENTRAL_LOWPWR_CFG; - __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); + writel_relaxed(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); /* Setting SEQ_OPTION register */ tmp = (S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0); - __raw_writel(tmp, S5P_CENTRAL_SEQ_OPTION); + writel_relaxed(tmp, S5P_CENTRAL_SEQ_OPTION); if (!soc_is_exynos5250()) { /* Save Power control register */ @@ -304,12 +304,12 @@ static void exynos_pm_resume(void) * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically * in this situation. */ - tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); + tmp = readl_relaxed(S5P_CENTRAL_SEQ_CONFIGURATION); if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) { tmp |= S5P_CENTRAL_LOWPWR_CFG; - __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); + writel_relaxed(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); /* clear the wakeup state register */ - __raw_writel(0x0, S5P_WAKEUP_STAT); + writel_relaxed(0x0, S5P_WAKEUP_STAT); /* No need to perform below restore code */ goto early_wakeup; } @@ -329,13 +329,13 @@ static void exynos_pm_resume(void) /* For release retention */ - __raw_writel((1 << 28), S5P_PAD_RET_MAUDIO_OPTION); - __raw_writel((1 << 28), S5P_PAD_RET_GPIO_OPTION); - __raw_writel((1 << 28), S5P_PAD_RET_UART_OPTION); - __raw_writel((1 << 28), S5P_PAD_RET_MMCA_OPTION); - __raw_writel((1 << 28), S5P_PAD_RET_MMCB_OPTION); - __raw_writel((1 << 28), S5P_PAD_RET_EBIA_OPTION); - __raw_writel((1 << 28), S5P_PAD_RET_EBIB_OPTION); + writel_relaxed((1 << 28), S5P_PAD_RET_MAUDIO_OPTION); + writel_relaxed((1 << 28), S5P_PAD_RET_GPIO_OPTION); + writel_relaxed((1 << 28), S5P_PAD_RET_UART_OPTION); + writel_relaxed((1 << 28), S5P_PAD_RET_MMCA_OPTION); + writel_relaxed((1 << 28), S5P_PAD_RET_MMCB_OPTION); + writel_relaxed((1 << 28), S5P_PAD_RET_EBIA_OPTION); + writel_relaxed((1 << 28), S5P_PAD_RET_EBIB_OPTION); if (soc_is_exynos5250()) s3c_pm_do_restore(exynos5_sys_save, @@ -354,7 +354,7 @@ static void exynos_pm_resume(void) early_wakeup: /* Clear SLEEP mode set in INFORM1 */ - __raw_writel(0x0, S5P_INFORM1); + writel_relaxed(0x0, S5P_INFORM1); return; } diff --git a/arch/arm/mach-exynos/pm_domains.c b/arch/arm/mach-exynos/pm_domains.c index 8fd24882f0b1eb..10b14d0dc12ffa 100644 --- a/arch/arm/mach-exynos/pm_domains.c +++ b/arch/arm/mach-exynos/pm_domains.c @@ -47,12 +47,12 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on) base = pd->base; pwr = power_on ? S5P_INT_LOCAL_PWR_EN : 0; - __raw_writel(pwr, base); + writel_relaxed(pwr, base); /* Wait max 1ms */ timeout = 10; - while ((__raw_readl(base + 0x4) & S5P_INT_LOCAL_PWR_EN) != pwr) { + while ((readl_relaxed(base + 0x4) & S5P_INT_LOCAL_PWR_EN) != pwr) { if (!timeout) { op = (power_on) ? "enable" : "disable"; pr_err("Power domain %s %s failed\n", domain->name, op); @@ -174,7 +174,7 @@ static __init int exynos4_pm_init_power_domain(void) platform_set_drvdata(pdev, pd); - on = __raw_readl(pd->base + 0x4) & S5P_INT_LOCAL_PWR_EN; + on = readl_relaxed(pd->base + 0x4) & S5P_INT_LOCAL_PWR_EN; pm_genpd_init(&pd->pd, NULL, !on); } diff --git a/arch/arm/mach-exynos/pmu.c b/arch/arm/mach-exynos/pmu.c index 05c7ce15322a7e..d667b15a697f16 100644 --- a/arch/arm/mach-exynos/pmu.c +++ b/arch/arm/mach-exynos/pmu.c @@ -347,27 +347,27 @@ static void exynos5_init_pmu(void) * Enable both SC_FEEDBACK and SC_COUNTER */ for (i = 0 ; i < ARRAY_SIZE(exynos5_list_both_cnt_feed) ; i++) { - tmp = __raw_readl(exynos5_list_both_cnt_feed[i]); + tmp = readl_relaxed(exynos5_list_both_cnt_feed[i]); tmp |= (EXYNOS5_USE_SC_FEEDBACK | EXYNOS5_USE_SC_COUNTER); - __raw_writel(tmp, exynos5_list_both_cnt_feed[i]); + writel_relaxed(tmp, exynos5_list_both_cnt_feed[i]); } /* * SKIP_DEACTIVATE_ACEACP_IN_PWDN_BITFIELD Enable */ - tmp = __raw_readl(EXYNOS5_ARM_COMMON_OPTION); + tmp = readl_relaxed(EXYNOS5_ARM_COMMON_OPTION); tmp |= EXYNOS5_SKIP_DEACTIVATE_ACEACP_IN_PWDN; - __raw_writel(tmp, EXYNOS5_ARM_COMMON_OPTION); + writel_relaxed(tmp, EXYNOS5_ARM_COMMON_OPTION); /* * Disable WFI/WFE on XXX_OPTION */ for (i = 0 ; i < ARRAY_SIZE(exynos5_list_diable_wfi_wfe) ; i++) { - tmp = __raw_readl(exynos5_list_diable_wfi_wfe[i]); + tmp = readl_relaxed(exynos5_list_diable_wfi_wfe[i]); tmp &= ~(EXYNOS5_OPTION_USE_STANDBYWFE | EXYNOS5_OPTION_USE_STANDBYWFI); - __raw_writel(tmp, exynos5_list_diable_wfi_wfe[i]); + writel_relaxed(tmp, exynos5_list_diable_wfi_wfe[i]); } } @@ -379,12 +379,12 @@ void exynos_sys_powerdown_conf(enum sys_powerdown mode) exynos5_init_pmu(); for (i = 0; (exynos_pmu_config[i].reg != PMU_TABLE_END) ; i++) - __raw_writel(exynos_pmu_config[i].val[mode], + writel_relaxed(exynos_pmu_config[i].val[mode], exynos_pmu_config[i].reg); if (soc_is_exynos4412()) { for (i = 0; exynos4412_pmu_config[i].reg != PMU_TABLE_END ; i++) - __raw_writel(exynos4412_pmu_config[i].val[mode], + writel_relaxed(exynos4412_pmu_config[i].val[mode], exynos4412_pmu_config[i].reg); } } @@ -406,13 +406,13 @@ static int __init exynos_pmu_init(void) * When SYS_WDTRESET is set, watchdog timer reset request * is ignored by power management unit. */ - value = __raw_readl(EXYNOS5_AUTO_WDTRESET_DISABLE); + value = readl_relaxed(EXYNOS5_AUTO_WDTRESET_DISABLE); value &= ~EXYNOS5_SYS_WDTRESET; - __raw_writel(value, EXYNOS5_AUTO_WDTRESET_DISABLE); + writel_relaxed(value, EXYNOS5_AUTO_WDTRESET_DISABLE); - value = __raw_readl(EXYNOS5_MASK_WDTRESET_REQUEST); + value = readl_relaxed(EXYNOS5_MASK_WDTRESET_REQUEST); value &= ~EXYNOS5_SYS_WDTRESET; - __raw_writel(value, EXYNOS5_MASK_WDTRESET_REQUEST); + writel_relaxed(value, EXYNOS5_MASK_WDTRESET_REQUEST); exynos_pmu_config = exynos5250_pmu_config; pr_info("EXYNOS5250 PMU Initialize\n"); diff --git a/arch/arm/mach-exynos/regs-pmu.h b/arch/arm/mach-exynos/regs-pmu.h index 7c029ce27711d7..819c1fad2331d5 100644 --- a/arch/arm/mach-exynos/regs-pmu.h +++ b/arch/arm/mach-exynos/regs-pmu.h @@ -312,4 +312,7 @@ #define EXYNOS5_OPTION_USE_RETENTION (1 << 4) +/* Only for EXYNOS5250 */ +#define EXYNOS5_SATA_PHY_CONTROL S5P_PMUREG(0x0724) + #endif /* __ASM_ARCH_REGS_PMU_H */ diff --git a/arch/arm/mach-hisi/Kconfig b/arch/arm/mach-hisi/Kconfig index 1abae5f6a41810..3a0c64f40bfcea 100644 --- a/arch/arm/mach-hisi/Kconfig +++ b/arch/arm/mach-hisi/Kconfig @@ -1,5 +1,7 @@ +menu "Hisilicon platform type" + config ARCH_HI3xxx - bool "Hisilicon Hi36xx/Hi37xx family" if ARCH_MULTI_V7 + bool "Hisilicon Hi36xx/Hi37xx Cortex A9 family" if ARCH_MULTI_V7 select ARM_AMBA select ARM_GIC select ARM_TIMER_SP804 @@ -14,3 +16,21 @@ config ARCH_HI3xxx select PINCTRL_SINGLE help Support for Hisilicon Hi36xx/Hi37xx processor family + +config ARCH_HIP04 + bool "Hisilicon HiP0x Cortex A15 family" if ARCH_MULTI_V7 + select ARCH_WANT_OPTIONAL_GPIOLIB + select ARM_AMBA + select ARM_GIC + select ARM_LPAE + select ARM_TIMER_SP804 + select CLKSRC_OF + select GENERIC_CLOCKEVENTS + select HAVE_ARM_ARCH_TIMER + select HAVE_SMP + select MCPM + select SMP + help + Support for Hisilicon HiP0x processor family + +endmenu diff --git a/arch/arm/mach-hisi/Makefile b/arch/arm/mach-hisi/Makefile index 6870058d0a48c2..31a5511f7cc7e7 100644 --- a/arch/arm/mach-hisi/Makefile +++ b/arch/arm/mach-hisi/Makefile @@ -2,6 +2,7 @@ # Makefile for Hisilicon processors family # -obj-y += hisilicon.o +obj-$(CONFIG_ARCH_HI3xxx) += hisilicon.o +obj-$(CONFIG_ARCH_HIP04) += hip04.o obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o diff --git a/arch/arm/mach-hisi/hip04.c b/arch/arm/mach-hisi/hip04.c new file mode 100644 index 00000000000000..b25cf3d60fbb86 --- /dev/null +++ b/arch/arm/mach-hisi/hip04.c @@ -0,0 +1,325 @@ +/* + * (Hisilicon's HiP04 SoC) flattened device tree enabled machine + * + * Copyright (c) 2013-2014 Hisilicon Ltd. + * Copyright (c) 2013-2014 Linaro Ltd. + * + * Author: Haojian Zhuang + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define BOOTWRAPPER_PHYS 0x10c00000 +#define BOOTWRAPPER_MAGIC 0xa5a5a5a5 +#define BOOTWRAPPER_SIZE 0x00010000 + +/* bits definition in SC_CPU_RESET_REQ[x]/SC_CPU_RESET_DREQ[x] + * 1 -- unreset; 0 -- reset + */ +#define CORE_RESET_BIT(x) (1 << x) +#define NEON_RESET_BIT(x) (1 << (x + 4)) +#define CORE_DEBUG_RESET_BIT(x) (1 << (x + 9)) +#define CLUSTER_L2_RESET_BIT (1 << 8) +#define CLUSTER_DEBUG_RESET_BIT (1 << 13) + +/* + * bits definition in SC_CPU_RESET_STATUS[x] + * 1 -- reset status; 0 -- unreset status + */ +#define CORE_RESET_STATUS(x) (1 << x) +#define NEON_RESET_STATUS(x) (1 << (x + 4)) +#define CORE_DEBUG_RESET_STATUS(x) (1 << (x + 9)) +#define CLUSTER_L2_RESET_STATUS (1 << 8) +#define CLUSTER_DEBUG_RESET_STATUS (1 << 13) +#define CORE_WFI_STATUS(x) (1 << (x + 16)) +#define CORE_WFE_STATUS(x) (1 << (x + 20)) +#define CORE_DEBUG_ACK(x) (1 << (x + 24)) + +#define SC_CPU_RESET_REQ(x) (0x520 + (x << 3)) /* reset */ +#define SC_CPU_RESET_DREQ(x) (0x524 + (x << 3)) /* unreset */ +#define SC_CPU_RESET_STATUS(x) (0x1520 + (x << 3)) + +#define FAB_SF_MODE 0x0c +#define FAB_SF_INVLD 0x10 + +/* bits definition in FB_SF_INVLD */ +#define FB_SF_INVLD_START (1 << 8) + +#define HIP04_MAX_CLUSTERS 4 +#define HIP04_MAX_CPUS_PER_CLUSTER 4 + +static void __iomem *relocation = NULL, *sysctrl = NULL, *fabric = NULL; +static int hip04_cpu_table[HIP04_MAX_CLUSTERS][HIP04_MAX_CPUS_PER_CLUSTER]; +static DEFINE_SPINLOCK(boot_lock); + +static bool hip04_cluster_down(unsigned int cluster) +{ + int i; + + for (i = 0; i < HIP04_MAX_CPUS_PER_CLUSTER; i++) + if (hip04_cpu_table[cluster][i]) + return false; + return true; +} + +static void hip04_set_snoop_filter(unsigned int cluster, unsigned int on) +{ + unsigned long data; + + if (!fabric) + return; + data = readl_relaxed(fabric + FAB_SF_MODE); + if (on) + data |= 1 << cluster; + else + data &= ~(1 << cluster); + writel_relaxed(data, fabric + FAB_SF_MODE); + while (1) { + if (data == readl_relaxed(fabric + FAB_SF_MODE)) + break; + } +} + +static int hip04_mcpm_power_up(unsigned int cpu, unsigned int cluster) +{ + unsigned long data, mask; + + if (!relocation || !sysctrl) + return -ENODEV; + if (cluster >= HIP04_MAX_CLUSTERS || cpu >= HIP04_MAX_CPUS_PER_CLUSTER) + return -EINVAL; + + spin_lock(&boot_lock); + writel_relaxed(BOOTWRAPPER_PHYS, relocation); + writel_relaxed(BOOTWRAPPER_MAGIC, relocation + 4); + writel_relaxed(virt_to_phys(mcpm_entry_point), relocation + 8); + writel_relaxed(0, relocation + 12); + + if (hip04_cluster_down(cluster)) { + data = CLUSTER_L2_RESET_BIT | CLUSTER_DEBUG_RESET_BIT; + writel_relaxed(data, sysctrl + SC_CPU_RESET_DREQ(cluster)); + do { + mask = CLUSTER_L2_RESET_STATUS | \ + CLUSTER_DEBUG_RESET_STATUS; + data = readl_relaxed(sysctrl + \ + SC_CPU_RESET_STATUS(cluster)); + } while (data & mask); + hip04_set_snoop_filter(cluster, 1); + } + + hip04_cpu_table[cluster][cpu]++; + + data = CORE_RESET_BIT(cpu) | NEON_RESET_BIT(cpu) | \ + CORE_DEBUG_RESET_BIT(cpu); + writel_relaxed(data, sysctrl + SC_CPU_RESET_DREQ(cluster)); + spin_unlock(&boot_lock); + + return 0; +} + +static void hip04_mcpm_power_down(void) +{ + unsigned int mpidr, cpu, cluster; + unsigned int v; + + spin_lock(&boot_lock); + spin_unlock(&boot_lock); + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + local_irq_disable(); + gic_cpu_if_down(); + + __mcpm_cpu_down(cpu, cluster); + + asm volatile( + " mrc p15, 0, %0, c1, c0, 0\n" + " bic %0, %0, %1\n" + " mcr p15, 0, %0, c1, c0, 0\n" + : "=&r" (v) + : "Ir" (CR_C) + : "cc"); + + flush_cache_louis(); + + asm volatile( + /* + * Turn off coherency + */ + " mrc p15, 0, %0, c1, c0, 1\n" + " bic %0, %0, %1\n" + " mcr p15, 0, %0, c1, c0, 1\n" + : "=&r" (v) + : "Ir" (0x40) + : "cc"); + + isb(); + dsb(); +} + +static int hip04_mcpm_power_down_finish(unsigned int cpu, unsigned int cluster) +{ + int ret = -EBUSY; + + spin_lock(&boot_lock); + BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP); + __mcpm_cpu_going_down(cpu, cluster); + + hip04_cpu_table[cluster][cpu]--; + if (hip04_cpu_table[cluster][cpu]) { + pr_err("Cluster %d CPU%d is still running\n", cluster, cpu); + goto out; + } + ret = 0; +out: + spin_unlock(&boot_lock); + return ret; +} + +static void hip04_mcpm_powered_up(void) +{ + if (!relocation) + return; + spin_lock(&boot_lock); + writel_relaxed(0, relocation); + writel_relaxed(0, relocation + 4); + writel_relaxed(0, relocation + 8); + writel_relaxed(0, relocation + 12); + spin_unlock(&boot_lock); +} + +static const struct mcpm_platform_ops hip04_mcpm_ops = { + .power_up = hip04_mcpm_power_up, + .power_down = hip04_mcpm_power_down, + .power_down_finish = hip04_mcpm_power_down_finish, + .powered_up = hip04_mcpm_powered_up, +}; + +static bool __init hip04_cpu_table_init(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + if (cluster >= HIP04_MAX_CLUSTERS || + cpu >= HIP04_MAX_CPUS_PER_CLUSTER) { + pr_err("%s: boot CPU is out of bound!\n", __func__); + return false; + } + hip04_set_snoop_filter(cluster, 1); + hip04_cpu_table[cluster][cpu] = 1; + return true; +} + +static int __init hip04_mcpm_init(void) +{ + struct device_node *np; + int ret = -ENODEV; + + np = of_find_compatible_node(NULL, NULL, "hisilicon,hip04-mcpm"); + if (!np) { + pr_err("failed to find hisilicon,hip04-mcpm node\n"); + goto err; + } + relocation = of_iomap(np, 0); + if (!relocation) { + pr_err("failed to get relocation space\n"); + ret = -ENOMEM; + goto err; + } + sysctrl = of_iomap(np, 1); + if (!sysctrl) { + pr_err("failed to get sysctrl base\n"); + ret = -ENOMEM; + goto err_sysctrl; + } + fabric = of_iomap(np, 2); + if (!fabric) { + pr_err("failed to get fabric base\n"); + ret = -ENOMEM; + goto err_fabric; + } + if (!hip04_cpu_table_init()) + return -EINVAL; + ret = mcpm_platform_register(&hip04_mcpm_ops); + if (!ret) { + mcpm_sync_init(NULL); + pr_info("HiP04 MCPM initialized\n"); + } + return ret; +err_fabric: + iounmap(sysctrl); +err_sysctrl: + iounmap(relocation); +err: + return ret; +} +early_initcall(hip04_mcpm_init); + +bool __init hip04_smp_init_ops(void) +{ + mcpm_smp_set_ops(); + return true; +} + +#define HIP04_SATA_BASE (0xea000000) + +static int sata_vsemiphy_init(struct device *dev, void __iomem *addr) +{ + return 0; +} + +static struct ahci_platform_data hip04_sata_pdata = { + .init = sata_vsemiphy_init, +}; + +static struct of_dev_auxdata hip04_auxdata_lookup[] __initdata = { + OF_DEV_AUXDATA("hisilicon,hisi-ahci", HIP04_SATA_BASE, + NULL, &hip04_sata_pdata), + {}, +}; + +static void __init hip04_init_machine(void) +{ + of_platform_populate(NULL, of_default_bus_match_table, + hip04_auxdata_lookup, NULL); +} + +static const char *hip04_compat[] __initconst = { + "hisilicon,hip04-d01", + NULL, +}; + +static void __init hip04_reserve(void) +{ + memblock_reserve(BOOTWRAPPER_PHYS, BOOTWRAPPER_SIZE); +} + +DT_MACHINE_START(HIP01, "Hisilicon HiP04 (Flattened Device Tree)") + .dt_compat = hip04_compat, + .smp_init = smp_init_ops(hip04_smp_init_ops), + .init_machine = hip04_init_machine, + .reserve = hip04_reserve, +MACHINE_END diff --git a/arch/arm/mach-hisi/hisilicon.c b/arch/arm/mach-hisi/hisilicon.c index 741faf3e710062..ae03e6128e257c 100644 --- a/arch/arm/mach-hisi/hisilicon.c +++ b/arch/arm/mach-hisi/hisilicon.c @@ -19,6 +19,7 @@ #include +#include #include #include @@ -45,6 +46,34 @@ static struct map_desc hi3620_io_desc[] __initdata = { }, }; +static struct of_device_id hi3xxx_l2cache_match[] __initdata = { + { .compatible = "arm,pl310-cache", }, + {} +}; + +static int hi3xxx_l2_init(void) +{ + struct device_node *node = NULL; + u32 data[2]; + int ret; + + node = of_find_matching_node(NULL, hi3xxx_l2cache_match); + WARN_ON(!node); + if (!node) { + pr_err("Failed to find PL310 L2 cache\n"); + return -ENOENT; + } + ret = of_property_read_u32_array(node, "hisilicon,l2cache-aux", + &data[0], 2); + if (ret < 0) { + data[0] = 0; + data[1] = ~0UL; + } + l2x0_of_init(data[0], data[1]); + return 0; +} +early_initcall(hi3xxx_l2_init); + static void __init hi3620_map_io(void) { debug_ll_io_init(); diff --git a/arch/arm/mach-omap2/Kconfig b/arch/arm/mach-omap2/Kconfig index 0af7ca02314d99..d3e641e6347305 100644 --- a/arch/arm/mach-omap2/Kconfig +++ b/arch/arm/mach-omap2/Kconfig @@ -45,6 +45,7 @@ config ARCH_OMAP4 select USB_ARCH_HAS_EHCI if USB_SUPPORT select ARM_ERRATA_754322 select ARM_ERRATA_775420 + select ARCH_SUPPORTS_BIG_ENDIAN config SOC_OMAP5 bool "TI OMAP5" diff --git a/arch/arm/mach-omap2/board-flash.c b/arch/arm/mach-omap2/board-flash.c index ac82512b9c8c64..84cc1482e584c4 100644 --- a/arch/arm/mach-omap2/board-flash.c +++ b/arch/arm/mach-omap2/board-flash.c @@ -160,13 +160,13 @@ static u8 get_gpmc0_type(void) if (!fpga_map_addr) return -ENOMEM; - if (!(__raw_readw(fpga_map_addr + REG_FPGA_REV))) + if (!(readw_relaxed(fpga_map_addr + REG_FPGA_REV))) /* we dont have an DEBUG FPGA??? */ /* Depend on #defines!! default to strata boot return param */ goto unmap; /* S8-DIP-OFF = 1, S8-DIP-ON = 0 */ - cs = __raw_readw(fpga_map_addr + REG_FPGA_DIP_SWITCH_INPUT2) & 0xf; + cs = readw_relaxed(fpga_map_addr + REG_FPGA_DIP_SWITCH_INPUT2) & 0xf; /* ES2.0 SDP's onwards 4 dip switches are provided for CS */ if (omap_rev() >= OMAP3430_REV_ES1_0) diff --git a/arch/arm/mach-omap2/clkt2xxx_dpllcore.c b/arch/arm/mach-omap2/clkt2xxx_dpllcore.c index 3ff32543493c58..59cf310bc1e92c 100644 --- a/arch/arm/mach-omap2/clkt2xxx_dpllcore.c +++ b/arch/arm/mach-omap2/clkt2xxx_dpllcore.c @@ -138,7 +138,7 @@ int omap2_reprogram_dpllcore(struct clk_hw *hw, unsigned long rate, if (!dd) return -EINVAL; - tmpset.cm_clksel1_pll = __raw_readl(dd->mult_div1_reg); + tmpset.cm_clksel1_pll = readl_relaxed(dd->mult_div1_reg); tmpset.cm_clksel1_pll &= ~(dd->mult_mask | dd->div1_mask); div = ((curr_prcm_set->xtal_speed / 1000000) - 1); diff --git a/arch/arm/mach-omap2/clkt2xxx_osc.c b/arch/arm/mach-omap2/clkt2xxx_osc.c index 19f54d433490cb..0717dff1bc04ca 100644 --- a/arch/arm/mach-omap2/clkt2xxx_osc.c +++ b/arch/arm/mach-omap2/clkt2xxx_osc.c @@ -39,9 +39,9 @@ int omap2_enable_osc_ck(struct clk_hw *clk) { u32 pcc; - pcc = __raw_readl(prcm_clksrc_ctrl); + pcc = readl_relaxed(prcm_clksrc_ctrl); - __raw_writel(pcc & ~OMAP_AUTOEXTCLKMODE_MASK, prcm_clksrc_ctrl); + writel_relaxed(pcc & ~OMAP_AUTOEXTCLKMODE_MASK, prcm_clksrc_ctrl); return 0; } @@ -57,9 +57,9 @@ void omap2_disable_osc_ck(struct clk_hw *clk) { u32 pcc; - pcc = __raw_readl(prcm_clksrc_ctrl); + pcc = readl_relaxed(prcm_clksrc_ctrl); - __raw_writel(pcc | OMAP_AUTOEXTCLKMODE_MASK, prcm_clksrc_ctrl); + writel_relaxed(pcc | OMAP_AUTOEXTCLKMODE_MASK, prcm_clksrc_ctrl); } unsigned long omap2_osc_clk_recalc(struct clk_hw *clk, diff --git a/arch/arm/mach-omap2/clkt2xxx_sys.c b/arch/arm/mach-omap2/clkt2xxx_sys.c index f467d072cd026d..58dd3a9b726c77 100644 --- a/arch/arm/mach-omap2/clkt2xxx_sys.c +++ b/arch/arm/mach-omap2/clkt2xxx_sys.c @@ -33,7 +33,7 @@ u32 omap2xxx_get_sysclkdiv(void) { u32 div; - div = __raw_readl(prcm_clksrc_ctrl); + div = readl_relaxed(prcm_clksrc_ctrl); div &= OMAP_SYSCLKDIV_MASK; div >>= OMAP_SYSCLKDIV_SHIFT; diff --git a/arch/arm/mach-omap2/cm2xxx_3xxx.h b/arch/arm/mach-omap2/cm2xxx_3xxx.h index bfbd16fe915110..72928a3ce2aab5 100644 --- a/arch/arm/mach-omap2/cm2xxx_3xxx.h +++ b/arch/arm/mach-omap2/cm2xxx_3xxx.h @@ -52,12 +52,12 @@ static inline u32 omap2_cm_read_mod_reg(s16 module, u16 idx) { - return __raw_readl(cm_base + module + idx); + return readl_relaxed(cm_base + module + idx); } static inline void omap2_cm_write_mod_reg(u32 val, s16 module, u16 idx) { - __raw_writel(val, cm_base + module + idx); + writel_relaxed(val, cm_base + module + idx); } /* Read-modify-write a register in a CM module. Caller must lock */ diff --git a/arch/arm/mach-omap2/cm33xx.c b/arch/arm/mach-omap2/cm33xx.c index 40a22e5649aead..b3f99e93def001 100644 --- a/arch/arm/mach-omap2/cm33xx.c +++ b/arch/arm/mach-omap2/cm33xx.c @@ -50,13 +50,13 @@ /* Read a register in a CM instance */ static inline u32 am33xx_cm_read_reg(u16 inst, u16 idx) { - return __raw_readl(cm_base + inst + idx); + return readl_relaxed(cm_base + inst + idx); } /* Write into a register in a CM */ static inline void am33xx_cm_write_reg(u32 val, u16 inst, u16 idx) { - __raw_writel(val, cm_base + inst + idx); + writel_relaxed(val, cm_base + inst + idx); } /* Read-modify-write a register in CM */ diff --git a/arch/arm/mach-omap2/cm3xxx.c b/arch/arm/mach-omap2/cm3xxx.c index f6f028867bfe17..9079f2558b1bae 100644 --- a/arch/arm/mach-omap2/cm3xxx.c +++ b/arch/arm/mach-omap2/cm3xxx.c @@ -388,7 +388,7 @@ void omap3_cm_save_context(void) omap2_cm_read_mod_reg(OMAP3430_IVA2_MOD, CM_CLKSEL1); cm_context.iva2_cm_clksel2 = omap2_cm_read_mod_reg(OMAP3430_IVA2_MOD, CM_CLKSEL2); - cm_context.cm_sysconfig = __raw_readl(OMAP3430_CM_SYSCONFIG); + cm_context.cm_sysconfig = readl_relaxed(OMAP3430_CM_SYSCONFIG); cm_context.sgx_cm_clksel = omap2_cm_read_mod_reg(OMAP3430ES2_SGX_MOD, CM_CLKSEL); cm_context.dss_cm_clksel = @@ -418,7 +418,7 @@ void omap3_cm_save_context(void) omap2_cm_read_mod_reg(PLL_MOD, OMAP3430ES2_CM_CLKSEL5); cm_context.pll_cm_clken2 = omap2_cm_read_mod_reg(PLL_MOD, OMAP3430ES2_CM_CLKEN2); - cm_context.cm_polctrl = __raw_readl(OMAP3430_CM_POLCTRL); + cm_context.cm_polctrl = readl_relaxed(OMAP3430_CM_POLCTRL); cm_context.iva2_cm_fclken = omap2_cm_read_mod_reg(OMAP3430_IVA2_MOD, CM_FCLKEN); cm_context.iva2_cm_clken_pll = @@ -519,7 +519,7 @@ void omap3_cm_restore_context(void) CM_CLKSEL1); omap2_cm_write_mod_reg(cm_context.iva2_cm_clksel2, OMAP3430_IVA2_MOD, CM_CLKSEL2); - __raw_writel(cm_context.cm_sysconfig, OMAP3430_CM_SYSCONFIG); + writel_relaxed(cm_context.cm_sysconfig, OMAP3430_CM_SYSCONFIG); omap2_cm_write_mod_reg(cm_context.sgx_cm_clksel, OMAP3430ES2_SGX_MOD, CM_CLKSEL); omap2_cm_write_mod_reg(cm_context.dss_cm_clksel, OMAP3430_DSS_MOD, @@ -547,7 +547,7 @@ void omap3_cm_restore_context(void) OMAP3430ES2_CM_CLKSEL5); omap2_cm_write_mod_reg(cm_context.pll_cm_clken2, PLL_MOD, OMAP3430ES2_CM_CLKEN2); - __raw_writel(cm_context.cm_polctrl, OMAP3430_CM_POLCTRL); + writel_relaxed(cm_context.cm_polctrl, OMAP3430_CM_POLCTRL); omap2_cm_write_mod_reg(cm_context.iva2_cm_fclken, OMAP3430_IVA2_MOD, CM_FCLKEN); omap2_cm_write_mod_reg(cm_context.iva2_cm_clken_pll, OMAP3430_IVA2_MOD, diff --git a/arch/arm/mach-omap2/cm44xx.c b/arch/arm/mach-omap2/cm44xx.c index 535d66e2822c26..30b6d9743b734e 100644 --- a/arch/arm/mach-omap2/cm44xx.c +++ b/arch/arm/mach-omap2/cm44xx.c @@ -30,23 +30,23 @@ /* Read a register in CM1 */ u32 omap4_cm1_read_inst_reg(s16 inst, u16 reg) { - return __raw_readl(OMAP44XX_CM1_REGADDR(inst, reg)); + return readl_relaxed(OMAP44XX_CM1_REGADDR(inst, reg)); } /* Write into a register in CM1 */ void omap4_cm1_write_inst_reg(u32 val, s16 inst, u16 reg) { - __raw_writel(val, OMAP44XX_CM1_REGADDR(inst, reg)); + writel_relaxed(val, OMAP44XX_CM1_REGADDR(inst, reg)); } /* Read a register in CM2 */ u32 omap4_cm2_read_inst_reg(s16 inst, u16 reg) { - return __raw_readl(OMAP44XX_CM2_REGADDR(inst, reg)); + return readl_relaxed(OMAP44XX_CM2_REGADDR(inst, reg)); } /* Write into a register in CM2 */ void omap4_cm2_write_inst_reg(u32 val, s16 inst, u16 reg) { - __raw_writel(val, OMAP44XX_CM2_REGADDR(inst, reg)); + writel_relaxed(val, OMAP44XX_CM2_REGADDR(inst, reg)); } diff --git a/arch/arm/mach-omap2/cminst44xx.c b/arch/arm/mach-omap2/cminst44xx.c index 731ca134348c34..50797ba52b59c2 100644 --- a/arch/arm/mach-omap2/cminst44xx.c +++ b/arch/arm/mach-omap2/cminst44xx.c @@ -116,7 +116,7 @@ u32 omap4_cminst_read_inst_reg(u8 part, u16 inst, u16 idx) BUG_ON(part >= OMAP4_MAX_PRCM_PARTITIONS || part == OMAP4430_INVALID_PRCM_PARTITION || !_cm_bases[part]); - return __raw_readl(_cm_bases[part] + inst + idx); + return readl_relaxed(_cm_bases[part] + inst + idx); } /* Write into a register in a CM instance */ @@ -125,7 +125,7 @@ void omap4_cminst_write_inst_reg(u32 val, u8 part, u16 inst, u16 idx) BUG_ON(part >= OMAP4_MAX_PRCM_PARTITIONS || part == OMAP4430_INVALID_PRCM_PARTITION || !_cm_bases[part]); - __raw_writel(val, _cm_bases[part] + inst + idx); + writel_relaxed(val, _cm_bases[part] + inst + idx); } /* Read-modify-write a register in CM1. Caller must lock */ diff --git a/arch/arm/mach-omap2/control.c b/arch/arm/mach-omap2/control.c index 44bb4d544dcf5c..751f3549bf6fcb 100644 --- a/arch/arm/mach-omap2/control.c +++ b/arch/arm/mach-omap2/control.c @@ -151,32 +151,32 @@ void __iomem *omap_ctrl_base_get(void) u8 omap_ctrl_readb(u16 offset) { - return __raw_readb(OMAP_CTRL_REGADDR(offset)); + return readb_relaxed(OMAP_CTRL_REGADDR(offset)); } u16 omap_ctrl_readw(u16 offset) { - return __raw_readw(OMAP_CTRL_REGADDR(offset)); + return readw_relaxed(OMAP_CTRL_REGADDR(offset)); } u32 omap_ctrl_readl(u16 offset) { - return __raw_readl(OMAP_CTRL_REGADDR(offset)); + return readl_relaxed(OMAP_CTRL_REGADDR(offset)); } void omap_ctrl_writeb(u8 val, u16 offset) { - __raw_writeb(val, OMAP_CTRL_REGADDR(offset)); + writeb_relaxed(val, OMAP_CTRL_REGADDR(offset)); } void omap_ctrl_writew(u16 val, u16 offset) { - __raw_writew(val, OMAP_CTRL_REGADDR(offset)); + writew_relaxed(val, OMAP_CTRL_REGADDR(offset)); } void omap_ctrl_writel(u32 val, u16 offset) { - __raw_writel(val, OMAP_CTRL_REGADDR(offset)); + writel_relaxed(val, OMAP_CTRL_REGADDR(offset)); } /* @@ -188,12 +188,12 @@ void omap_ctrl_writel(u32 val, u16 offset) u32 omap4_ctrl_pad_readl(u16 offset) { - return __raw_readl(OMAP4_CTRL_PAD_REGADDR(offset)); + return readl_relaxed(OMAP4_CTRL_PAD_REGADDR(offset)); } void omap4_ctrl_pad_writel(u32 val, u16 offset) { - __raw_writel(val, OMAP4_CTRL_PAD_REGADDR(offset)); + writel_relaxed(val, OMAP4_CTRL_PAD_REGADDR(offset)); } #ifdef CONFIG_ARCH_OMAP3 @@ -222,7 +222,7 @@ void omap3_ctrl_write_boot_mode(u8 bootmode) * * XXX This should use some omap_ctrl_writel()-type function */ - __raw_writel(l, OMAP2_L4_IO_ADDRESS(OMAP343X_SCRATCHPAD + 4)); + writel_relaxed(l, OMAP2_L4_IO_ADDRESS(OMAP343X_SCRATCHPAD + 4)); } #endif @@ -285,7 +285,7 @@ void omap3_clear_scratchpad_contents(void) if (omap2_prm_read_mod_reg(OMAP3430_GR_MOD, OMAP3_PRM_RSTST_OFFSET) & OMAP3430_GLOBAL_COLD_RST_MASK) { for ( ; offset <= max_offset; offset += 0x4) - __raw_writel(0x0, (v_addr + offset)); + writel_relaxed(0x0, (v_addr + offset)); omap2_prm_set_mod_reg_bits(OMAP3430_GLOBAL_COLD_RST_MASK, OMAP3430_GR_MOD, OMAP3_PRM_RSTST_OFFSET); diff --git a/arch/arm/mach-omap2/dma.c b/arch/arm/mach-omap2/dma.c index 49fd0d501c9bc5..2582ed27644bb3 100644 --- a/arch/arm/mach-omap2/dma.c +++ b/arch/arm/mach-omap2/dma.c @@ -98,7 +98,7 @@ static inline void dma_write(u32 val, int reg, int lch) stride = (reg >= dma_common_ch_start) ? dma_stride : 0; offset = reg_map[reg] + (stride * lch); - __raw_writel(val, dma_base + offset); + writel_relaxed(val, dma_base + offset); } static inline u32 dma_read(int reg, int lch) @@ -108,7 +108,7 @@ static inline u32 dma_read(int reg, int lch) stride = (reg >= dma_common_ch_start) ? dma_stride : 0; offset = reg_map[reg] + (stride * lch); - val = __raw_readl(dma_base + offset); + val = readl_relaxed(dma_base + offset); return val; } diff --git a/arch/arm/mach-omap2/gpmc.c b/arch/arm/mach-omap2/gpmc.c index ab43755364f5a7..c99a6d7131d21f 100644 --- a/arch/arm/mach-omap2/gpmc.c +++ b/arch/arm/mach-omap2/gpmc.c @@ -170,12 +170,12 @@ static irqreturn_t gpmc_handle_irq(int irq, void *dev); static void gpmc_write_reg(int idx, u32 val) { - __raw_writel(val, gpmc_base + idx); + writel_relaxed(val, gpmc_base + idx); } static u32 gpmc_read_reg(int idx) { - return __raw_readl(gpmc_base + idx); + return readl_relaxed(gpmc_base + idx); } void gpmc_cs_write_reg(int cs, int idx, u32 val) @@ -183,7 +183,7 @@ void gpmc_cs_write_reg(int cs, int idx, u32 val) void __iomem *reg_addr; reg_addr = gpmc_base + GPMC_CS0_OFFSET + (cs * GPMC_CS_SIZE) + idx; - __raw_writel(val, reg_addr); + writel_relaxed(val, reg_addr); } static u32 gpmc_cs_read_reg(int cs, int idx) @@ -191,7 +191,7 @@ static u32 gpmc_cs_read_reg(int cs, int idx) void __iomem *reg_addr; reg_addr = gpmc_base + GPMC_CS0_OFFSET + (cs * GPMC_CS_SIZE) + idx; - return __raw_readl(reg_addr); + return readl_relaxed(reg_addr); } /* TODO: Add support for gpmc_fck to clock framework and use it */ diff --git a/arch/arm/mach-omap2/id.c b/arch/arm/mach-omap2/id.c index 9428c5f9d4f2fa..c9313e2b5d8164 100644 --- a/arch/arm/mach-omap2/id.c +++ b/arch/arm/mach-omap2/id.c @@ -94,7 +94,7 @@ EXPORT_SYMBOL(omap_type); #define OMAP_TAP_DIE_ID_44XX_2 0x020c #define OMAP_TAP_DIE_ID_44XX_3 0x0210 -#define read_tap_reg(reg) __raw_readl(tap_base + (reg)) +#define read_tap_reg(reg) readl_relaxed(tap_base + (reg)) struct omap_id { u16 hawkeye; /* Silicon type (Hawkeye id) */ diff --git a/arch/arm/mach-omap2/irq.c b/arch/arm/mach-omap2/irq.c index e022a869bff238..ae81f472f10468 100644 --- a/arch/arm/mach-omap2/irq.c +++ b/arch/arm/mach-omap2/irq.c @@ -83,12 +83,12 @@ struct omap3_intc_regs { static void intc_bank_write_reg(u32 val, struct omap_irq_bank *bank, u16 reg) { - __raw_writel(val, bank->base_reg + reg); + writel_relaxed(val, bank->base_reg + reg); } static u32 intc_bank_read_reg(struct omap_irq_bank *bank, u16 reg) { - return __raw_readl(bank->base_reg + reg); + return readl_relaxed(bank->base_reg + reg); } /* XXX: FIQ and additional INTC support (only MPU at the moment) */ diff --git a/arch/arm/mach-omap2/mux.c b/arch/arm/mach-omap2/mux.c index 48094b58c88f77..fd88edeb027f47 100644 --- a/arch/arm/mach-omap2/mux.c +++ b/arch/arm/mach-omap2/mux.c @@ -70,18 +70,18 @@ struct omap_mux_partition *omap_mux_get(const char *name) u16 omap_mux_read(struct omap_mux_partition *partition, u16 reg) { if (partition->flags & OMAP_MUX_REG_8BIT) - return __raw_readb(partition->base + reg); + return readb_relaxed(partition->base + reg); else - return __raw_readw(partition->base + reg); + return readw_relaxed(partition->base + reg); } void omap_mux_write(struct omap_mux_partition *partition, u16 val, u16 reg) { if (partition->flags & OMAP_MUX_REG_8BIT) - __raw_writeb(val, partition->base + reg); + writeb_relaxed(val, partition->base + reg); else - __raw_writew(val, partition->base + reg); + writew_relaxed(val, partition->base + reg); } void omap_mux_write_array(struct omap_mux_partition *partition, diff --git a/arch/arm/mach-omap2/omap-headsmp.S b/arch/arm/mach-omap2/omap-headsmp.S index 75e92952c18efe..75c98d439b1841 100644 --- a/arch/arm/mach-omap2/omap-headsmp.S +++ b/arch/arm/mach-omap2/omap-headsmp.S @@ -17,6 +17,7 @@ #include #include +#include #include "omap44xx.h" @@ -57,6 +58,12 @@ hold: ldr r12,=0x103 cmp r0, r4 bne hold + /* + * ROM code operates in little endian mode, when we get control we + * need to switch it back to big endian mode. + */ +ARM_BE8(setend be) + /* * we've been released from the wait loop,secondary_stack * should now contain the SVC stack for this core @@ -74,6 +81,12 @@ hold_2: ldr r12,=0x103 cmp r0, r4 bne hold_2 + /* + * ROM code operates in little endian mode, when we get control we + * need to switch it back to big endian mode. + */ +ARM_BE8(setend be) + /* * GIC distributor control register has changed between * CortexA9 r1pX and r2pX. The Control Register secure diff --git a/arch/arm/mach-omap2/omap-hotplug.c b/arch/arm/mach-omap2/omap-hotplug.c index 458f72f9dc8ff3..971791fe9a3f2f 100644 --- a/arch/arm/mach-omap2/omap-hotplug.c +++ b/arch/arm/mach-omap2/omap-hotplug.c @@ -39,7 +39,7 @@ void __ref omap4_cpu_die(unsigned int cpu) if (omap_modify_auxcoreboot0(0x0, 0x200) != 0x0) pr_err("Secure clear status failed\n"); } else { - __raw_writel(0, base + OMAP_AUX_CORE_BOOT_0); + writel_relaxed(0, base + OMAP_AUX_CORE_BOOT_0); } @@ -53,7 +53,7 @@ void __ref omap4_cpu_die(unsigned int cpu) boot_cpu = omap_read_auxcoreboot0(); else boot_cpu = - __raw_readl(base + OMAP_AUX_CORE_BOOT_0) >> 5; + readl_relaxed(base + OMAP_AUX_CORE_BOOT_0) >> 5; if (boot_cpu == smp_processor_id()) { /* diff --git a/arch/arm/mach-omap2/omap-mpuss-lowpower.c b/arch/arm/mach-omap2/omap-mpuss-lowpower.c index 667915d236f3db..eb76e47091adb5 100644 --- a/arch/arm/mach-omap2/omap-mpuss-lowpower.c +++ b/arch/arm/mach-omap2/omap-mpuss-lowpower.c @@ -116,7 +116,7 @@ static inline void set_cpu_wakeup_addr(unsigned int cpu_id, u32 addr) { struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); - __raw_writel(addr, pm_info->wkup_sar_addr); + writel_relaxed(addr, pm_info->wkup_sar_addr); } /* @@ -141,7 +141,7 @@ static void scu_pwrst_prepare(unsigned int cpu_id, unsigned int cpu_state) break; } - __raw_writel(scu_pwr_st, pm_info->scu_sar_addr); + writel_relaxed(scu_pwr_st, pm_info->scu_sar_addr); } /* Helper functions for MPUSS OSWR */ @@ -179,7 +179,7 @@ static void l2x0_pwrst_prepare(unsigned int cpu_id, unsigned int save_state) { struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); - __raw_writel(save_state, pm_info->l2x0_sar_addr); + writel_relaxed(save_state, pm_info->l2x0_sar_addr); } /* @@ -192,10 +192,10 @@ static void save_l2x0_context(void) u32 val; void __iomem *l2x0_base = omap4_get_l2cache_base(); if (l2x0_base) { - val = __raw_readl(l2x0_base + L2X0_AUX_CTRL); - __raw_writel(val, sar_base + L2X0_AUXCTRL_OFFSET); - val = __raw_readl(l2x0_base + L2X0_PREFETCH_CTRL); - __raw_writel(val, sar_base + L2X0_PREFETCH_CTRL_OFFSET); + val = readl_relaxed(l2x0_base + L2X0_AUX_CTRL); + writel_relaxed(val, sar_base + L2X0_AUXCTRL_OFFSET); + val = readl_relaxed(l2x0_base + L2X0_PREFETCH_CTRL); + writel_relaxed(val, sar_base + L2X0_PREFETCH_CTRL_OFFSET); } } #else @@ -386,9 +386,9 @@ int __init omap4_mpuss_init(void) /* Save device type on scratchpad for low level code to use */ if (omap_type() != OMAP2_DEVICE_TYPE_GP) - __raw_writel(1, sar_base + OMAP_TYPE_OFFSET); + writel_relaxed(1, sar_base + OMAP_TYPE_OFFSET); else - __raw_writel(0, sar_base + OMAP_TYPE_OFFSET); + writel_relaxed(0, sar_base + OMAP_TYPE_OFFSET); save_l2x0_context(); diff --git a/arch/arm/mach-omap2/omap-smp.c b/arch/arm/mach-omap2/omap-smp.c index 17550aa39d0f39..c942bbe0c4b3e9 100644 --- a/arch/arm/mach-omap2/omap-smp.c +++ b/arch/arm/mach-omap2/omap-smp.c @@ -99,7 +99,7 @@ static int omap4_boot_secondary(unsigned int cpu, struct task_struct *idle) if (omap_secure_apis_support()) omap_modify_auxcoreboot0(0x200, 0xfffffdff); else - __raw_writel(0x20, base + OMAP_AUX_CORE_BOOT_0); + writel_relaxed(0x20, base + OMAP_AUX_CORE_BOOT_0); if (!cpu1_clkdm && !cpu1_pwrdm) { cpu1_clkdm = clkdm_lookup("mpu1_clkdm"); @@ -227,7 +227,7 @@ static void __init omap4_smp_prepare_cpus(unsigned int max_cpus) if (omap_secure_apis_support()) omap_auxcoreboot_addr(virt_to_phys(startup_addr)); else - __raw_writel(virt_to_phys(omap5_secondary_startup), + writel_relaxed(virt_to_phys(omap5_secondary_startup), base + OMAP_AUX_CORE_BOOT_1); } diff --git a/arch/arm/mach-omap2/omap-wakeupgen.c b/arch/arm/mach-omap2/omap-wakeupgen.c index 3664562f91481e..095829f0a50b53 100644 --- a/arch/arm/mach-omap2/omap-wakeupgen.c +++ b/arch/arm/mach-omap2/omap-wakeupgen.c @@ -60,19 +60,19 @@ static unsigned int omap_secure_apis; */ static inline u32 wakeupgen_readl(u8 idx, u32 cpu) { - return __raw_readl(wakeupgen_base + OMAP_WKG_ENB_A_0 + + return readl_relaxed(wakeupgen_base + OMAP_WKG_ENB_A_0 + (cpu * CPU_ENA_OFFSET) + (idx * 4)); } static inline void wakeupgen_writel(u32 val, u8 idx, u32 cpu) { - __raw_writel(val, wakeupgen_base + OMAP_WKG_ENB_A_0 + + writel_relaxed(val, wakeupgen_base + OMAP_WKG_ENB_A_0 + (cpu * CPU_ENA_OFFSET) + (idx * 4)); } static inline void sar_writel(u32 val, u32 offset, u8 idx) { - __raw_writel(val, sar_base + offset + (idx * 4)); + writel_relaxed(val, sar_base + offset + (idx * 4)); } static inline int _wakeupgen_get_irq_info(u32 irq, u32 *bit_posn, u8 *reg_index) @@ -231,21 +231,21 @@ static inline void omap4_irq_save_context(void) } /* Save AuxBoot* registers */ - val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); - __raw_writel(val, sar_base + AUXCOREBOOT0_OFFSET); - val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_1); - __raw_writel(val, sar_base + AUXCOREBOOT1_OFFSET); + val = readl_relaxed(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); + writel_relaxed(val, sar_base + AUXCOREBOOT0_OFFSET); + val = readl_relaxed(wakeupgen_base + OMAP_AUX_CORE_BOOT_1); + writel_relaxed(val, sar_base + AUXCOREBOOT1_OFFSET); /* Save SyncReq generation logic */ - val = __raw_readl(wakeupgen_base + OMAP_PTMSYNCREQ_MASK); - __raw_writel(val, sar_base + PTMSYNCREQ_MASK_OFFSET); - val = __raw_readl(wakeupgen_base + OMAP_PTMSYNCREQ_EN); - __raw_writel(val, sar_base + PTMSYNCREQ_EN_OFFSET); + val = readl_relaxed(wakeupgen_base + OMAP_PTMSYNCREQ_MASK); + writel_relaxed(val, sar_base + PTMSYNCREQ_MASK_OFFSET); + val = readl_relaxed(wakeupgen_base + OMAP_PTMSYNCREQ_EN); + writel_relaxed(val, sar_base + PTMSYNCREQ_EN_OFFSET); /* Set the Backup Bit Mask status */ - val = __raw_readl(sar_base + SAR_BACKUP_STATUS_OFFSET); + val = readl_relaxed(sar_base + SAR_BACKUP_STATUS_OFFSET); val |= SAR_BACKUP_STATUS_WAKEUPGEN; - __raw_writel(val, sar_base + SAR_BACKUP_STATUS_OFFSET); + writel_relaxed(val, sar_base + SAR_BACKUP_STATUS_OFFSET); } @@ -264,15 +264,15 @@ static inline void omap5_irq_save_context(void) } /* Save AuxBoot* registers */ - val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); - __raw_writel(val, sar_base + OMAP5_AUXCOREBOOT0_OFFSET); - val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); - __raw_writel(val, sar_base + OMAP5_AUXCOREBOOT1_OFFSET); + val = readl_relaxed(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); + writel_relaxed(val, sar_base + OMAP5_AUXCOREBOOT0_OFFSET); + val = readl_relaxed(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); + writel_relaxed(val, sar_base + OMAP5_AUXCOREBOOT1_OFFSET); /* Set the Backup Bit Mask status */ - val = __raw_readl(sar_base + OMAP5_SAR_BACKUP_STATUS_OFFSET); + val = readl_relaxed(sar_base + OMAP5_SAR_BACKUP_STATUS_OFFSET); val |= SAR_BACKUP_STATUS_WAKEUPGEN; - __raw_writel(val, sar_base + OMAP5_SAR_BACKUP_STATUS_OFFSET); + writel_relaxed(val, sar_base + OMAP5_SAR_BACKUP_STATUS_OFFSET); } @@ -306,9 +306,9 @@ static void irq_sar_clear(void) if (soc_is_omap54xx()) offset = OMAP5_SAR_BACKUP_STATUS_OFFSET; - val = __raw_readl(sar_base + offset); + val = readl_relaxed(sar_base + offset); val &= ~SAR_BACKUP_STATUS_WAKEUPGEN; - __raw_writel(val, sar_base + offset); + writel_relaxed(val, sar_base + offset); } /* diff --git a/arch/arm/mach-omap2/omap4-common.c b/arch/arm/mach-omap2/omap4-common.c index 6cd3f3772ecf4e..cbd8f08955b886 100644 --- a/arch/arm/mach-omap2/omap4-common.c +++ b/arch/arm/mach-omap2/omap4-common.c @@ -124,25 +124,25 @@ void __init gic_init_irq(void) void gic_dist_disable(void) { if (gic_dist_base_addr) - __raw_writel(0x0, gic_dist_base_addr + GIC_DIST_CTRL); + writel_relaxed(0x0, gic_dist_base_addr + GIC_DIST_CTRL); } void gic_dist_enable(void) { if (gic_dist_base_addr) - __raw_writel(0x1, gic_dist_base_addr + GIC_DIST_CTRL); + writel_relaxed(0x1, gic_dist_base_addr + GIC_DIST_CTRL); } bool gic_dist_disabled(void) { - return !(__raw_readl(gic_dist_base_addr + GIC_DIST_CTRL) & 0x1); + return !(readl_relaxed(gic_dist_base_addr + GIC_DIST_CTRL) & 0x1); } void gic_timer_retrigger(void) { - u32 twd_int = __raw_readl(twd_base + TWD_TIMER_INTSTAT); - u32 gic_int = __raw_readl(gic_dist_base_addr + GIC_DIST_PENDING_SET); - u32 twd_ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL); + u32 twd_int = readl_relaxed(twd_base + TWD_TIMER_INTSTAT); + u32 gic_int = readl_relaxed(gic_dist_base_addr + GIC_DIST_PENDING_SET); + u32 twd_ctrl = readl_relaxed(twd_base + TWD_TIMER_CONTROL); if (twd_int && !(gic_int & BIT(IRQ_LOCALTIMER))) { /* @@ -150,11 +150,11 @@ void gic_timer_retrigger(void) * disabled. Ack the pending interrupt, and retrigger it. */ pr_warn("%s: lost localtimer interrupt\n", __func__); - __raw_writel(1, twd_base + TWD_TIMER_INTSTAT); + writel_relaxed(1, twd_base + TWD_TIMER_INTSTAT); if (!(twd_ctrl & TWD_TIMER_CONTROL_PERIODIC)) { - __raw_writel(1, twd_base + TWD_TIMER_COUNTER); + writel_relaxed(1, twd_base + TWD_TIMER_COUNTER); twd_ctrl |= TWD_TIMER_CONTROL_ENABLE; - __raw_writel(twd_ctrl, twd_base + TWD_TIMER_CONTROL); + writel_relaxed(twd_ctrl, twd_base + TWD_TIMER_CONTROL); } } } diff --git a/arch/arm/mach-omap2/omap_hwmod.c b/arch/arm/mach-omap2/omap_hwmod.c index 1f33f5db10d5a2..262ba8c9a1a24d 100644 --- a/arch/arm/mach-omap2/omap_hwmod.c +++ b/arch/arm/mach-omap2/omap_hwmod.c @@ -72,7 +72,7 @@ * | (../mach-omap2/omap_hwmod*) | * +-------------------------------+ * | OMAP clock/PRCM/register fns | - * | (__raw_{read,write}l, clk*) | + * | ({read,write}l_relaxed, clk*) | * +-------------------------------+ * * Device drivers should not contain any OMAP-specific code or data in @@ -3229,17 +3229,17 @@ static int _am33xx_is_hardreset_asserted(struct omap_hwmod *oh, u32 omap_hwmod_read(struct omap_hwmod *oh, u16 reg_offs) { if (oh->flags & HWMOD_16BIT_REG) - return __raw_readw(oh->_mpu_rt_va + reg_offs); + return readw_relaxed(oh->_mpu_rt_va + reg_offs); else - return __raw_readl(oh->_mpu_rt_va + reg_offs); + return readl_relaxed(oh->_mpu_rt_va + reg_offs); } void omap_hwmod_write(u32 v, struct omap_hwmod *oh, u16 reg_offs) { if (oh->flags & HWMOD_16BIT_REG) - __raw_writew(v, oh->_mpu_rt_va + reg_offs); + writew_relaxed(v, oh->_mpu_rt_va + reg_offs); else - __raw_writel(v, oh->_mpu_rt_va + reg_offs); + writel_relaxed(v, oh->_mpu_rt_va + reg_offs); } /** diff --git a/arch/arm/mach-omap2/omap_phy_internal.c b/arch/arm/mach-omap2/omap_phy_internal.c index eb8a25de67ed94..50640b38f0bf73 100644 --- a/arch/arm/mach-omap2/omap_phy_internal.c +++ b/arch/arm/mach-omap2/omap_phy_internal.c @@ -57,7 +57,7 @@ static int __init omap4430_phy_power_down(void) } /* Power down the phy */ - __raw_writel(PHY_PD, ctrl_base + CONTROL_DEV_CONF); + writel_relaxed(PHY_PD, ctrl_base + CONTROL_DEV_CONF); iounmap(ctrl_base); @@ -162,7 +162,7 @@ void ti81xx_musb_phy_power(u8 on) return; } - usbphycfg = __raw_readl(scm_base + USBCTRL0); + usbphycfg = readl_relaxed(scm_base + USBCTRL0); if (on) { if (cpu_is_ti816x()) { @@ -181,7 +181,7 @@ void ti81xx_musb_phy_power(u8 on) usbphycfg |= USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN; } - __raw_writel(usbphycfg, scm_base + USBCTRL0); + writel_relaxed(usbphycfg, scm_base + USBCTRL0); iounmap(scm_base); } diff --git a/arch/arm/mach-omap2/prcm_mpu44xx.c b/arch/arm/mach-omap2/prcm_mpu44xx.c index c30e44a7fab082..cdbee6326d2921 100644 --- a/arch/arm/mach-omap2/prcm_mpu44xx.c +++ b/arch/arm/mach-omap2/prcm_mpu44xx.c @@ -30,12 +30,12 @@ void __iomem *prcm_mpu_base; u32 omap4_prcm_mpu_read_inst_reg(s16 inst, u16 reg) { - return __raw_readl(OMAP44XX_PRCM_MPU_REGADDR(inst, reg)); + return readl_relaxed(OMAP44XX_PRCM_MPU_REGADDR(inst, reg)); } void omap4_prcm_mpu_write_inst_reg(u32 val, s16 inst, u16 reg) { - __raw_writel(val, OMAP44XX_PRCM_MPU_REGADDR(inst, reg)); + writel_relaxed(val, OMAP44XX_PRCM_MPU_REGADDR(inst, reg)); } u32 omap4_prcm_mpu_rmw_inst_reg_bits(u32 mask, u32 bits, s16 inst, s16 reg) diff --git a/arch/arm/mach-omap2/prm2xxx.h b/arch/arm/mach-omap2/prm2xxx.h index 3194dd87e0e427..d2cb6365716f55 100644 --- a/arch/arm/mach-omap2/prm2xxx.h +++ b/arch/arm/mach-omap2/prm2xxx.h @@ -27,7 +27,7 @@ /* * OMAP2-specific global PRM registers - * Use __raw_{read,write}l() with these registers. + * Use {read,write}l_relaxed() with these registers. * * With a few exceptions, these are the register names beginning with * PRCM_* on 24xx. (The exceptions are the IRQSTATUS and IRQENABLE diff --git a/arch/arm/mach-omap2/prm2xxx_3xxx.h b/arch/arm/mach-omap2/prm2xxx_3xxx.h index 9624b40836d400..1a3a96392b97be 100644 --- a/arch/arm/mach-omap2/prm2xxx_3xxx.h +++ b/arch/arm/mach-omap2/prm2xxx_3xxx.h @@ -55,12 +55,12 @@ /* Power/reset management domain register get/set */ static inline u32 omap2_prm_read_mod_reg(s16 module, u16 idx) { - return __raw_readl(prm_base + module + idx); + return readl_relaxed(prm_base + module + idx); } static inline void omap2_prm_write_mod_reg(u32 val, s16 module, u16 idx) { - __raw_writel(val, prm_base + module + idx); + writel_relaxed(val, prm_base + module + idx); } /* Read-modify-write a register in a PRM module. Caller must lock */ diff --git a/arch/arm/mach-omap2/prm33xx.c b/arch/arm/mach-omap2/prm33xx.c index 72044073774419..93ba48a7d9078d 100644 --- a/arch/arm/mach-omap2/prm33xx.c +++ b/arch/arm/mach-omap2/prm33xx.c @@ -27,13 +27,13 @@ /* Read a register in a PRM instance */ u32 am33xx_prm_read_reg(s16 inst, u16 idx) { - return __raw_readl(prm_base + inst + idx); + return readl_relaxed(prm_base + inst + idx); } /* Write into a register in a PRM instance */ void am33xx_prm_write_reg(u32 val, s16 inst, u16 idx) { - __raw_writel(val, prm_base + inst + idx); + writel_relaxed(val, prm_base + inst + idx); } /* Read-modify-write a register in PRM. Caller must lock */ diff --git a/arch/arm/mach-omap2/prm3xxx.h b/arch/arm/mach-omap2/prm3xxx.h index f8eb83323b1a06..1dacfc5b19591f 100644 --- a/arch/arm/mach-omap2/prm3xxx.h +++ b/arch/arm/mach-omap2/prm3xxx.h @@ -26,7 +26,7 @@ /* * OMAP3-specific global PRM registers - * Use __raw_{read,write}l() with these registers. + * Use {read,write}l_relaxed() with these registers. * * With a few exceptions, these are the register names beginning with * PRM_* on 34xx. (The exceptions are the IRQSTATUS and IRQENABLE diff --git a/arch/arm/mach-omap2/prm44xx.c b/arch/arm/mach-omap2/prm44xx.c index 03a603476cfc45..94a43b3cf0f0c7 100644 --- a/arch/arm/mach-omap2/prm44xx.c +++ b/arch/arm/mach-omap2/prm44xx.c @@ -81,13 +81,13 @@ static struct prm_reset_src_map omap44xx_prm_reset_src_map[] = { /* Read a register in a CM/PRM instance in the PRM module */ u32 omap4_prm_read_inst_reg(s16 inst, u16 reg) { - return __raw_readl(prm_base + inst + reg); + return readl_relaxed(prm_base + inst + reg); } /* Write into a register in a CM/PRM instance in the PRM module */ void omap4_prm_write_inst_reg(u32 val, s16 inst, u16 reg) { - __raw_writel(val, prm_base + inst + reg); + writel_relaxed(val, prm_base + inst + reg); } /* Read-modify-write a register in a PRM module. Caller must lock */ diff --git a/arch/arm/mach-omap2/prminst44xx.c b/arch/arm/mach-omap2/prminst44xx.c index 280f3c58abe505..672e4b5ed0b922 100644 --- a/arch/arm/mach-omap2/prminst44xx.c +++ b/arch/arm/mach-omap2/prminst44xx.c @@ -48,7 +48,7 @@ u32 omap4_prminst_read_inst_reg(u8 part, s16 inst, u16 idx) BUG_ON(part >= OMAP4_MAX_PRCM_PARTITIONS || part == OMAP4430_INVALID_PRCM_PARTITION || !_prm_bases[part]); - return __raw_readl(_prm_bases[part] + inst + idx); + return readl_relaxed(_prm_bases[part] + inst + idx); } /* Write into a register in a PRM instance */ @@ -57,7 +57,7 @@ void omap4_prminst_write_inst_reg(u32 val, u8 part, s16 inst, u16 idx) BUG_ON(part >= OMAP4_MAX_PRCM_PARTITIONS || part == OMAP4430_INVALID_PRCM_PARTITION || !_prm_bases[part]); - __raw_writel(val, _prm_bases[part] + inst + idx); + writel_relaxed(val, _prm_bases[part] + inst + idx); } /* Read-modify-write a register in PRM. Caller must lock */ diff --git a/arch/arm/mach-omap2/sdrc.h b/arch/arm/mach-omap2/sdrc.h index 446aa13511fde8..645a2a46b21325 100644 --- a/arch/arm/mach-omap2/sdrc.h +++ b/arch/arm/mach-omap2/sdrc.h @@ -31,24 +31,24 @@ extern void __iomem *omap2_sms_base; static inline void sdrc_write_reg(u32 val, u16 reg) { - __raw_writel(val, OMAP_SDRC_REGADDR(reg)); + writel_relaxed(val, OMAP_SDRC_REGADDR(reg)); } static inline u32 sdrc_read_reg(u16 reg) { - return __raw_readl(OMAP_SDRC_REGADDR(reg)); + return readl_relaxed(OMAP_SDRC_REGADDR(reg)); } /* SMS global register get/set */ static inline void sms_write_reg(u32 val, u16 reg) { - __raw_writel(val, OMAP_SMS_REGADDR(reg)); + writel_relaxed(val, OMAP_SMS_REGADDR(reg)); } static inline u32 sms_read_reg(u16 reg) { - return __raw_readl(OMAP_SMS_REGADDR(reg)); + return readl_relaxed(OMAP_SMS_REGADDR(reg)); } extern void omap2_set_globals_sdrc(void __iomem *sdrc, void __iomem *sms); diff --git a/arch/arm/mach-omap2/sdrc2xxx.c b/arch/arm/mach-omap2/sdrc2xxx.c index 90729171464300..ae3f1553158d74 100644 --- a/arch/arm/mach-omap2/sdrc2xxx.c +++ b/arch/arm/mach-omap2/sdrc2xxx.c @@ -103,9 +103,9 @@ u32 omap2xxx_sdrc_reprogram(u32 level, u32 force) * prm2xxx.c function */ if (cpu_is_omap2420()) - __raw_writel(0xffff, OMAP2420_PRCM_VOLTSETUP); + writel_relaxed(0xffff, OMAP2420_PRCM_VOLTSETUP); else - __raw_writel(0xffff, OMAP2430_PRCM_VOLTSETUP); + writel_relaxed(0xffff, OMAP2430_PRCM_VOLTSETUP); omap2_sram_reprogram_sdrc(level, dll_ctrl, m_type); curr_perf_level = level; local_irq_restore(flags); diff --git a/arch/arm/mach-omap2/sleep44xx.S b/arch/arm/mach-omap2/sleep44xx.S index 9086ce03ae12a0..f509dc580c5301 100644 --- a/arch/arm/mach-omap2/sleep44xx.S +++ b/arch/arm/mach-omap2/sleep44xx.S @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "omap-secure.h" @@ -74,6 +75,7 @@ ENTRY(omap4_finish_suspend) */ bl omap4_get_sar_ram_base ldr r9, [r0, #OMAP_TYPE_OFFSET] +ARM_BE8(rev r9, r9) cmp r9, #0x1 @ Check for HS device bne skip_secure_l1_clean mov r0, #SCU_PM_NORMAL @@ -113,12 +115,14 @@ skip_secure_l1_clean: bl omap4_get_sar_ram_base mov r8, r0 ldr r9, [r8, #OMAP_TYPE_OFFSET] +ARM_BE8(rev r9, r9) cmp r9, #0x1 @ Check for HS device bne scu_gp_set mrc p15, 0, r0, c0, c0, 5 @ Read MPIDR ands r0, r0, #0x0f ldreq r0, [r8, #SCU_OFFSET0] ldrne r0, [r8, #SCU_OFFSET1] +ARM_BE8(rev r0, r0) mov r1, #0x00 stmfd r13!, {r4-r12, r14} ldr r12, =OMAP4_MON_SCU_PWR_INDEX @@ -130,6 +134,7 @@ scu_gp_set: ands r0, r0, #0x0f ldreq r1, [r8, #SCU_OFFSET0] ldrne r1, [r8, #SCU_OFFSET1] +ARM_BE8(rev r1, r1) bl omap4_get_scu_base bl scu_power_mode skip_scu_gp_set: @@ -157,6 +162,7 @@ skip_scu_gp_set: ands r5, r5, #0x0f ldreq r0, [r8, #L2X0_SAVE_OFFSET0] @ Retrieve L2 state from SAR ldrne r0, [r8, #L2X0_SAVE_OFFSET1] @ memory. +ARM_BE8(rev r0, r0) cmp r0, #3 bne do_WFI #ifdef CONFIG_PL310_ERRATA_727915 @@ -167,9 +173,11 @@ skip_scu_gp_set: bl omap4_get_l2cache_base mov r2, r0 ldr r0, =0xffff +ARM_BE8(rev r0, r0) str r0, [r2, #L2X0_CLEAN_INV_WAY] wait: ldr r0, [r2, #L2X0_CLEAN_INV_WAY] +ARM_BE8(rev r0, r0) ldr r1, =0xffff ands r0, r0, r1 bne wait @@ -182,9 +190,11 @@ l2x_sync: bl omap4_get_l2cache_base mov r2, r0 mov r0, #0x0 +ARM_BE8(rev r0, r0) str r0, [r2, #L2X0_CACHE_SYNC] sync: ldr r0, [r2, #L2X0_CACHE_SYNC] +ARM_BE8(rev r0, r0) ands r0, r0, #0x1 bne sync #endif @@ -216,6 +226,7 @@ do_WFI: bl omap4_get_sar_ram_base mov r8, r0 ldr r9, [r8, #OMAP_TYPE_OFFSET] +ARM_BE8(rev r9, r9) cmp r9, #0x1 @ Check for HS device bne scu_gp_clear mov r0, #SCU_PM_NORMAL @@ -248,6 +259,12 @@ ENDPROC(omap4_finish_suspend) * The restore function pointer is stored at CPUx_WAKEUP_NS_PA_ADDR_OFFSET. */ ENTRY(omap4_cpu_resume) + /* + * ROM code operates in little endian mode, when we get control we + * need to switch it back to big endian mode. + */ +ARM_BE8(setend be) + /* * Configure ACTRL and enable NS SMP bit access on CPU1 on HS device. * OMAP44XX EMU/HS devices - CPU0 SMP bit access is enabled in PPA @@ -258,6 +275,7 @@ ENTRY(omap4_cpu_resume) */ ldr r8, =OMAP44XX_SAR_RAM_BASE ldr r9, [r8, #OMAP_TYPE_OFFSET] +ARM_BE8(rev r9, r9) cmp r9, #0x1 @ Skip if GP device bne skip_ns_smp_enable mrc p15, 0, r0, c0, c0, 5 @@ -292,16 +310,19 @@ skip_ns_smp_enable: */ ldr r2, =OMAP44XX_L2CACHE_BASE ldr r0, [r2, #L2X0_CTRL] +ARM_BE8(rev r0, r0) and r0, #0x0f cmp r0, #1 beq skip_l2en @ Skip if already enabled ldr r3, =OMAP44XX_SAR_RAM_BASE ldr r1, [r3, #OMAP_TYPE_OFFSET] +ARM_BE8(rev r1, r1) cmp r1, #0x1 @ Check for HS device bne set_gp_por ldr r0, =OMAP4_PPA_L2_POR_INDEX ldr r1, =OMAP44XX_SAR_RAM_BASE ldr r4, [r1, #L2X0_PREFETCH_CTRL_OFFSET] +ARM_BE8(rev r4, r4) adr r3, ppa_por_params str r4, [r3, #0x04] mov r1, #0x0 @ Process ID @@ -313,11 +334,13 @@ skip_ns_smp_enable: set_gp_por: ldr r1, =OMAP44XX_SAR_RAM_BASE ldr r0, [r1, #L2X0_PREFETCH_CTRL_OFFSET] +ARM_BE8(rev r0, r0) ldr r12, =OMAP4_MON_L2X0_PREFETCH_INDEX @ Setup L2 PREFETCH DO_SMC set_aux_ctrl: ldr r1, =OMAP44XX_SAR_RAM_BASE ldr r0, [r1, #L2X0_AUXCTRL_OFFSET] +ARM_BE8(rev r0, r0) ldr r12, =OMAP4_MON_L2X0_AUXCTRL_INDEX @ Setup L2 AUXCTRL DO_SMC mov r0, #0x1 diff --git a/arch/arm/mach-omap2/sr_device.c b/arch/arm/mach-omap2/sr_device.c index d7bc33f153440c..1b91ef0c182a0a 100644 --- a/arch/arm/mach-omap2/sr_device.c +++ b/arch/arm/mach-omap2/sr_device.c @@ -57,7 +57,7 @@ static void __init sr_set_nvalues(struct omap_volt_data *volt_data, /* * In OMAP4 the efuse registers are 24 bit aligned. - * A __raw_readl will fail for non-32 bit aligned address + * A readl_relaxed will fail for non-32 bit aligned address * and hence the 8-bit read and shift. */ if (cpu_is_omap44xx()) { diff --git a/arch/arm/mach-omap2/sram.c b/arch/arm/mach-omap2/sram.c index 4bd096836235bf..ddf1818af22879 100644 --- a/arch/arm/mach-omap2/sram.c +++ b/arch/arm/mach-omap2/sram.c @@ -70,16 +70,16 @@ static int is_sram_locked(void) if (OMAP2_DEVICE_TYPE_GP == omap_type()) { /* RAMFW: R/W access to all initiators for all qualifier sets */ if (cpu_is_omap242x()) { - __raw_writel(0xFF, OMAP24XX_VA_REQINFOPERM0); /* all q-vects */ - __raw_writel(0xCFDE, OMAP24XX_VA_READPERM0); /* all i-read */ - __raw_writel(0xCFDE, OMAP24XX_VA_WRITEPERM0); /* all i-write */ + writel_relaxed(0xFF, OMAP24XX_VA_REQINFOPERM0); /* all q-vects */ + writel_relaxed(0xCFDE, OMAP24XX_VA_READPERM0); /* all i-read */ + writel_relaxed(0xCFDE, OMAP24XX_VA_WRITEPERM0); /* all i-write */ } if (cpu_is_omap34xx()) { - __raw_writel(0xFFFF, OMAP34XX_VA_REQINFOPERM0); /* all q-vects */ - __raw_writel(0xFFFF, OMAP34XX_VA_READPERM0); /* all i-read */ - __raw_writel(0xFFFF, OMAP34XX_VA_WRITEPERM0); /* all i-write */ - __raw_writel(0x0, OMAP34XX_VA_ADDR_MATCH2); - __raw_writel(0xFFFFFFFF, OMAP34XX_VA_SMS_RG_ATT0); + writel_relaxed(0xFFFF, OMAP34XX_VA_REQINFOPERM0); /* all q-vects */ + writel_relaxed(0xFFFF, OMAP34XX_VA_READPERM0); /* all i-read */ + writel_relaxed(0xFFFF, OMAP34XX_VA_WRITEPERM0); /* all i-write */ + writel_relaxed(0x0, OMAP34XX_VA_ADDR_MATCH2); + writel_relaxed(0xFFFFFFFF, OMAP34XX_VA_SMS_RG_ATT0); } return 0; } else diff --git a/arch/arm/mach-omap2/timer.c b/arch/arm/mach-omap2/timer.c index 74044aaf438b6f..bc01d5a1c99843 100644 --- a/arch/arm/mach-omap2/timer.c +++ b/arch/arm/mach-omap2/timer.c @@ -546,15 +546,15 @@ static void __init realtime_counter_init(void) } /* Program numerator and denumerator registers */ - reg = __raw_readl(base + INCREMENTER_NUMERATOR_OFFSET) & + reg = readl_relaxed(base + INCREMENTER_NUMERATOR_OFFSET) & NUMERATOR_DENUMERATOR_MASK; reg |= num; - __raw_writel(reg, base + INCREMENTER_NUMERATOR_OFFSET); + writel_relaxed(reg, base + INCREMENTER_NUMERATOR_OFFSET); - reg = __raw_readl(base + INCREMENTER_DENUMERATOR_RELOAD_OFFSET) & + reg = readl_relaxed(base + INCREMENTER_DENUMERATOR_RELOAD_OFFSET) & NUMERATOR_DENUMERATOR_MASK; reg |= den; - __raw_writel(reg, base + INCREMENTER_DENUMERATOR_RELOAD_OFFSET); + writel_relaxed(reg, base + INCREMENTER_DENUMERATOR_RELOAD_OFFSET); arch_timer_freq = (rate / den) * num; set_cntfreq(); diff --git a/arch/arm/mach-omap2/vc.c b/arch/arm/mach-omap2/vc.c index 49ac7977e03e54..267f204559c32e 100644 --- a/arch/arm/mach-omap2/vc.c +++ b/arch/arm/mach-omap2/vc.c @@ -462,7 +462,7 @@ static void omap4_set_timings(struct voltagedomain *voltdm, bool off_mode) val |= omap4_usec_to_val_scrm(tshut, OMAP4_DOWNTIME_SHIFT, OMAP4_DOWNTIME_MASK); - __raw_writel(val, OMAP4_SCRM_CLKSETUPTIME); + writel_relaxed(val, OMAP4_SCRM_CLKSETUPTIME); } /* OMAP4 specific voltage init functions */ @@ -584,7 +584,7 @@ static void __init omap4_vc_i2c_timing_init(struct voltagedomain *voltdm) val = i2c_data->loadbits << 25 | i2c_data->loadbits << 29; /* Write to SYSCTRL_PADCONF_WKUP_CTRL_I2C_2 to setup I2C pull */ - __raw_writel(val, OMAP2_L4_IO_ADDRESS(OMAP4_CTRL_MODULE_PAD_WKUP + + writel_relaxed(val, OMAP2_L4_IO_ADDRESS(OMAP4_CTRL_MODULE_PAD_WKUP + OMAP4_CTRL_MODULE_PAD_WKUP_CONTROL_I2C_2)); /* HSSCLH can always be zero */ diff --git a/arch/arm/mach-omap2/wd_timer.c b/arch/arm/mach-omap2/wd_timer.c index d15c7bbab8e294..97d6607d447a5c 100644 --- a/arch/arm/mach-omap2/wd_timer.c +++ b/arch/arm/mach-omap2/wd_timer.c @@ -49,12 +49,12 @@ int omap2_wd_timer_disable(struct omap_hwmod *oh) } /* sequence required to disable watchdog */ - __raw_writel(0xAAAA, base + OMAP_WDT_SPR); - while (__raw_readl(base + OMAP_WDT_WPS) & 0x10) + writel_relaxed(0xAAAA, base + OMAP_WDT_SPR); + while (readl_relaxed(base + OMAP_WDT_WPS) & 0x10) cpu_relax(); - __raw_writel(0x5555, base + OMAP_WDT_SPR); - while (__raw_readl(base + OMAP_WDT_WPS) & 0x10) + writel_relaxed(0x5555, base + OMAP_WDT_SPR); + while (readl_relaxed(base + OMAP_WDT_WPS) & 0x10) cpu_relax(); return 0; diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 4a70be485ff8fe..556b01d305a8e9 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -55,6 +55,7 @@ config ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA config ARCH_VEXPRESS_CA9X4 bool "Versatile Express Cortex-A9x4 tile" + select ARM_ERRATA_643719 config ARCH_VEXPRESS_DCSCB bool "Dual Cluster System Control Block (DCSCB) support" diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 0997e0b7494c31..191f2b591adcab 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -8,8 +8,15 @@ obj-y := v2m.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o obj-$(CONFIG_ARCH_VEXPRESS_DCSCB) += dcscb.o dcscb_setup.o CFLAGS_dcscb.o += -march=armv7-a +CFLAGS_REMOVE_dcscb.o = -pg obj-$(CONFIG_ARCH_VEXPRESS_SPC) += spc.o +CFLAGS_REMOVE_spc.o = -pg obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += tc2_pm.o CFLAGS_tc2_pm.o += -march=armv7-a +CFLAGS_REMOVE_tc2_pm.o = -pg +ifeq ($(CONFIG_ARCH_VEXPRESS_TC2_PM),y) +obj-$(CONFIG_ARM_PSCI) += tc2_pm_psci.o +CFLAGS_REMOVE_tc2_pm_psci.o = -pg +endif obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o diff --git a/arch/arm/mach-vexpress/dcscb.c b/arch/arm/mach-vexpress/dcscb.c index 14d499688736b3..b35700f8e01f33 100644 --- a/arch/arm/mach-vexpress/dcscb.c +++ b/arch/arm/mach-vexpress/dcscb.c @@ -23,6 +23,7 @@ #include #include #include +#include #define RST_HOLD0 0x0 @@ -193,6 +194,12 @@ static int __init dcscb_init(void) unsigned int cfg; int ret; + ret = psci_probe(); + if (!ret) { + pr_debug("psci found. Aborting native init\n"); + return -ENODEV; + } + if (!cci_probed()) return -ENODEV; diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index 29e7785a54bcbb..a32a179080d5bf 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -329,6 +330,12 @@ static int __init tc2_pm_init(void) u32 a15_cluster_id, a7_cluster_id, sys_info; struct device_node *np; + ret = psci_probe(); + if (!ret) { + pr_debug("psci found. Aborting native init\n"); + return -ENODEV; + } + /* * The power management-related features are hidden behind * SCC registers. We need to extract runtime information like diff --git a/arch/arm/mach-vexpress/tc2_pm_psci.c b/arch/arm/mach-vexpress/tc2_pm_psci.c new file mode 100644 index 00000000000000..7cb2148caebcd2 --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm_psci.c @@ -0,0 +1,173 @@ +/* + * arch/arm/mach-vexpress/tc2_pm_psci.c - TC2 PSCI support + * + * Created by: Achin Gupta, December 2012 + * Copyright: (C) 2012 ARM Limited + * + * Some portions of this file were originally written by Nicolas Pitre + * Copyright: (C) 2012 Linaro Limited + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* + * Platform specific state id understood by the firmware and used to + * program the power controller + */ +#define PSCI_POWER_STATE_ID 0 + +#define TC2_CLUSTERS 2 +#define TC2_MAX_CPUS_PER_CLUSTER 3 + +static atomic_t tc2_pm_use_count[TC2_MAX_CPUS_PER_CLUSTER][TC2_CLUSTERS]; + +static int tc2_pm_psci_power_up(unsigned int cpu, unsigned int cluster) +{ + unsigned int mpidr = (cluster << 8) | cpu; + int ret = 0; + + BUG_ON(!psci_ops.cpu_on); + + switch (atomic_inc_return(&tc2_pm_use_count[cpu][cluster])) { + case 1: + /* + * This is a request to power up a cpu that linux thinks has + * been powered down. Retries are needed if the firmware has + * seen the power down request as yet. + */ + do + ret = psci_ops.cpu_on(mpidr, + virt_to_phys(mcpm_entry_point)); + while (ret == -EAGAIN); + + return ret; + case 2: + /* This power up request has overtaken a power down request */ + return ret; + default: + /* Any other value is a bug */ + BUG(); + } +} + +static void tc2_pm_psci_power_down(void) +{ + struct psci_power_state power_state; + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + BUG_ON(!psci_ops.cpu_off); + + switch (atomic_dec_return(&tc2_pm_use_count[cpu][cluster])) { + case 1: + /* + * Overtaken by a power up. Flush caches, exit coherency, + * return & fake a reset + */ + set_cr(get_cr() & ~CR_C); + + flush_cache_louis(); + + asm volatile ("clrex"); + set_auxcr(get_auxcr() & ~(1 << 6)); + + return; + case 0: + /* A normal request to possibly power down the cluster */ + power_state.id = PSCI_POWER_STATE_ID; + power_state.type = PSCI_POWER_STATE_TYPE_POWER_DOWN; + power_state.affinity_level = PSCI_POWER_STATE_AFFINITY_LEVEL1; + + psci_ops.cpu_off(power_state); + + /* On success this function never returns */ + default: + /* Any other value is a bug */ + BUG(); + } +} + +static void tc2_pm_psci_suspend(u64 unused) +{ + struct psci_power_state power_state; + + BUG_ON(!psci_ops.cpu_suspend); + + /* On TC2 always attempt to power down the cluster */ + power_state.id = PSCI_POWER_STATE_ID; + power_state.type = PSCI_POWER_STATE_TYPE_POWER_DOWN; + power_state.affinity_level = PSCI_POWER_STATE_AFFINITY_LEVEL1; + + psci_ops.cpu_suspend(power_state, virt_to_phys(mcpm_entry_point)); + + /* On success this function never returns */ + BUG(); +} + +static const struct mcpm_platform_ops tc2_pm_power_ops = { + .power_up = tc2_pm_psci_power_up, + .power_down = tc2_pm_psci_power_down, + .suspend = tc2_pm_psci_suspend, +}; + +static void __init tc2_pm_usage_count_init(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cluster >= TC2_CLUSTERS || cpu >= TC2_MAX_CPUS_PER_CLUSTER); + + atomic_set(&tc2_pm_use_count[cpu][cluster], 1); +} + +static int __init tc2_pm_psci_init(void) +{ + int ret; + + ret = psci_probe(); + if (ret) { + pr_debug("psci not found. Aborting psci init\n"); + return -ENODEV; + } + + if (!of_machine_is_compatible("arm,vexpress,v2p-ca15_a7")) + return -ENODEV; + + tc2_pm_usage_count_init(); + + ret = mcpm_platform_register(&tc2_pm_power_ops); + if (!ret) + ret = mcpm_sync_init(NULL); + if (!ret) + pr_info("TC2 power management using PSCI initialized\n"); + return ret; +} + +early_initcall(tc2_pm_psci_init); diff --git a/arch/arm/mach-vexpress/v2m.c b/arch/arm/mach-vexpress/v2m.c index 4f8b8cb17ff505..ffa9b73b7d340e 100644 --- a/arch/arm/mach-vexpress/v2m.c +++ b/arch/arm/mach-vexpress/v2m.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -369,6 +370,31 @@ MACHINE_START(VEXPRESS, "ARM-Versatile Express") .init_machine = v2m_init, MACHINE_END +static void __init v2m_dt_hdlcd_init(void) +{ + struct device_node *node; + int len, na, ns; + const __be32 *prop; + phys_addr_t fb_base, fb_size; + + node = of_find_compatible_node(NULL, NULL, "arm,hdlcd"); + if (!node) + return; + + na = of_n_addr_cells(node); + ns = of_n_size_cells(node); + + prop = of_get_property(node, "framebuffer", &len); + if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop))) + return; + + fb_base = of_read_number(prop, na); + fb_size = of_read_number(prop + na, ns); + + if (WARN_ON(memblock_remove(fb_base, fb_size))) + return; +}; + static struct map_desc v2m_rs1_io_desc __initdata = { .virtual = V2M_PERIPH, .pfn = __phys_to_pfn(0x1c000000), @@ -421,6 +447,8 @@ void __init v2m_dt_init_early(void) } versatile_sched_clock_init(vexpress_get_24mhz_clock_base(), 24000000); + + v2m_dt_hdlcd_init(); } static const struct of_device_id v2m_dt_bus_match[] __initconst = { diff --git a/arch/arm/mm/cache-l2x0.c b/arch/arm/mm/cache-l2x0.c index 7abde2ce897336..d96d5bd520e1d3 100644 --- a/arch/arm/mm/cache-l2x0.c +++ b/arch/arm/mm/cache-l2x0.c @@ -34,6 +34,9 @@ static void __iomem *l2x0_base; static DEFINE_RAW_SPINLOCK(l2x0_lock); static u32 l2x0_way_mask; /* Bitmask of active ways */ static u32 l2x0_size; +static u32 l2x0_cache_id; +static unsigned int l2x0_sets; +static unsigned int l2x0_ways; static unsigned long sync_reg_offset = L2X0_CACHE_SYNC; /* Aurora don't have the cache ID register available, so we have to @@ -50,6 +53,13 @@ struct l2x0_of_data { static bool of_init = false; +static inline bool is_pl310_rev(int rev) +{ + return (l2x0_cache_id & + (L2X0_CACHE_ID_PART_MASK | L2X0_CACHE_ID_REV_MASK)) == + (L2X0_CACHE_ID_PART_L310 | rev); +} + static inline void cache_wait_way(void __iomem *reg, unsigned long mask) { /* wait for cache operation by line or way to complete */ @@ -138,6 +148,23 @@ static void l2x0_cache_sync(void) raw_spin_unlock_irqrestore(&l2x0_lock, flags); } +#ifdef CONFIG_PL310_ERRATA_727915 +static void l2x0_for_each_set_way(void __iomem *reg) +{ + int set; + int way; + unsigned long flags; + + for (way = 0; way < l2x0_ways; way++) { + raw_spin_lock_irqsave(&l2x0_lock, flags); + for (set = 0; set < l2x0_sets; set++) + writel_relaxed((way << 28) | (set << 5), reg); + cache_sync(); + raw_spin_unlock_irqrestore(&l2x0_lock, flags); + } +} +#endif + static void __l2x0_flush_all(void) { debug_writel(0x03); @@ -151,6 +178,13 @@ static void l2x0_flush_all(void) { unsigned long flags; +#ifdef CONFIG_PL310_ERRATA_727915 + if (is_pl310_rev(REV_PL310_R2P0)) { + l2x0_for_each_set_way(l2x0_base + L2X0_CLEAN_INV_LINE_IDX); + return; + } +#endif + /* clean all ways */ raw_spin_lock_irqsave(&l2x0_lock, flags); __l2x0_flush_all(); @@ -161,11 +195,20 @@ static void l2x0_clean_all(void) { unsigned long flags; +#ifdef CONFIG_PL310_ERRATA_727915 + if (is_pl310_rev(REV_PL310_R2P0)) { + l2x0_for_each_set_way(l2x0_base + L2X0_CLEAN_LINE_IDX); + return; + } +#endif + /* clean all ways */ raw_spin_lock_irqsave(&l2x0_lock, flags); + debug_writel(0x03); writel_relaxed(l2x0_way_mask, l2x0_base + L2X0_CLEAN_WAY); cache_wait_way(l2x0_base + L2X0_CLEAN_WAY, l2x0_way_mask); cache_sync(); + debug_writel(0x00); raw_spin_unlock_irqrestore(&l2x0_lock, flags); } @@ -324,65 +367,64 @@ static void l2x0_unlock(u32 cache_id) void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask) { u32 aux; - u32 cache_id; u32 way_size = 0; - int ways; int way_size_shift = L2X0_WAY_SIZE_SHIFT; const char *type; l2x0_base = base; if (cache_id_part_number_from_dt) - cache_id = cache_id_part_number_from_dt; + l2x0_cache_id = cache_id_part_number_from_dt; else - cache_id = readl_relaxed(l2x0_base + L2X0_CACHE_ID); + l2x0_cache_id = readl_relaxed(l2x0_base + L2X0_CACHE_ID); aux = readl_relaxed(l2x0_base + L2X0_AUX_CTRL); aux &= aux_mask; aux |= aux_val; /* Determine the number of ways */ - switch (cache_id & L2X0_CACHE_ID_PART_MASK) { + switch (l2x0_cache_id & L2X0_CACHE_ID_PART_MASK) { case L2X0_CACHE_ID_PART_L310: if (aux & (1 << 16)) - ways = 16; + l2x0_ways = 16; else - ways = 8; + l2x0_ways = 8; type = "L310"; #ifdef CONFIG_PL310_ERRATA_753970 /* Unmapped register. */ sync_reg_offset = L2X0_DUMMY_REG; #endif - if ((cache_id & L2X0_CACHE_ID_RTL_MASK) <= L2X0_CACHE_ID_RTL_R3P0) + if ((l2x0_cache_id & L2X0_CACHE_ID_RTL_MASK) <= L2X0_CACHE_ID_RTL_R3P0) outer_cache.set_debug = pl310_set_debug; break; case L2X0_CACHE_ID_PART_L210: - ways = (aux >> 13) & 0xf; + l2x0_ways = (aux >> 13) & 0xf; type = "L210"; break; case AURORA_CACHE_ID: sync_reg_offset = AURORA_SYNC_REG; - ways = (aux >> 13) & 0xf; - ways = 2 << ((ways + 1) >> 2); + l2x0_ways = (aux >> 13) & 0xf; + l2x0_ways = 2 << ((l2x0_ways + 1) >> 2); way_size_shift = AURORA_WAY_SIZE_SHIFT; type = "Aurora"; break; default: /* Assume unknown chips have 8 ways */ - ways = 8; + l2x0_ways = 8; type = "L2x0 series"; break; } - l2x0_way_mask = (1 << ways) - 1; + l2x0_way_mask = (1 << l2x0_ways) - 1; /* * L2 cache Size = Way size * Number of ways */ way_size = (aux & L2X0_AUX_CTRL_WAY_SIZE_MASK) >> 17; - way_size = 1 << (way_size + way_size_shift); + way_size = SZ_1K << (way_size + way_size_shift); - l2x0_size = ways * way_size * SZ_1K; + l2x0_size = l2x0_ways * way_size; + l2x0_sets = way_size / CACHE_LINE_SIZE; /* * Check if l2x0 controller is already enabled. @@ -391,7 +433,7 @@ void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask) */ if (!(readl_relaxed(l2x0_base + L2X0_CTRL) & L2X0_CTRL_EN)) { /* Make sure that I&D is not locked down when starting */ - l2x0_unlock(cache_id); + l2x0_unlock(l2x0_cache_id); /* l2x0 controller is disabled */ writel_relaxed(aux, l2x0_base + L2X0_AUX_CTRL); @@ -420,7 +462,7 @@ void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask) pr_info("%s cache controller enabled\n", type); pr_info("l2x0: %d ways, CACHE_ID 0x%08x, AUX_CTRL 0x%08x, Cache size: %d kB\n", - ways, cache_id, aux, l2x0_size >> 10); + l2x0_ways, l2x0_cache_id, aux, l2x0_size >> 10); } #ifdef CONFIG_OF diff --git a/arch/arm/mm/cache-v6.S b/arch/arm/mm/cache-v6.S index d8fd4d4bd3d45e..7a3d3d8d98d7fa 100644 --- a/arch/arm/mm/cache-v6.S +++ b/arch/arm/mm/cache-v6.S @@ -270,6 +270,11 @@ v6_dma_clean_range: * - end - virtual end address of region */ ENTRY(v6_dma_flush_range) +#ifdef CONFIG_CACHE_FLUSH_RANGE_LIMIT + sub r2, r1, r0 + cmp r2, #CONFIG_CACHE_FLUSH_RANGE_LIMIT + bhi v6_dma_flush_dcache_all +#endif #ifdef CONFIG_DMA_CACHE_RWFO ldrb r2, [r0] @ read for ownership strb r2, [r0] @ write for ownership @@ -292,6 +297,18 @@ ENTRY(v6_dma_flush_range) mcr p15, 0, r0, c7, c10, 4 @ drain write buffer mov pc, lr +#ifdef CONFIG_CACHE_FLUSH_RANGE_LIMIT +v6_dma_flush_dcache_all: + mov r0, #0 +#ifdef HARVARD_CACHE + mcr p15, 0, r0, c7, c14, 0 @ D cache clean+invalidate +#else + mcr p15, 0, r0, c7, c15, 0 @ Cache clean+invalidate +#endif + mcr p15, 0, r0, c7, c10, 4 @ drain write buffer + mov pc, lr +#endif + /* * dma_map_area(start, size, dir) * - start - kernel virtual start address diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index eb8830a4c5edeb..3bbfd5dbe741e4 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -274,10 +274,10 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) local_irq_enable(); /* - * If we're in an interrupt or have no user + * If we're in an interrupt, or have no irqs, or have no user * context, we must not take the fault.. */ - if (in_atomic() || !mm) + if (in_atomic() || irqs_disabled() || !mm) goto no_context; if (user_mode(regs)) @@ -449,8 +449,16 @@ do_translation_fault(unsigned long addr, unsigned int fsr, if (pud_none(*pud_k)) goto bad_area; - if (!pud_present(*pud)) + if (!pud_present(*pud)) { set_pud(pud, *pud_k); + /* + * There is a small window during free_pgtables() where the + * user *pud entry is 0 but the TLB has not been invalidated + * and we get a level 2 (pmd) translation fault caused by the + * intermediate TLB caching of the old level 1 (pud) entry. + */ + flush_tlb_kernel_page(addr); + } pmd = pmd_offset(pud, addr); pmd_k = pmd_offset(pud_k, addr); @@ -473,8 +481,9 @@ do_translation_fault(unsigned long addr, unsigned int fsr, #endif if (pmd_none(pmd_k[index])) goto bad_area; + if (!pmd_present(pmd[index])) + copy_pmd(pmd, pmd_k); - copy_pmd(pmd, pmd_k); return 0; bad_area: diff --git a/arch/arm/plat-omap/counter_32k.c b/arch/arm/plat-omap/counter_32k.c index 384a776d8eb2c8..61b4d705c26720 100644 --- a/arch/arm/plat-omap/counter_32k.c +++ b/arch/arm/plat-omap/counter_32k.c @@ -40,7 +40,7 @@ static void __iomem *sync32k_cnt_reg; static u64 notrace omap_32k_read_sched_clock(void) { - return sync32k_cnt_reg ? __raw_readl(sync32k_cnt_reg) : 0; + return sync32k_cnt_reg ? readl_relaxed(sync32k_cnt_reg) : 0; } /** @@ -64,7 +64,7 @@ static void omap_read_persistent_clock(struct timespec *ts) spin_lock_irqsave(&read_persistent_clock_lock, flags); last_cycles = cycles; - cycles = sync32k_cnt_reg ? __raw_readl(sync32k_cnt_reg) : 0; + cycles = sync32k_cnt_reg ? readl_relaxed(sync32k_cnt_reg) : 0; nsecs = clocksource_cyc2ns(cycles - last_cycles, persistent_mult, persistent_shift); @@ -95,7 +95,7 @@ int __init omap_init_clocksource_32k(void __iomem *vbase) * The 'SCHEME' bits(30-31) of the revision register is used * to identify the version. */ - if (__raw_readl(vbase + OMAP2_32KSYNCNT_REV_OFF) & + if (readl_relaxed(vbase + OMAP2_32KSYNCNT_REV_OFF) & OMAP2_32KSYNCNT_REV_SCHEME) sync32k_cnt_reg = vbase + OMAP2_32KSYNCNT_CR_OFF_HIGH; else diff --git a/arch/arm/plat-omap/debug-leds.c b/arch/arm/plat-omap/debug-leds.c index aa7ebc6bcd6527..48b69de89a5d94 100644 --- a/arch/arm/plat-omap/debug-leds.c +++ b/arch/arm/plat-omap/debug-leds.c @@ -85,12 +85,12 @@ static void dbg_led_set(struct led_classdev *cdev, struct dbg_led *led = container_of(cdev, struct dbg_led, cdev); u16 reg; - reg = __raw_readw(&fpga->leds); + reg = readw_relaxed(&fpga->leds); if (b != LED_OFF) reg |= led->mask; else reg &= ~led->mask; - __raw_writew(reg, &fpga->leds); + writew_relaxed(reg, &fpga->leds); } static enum led_brightness dbg_led_get(struct led_classdev *cdev) @@ -98,7 +98,7 @@ static enum led_brightness dbg_led_get(struct led_classdev *cdev) struct dbg_led *led = container_of(cdev, struct dbg_led, cdev); u16 reg; - reg = __raw_readw(&fpga->leds); + reg = readw_relaxed(&fpga->leds); return (reg & led->mask) ? LED_FULL : LED_OFF; } @@ -112,7 +112,7 @@ static int fpga_probe(struct platform_device *pdev) return -ENODEV; fpga = ioremap(iomem->start, resource_size(iomem)); - __raw_writew(0xff, &fpga->leds); + writew_relaxed(0xff, &fpga->leds); for (i = 0; i < ARRAY_SIZE(dbg_leds); i++) { struct dbg_led *led; @@ -138,15 +138,15 @@ static int fpga_probe(struct platform_device *pdev) static int fpga_suspend_noirq(struct device *dev) { - fpga_led_state = __raw_readw(&fpga->leds); - __raw_writew(0xff, &fpga->leds); + fpga_led_state = readw_relaxed(&fpga->leds); + writew_relaxed(0xff, &fpga->leds); return 0; } static int fpga_resume_noirq(struct device *dev) { - __raw_writew(~fpga_led_state, &fpga->leds); + writew_relaxed(~fpga_led_state, &fpga->leds); return 0; } diff --git a/arch/arm/plat-omap/dmtimer.c b/arch/arm/plat-omap/dmtimer.c index 869254cebf842f..db10169a08de78 100644 --- a/arch/arm/plat-omap/dmtimer.c +++ b/arch/arm/plat-omap/dmtimer.c @@ -103,7 +103,7 @@ static void omap_timer_restore_context(struct omap_dm_timer *timer) timer->context.tmar); omap_dm_timer_write_reg(timer, OMAP_TIMER_IF_CTRL_REG, timer->context.tsicr); - __raw_writel(timer->context.tier, timer->irq_ena); + writel_relaxed(timer->context.tier, timer->irq_ena); omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, timer->context.tclr); } @@ -699,9 +699,9 @@ int omap_dm_timer_set_int_disable(struct omap_dm_timer *timer, u32 mask) omap_dm_timer_enable(timer); if (timer->revision == 1) - l = __raw_readl(timer->irq_ena) & ~mask; + l = readl_relaxed(timer->irq_ena) & ~mask; - __raw_writel(l, timer->irq_dis); + writel_relaxed(l, timer->irq_dis); l = omap_dm_timer_read_reg(timer, OMAP_TIMER_WAKEUP_EN_REG) & ~mask; omap_dm_timer_write_reg(timer, OMAP_TIMER_WAKEUP_EN_REG, l); @@ -722,7 +722,7 @@ unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer) return 0; } - l = __raw_readl(timer->irq_stat); + l = readl_relaxed(timer->irq_stat); return l; } diff --git a/arch/arm/plat-omap/include/plat/dmtimer.h b/arch/arm/plat-omap/include/plat/dmtimer.h index 2861b155485aef..dd79f3005cdf0f 100644 --- a/arch/arm/plat-omap/include/plat/dmtimer.h +++ b/arch/arm/plat-omap/include/plat/dmtimer.h @@ -280,20 +280,20 @@ static inline u32 __omap_dm_timer_read(struct omap_dm_timer *timer, u32 reg, int posted) { if (posted) - while (__raw_readl(timer->pend) & (reg >> WPSHIFT)) + while (readl_relaxed(timer->pend) & (reg >> WPSHIFT)) cpu_relax(); - return __raw_readl(timer->func_base + (reg & 0xff)); + return readl_relaxed(timer->func_base + (reg & 0xff)); } static inline void __omap_dm_timer_write(struct omap_dm_timer *timer, u32 reg, u32 val, int posted) { if (posted) - while (__raw_readl(timer->pend) & (reg >> WPSHIFT)) + while (readl_relaxed(timer->pend) & (reg >> WPSHIFT)) cpu_relax(); - __raw_writel(val, timer->func_base + (reg & 0xff)); + writel_relaxed(val, timer->func_base + (reg & 0xff)); } static inline void __omap_dm_timer_init_regs(struct omap_dm_timer *timer) @@ -301,7 +301,7 @@ static inline void __omap_dm_timer_init_regs(struct omap_dm_timer *timer) u32 tidr; /* Assume v1 ip if bits [31:16] are zero */ - tidr = __raw_readl(timer->io_base); + tidr = readl_relaxed(timer->io_base); if (!(tidr >> 16)) { timer->revision = 1; timer->irq_stat = timer->io_base + OMAP_TIMER_V1_STAT_OFFSET; @@ -385,7 +385,7 @@ static inline void __omap_dm_timer_stop(struct omap_dm_timer *timer, } /* Ack possibly pending interrupt */ - __raw_writel(OMAP_TIMER_INT_OVERFLOW, timer->irq_stat); + writel_relaxed(OMAP_TIMER_INT_OVERFLOW, timer->irq_stat); } static inline void __omap_dm_timer_load_start(struct omap_dm_timer *timer, @@ -399,7 +399,7 @@ static inline void __omap_dm_timer_load_start(struct omap_dm_timer *timer, static inline void __omap_dm_timer_int_enable(struct omap_dm_timer *timer, unsigned int value) { - __raw_writel(value, timer->irq_ena); + writel_relaxed(value, timer->irq_ena); __omap_dm_timer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, value, 0); } @@ -412,7 +412,7 @@ __omap_dm_timer_read_counter(struct omap_dm_timer *timer, int posted) static inline void __omap_dm_timer_write_status(struct omap_dm_timer *timer, unsigned int value) { - __raw_writel(value, timer->irq_stat); + writel_relaxed(value, timer->irq_stat); } #endif /* __ASM_ARCH_DMTIMER_H */ diff --git a/arch/arm/plat-samsung/cpu.c b/arch/arm/plat-samsung/cpu.c index 46b426e8aff599..699fce15b463f7 100644 --- a/arch/arm/plat-samsung/cpu.c +++ b/arch/arm/plat-samsung/cpu.c @@ -37,14 +37,14 @@ void __init s3c24xx_init_cpu(void) void __init s3c64xx_init_cpu(void) { - samsung_cpu_id = __raw_readl(S3C_VA_SYS + 0x118); + samsung_cpu_id = readl_relaxed(S3C_VA_SYS + 0x118); if (!samsung_cpu_id) { /* * S3C6400 has the ID register in a different place, * and needs a write before it can be read. */ - __raw_writel(0x0, S3C_VA_SYS + 0xA1C); - samsung_cpu_id = __raw_readl(S3C_VA_SYS + 0xA1C); + writel_relaxed(0x0, S3C_VA_SYS + 0xA1C); + samsung_cpu_id = readl_relaxed(S3C_VA_SYS + 0xA1C); } samsung_cpu_rev = 0; @@ -52,6 +52,6 @@ void __init s3c64xx_init_cpu(void) void __init s5p_init_cpu(void __iomem *cpuid_addr) { - samsung_cpu_id = __raw_readl(cpuid_addr); + samsung_cpu_id = readl_relaxed(cpuid_addr); samsung_cpu_rev = samsung_cpu_id & 0xFF; } diff --git a/arch/arm/plat-samsung/include/plat/cpu.h b/arch/arm/plat-samsung/include/plat/cpu.h index 335beb34135566..8f09488e7b3b84 100644 --- a/arch/arm/plat-samsung/include/plat/cpu.h +++ b/arch/arm/plat-samsung/include/plat/cpu.h @@ -46,6 +46,7 @@ extern unsigned long samsung_cpu_id; #define EXYNOS4_CPU_MASK 0xFFFE0000 #define EXYNOS5250_SOC_ID 0x43520000 +#define EXYNOS5410_SOC_ID 0xE5410000 #define EXYNOS5420_SOC_ID 0xE5420000 #define EXYNOS5440_SOC_ID 0xE5440000 #define EXYNOS5_SOC_MASK 0xFFFFF000 @@ -68,6 +69,7 @@ IS_SAMSUNG_CPU(exynos4210, EXYNOS4210_CPU_ID, EXYNOS4_CPU_MASK) IS_SAMSUNG_CPU(exynos4212, EXYNOS4212_CPU_ID, EXYNOS4_CPU_MASK) IS_SAMSUNG_CPU(exynos4412, EXYNOS4412_CPU_ID, EXYNOS4_CPU_MASK) IS_SAMSUNG_CPU(exynos5250, EXYNOS5250_SOC_ID, EXYNOS5_SOC_MASK) +IS_SAMSUNG_CPU(exynos5410, EXYNOS5410_SOC_ID, EXYNOS5_SOC_MASK) IS_SAMSUNG_CPU(exynos5420, EXYNOS5420_SOC_ID, EXYNOS5_SOC_MASK) IS_SAMSUNG_CPU(exynos5440, EXYNOS5440_SOC_ID, EXYNOS5_SOC_MASK) @@ -148,6 +150,12 @@ IS_SAMSUNG_CPU(exynos5440, EXYNOS5440_SOC_ID, EXYNOS5_SOC_MASK) # define soc_is_exynos5250() 0 #endif +#if defined(CONFIG_SOC_EXYNOS5410) +# define soc_is_exynos5410() is_samsung_exynos5410() +#else +# define soc_is_exynos5410() 0 +#endif + #if defined(CONFIG_SOC_EXYNOS5420) # define soc_is_exynos5420() is_samsung_exynos5420() #else diff --git a/arch/arm/plat-samsung/include/plat/map-s5p.h b/arch/arm/plat-samsung/include/plat/map-s5p.h index c18678610bc0ad..631092874e8802 100644 --- a/arch/arm/plat-samsung/include/plat/map-s5p.h +++ b/arch/arm/plat-samsung/include/plat/map-s5p.h @@ -57,6 +57,9 @@ #define S3C_UART_OFFSET (0x400) #endif +#define S5P_VA_SYSRAM_NS_X(x) (S5P_VA_SYSRAM_NS + x) +#define EXYNOS5410_BOOT_REG S5P_VA_SYSRAM_NS_X(0x1c) + #include #endif /* __ASM_PLAT_MAP_S5P_H */ diff --git a/arch/arm/plat-samsung/include/plat/uncompress.h b/arch/arm/plat-samsung/include/plat/uncompress.h index f48dc0a4736c8d..94fef0470777df 100644 --- a/arch/arm/plat-samsung/include/plat/uncompress.h +++ b/arch/arm/plat-samsung/include/plat/uncompress.h @@ -14,6 +14,8 @@ #ifndef __ASM_PLAT_UNCOMPRESS_H #define __ASM_PLAT_UNCOMPRESS_H +#include + typedef unsigned int upf_t; /* cannot include linux/serial_core.h */ /* uart setup */ @@ -54,6 +56,9 @@ uart_wr(unsigned int reg, unsigned int val) volatile unsigned int *ptr; ptr = (volatile unsigned int *)(reg + uart_base); +#ifdef CONFIG_CPU_BIG_ENDIAN + val = __swab32(val); +#endif /* CONFIG_CPU_BIG_ENDIAN */ *ptr = val; } @@ -63,7 +68,11 @@ uart_rd(unsigned int reg) volatile unsigned int *ptr; ptr = (volatile unsigned int *)(reg + uart_base); +#ifdef CONFIG_CPU_BIG_ENDIAN + return __swab32(*ptr); +#else return *ptr; +#endif /* CONFIG_CPU_BIG_ENDIAN */ } /* we can deal with the case the UARTs are being run @@ -102,10 +111,17 @@ static inline void flush(void) { } +#ifdef CONFIG_CPU_BIG_ENDIAN +#define __raw_writel(d, ad) \ + do { \ + *((volatile unsigned int __force *)(ad)) = __swab32(d); \ + } while (0) +#else #define __raw_writel(d, ad) \ do { \ *((volatile unsigned int __force *)(ad)) = (d); \ } while (0) +#endif /* CONFIG_CPU_BIG_ENDIAN */ #ifdef CONFIG_S3C_BOOT_ERROR_RESET diff --git a/arch/arm/plat-samsung/pm-gpio.c b/arch/arm/plat-samsung/pm-gpio.c index dd4c15d0d68f82..a1789225923a44 100644 --- a/arch/arm/plat-samsung/pm-gpio.c +++ b/arch/arm/plat-samsung/pm-gpio.c @@ -34,15 +34,15 @@ static void samsung_gpio_pm_1bit_save(struct samsung_gpio_chip *chip) { - chip->pm_save[0] = __raw_readl(chip->base + OFFS_CON); - chip->pm_save[1] = __raw_readl(chip->base + OFFS_DAT); + chip->pm_save[0] = readl_relaxed(chip->base + OFFS_CON); + chip->pm_save[1] = readl_relaxed(chip->base + OFFS_DAT); } static void samsung_gpio_pm_1bit_resume(struct samsung_gpio_chip *chip) { void __iomem *base = chip->base; - u32 old_gpcon = __raw_readl(base + OFFS_CON); - u32 old_gpdat = __raw_readl(base + OFFS_DAT); + u32 old_gpcon = readl_relaxed(base + OFFS_CON); + u32 old_gpdat = readl_relaxed(base + OFFS_DAT); u32 gps_gpcon = chip->pm_save[0]; u32 gps_gpdat = chip->pm_save[1]; u32 gpcon; @@ -53,12 +53,12 @@ static void samsung_gpio_pm_1bit_resume(struct samsung_gpio_chip *chip) /* first set all SFN bits to SFN */ gpcon = old_gpcon | gps_gpcon; - __raw_writel(gpcon, base + OFFS_CON); + writel_relaxed(gpcon, base + OFFS_CON); /* now set all the other bits */ - __raw_writel(gps_gpdat, base + OFFS_DAT); - __raw_writel(gps_gpcon, base + OFFS_CON); + writel_relaxed(gps_gpdat, base + OFFS_DAT); + writel_relaxed(gps_gpcon, base + OFFS_CON); S3C_PMDBG("%s: CON %08x => %08x, DAT %08x => %08x\n", chip->chip.label, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat); @@ -71,9 +71,9 @@ struct samsung_gpio_pm samsung_gpio_pm_1bit = { static void samsung_gpio_pm_2bit_save(struct samsung_gpio_chip *chip) { - chip->pm_save[0] = __raw_readl(chip->base + OFFS_CON); - chip->pm_save[1] = __raw_readl(chip->base + OFFS_DAT); - chip->pm_save[2] = __raw_readl(chip->base + OFFS_UP); + chip->pm_save[0] = readl_relaxed(chip->base + OFFS_CON); + chip->pm_save[1] = readl_relaxed(chip->base + OFFS_DAT); + chip->pm_save[2] = readl_relaxed(chip->base + OFFS_UP); } /* Test whether the given masked+shifted bits of an GPIO configuration @@ -128,8 +128,8 @@ static inline int is_out(unsigned long con) static void samsung_gpio_pm_2bit_resume(struct samsung_gpio_chip *chip) { void __iomem *base = chip->base; - u32 old_gpcon = __raw_readl(base + OFFS_CON); - u32 old_gpdat = __raw_readl(base + OFFS_DAT); + u32 old_gpcon = readl_relaxed(base + OFFS_CON); + u32 old_gpdat = readl_relaxed(base + OFFS_DAT); u32 gps_gpcon = chip->pm_save[0]; u32 gps_gpdat = chip->pm_save[1]; u32 gpcon, old, new, mask; @@ -137,7 +137,7 @@ static void samsung_gpio_pm_2bit_resume(struct samsung_gpio_chip *chip) int nr; /* restore GPIO pull-up settings */ - __raw_writel(chip->pm_save[2], base + OFFS_UP); + writel_relaxed(chip->pm_save[2], base + OFFS_UP); /* Create a change_mask of all the items that need to have * their CON value changed before their DAT value, so that @@ -180,12 +180,12 @@ static void samsung_gpio_pm_2bit_resume(struct samsung_gpio_chip *chip) gpcon = old_gpcon & ~change_mask; gpcon |= gps_gpcon & change_mask; - __raw_writel(gpcon, base + OFFS_CON); + writel_relaxed(gpcon, base + OFFS_CON); /* Now change any items that require DAT,CON */ - __raw_writel(gps_gpdat, base + OFFS_DAT); - __raw_writel(gps_gpcon, base + OFFS_CON); + writel_relaxed(gps_gpdat, base + OFFS_DAT); + writel_relaxed(gps_gpcon, base + OFFS_CON); S3C_PMDBG("%s: CON %08x => %08x, DAT %08x => %08x\n", chip->chip.label, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat); @@ -200,12 +200,12 @@ struct samsung_gpio_pm samsung_gpio_pm_2bit = { || defined(CONFIG_ARCH_EXYNOS) static void samsung_gpio_pm_4bit_save(struct samsung_gpio_chip *chip) { - chip->pm_save[1] = __raw_readl(chip->base + OFFS_CON); - chip->pm_save[2] = __raw_readl(chip->base + OFFS_DAT); - chip->pm_save[3] = __raw_readl(chip->base + OFFS_UP); + chip->pm_save[1] = readl_relaxed(chip->base + OFFS_CON); + chip->pm_save[2] = readl_relaxed(chip->base + OFFS_DAT); + chip->pm_save[3] = readl_relaxed(chip->base + OFFS_UP); if (chip->chip.ngpio > 8) - chip->pm_save[0] = __raw_readl(chip->base - 4); + chip->pm_save[0] = readl_relaxed(chip->base - 4); } static u32 samsung_gpio_pm_4bit_mask(u32 old_gpcon, u32 gps_gpcon) @@ -250,7 +250,7 @@ static u32 samsung_gpio_pm_4bit_mask(u32 old_gpcon, u32 gps_gpcon) static void samsung_gpio_pm_4bit_con(struct samsung_gpio_chip *chip, int index) { void __iomem *con = chip->base + (index * 4); - u32 old_gpcon = __raw_readl(con); + u32 old_gpcon = readl_relaxed(con); u32 gps_gpcon = chip->pm_save[index + 1]; u32 gpcon, mask; @@ -259,47 +259,47 @@ static void samsung_gpio_pm_4bit_con(struct samsung_gpio_chip *chip, int index) gpcon = old_gpcon & ~mask; gpcon |= gps_gpcon & mask; - __raw_writel(gpcon, con); + writel_relaxed(gpcon, con); } static void samsung_gpio_pm_4bit_resume(struct samsung_gpio_chip *chip) { void __iomem *base = chip->base; u32 old_gpcon[2]; - u32 old_gpdat = __raw_readl(base + OFFS_DAT); + u32 old_gpdat = readl_relaxed(base + OFFS_DAT); u32 gps_gpdat = chip->pm_save[2]; /* First, modify the CON settings */ old_gpcon[0] = 0; - old_gpcon[1] = __raw_readl(base + OFFS_CON); + old_gpcon[1] = readl_relaxed(base + OFFS_CON); samsung_gpio_pm_4bit_con(chip, 0); if (chip->chip.ngpio > 8) { - old_gpcon[0] = __raw_readl(base - 4); + old_gpcon[0] = readl_relaxed(base - 4); samsung_gpio_pm_4bit_con(chip, -1); } /* Now change the configurations that require DAT,CON */ - __raw_writel(chip->pm_save[2], base + OFFS_DAT); - __raw_writel(chip->pm_save[1], base + OFFS_CON); + writel_relaxed(chip->pm_save[2], base + OFFS_DAT); + writel_relaxed(chip->pm_save[1], base + OFFS_CON); if (chip->chip.ngpio > 8) - __raw_writel(chip->pm_save[0], base - 4); + writel_relaxed(chip->pm_save[0], base - 4); - __raw_writel(chip->pm_save[2], base + OFFS_DAT); - __raw_writel(chip->pm_save[3], base + OFFS_UP); + writel_relaxed(chip->pm_save[2], base + OFFS_DAT); + writel_relaxed(chip->pm_save[3], base + OFFS_UP); if (chip->chip.ngpio > 8) { S3C_PMDBG("%s: CON4 %08x,%08x => %08x,%08x, DAT %08x => %08x\n", chip->chip.label, old_gpcon[0], old_gpcon[1], - __raw_readl(base - 4), - __raw_readl(base + OFFS_CON), + readl_relaxed(base - 4), + readl_relaxed(base + OFFS_CON), old_gpdat, gps_gpdat); } else S3C_PMDBG("%s: CON4 %08x => %08x, DAT %08x => %08x\n", chip->chip.label, old_gpcon[1], - __raw_readl(base + OFFS_CON), + readl_relaxed(base + OFFS_CON), old_gpdat, gps_gpdat); } diff --git a/arch/arm/plat-samsung/pm.c b/arch/arm/plat-samsung/pm.c index e5b0f2c2d8845d..4a9223bc69654a 100644 --- a/arch/arm/plat-samsung/pm.c +++ b/arch/arm/plat-samsung/pm.c @@ -88,14 +88,14 @@ static void s3c_pm_save_uart(unsigned int uart, struct pm_uart_save *save) { void __iomem *regs = S3C_VA_UARTx(uart); - save->ulcon = __raw_readl(regs + S3C2410_ULCON); - save->ucon = __raw_readl(regs + S3C2410_UCON); - save->ufcon = __raw_readl(regs + S3C2410_UFCON); - save->umcon = __raw_readl(regs + S3C2410_UMCON); - save->ubrdiv = __raw_readl(regs + S3C2410_UBRDIV); + save->ulcon = readl_relaxed(regs + S3C2410_ULCON); + save->ucon = readl_relaxed(regs + S3C2410_UCON); + save->ufcon = readl_relaxed(regs + S3C2410_UFCON); + save->umcon = readl_relaxed(regs + S3C2410_UMCON); + save->ubrdiv = readl_relaxed(regs + S3C2410_UBRDIV); if (pm_uart_udivslot) - save->udivslot = __raw_readl(regs + S3C2443_DIVSLOT); + save->udivslot = readl_relaxed(regs + S3C2443_DIVSLOT); S3C_PMDBG("UART[%d]: ULCON=%04x, UCON=%04x, UFCON=%04x, UBRDIV=%04x\n", uart, save->ulcon, save->ucon, save->ufcon, save->ubrdiv); @@ -112,14 +112,14 @@ static void s3c_pm_restore_uart(unsigned int uart, struct pm_uart_save *save) s3c_pm_arch_update_uart(regs, save); - __raw_writel(save->ulcon, regs + S3C2410_ULCON); - __raw_writel(save->ucon, regs + S3C2410_UCON); - __raw_writel(save->ufcon, regs + S3C2410_UFCON); - __raw_writel(save->umcon, regs + S3C2410_UMCON); - __raw_writel(save->ubrdiv, regs + S3C2410_UBRDIV); + writel_relaxed(save->ulcon, regs + S3C2410_ULCON); + writel_relaxed(save->ucon, regs + S3C2410_UCON); + writel_relaxed(save->ufcon, regs + S3C2410_UFCON); + writel_relaxed(save->umcon, regs + S3C2410_UMCON); + writel_relaxed(save->ubrdiv, regs + S3C2410_UBRDIV); if (pm_uart_udivslot) - __raw_writel(save->udivslot, regs + S3C2443_DIVSLOT); + writel_relaxed(save->udivslot, regs + S3C2443_DIVSLOT); } static void s3c_pm_restore_uarts(void) @@ -168,7 +168,7 @@ int s3c_irqext_wake(struct irq_data *data, unsigned int state) void s3c_pm_do_save(struct sleep_save *ptr, int count) { for (; count > 0; count--, ptr++) { - ptr->val = __raw_readl(ptr->reg); + ptr->val = readl_relaxed(ptr->reg); S3C_PMDBG("saved %p value %08lx\n", ptr->reg, ptr->val); } } @@ -188,9 +188,9 @@ void s3c_pm_do_restore(const struct sleep_save *ptr, int count) { for (; count > 0; count--, ptr++) { printk(KERN_DEBUG "restore %p (restore %08lx, was %08x)\n", - ptr->reg, ptr->val, __raw_readl(ptr->reg)); + ptr->reg, ptr->val, readl_relaxed(ptr->reg)); - __raw_writel(ptr->val, ptr->reg); + writel_relaxed(ptr->val, ptr->reg); } } @@ -208,7 +208,7 @@ void s3c_pm_do_restore(const struct sleep_save *ptr, int count) void s3c_pm_do_restore_core(const struct sleep_save *ptr, int count) { for (; count > 0; count--, ptr++) - __raw_writel(ptr->val, ptr->reg); + writel_relaxed(ptr->val, ptr->reg); } /* s3c2410_pm_show_resume_irqs diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 27bbcfc7202a8d..516d8a7ca697d5 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -16,6 +16,7 @@ config ARM64 select DCACHE_WORD_ACCESS select GENERIC_CLOCKEVENTS select GENERIC_CLOCKEVENTS_BROADCAST if SMP + select GENERIC_CPU_AUTOPROBE select GENERIC_IOMAP select GENERIC_IRQ_PROBE select GENERIC_IRQ_SHOW @@ -26,6 +27,7 @@ config ARM64 select GENERIC_TIME_VSYSCALL select HARDIRQS_SW_RESEND select HAVE_ARCH_JUMP_LABEL + select HAVE_ARCH_KGDB select HAVE_ARCH_TRACEHOOK select HAVE_DEBUG_BUGVERBOSE select HAVE_DEBUG_KMEMLEAK @@ -38,6 +40,8 @@ config ARM64 select HAVE_MEMBLOCK select HAVE_PATA_PLATFORM select HAVE_PERF_EVENTS + select HAVE_PERF_REGS + select HAVE_PERF_USER_STACK_DUMP select IRQ_DOMAIN select MODULES_USE_ELF_RELA select NO_BOOTMEM @@ -73,7 +77,7 @@ config LOCKDEP_SUPPORT config TRACE_IRQFLAGS_SUPPORT def_bool y -config RWSEM_GENERIC_SPINLOCK +config RWSEM_XCHGADD_ALGORITHM def_bool y config GENERIC_HWEIGHT @@ -85,7 +89,7 @@ config GENERIC_CSUM config GENERIC_CALIBRATE_DELAY def_bool y -config ZONE_DMA32 +config ZONE_DMA def_bool y config ARCH_DMA_ADDR_T_64BIT @@ -164,6 +168,22 @@ config SMP If you don't know what to do here, say N. +config SCHED_MC + bool "Multi-core scheduler support" + depends on SMP + help + Multi-core scheduler support improves the CPU scheduler's decision + making when dealing with multi-core CPU chips at a cost of slightly + increased overhead in some places. If unsure say N here. + +config SCHED_SMT + bool "SMT scheduler support" + depends on SMP + help + Improves the CPU scheduler's decision making when dealing with + MultiThreading at a cost of slightly increased overhead in some + places. If unsure say N here. + config NR_CPUS int "Maximum number of CPUs (2-32)" range 2 32 @@ -301,6 +321,8 @@ menu "CPU Power Management" source "drivers/cpuidle/Kconfig" +source "drivers/cpufreq/Kconfig" + endmenu source "net/Kconfig" diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile index 2fceb71ac3b7a8..c71005e9fb8871 100644 --- a/arch/arm64/Makefile +++ b/arch/arm64/Makefile @@ -19,13 +19,20 @@ LIBGCC := $(shell $(CC) $(KBUILD_CFLAGS) -print-libgcc-file-name) KBUILD_DEFCONFIG := defconfig -KBUILD_CFLAGS += -mgeneral-regs-only -ifeq ($(CONFIG_CPU_BIG_ENDIAN), y) -KBUILD_CPPFLAGS += -mbig-endian +ifeq ($(COMPILER),clang) +# Clang options +KBUILD_CPPFLAGS += $(call cc-option,-target $(CROSS_COMPILE:-=),) +KBUILD_CFLAGS += $(call cc-option,-fno-builtin,) +KBUILD_CFLAGS += $(call cc-disable-warning,asm-operand-widths) +endif + +KBUILD_CFLAGS += $(call cc-option,-mgeneral-regs-only,) +ifeq ($(CONFIG_CPU_BG_ENDIAN), y) +KBUILD_CPPFLAGS += $(call cc-option,-mbig-endian,) AS += -EB LD += -EB else -KBUILD_CPPFLAGS += -mlittle-endian +KBUILD_CPPFLAGS += $(call cc-option,-mlittle-endian,) AS += -EL LD += -EL endif diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile index c52bdb051f664b..c4cc86bf19956f 100644 --- a/arch/arm64/boot/dts/Makefile +++ b/arch/arm64/boot/dts/Makefile @@ -1,5 +1,6 @@ -dtb-$(CONFIG_ARCH_VEXPRESS) += rtsm_ve-aemv8a.dtb foundation-v8.dtb +dtb-$(CONFIG_ARCH_VEXPRESS) += rtsm_ve-aemv8a.dtb foundation-v8.dtb fvp-base-gicv2legacy-psci.dtb fvp-base-gicv2-psci.dtb fvp-base-gicv3-psci.dtb fvp-foundation-gicv2legacy-psci.dtb fvp-foundation-gicv2-psci.dtb fvp-foundation-gicv3-psci.dtb dtb-$(CONFIG_ARCH_XGENE) += apm-mustang.dtb +dtb-y += juno.dtb targets += dtbs targets += $(dtb-y) diff --git a/arch/arm64/boot/dts/clcd-panels.dtsi b/arch/arm64/boot/dts/clcd-panels.dtsi new file mode 100644 index 00000000000000..0b0ff6ead4b213 --- /dev/null +++ b/arch/arm64/boot/dts/clcd-panels.dtsi @@ -0,0 +1,52 @@ +/* + * ARM Ltd. Versatile Express + * + */ + +/ { + panels { + panel@0 { + compatible = "panel"; + mode = "VGA"; + refresh = <60>; + xres = <640>; + yres = <480>; + pixclock = <39721>; + left_margin = <40>; + right_margin = <24>; + upper_margin = <32>; + lower_margin = <11>; + hsync_len = <96>; + vsync_len = <2>; + sync = <0>; + vmode = "FB_VMODE_NONINTERLACED"; + + tim2 = "TIM2_BCD", "TIM2_IPC"; + cntl = "CNTL_LCDTFT", "CNTL_BGR", "CNTL_LCDVCOMP(1)"; + caps = "CLCD_CAP_5551", "CLCD_CAP_565", "CLCD_CAP_888"; + bpp = <16>; + }; + + panel@1 { + compatible = "panel"; + mode = "XVGA"; + refresh = <60>; + xres = <1024>; + yres = <768>; + pixclock = <15748>; + left_margin = <152>; + right_margin = <48>; + upper_margin = <23>; + lower_margin = <3>; + hsync_len = <104>; + vsync_len = <4>; + sync = <0>; + vmode = "FB_VMODE_NONINTERLACED"; + + tim2 = "TIM2_BCD", "TIM2_IPC"; + cntl = "CNTL_LCDTFT", "CNTL_BGR", "CNTL_LCDVCOMP(1)"; + caps = "CLCD_CAP_5551", "CLCD_CAP_565", "CLCD_CAP_888"; + bpp = <16>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/fvp-base-gicv2-psci.dts b/arch/arm64/boot/dts/fvp-base-gicv2-psci.dts new file mode 100644 index 00000000000000..fb3f64e258e87d --- /dev/null +++ b/arch/arm64/boot/dts/fvp-base-gicv2-psci.dts @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2013, ARM Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of ARM nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/dts-v1/; + +/memreserve/ 0x80000000 0x00010000; + +/ { +}; + +/ { + model = "FVP Base"; + compatible = "arm,vfp-base", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + psci { + compatible = "arm,psci"; + method = "smc"; + cpu_suspend = <0xc4000001>; + cpu_off = <0x84000002>; + cpu_on = <0xc4000003>; + }; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x0>; + enable-method = "psci"; + }; + cpu@1 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x1>; + enable-method = "psci"; + }; + cpu@2 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x2>; + enable-method = "psci"; + }; + cpu@3 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x3>; + enable-method = "psci"; + }; + cpu@100 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x100>; + enable-method = "psci"; + }; + cpu@101 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x101>; + enable-method = "psci"; + }; + cpu@102 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x102>; + enable-method = "psci"; + }; + cpu@103 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x103>; + enable-method = "psci"; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x00000000 0x80000000 0 0x80000000>; + /* + <0x00000008 0x80000000 0 0x80000000>; + */ + }; + + gic: interrupt-controller@2f000000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x0 0x2f000000 0 0x10000>, + <0x0 0x2c000000 0 0x2000>, + <0x0 0x2c010000 0 0x2000>, + <0x0 0x2c02F000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = <1 13 0xff01>, + <1 14 0xff01>, + <1 11 0xff01>, + <1 10 0xff01>; + clock-frequency = <100000000>; + }; + + timer@2a810000 { + compatible = "arm,armv7-timer-mem"; + reg = <0x0 0x2a810000 0x0 0x10000>; + clock-frequency = <100000000>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + frame@2a820000 { + frame-number = <0>; + interrupts = <0 25 4>; + reg = <0x0 0x2a820000 0x0 0x10000>; + }; + }; + + pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = <0 60 4>, + <0 61 4>, + <0 62 4>, + <0 63 4>; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm64/boot/dts/fvp-base-gicv2legacy-psci.dts b/arch/arm64/boot/dts/fvp-base-gicv2legacy-psci.dts new file mode 100644 index 00000000000000..0d9eb4edf325b1 --- /dev/null +++ b/arch/arm64/boot/dts/fvp-base-gicv2legacy-psci.dts @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2013, ARM Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of ARM nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/dts-v1/; + +/memreserve/ 0x80000000 0x00010000; + +/ { +}; + +/ { + model = "FVP Base"; + compatible = "arm,vfp-base", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + psci { + compatible = "arm,psci"; + method = "smc"; + cpu_suspend = <0xc4000001>; + cpu_off = <0x84000002>; + cpu_on = <0xc4000003>; + }; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x0>; + enable-method = "psci"; + }; + cpu@1 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x1>; + enable-method = "psci"; + }; + cpu@2 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x2>; + enable-method = "psci"; + }; + cpu@3 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x3>; + enable-method = "psci"; + }; + cpu@100 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x100>; + enable-method = "psci"; + }; + cpu@101 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x101>; + enable-method = "psci"; + }; + cpu@102 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x102>; + enable-method = "psci"; + }; + cpu@103 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x103>; + enable-method = "psci"; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x00000000 0x80000000 0 0x80000000>; + /* + <0x00000008 0x80000000 0 0x80000000>; + */ + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x0 0x2c001000 0 0x1000>, + <0x0 0x2c002000 0 0x1000>, + <0x0 0x2c004000 0 0x2000>, + <0x0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = <1 13 0xff01>, + <1 14 0xff01>, + <1 11 0xff01>, + <1 10 0xff01>; + clock-frequency = <100000000>; + }; + + timer@2a810000 { + compatible = "arm,armv7-timer-mem"; + reg = <0x0 0x2a810000 0x0 0x10000>; + clock-frequency = <100000000>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + frame@2a820000 { + frame-number = <0>; + interrupts = <0 25 4>; + reg = <0x0 0x2a820000 0x0 0x10000>; + }; + }; + + pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = <0 60 4>, + <0 61 4>, + <0 62 4>, + <0 63 4>; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm64/boot/dts/fvp-base-gicv3-psci.dts b/arch/arm64/boot/dts/fvp-base-gicv3-psci.dts new file mode 100644 index 00000000000000..a09895df7fb387 --- /dev/null +++ b/arch/arm64/boot/dts/fvp-base-gicv3-psci.dts @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2013, ARM Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of ARM nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/dts-v1/; + +/memreserve/ 0x80000000 0x00010000; + +/ { +}; + +/ { + model = "FVP Base"; + compatible = "arm,vfp-base", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + psci { + compatible = "arm,psci"; + method = "smc"; + cpu_suspend = <0xc4000001>; + cpu_off = <0x84000002>; + cpu_on = <0xc4000003>; + }; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x0>; + enable-method = "psci"; + }; + cpu@1 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x1>; + enable-method = "psci"; + }; + cpu@2 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x2>; + enable-method = "psci"; + }; + cpu@3 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x3>; + enable-method = "psci"; + }; + cpu@100 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x100>; + enable-method = "psci"; + }; + cpu@101 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x101>; + enable-method = "psci"; + }; + cpu@102 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x102>; + enable-method = "psci"; + }; + cpu@103 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x103>; + enable-method = "psci"; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x00000000 0x80000000 0 0x80000000>; + /* + <0x00000008 0x80000000 0 0x80000000>; + */ + }; + + gic: interrupt-controller@2f000000 { + compatible = "arm,gic-v3"; + #interrupt-cells = <3>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + interrupt-controller; + reg = <0x0 0x2f000000 0 0x10000>, // GICD + <0x0 0x2f100000 0 0x200000>, // GICR + <0x0 0x2c000000 0 0x2000>, // GICC + <0x0 0x2c010000 0 0x2000>, // GICH + <0x0 0x2c02f000 0 0x2000>; // GICV + interrupts = <1 9 4>; + + its: its@2f020000 { + compatible = "arm,gic-v3-its"; + msi-controller; + reg = <0x0 0x2f020000 0x0 0x20000>; // GITS + }; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = <1 13 0xff01>, + <1 14 0xff01>, + <1 11 0xff01>, + <1 10 0xff01>; + clock-frequency = <100000000>; + }; + + timer@2a810000 { + compatible = "arm,armv7-timer-mem"; + reg = <0x0 0x2a810000 0x0 0x10000>; + clock-frequency = <100000000>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + frame@2a820000 { + frame-number = <0>; + interrupts = <0 25 4>; + reg = <0x0 0x2a820000 0x0 0x10000>; + }; + }; + + pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = <0 60 4>, + <0 61 4>, + <0 62 4>, + <0 63 4>; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 0 0 4>, + <0 0 1 &gic 0 0 0 1 4>, + <0 0 2 &gic 0 0 0 2 4>, + <0 0 3 &gic 0 0 0 3 4>, + <0 0 4 &gic 0 0 0 4 4>, + <0 0 5 &gic 0 0 0 5 4>, + <0 0 6 &gic 0 0 0 6 4>, + <0 0 7 &gic 0 0 0 7 4>, + <0 0 8 &gic 0 0 0 8 4>, + <0 0 9 &gic 0 0 0 9 4>, + <0 0 10 &gic 0 0 0 10 4>, + <0 0 11 &gic 0 0 0 11 4>, + <0 0 12 &gic 0 0 0 12 4>, + <0 0 13 &gic 0 0 0 13 4>, + <0 0 14 &gic 0 0 0 14 4>, + <0 0 15 &gic 0 0 0 15 4>, + <0 0 16 &gic 0 0 0 16 4>, + <0 0 17 &gic 0 0 0 17 4>, + <0 0 18 &gic 0 0 0 18 4>, + <0 0 19 &gic 0 0 0 19 4>, + <0 0 20 &gic 0 0 0 20 4>, + <0 0 21 &gic 0 0 0 21 4>, + <0 0 22 &gic 0 0 0 22 4>, + <0 0 23 &gic 0 0 0 23 4>, + <0 0 24 &gic 0 0 0 24 4>, + <0 0 25 &gic 0 0 0 25 4>, + <0 0 26 &gic 0 0 0 26 4>, + <0 0 27 &gic 0 0 0 27 4>, + <0 0 28 &gic 0 0 0 28 4>, + <0 0 29 &gic 0 0 0 29 4>, + <0 0 30 &gic 0 0 0 30 4>, + <0 0 31 &gic 0 0 0 31 4>, + <0 0 32 &gic 0 0 0 32 4>, + <0 0 33 &gic 0 0 0 33 4>, + <0 0 34 &gic 0 0 0 34 4>, + <0 0 35 &gic 0 0 0 35 4>, + <0 0 36 &gic 0 0 0 36 4>, + <0 0 37 &gic 0 0 0 37 4>, + <0 0 38 &gic 0 0 0 38 4>, + <0 0 39 &gic 0 0 0 39 4>, + <0 0 40 &gic 0 0 0 40 4>, + <0 0 41 &gic 0 0 0 41 4>, + <0 0 42 &gic 0 0 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" + }; +}; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm64/boot/dts/fvp-foundation-gicv2-psci.dts b/arch/arm64/boot/dts/fvp-foundation-gicv2-psci.dts new file mode 100644 index 00000000000000..7924ff2124da9b --- /dev/null +++ b/arch/arm64/boot/dts/fvp-foundation-gicv2-psci.dts @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2013, ARM Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the ARM nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/dts-v1/; + +/memreserve/ 0x80000000 0x00010000; + +/ { +}; + +/ { + model = "FVP Base"; + compatible = "arm,fvp-base", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + psci { + compatible = "arm,psci"; + method = "smc"; + cpu_suspend = <0xc4000001>; + cpu_off = <0x84000002>; + cpu_on = <0xc4000003>; + }; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x0>; + enable-method = "psci"; + }; + cpu@1 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x1>; + enable-method = "psci"; + }; + cpu@2 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x2>; + enable-method = "psci"; + }; + cpu@3 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x3>; + enable-method = "psci"; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x00000000 0x80000000 0 0x80000000>, + <0x00000008 0x80000000 0 0x80000000>; + }; + + gic: interrupt-controller@2f000000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x0 0x2f000000 0 0x10000>, + <0x0 0x2c000000 0 0x2000>, + <0x0 0x2c010000 0 0x2000>, + <0x0 0x2c02F000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = <1 13 0xff01>, + <1 14 0xff01>, + <1 11 0xff01>, + <1 10 0xff01>; + clock-frequency = <100000000>; + }; + + timer@2a810000 { + compatible = "arm,armv7-timer-mem"; + reg = <0x0 0x2a810000 0x0 0x10000>; + clock-frequency = <100000000>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + frame@2a820000 { + frame-number = <0>; + interrupts = <0 25 4>; + reg = <0x0 0x2a820000 0x0 0x10000>; + }; + }; + + pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = <0 60 4>, + <0 61 4>, + <0 62 4>, + <0 63 4>; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "fvp-foundation-motherboard.dtsi" + }; +}; diff --git a/arch/arm64/boot/dts/fvp-foundation-gicv2legacy-psci.dts b/arch/arm64/boot/dts/fvp-foundation-gicv2legacy-psci.dts new file mode 100644 index 00000000000000..656802eaa9b9ba --- /dev/null +++ b/arch/arm64/boot/dts/fvp-foundation-gicv2legacy-psci.dts @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2013, ARM Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the ARM nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/dts-v1/; + +/memreserve/ 0x80000000 0x00010000; + +/ { +}; + +/ { + model = "FVP Base"; + compatible = "arm,fvp-base", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + psci { + compatible = "arm,psci"; + method = "smc"; + cpu_suspend = <0xc4000001>; + cpu_off = <0x84000002>; + cpu_on = <0xc4000003>; + }; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x0>; + enable-method = "psci"; + }; + cpu@1 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x1>; + enable-method = "psci"; + }; + cpu@2 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x2>; + enable-method = "psci"; + }; + cpu@3 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x3>; + enable-method = "psci"; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x00000000 0x80000000 0 0x80000000>, + <0x00000008 0x80000000 0 0x80000000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x0 0x2c001000 0 0x1000>, + <0x0 0x2c002000 0 0x1000>, + <0x0 0x2c004000 0 0x2000>, + <0x0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = <1 13 0xff01>, + <1 14 0xff01>, + <1 11 0xff01>, + <1 10 0xff01>; + clock-frequency = <100000000>; + }; + + timer@2a810000 { + compatible = "arm,armv7-timer-mem"; + reg = <0x0 0x2a810000 0x0 0x10000>; + clock-frequency = <100000000>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + frame@2a820000 { + frame-number = <0>; + interrupts = <0 25 4>; + reg = <0x0 0x2a820000 0x0 0x10000>; + }; + }; + + pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = <0 60 4>, + <0 61 4>, + <0 62 4>, + <0 63 4>; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + + /include/ "fvp-foundation-motherboard.dtsi" + }; +}; diff --git a/arch/arm64/boot/dts/fvp-foundation-gicv3-psci.dts b/arch/arm64/boot/dts/fvp-foundation-gicv3-psci.dts new file mode 100644 index 00000000000000..87ee3c01619fa5 --- /dev/null +++ b/arch/arm64/boot/dts/fvp-foundation-gicv3-psci.dts @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2013, ARM Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of ARM nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/dts-v1/; + +/memreserve/ 0x80000000 0x00010000; + +/ { +}; + +/ { + model = "FVP Base"; + compatible = "arm,fvp-base", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + psci { + compatible = "arm,psci"; + method = "smc"; + cpu_suspend = <0xc4000001>; + cpu_off = <0x84000002>; + cpu_on = <0xc4000003>; + }; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x0>; + enable-method = "psci"; + }; + cpu@1 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x1>; + enable-method = "psci"; + }; + cpu@2 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x2>; + enable-method = "psci"; + }; + cpu@3 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x3>; + enable-method = "psci"; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x00000000 0x80000000 0 0x80000000>, + <0x00000008 0x80000000 0 0x80000000>; + }; + + gic: interrupt-controller@2f000000 { + compatible = "arm,gic-v3"; + #interrupt-cells = <3>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + interrupt-controller; + reg = <0x0 0x2f000000 0 0x10000>, // GICD + <0x0 0x2f100000 0 0x200000>, // GICR + <0x0 0x2c000000 0 0x2000>, // GICC + <0x0 0x2c010000 0 0x2000>, // GICH + <0x0 0x2c02f000 0 0x2000>; // GICV + interrupts = <1 9 4>; + + its: its@2f020000 { + compatible = "arm,gic-v3-its"; + msi-controller; + reg = <0x0 0x2f020000 0x0 0x20000>; // GITS + }; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = <1 13 0xff01>, + <1 14 0xff01>, + <1 11 0xff01>, + <1 10 0xff01>; + clock-frequency = <100000000>; + }; + + timer@2a810000 { + compatible = "arm,armv7-timer-mem"; + reg = <0x0 0x2a810000 0x0 0x10000>; + clock-frequency = <100000000>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + frame@2a820000 { + frame-number = <0>; + interrupts = <0 25 4>; + reg = <0x0 0x2a820000 0x0 0x10000>; + }; + }; + + pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = <0 60 4>, + <0 61 4>, + <0 62 4>, + <0 63 4>; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 0 0 4>, + <0 0 1 &gic 0 0 0 1 4>, + <0 0 2 &gic 0 0 0 2 4>, + <0 0 3 &gic 0 0 0 3 4>, + <0 0 4 &gic 0 0 0 4 4>, + <0 0 5 &gic 0 0 0 5 4>, + <0 0 6 &gic 0 0 0 6 4>, + <0 0 7 &gic 0 0 0 7 4>, + <0 0 8 &gic 0 0 0 8 4>, + <0 0 9 &gic 0 0 0 9 4>, + <0 0 10 &gic 0 0 0 10 4>, + <0 0 11 &gic 0 0 0 11 4>, + <0 0 12 &gic 0 0 0 12 4>, + <0 0 13 &gic 0 0 0 13 4>, + <0 0 14 &gic 0 0 0 14 4>, + <0 0 15 &gic 0 0 0 15 4>, + <0 0 16 &gic 0 0 0 16 4>, + <0 0 17 &gic 0 0 0 17 4>, + <0 0 18 &gic 0 0 0 18 4>, + <0 0 19 &gic 0 0 0 19 4>, + <0 0 20 &gic 0 0 0 20 4>, + <0 0 21 &gic 0 0 0 21 4>, + <0 0 22 &gic 0 0 0 22 4>, + <0 0 23 &gic 0 0 0 23 4>, + <0 0 24 &gic 0 0 0 24 4>, + <0 0 25 &gic 0 0 0 25 4>, + <0 0 26 &gic 0 0 0 26 4>, + <0 0 27 &gic 0 0 0 27 4>, + <0 0 28 &gic 0 0 0 28 4>, + <0 0 29 &gic 0 0 0 29 4>, + <0 0 30 &gic 0 0 0 30 4>, + <0 0 31 &gic 0 0 0 31 4>, + <0 0 32 &gic 0 0 0 32 4>, + <0 0 33 &gic 0 0 0 33 4>, + <0 0 34 &gic 0 0 0 34 4>, + <0 0 35 &gic 0 0 0 35 4>, + <0 0 36 &gic 0 0 0 36 4>, + <0 0 37 &gic 0 0 0 37 4>, + <0 0 38 &gic 0 0 0 38 4>, + <0 0 39 &gic 0 0 0 39 4>, + <0 0 40 &gic 0 0 0 40 4>, + <0 0 41 &gic 0 0 0 41 4>, + <0 0 42 &gic 0 0 0 42 4>; + + /include/ "fvp-foundation-motherboard.dtsi" + }; +}; diff --git a/arch/arm64/boot/dts/fvp-foundation-motherboard.dtsi b/arch/arm64/boot/dts/fvp-foundation-motherboard.dtsi new file mode 100644 index 00000000000000..76cae31f4f8594 --- /dev/null +++ b/arch/arm64/boot/dts/fvp-foundation-motherboard.dtsi @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2013, ARM Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the ARM nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + motherboard { + arm,v2m-memory-map = "rs1"; + compatible = "arm,vexpress,v2m-p1", "simple-bus"; + #address-cells = <2>; /* SMB chipselect number and offset */ + #size-cells = <1>; + #interrupt-cells = <1>; + ranges; + + ethernet@2,02000000 { + compatible = "smsc,lan91c111"; + reg = <2 0x02000000 0x10000>; + interrupts = <15>; + }; + + v2m_clk24mhz: clk24mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "v2m:clk24mhz"; + }; + + v2m_refclk1mhz: refclk1mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <1000000>; + clock-output-names = "v2m:refclk1mhz"; + }; + + v2m_refclk32khz: refclk32khz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <32768>; + clock-output-names = "v2m:refclk32khz"; + }; + + iofpga@3,00000000 { + compatible = "arm,amba-bus", "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 3 0 0x200000>; + + v2m_sysreg: sysreg@010000 { + compatible = "arm,vexpress-sysreg"; + reg = <0x010000 0x1000>; + gpio-controller; + #gpio-cells = <2>; + }; + + v2m_sysctl: sysctl@020000 { + compatible = "arm,sp810", "arm,primecell"; + reg = <0x020000 0x1000>; + clocks = <&v2m_refclk32khz>, <&v2m_refclk1mhz>, <&v2m_clk24mhz>; + clock-names = "refclk", "timclk", "apb_pclk"; + #clock-cells = <1>; + clock-output-names = "timerclken0", "timerclken1", "timerclken2", "timerclken3"; + }; + + v2m_serial0: uart@090000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x090000 0x1000>; + interrupts = <5>; + clocks = <&v2m_clk24mhz>, <&v2m_clk24mhz>; + clock-names = "uartclk", "apb_pclk"; + }; + + v2m_serial1: uart@0a0000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0a0000 0x1000>; + interrupts = <6>; + clocks = <&v2m_clk24mhz>, <&v2m_clk24mhz>; + clock-names = "uartclk", "apb_pclk"; + }; + + v2m_serial2: uart@0b0000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0b0000 0x1000>; + interrupts = <7>; + clocks = <&v2m_clk24mhz>, <&v2m_clk24mhz>; + clock-names = "uartclk", "apb_pclk"; + }; + + v2m_serial3: uart@0c0000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0c0000 0x1000>; + interrupts = <8>; + clocks = <&v2m_clk24mhz>, <&v2m_clk24mhz>; + clock-names = "uartclk", "apb_pclk"; + }; + + wdt@0f0000 { + compatible = "arm,sp805", "arm,primecell"; + reg = <0x0f0000 0x1000>; + interrupts = <0>; + clocks = <&v2m_refclk32khz>, <&v2m_clk24mhz>; + clock-names = "wdogclk", "apb_pclk"; + }; + + v2m_timer01: timer@110000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x110000 0x1000>; + interrupts = <2>; + clocks = <&v2m_sysctl 0>, <&v2m_sysctl 1>, <&v2m_clk24mhz>; + clock-names = "timclken1", "timclken2", "apb_pclk"; + }; + + v2m_timer23: timer@120000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x120000 0x1000>; + interrupts = <3>; + clocks = <&v2m_sysctl 2>, <&v2m_sysctl 3>, <&v2m_clk24mhz>; + clock-names = "timclken1", "timclken2", "apb_pclk"; + }; + + rtc@170000 { + compatible = "arm,pl031", "arm,primecell"; + reg = <0x170000 0x1000>; + interrupts = <4>; + clocks = <&v2m_clk24mhz>; + clock-names = "apb_pclk"; + }; + + virtio_block@0130000 { + compatible = "virtio,mmio"; + reg = <0x130000 0x1000>; + interrupts = <0x2a>; + }; + }; + + v2m_fixed_3v3: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "3V3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + + mcc { + compatible = "arm,vexpress,config-bus", "simple-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + reset@0 { + compatible = "arm,vexpress-reset"; + arm,vexpress-sysreg,func = <5 0>; + }; + + muxfpga@0 { + compatible = "arm,vexpress-muxfpga"; + arm,vexpress-sysreg,func = <7 0>; + }; + + shutdown@0 { + compatible = "arm,vexpress-shutdown"; + arm,vexpress-sysreg,func = <8 0>; + }; + + reboot@0 { + compatible = "arm,vexpress-reboot"; + arm,vexpress-sysreg,func = <9 0>; + }; + + dvimode@0 { + compatible = "arm,vexpress-dvimode"; + arm,vexpress-sysreg,func = <11 0>; + }; + }; + }; diff --git a/arch/arm64/boot/dts/include/dt-bindings b/arch/arm64/boot/dts/include/dt-bindings new file mode 120000 index 00000000000000..08c00e4972fa54 --- /dev/null +++ b/arch/arm64/boot/dts/include/dt-bindings @@ -0,0 +1 @@ +../../../../../include/dt-bindings \ No newline at end of file diff --git a/arch/arm64/boot/dts/juno.dts b/arch/arm64/boot/dts/juno.dts new file mode 100644 index 00000000000000..70b23b946890cc --- /dev/null +++ b/arch/arm64/boot/dts/juno.dts @@ -0,0 +1,466 @@ +/* + * ARM Ltd. Juno Plaform + * + * Fast Models FVP v2 support + */ + +/dts-v1/; + +#include + +/ { + model = "Juno"; + compatible = "arm,juno", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + aliases { + serial0 = &soc_uart0; + }; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@100 { + device_type = "cpu"; + compatible = "arm,cortex-a53","arm,armv8"; + reg = <0x0 0x100>; + enable-method = "psci"; + }; + + cpu@101 { + device_type = "cpu"; + compatible = "arm,cortex-a53","arm,armv8"; + reg = <0x0 0x101>; + enable-method = "psci"; + }; + + cpu@102 { + device_type = "cpu"; + compatible = "arm,cortex-a53","arm,armv8"; + reg = <0x0 0x102>; + enable-method = "psci"; + }; + + cpu@103 { + device_type = "cpu"; + compatible = "arm,cortex-a53","arm,armv8"; + reg = <0x0 0x103>; + enable-method = "psci"; + }; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a57","arm,armv8"; + reg = <0x0 0x0>; + enable-method = "psci"; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a57","arm,armv8"; + reg = <0x0 0x1>; + enable-method = "psci"; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x00000000 0x80000000 0x0 0x80000000> /*, + <0x00000008 0x80000000 0x1 0x80000000> */; + }; + + /* memory@14000000 { + device_type = "memory"; + reg = <0x00000000 0x14000000 0x0 0x02000000>; + }; */ + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x0 0x2c010000 0 0x1000>, + <0x0 0x2c02f000 0 0x1000>, + <0x0 0x2c04f000 0 0x2000>, + <0x0 0x2c06f000 0 0x2000>; + interrupts = ; + }; + + msi0: msi@30000000 { + compatible = "arm,gic-msi"; + reg = <0x0 0x2c1c0000 0 0x10000 + 0x0 0x2c1d0000 0 0x10000 + 0x0 0x2c1e0000 0 0x10000 + 0x0 0x2c1f0000 0 0x10000>; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = , + , + , + ; + }; + + pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = , + , + , + ; + }; + + psci { + compatible = "arm,psci"; + method = "smc"; + cpu_suspend = <0xC4000001>; + cpu_off = <0x84000002>; + cpu_on = <0xC4000003>; + migrate = <0xC4000005>; + }; + + pci0: pci@30000000 { + compatible = "arm,pcie-xr3"; + device_type = "pci"; + reg = <0 0x7ff30000 0 0x1000 + 0 0x7ff20000 0 0x10000 + 0 0x40000000 0 0x10000000>; + bus-range = <0 255>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x01000000 0x0 0x00000000 0x00 0x5ff00000 0x0 0x00100000 + 0x02000000 0x0 0x00000000 0x40 0x00000000 0x0 0x80000000 + 0x42000000 0x0 0x80000000 0x40 0x80000000 0x0 0x80000000>; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &gic 0 136 4 + 0 0 0 2 &gic 0 137 4 + 0 0 0 3 &gic 0 138 4 + 0 0 0 4 &gic 0 139 4>; + }; + + scpi: scpi@2b1f0000 { + compatible = "arm,scpi-mhu"; + reg = <0x0 0x2b1f0000 0x0 0x10000>, /* MHU registers */ + <0x0 0x2e000000 0x0 0x10000>; /* Payload area */ + interrupts = <0 36 4>, /* low priority interrupt */ + <0 35 4>, /* high priority interrupt */ + <0 37 4>; /* secure channel interrupt */ + #clock-cells = <1>; + clock-output-names = "a57", "a53", "gpu", "hdlcd0", "hdlcd1"; + }; + + hdlcd0_osc: scpi_osc@3 { + compatible = "arm,scpi-osc"; + #clock-cells = <0>; + clocks = <&scpi 3>; + frequency-range = <23000000 210000000>; + clock-output-names = "pxlclk0"; + }; + + hdlcd1_osc: scpi_osc@4 { + compatible = "arm,scpi-osc"; + #clock-cells = <0>; + clocks = <&scpi 4>; + frequency-range = <23000000 210000000>; + clock-output-names = "pxlclk1"; + }; + + soc_uartclk: refclk72738khz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <7273800>; + clock-output-names = "juno:uartclk"; + }; + + soc_refclk24mhz: clk24mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "juno:clk24mhz"; + }; + + mb_eth25mhz: clk25mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "ethclk25mhz"; + }; + + soc_usb48mhz: clk48mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <48000000>; + clock-output-names = "clk48mhz"; + }; + + soc_smc50mhz: clk50mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <50000000>; + clock-output-names = "smc_clk"; + }; + + soc_refclk100mhz: refclk100mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <100000000>; + clock-output-names = "apb_pclk"; + }; + + soc_faxiclk: refclk533mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <533000000>; + clock-output-names = "faxi_clk"; + }; + + soc_fixed_3v3: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "3V3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + memory-controller@7ffd0000 { + compatible = "arm,pl354", "arm,primecell"; + reg = <0 0x7ffd0000 0 0x1000>; + interrupts = <0 86 4>, + <0 87 4>; + clocks = <&soc_smc50mhz>; + clock-names = "apb_pclk"; + chip5-memwidth = <16>; + }; + + dma0: dma@0x7ff00000 { + compatible = "arm,pl330", "arm,primecell"; + reg = <0x0 0x7ff00000 0 0x1000>; + interrupts = <0 95 4>, + <0 88 4>, + <0 89 4>, + <0 90 4>, + <0 91 4>, + <0 108 4>, + <0 109 4>, + <0 110 4>, + <0 111 4>; + #dma-cells = <1>; + #dma-channels = <8>; + #dma-requests = <32>; + clocks = <&soc_faxiclk>; + clock-names = "apb_pclk"; + }; + + soc_uart0: uart@7ff80000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x7ff80000 0x0 0x1000>; + interrupts = <0 83 4>; + clocks = <&soc_uartclk>, <&soc_refclk100mhz>; + clock-names = "uartclk", "apb_pclk"; + dmas = <&dma0 1 + &dma0 2>; + dma-names = "rx", "tx"; + }; + + /* this UART is reserved for secure software. + soc_uart1: uart@7ff70000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x7ff70000 0x0 0x1000>; + interrupts = <0 84 4>; + clocks = <&soc_uartclk>, <&soc_refclk100mhz>; + clock-names = "uartclk", "apb_pclk"; + }; */ + + ulpi_phy: phy@0 { + compatible = "phy-ulpi-generic"; + reg = <0x0 0x94 0x0 0x4>; + phy-id = <0>; + }; + + ehci@7ffc0000 { + compatible = "snps,ehci-h20ahb"; + /* compatible = "arm,h20ahb-ehci"; */ + reg = <0x0 0x7ffc0000 0x0 0x10000>; + interrupts = <0 117 4>; + clocks = <&soc_usb48mhz>; + clock-names = "otg"; + phys = <&ulpi_phy>; + }; + + ohci@0x7ffb0000 { + compatible = "arm,ohci", "usb-ohci"; + reg = <0x0 0x7ffb0000 0x0 0x10000>; + interrupts = <0 116 4>; + clocks = <&soc_usb48mhz>; + clock-names = "otg"; + }; + + i2c@0x7ffa0000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "snps,designware-i2c"; + reg = <0x0 0x7ffa0000 0x0 0x1000>; + interrupts = <0 104 4>; + clock-frequency = <400000>; + i2c-sda-hold-time-ns = <500>; + clocks = <&soc_smc50mhz>; + + dvi0: dvi-transmitter@70 { + compatible = "nxp,tda998x"; + reg = <0x70>; + }; + + dvi1: dvi-transmitter@71 { + compatible = "nxp,tda998x"; + reg = <0x71>; + }; + }; + + /* mmci@1c050000 { + compatible = "arm,pl180", "arm,primecell"; + reg = <0x0 0x1c050000 0x0 0x1000>; + interrupts = <0 73 4>, + <0 74 4>; + max-frequency = <12000000>; + vmmc-supply = <&soc_fixed_3v3>; + clocks = <&soc_refclk24mhz>, <&soc_refclk100mhz>; + clock-names = "mclk", "apb_pclk"; + }; */ + + hdlcd@7ff60000 { + compatible = "arm,hdlcd"; + reg = <0 0x7ff60000 0 0x1000>; + interrupts = <0 85 4>; + clocks = <&hdlcd0_osc>; + clock-names = "pxlclk"; + i2c-slave = <&dvi0>; + + /* display-timings { + native-mode = <&timing0>; + timing0: timing@0 { + /* 1024 x 768 framebufer, standard VGA timings * / + clock-frequency = <65000>; + hactive = <1024>; + vactive = <768>; + hfront-porch = <24>; + hback-porch = <160>; + hsync-len = <136>; + vfront-porch = <3>; + vback-porch = <29>; + vsync-len = <6>; + }; + }; */ + }; + + hdlcd@7ff50000 { + compatible = "arm,hdlcd"; + reg = <0 0x7ff50000 0 0x1000>; + interrupts = <0 93 4>; + clocks = <&hdlcd1_osc>; + clock-names = "pxlclk"; + i2c-slave = <&dvi1>; + + display-timings { + native-mode = <&timing1>; + timing1: timing@1 { + /* 1024 x 768 framebufer, standard VGA timings */ + clock-frequency = <65000>; + hactive = <1024>; + vactive = <768>; + hfront-porch = <24>; + hback-porch = <160>; + hsync-len = <136>; + vfront-porch = <3>; + vback-porch = <29>; + vsync-len = <6>; + }; + }; + }; + + smb { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 15>; + interrupt-map = <0 0 0 &gic 0 68 4>, + <0 0 1 &gic 0 69 4>, + <0 0 2 &gic 0 70 4>, + <0 0 3 &gic 0 160 4>, + <0 0 4 &gic 0 161 4>, + <0 0 5 &gic 0 162 4>, + <0 0 6 &gic 0 163 4>, + <0 0 7 &gic 0 164 4>, + <0 0 8 &gic 0 165 4>, + <0 0 9 &gic 0 166 4>, + <0 0 10 &gic 0 167 4>, + <0 0 11 &gic 0 168 4>, + <0 0 12 &gic 0 169 4>; + + motherboard { + model = "V2M-Juno"; + arm,hbi = <0x252>; + arm,vexpress,site = <0>; + arm,v2m-memory-map = "rs1"; + compatible = "arm,vexpress,v2p-p1", "simple-bus"; + #address-cells = <2>; /* SMB chipselect number and offset */ + #size-cells = <1>; + #interrupt-cells = <1>; + ranges; + + usb@5,00000000 { + compatible = "nxp,usb-isp1763"; + reg = <5 0x00000000 0x20000>; + bus-width = <16>; + interrupts = <4>; + }; + + ethernet@2,00000000 { + compatible = "smsc,lan9118", "smsc,lan9115"; + reg = <2 0x00000000 0x10000>; + interrupts = <3>; + phy-mode = "mii"; + reg-io-width = <4>; + smsc,irq-active-high; + smsc,irq-push-pull; + clocks = <&mb_eth25mhz>; + vdd33a-supply = <&soc_fixed_3v3>; /* change this */ + vddvario-supply = <&soc_fixed_3v3>; /* and this */ + }; + + iofpga@3,00000000 { + compatible = "arm,amba-bus", "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 3 0 0x200000>; + + kmi@060000 { + compatible = "arm,pl050", "arm,primecell"; + reg = <0x060000 0x1000>; + interrupts = <8>; + clocks = <&soc_refclk24mhz>, <&soc_smc50mhz>; + clock-names = "KMIREFCLK", "apb_pclk"; + }; + + kmi@070000 { + compatible = "arm,pl050", "arm,primecell"; + reg = <0x070000 0x1000>; + interrupts = <8>; + clocks = <&soc_refclk24mhz>, <&soc_smc50mhz>; + clock-names = "KMIREFCLK", "apb_pclk"; + }; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/rtsm_ve-aemv8a.dts b/arch/arm64/boot/dts/rtsm_ve-aemv8a.dts index 572005ea2217ff..ed5e5a15c55f14 100644 --- a/arch/arm64/boot/dts/rtsm_ve-aemv8a.dts +++ b/arch/arm64/boot/dts/rtsm_ve-aemv8a.dts @@ -11,6 +11,9 @@ /memreserve/ 0x80000000 0x00010000; +/ { +}; + / { model = "RTSM_VE_AEMv8A"; compatible = "arm,rtsm_ve,aemv8a", "arm,vexpress"; @@ -157,3 +160,5 @@ /include/ "rtsm_ve-motherboard.dtsi" }; }; + +/include/ "clcd-panels.dtsi" diff --git a/arch/arm64/boot/dts/rtsm_ve-motherboard.dtsi b/arch/arm64/boot/dts/rtsm_ve-motherboard.dtsi index 2f2ecd217363d4..b683d47035820d 100644 --- a/arch/arm64/boot/dts/rtsm_ve-motherboard.dtsi +++ b/arch/arm64/boot/dts/rtsm_ve-motherboard.dtsi @@ -182,6 +182,9 @@ interrupts = <14>; clocks = <&v2m_oscclk1>, <&v2m_clk24mhz>; clock-names = "clcdclk", "apb_pclk"; + mode = "XVGA"; + use_dma = <0>; + framebuffer = <0x18000000 0x00180000>; }; virtio_block@0130000 { diff --git a/arch/arm64/include/asm/Kbuild b/arch/arm64/include/asm/Kbuild index 71c53ecfcc3ae3..41cde70943fb0c 100644 --- a/arch/arm64/include/asm/Kbuild +++ b/arch/arm64/include/asm/Kbuild @@ -29,6 +29,7 @@ generic-y += pci.h generic-y += poll.h generic-y += posix_types.h generic-y += resource.h +generic-y += rwsem.h generic-y += scatterlist.h generic-y += sections.h generic-y += segment.h diff --git a/arch/arm64/include/asm/arch_timer.h b/arch/arm64/include/asm/arch_timer.h index 9400596a0f3972..bcd992e5738a95 100644 --- a/arch/arm64/include/asm/arch_timer.h +++ b/arch/arm64/include/asm/arch_timer.h @@ -37,19 +37,19 @@ void arch_timer_reg_write_cp15(int access, enum arch_timer_reg reg, u32 val) if (access == ARCH_TIMER_PHYS_ACCESS) { switch (reg) { case ARCH_TIMER_REG_CTRL: - asm volatile("msr cntp_ctl_el0, %0" : : "r" (val)); + asm volatile("msr cntp_ctl_el0, %0" : : "r" ((u64)val)); break; case ARCH_TIMER_REG_TVAL: - asm volatile("msr cntp_tval_el0, %0" : : "r" (val)); + asm volatile("msr cntp_tval_el0, %0" : : "r" ((u64)val)); break; } } else if (access == ARCH_TIMER_VIRT_ACCESS) { switch (reg) { case ARCH_TIMER_REG_CTRL: - asm volatile("msr cntv_ctl_el0, %0" : : "r" (val)); + asm volatile("msr cntv_ctl_el0, %0" : : "r" ((u64)val)); break; case ARCH_TIMER_REG_TVAL: - asm volatile("msr cntv_tval_el0, %0" : : "r" (val)); + asm volatile("msr cntv_tval_el0, %0" : : "r" ((u64)val)); break; } } @@ -60,7 +60,7 @@ void arch_timer_reg_write_cp15(int access, enum arch_timer_reg reg, u32 val) static __always_inline u32 arch_timer_reg_read_cp15(int access, enum arch_timer_reg reg) { - u32 val; + u64 val; if (access == ARCH_TIMER_PHYS_ACCESS) { switch (reg) { @@ -82,7 +82,7 @@ u32 arch_timer_reg_read_cp15(int access, enum arch_timer_reg reg) } } - return val; + return (u32)val; } static inline u32 arch_timer_get_cntfrq(void) diff --git a/arch/arm64/include/asm/barrier.h b/arch/arm64/include/asm/barrier.h index 409ca370cfe2dd..66eb7648043bc9 100644 --- a/arch/arm64/include/asm/barrier.h +++ b/arch/arm64/include/asm/barrier.h @@ -25,6 +25,7 @@ #define wfi() asm volatile("wfi" : : : "memory") #define isb() asm volatile("isb" : : : "memory") +#define dmb(opt) asm volatile("dmb sy" : : : "memory") #define dsb(opt) asm volatile("dsb sy" : : : "memory") #define mb() dsb() diff --git a/arch/arm64/include/asm/cacheflush.h b/arch/arm64/include/asm/cacheflush.h index 889324981aa4f5..4c60e64a801c5c 100644 --- a/arch/arm64/include/asm/cacheflush.h +++ b/arch/arm64/include/asm/cacheflush.h @@ -84,6 +84,13 @@ static inline void flush_cache_page(struct vm_area_struct *vma, { } +/* + * Cache maintenance functions used by the DMA API. No to be used directly. + */ +extern void __dma_map_area(const void *, size_t, int); +extern void __dma_unmap_area(const void *, size_t, int); +extern void __dma_flush_range(const void *, const void *); + /* * Copy user data from/to a page which is mapped into a different * processes address space. Really, we want to allow our "user diff --git a/arch/arm64/include/asm/cmpxchg.h b/arch/arm64/include/asm/cmpxchg.h index 57c0fa7bf71141..b331cf8ec812fb 100644 --- a/arch/arm64/include/asm/cmpxchg.h +++ b/arch/arm64/include/asm/cmpxchg.h @@ -64,6 +64,7 @@ static inline unsigned long __xchg(unsigned long x, volatile void *ptr, int size : "memory"); break; default: + ret = 0; BUILD_BUG(); } @@ -141,6 +142,7 @@ static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old, break; default: + res = 0; BUILD_BUG(); } diff --git a/arch/arm64/include/asm/compat.h b/arch/arm64/include/asm/compat.h index fda2704b3f9f9a..e71f81fe127a50 100644 --- a/arch/arm64/include/asm/compat.h +++ b/arch/arm64/include/asm/compat.h @@ -228,7 +228,7 @@ static inline compat_uptr_t ptr_to_compat(void __user *uptr) return (u32)(unsigned long)uptr; } -#define compat_user_stack_pointer() (current_pt_regs()->compat_sp) +#define compat_user_stack_pointer() (user_stack_pointer(current_pt_regs())) static inline void __user *arch_compat_alloc_user_space(long len) { diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h new file mode 100644 index 00000000000000..cd4ac0516488fe --- /dev/null +++ b/arch/arm64/include/asm/cpufeature.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 Linaro 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. + */ + +#ifndef __ASM_CPUFEATURE_H +#define __ASM_CPUFEATURE_H + +#include + +/* + * In the arm64 world (as in the ARM world), elf_hwcap is used both internally + * in the kernel and for user space to keep track of which optional features + * are supported by the current system. So let's map feature 'x' to HWCAP_x. + * Note that HWCAP_x constants are bit fields so we need to take the log. + */ + +#define MAX_CPU_FEATURES (8 * sizeof(elf_hwcap)) +#define cpu_feature(x) ilog2(HWCAP_ ## x) + +static inline bool cpu_have_feature(unsigned int num) +{ + return elf_hwcap & (1UL << num); +} + +#endif diff --git a/arch/arm64/include/asm/debug-monitors.h b/arch/arm64/include/asm/debug-monitors.h index 62314791570cbd..6e9b5b36921cce 100644 --- a/arch/arm64/include/asm/debug-monitors.h +++ b/arch/arm64/include/asm/debug-monitors.h @@ -26,6 +26,53 @@ #define DBG_ESR_EVT_HWWP 0x2 #define DBG_ESR_EVT_BRK 0x6 +/* + * Break point instruction encoding + */ +#define BREAK_INSTR_SIZE 4 + +/* + * ESR values expected for dynamic and compile time BRK instruction + */ +#define DBG_ESR_VAL_BRK(x) (0xf2000000 | ((x) & 0xfffff)) + +/* + * #imm16 values used for BRK instruction generation + * Allowed values for kgbd are 0x400 - 0x7ff + * 0x400: for dynamic BRK instruction + * 0x401: for compile time BRK instruction + */ +#define KGDB_DYN_DGB_BRK_IMM 0x400 +#define KDBG_COMPILED_DBG_BRK_IMM 0x401 + +/* + * BRK instruction encoding + * The #imm16 value should be placed at bits[20:5] within BRK ins + */ +#define AARCH64_BREAK_MON 0xd4200000 + +/* + * Extract byte from BRK instruction + */ +#define KGDB_DYN_DGB_BRK_INS_BYTE(x) \ + ((((AARCH64_BREAK_MON) & 0xffe0001f) >> (x * 8)) & 0xff) + +/* + * Extract byte from BRK #imm16 + */ +#define KGBD_DYN_DGB_BRK_IMM_BYTE(x) \ + (((((KGDB_DYN_DGB_BRK_IMM) & 0xffff) << 5) >> (x * 8)) & 0xff) + +#define KGDB_DYN_DGB_BRK_BYTE(x) \ + (KGDB_DYN_DGB_BRK_INS_BYTE(x) | KGBD_DYN_DGB_BRK_IMM_BYTE(x)) + +#define KGDB_DYN_BRK_INS_BYTE0 KGDB_DYN_DGB_BRK_BYTE(0) +#define KGDB_DYN_BRK_INS_BYTE1 KGDB_DYN_DGB_BRK_BYTE(1) +#define KGDB_DYN_BRK_INS_BYTE2 KGDB_DYN_DGB_BRK_BYTE(2) +#define KGDB_DYN_BRK_INS_BYTE3 KGDB_DYN_DGB_BRK_BYTE(3) + +#define CACHE_FLUSH_IS_SAFE 1 + enum debug_el { DBG_ACTIVE_EL0 = 0, DBG_ACTIVE_EL1, @@ -43,23 +90,6 @@ enum debug_el { #ifndef __ASSEMBLY__ struct task_struct; -#define local_dbg_save(flags) \ - do { \ - typecheck(unsigned long, flags); \ - asm volatile( \ - "mrs %0, daif // local_dbg_save\n" \ - "msr daifset, #8" \ - : "=r" (flags) : : "memory"); \ - } while (0) - -#define local_dbg_restore(flags) \ - do { \ - typecheck(unsigned long, flags); \ - asm volatile( \ - "msr daif, %0 // local_dbg_restore\n" \ - : : "r" (flags) : "memory"); \ - } while (0) - #define DBG_ARCH_ID_RESERVED 0 /* In case of ptrace ABI updates. */ #define DBG_HOOK_HANDLED 0 diff --git a/arch/arm64/include/asm/dma-mapping.h b/arch/arm64/include/asm/dma-mapping.h index fd0c0c0e447a65..3a4572ec327326 100644 --- a/arch/arm64/include/asm/dma-mapping.h +++ b/arch/arm64/include/asm/dma-mapping.h @@ -30,6 +30,8 @@ #define DMA_ERROR_CODE (~(dma_addr_t)0) extern struct dma_map_ops *dma_ops; +extern struct dma_map_ops coherent_swiotlb_dma_ops; +extern struct dma_map_ops noncoherent_swiotlb_dma_ops; static inline struct dma_map_ops *__generic_dma_ops(struct device *dev) { @@ -47,6 +49,11 @@ static inline struct dma_map_ops *get_dma_ops(struct device *dev) return __generic_dma_ops(dev); } +static inline void set_dma_ops(struct device *dev, struct dma_map_ops *ops) +{ + dev->archdata.dma_ops = ops; +} + #include static inline dma_addr_t phys_to_dma(struct device *dev, phys_addr_t paddr) diff --git a/arch/arm64/include/asm/futex.h b/arch/arm64/include/asm/futex.h index 5f750dc96e0fd6..b6f97929056374 100644 --- a/arch/arm64/include/asm/futex.h +++ b/arch/arm64/include/asm/futex.h @@ -108,7 +108,7 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, int ret = 0; u32 val, tmp; - if (!access_ok(VERIFY_WRITE, uaddr, sizeof(u32))) + if (uaddr == 0 || !access_ok(VERIFY_WRITE, uaddr, sizeof(u32))) return -EFAULT; asm volatile("// futex_atomic_cmpxchg_inatomic\n" diff --git a/arch/arm64/include/asm/hwcap.h b/arch/arm64/include/asm/hwcap.h index 6cddbb0c9f5459..024c46183c3cc4 100644 --- a/arch/arm64/include/asm/hwcap.h +++ b/arch/arm64/include/asm/hwcap.h @@ -32,6 +32,12 @@ #define COMPAT_HWCAP_IDIV (COMPAT_HWCAP_IDIVA|COMPAT_HWCAP_IDIVT) #define COMPAT_HWCAP_EVTSTRM (1 << 21) +#define COMPAT_HWCAP2_AES (1 << 0) +#define COMPAT_HWCAP2_PMULL (1 << 1) +#define COMPAT_HWCAP2_SHA1 (1 << 2) +#define COMPAT_HWCAP2_SHA2 (1 << 3) +#define COMPAT_HWCAP2_CRC32 (1 << 4) + #ifndef __ASSEMBLY__ /* * This yields a mask that user programs can use to figure out what @@ -41,7 +47,8 @@ #ifdef CONFIG_COMPAT #define COMPAT_ELF_HWCAP (compat_elf_hwcap) -extern unsigned int compat_elf_hwcap; +#define COMPAT_ELF_HWCAP2 (compat_elf_hwcap2) +extern unsigned int compat_elf_hwcap, compat_elf_hwcap2; #endif extern unsigned long elf_hwcap; diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h index 4cc813eddacbeb..7846a6bb08334d 100644 --- a/arch/arm64/include/asm/io.h +++ b/arch/arm64/include/asm/io.h @@ -121,7 +121,7 @@ static inline u64 __raw_readq(const volatile void __iomem *addr) * I/O port access primitives. */ #define IO_SPACE_LIMIT 0xffff -#define PCI_IOBASE ((void __iomem *)(MODULES_VADDR - SZ_2M)) +#define PCI_IOBASE ((void __iomem *)(MODULES_VADDR - SZ_32M)) static inline u8 inb(unsigned long addr) { diff --git a/arch/arm64/include/asm/irqflags.h b/arch/arm64/include/asm/irqflags.h index b2fcfbc51ecc4b..11cc941bd107b5 100644 --- a/arch/arm64/include/asm/irqflags.h +++ b/arch/arm64/include/asm/irqflags.h @@ -90,5 +90,28 @@ static inline int arch_irqs_disabled_flags(unsigned long flags) return flags & PSR_I_BIT; } +/* + * save and restore debug state + */ +#define local_dbg_save(flags) \ + do { \ + typecheck(unsigned long, flags); \ + asm volatile( \ + "mrs %0, daif // local_dbg_save\n" \ + "msr daifset, #8" \ + : "=r" (flags) : : "memory"); \ + } while (0) + +#define local_dbg_restore(flags) \ + do { \ + typecheck(unsigned long, flags); \ + asm volatile( \ + "msr daif, %0 // local_dbg_restore\n" \ + : : "r" (flags) : "memory"); \ + } while (0) + +#define local_dbg_enable() asm("msr daifclr, #8" : : : "memory") +#define local_dbg_disable() asm("msr daifset, #8" : : : "memory") + #endif #endif diff --git a/arch/arm64/include/asm/kgdb.h b/arch/arm64/include/asm/kgdb.h new file mode 100644 index 00000000000000..3c8aafc1082f9e --- /dev/null +++ b/arch/arm64/include/asm/kgdb.h @@ -0,0 +1,84 @@ +/* + * AArch64 KGDB support + * + * Based on arch/arm/include/kgdb.h + * + * Copyright (C) 2013 Cavium Inc. + * Author: Vijaya Kumar K + * + * 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 . + */ + +#ifndef __ARM_KGDB_H +#define __ARM_KGDB_H + +#include +#include + +#ifndef __ASSEMBLY__ + +static inline void arch_kgdb_breakpoint(void) +{ + asm ("brk %0" : : "I" (KDBG_COMPILED_DBG_BRK_IMM)); +} + +extern void kgdb_handle_bus_error(void); +extern int kgdb_fault_expected; + +#endif /* !__ASSEMBLY__ */ + +/* + * gdb is expecting the following registers layout. + * + * General purpose regs: + * r0-r30: 64 bit + * sp,pc : 64 bit + * pstate : 64 bit + * Total: 34 + * FPU regs: + * f0-f31: 128 bit + * Total: 32 + * Extra regs + * fpsr & fpcr: 32 bit + * Total: 2 + * + */ + +#define _GP_REGS 34 +#define _FP_REGS 32 +#define _EXTRA_REGS 2 +/* + * general purpose registers size in bytes. + * pstate is only 4 bytes. subtract 4 bytes + */ +#define GP_REG_BYTES (_GP_REGS * 8) +#define DBG_MAX_REG_NUM (_GP_REGS + _FP_REGS + _EXTRA_REGS) + +/* + * Size of I/O buffer for gdb packet. + * considering to hold all register contents, size is set + */ + +#define BUFMAX 2048 + +/* + * Number of bytes required for gdb_regs buffer. + * _GP_REGS: 8 bytes, _FP_REGS: 16 bytes and _EXTRA_REGS: 4 bytes each + * GDB fails to connect for size beyond this with error + * "'g' packet reply is too long" + */ + +#define NUMREGBYTES ((_GP_REGS * 8) + (_FP_REGS * 16) + \ + (_EXTRA_REGS * 4)) + +#endif /* __ASM_KGDB_H */ diff --git a/arch/arm64/include/asm/kvm_arm.h b/arch/arm64/include/asm/kvm_arm.h index 0eb39865537839..21ef48d32ff271 100644 --- a/arch/arm64/include/asm/kvm_arm.h +++ b/arch/arm64/include/asm/kvm_arm.h @@ -106,7 +106,6 @@ /* VTCR_EL2 Registers bits */ #define VTCR_EL2_PS_MASK (7 << 16) -#define VTCR_EL2_PS_40B (2 << 16) #define VTCR_EL2_TG0_MASK (1 << 14) #define VTCR_EL2_TG0_4K (0 << 14) #define VTCR_EL2_TG0_64K (1 << 14) @@ -129,10 +128,9 @@ * 64kB pages (TG0 = 1) * 2 level page tables (SL = 1) */ -#define VTCR_EL2_FLAGS (VTCR_EL2_PS_40B | VTCR_EL2_TG0_64K | \ - VTCR_EL2_SH0_INNER | VTCR_EL2_ORGN0_WBWA | \ - VTCR_EL2_IRGN0_WBWA | VTCR_EL2_SL0_LVL1 | \ - VTCR_EL2_T0SZ_40B) +#define VTCR_EL2_FLAGS (VTCR_EL2_TG0_64K | VTCR_EL2_SH0_INNER | \ + VTCR_EL2_ORGN0_WBWA | VTCR_EL2_IRGN0_WBWA | \ + VTCR_EL2_SL0_LVL1 | VTCR_EL2_T0SZ_40B) #define VTTBR_X (38 - VTCR_EL2_T0SZ_40B) #else /* @@ -142,10 +140,9 @@ * 4kB pages (TG0 = 0) * 3 level page tables (SL = 1) */ -#define VTCR_EL2_FLAGS (VTCR_EL2_PS_40B | VTCR_EL2_TG0_4K | \ - VTCR_EL2_SH0_INNER | VTCR_EL2_ORGN0_WBWA | \ - VTCR_EL2_IRGN0_WBWA | VTCR_EL2_SL0_LVL1 | \ - VTCR_EL2_T0SZ_40B) +#define VTCR_EL2_FLAGS (VTCR_EL2_TG0_4K | VTCR_EL2_SH0_INNER | \ + VTCR_EL2_ORGN0_WBWA | VTCR_EL2_IRGN0_WBWA | \ + VTCR_EL2_SL0_LVL1 | VTCR_EL2_T0SZ_40B) #define VTTBR_X (37 - VTCR_EL2_T0SZ_40B) #endif diff --git a/arch/arm64/include/asm/pgtable-hwdef.h b/arch/arm64/include/asm/pgtable-hwdef.h index b1d2e26c3c883e..f7af66b54cb216 100644 --- a/arch/arm64/include/asm/pgtable-hwdef.h +++ b/arch/arm64/include/asm/pgtable-hwdef.h @@ -100,9 +100,9 @@ #define PTE_HYP PTE_USER /* - * 40-bit physical address supported. + * Highest possible physical address supported. */ -#define PHYS_MASK_SHIFT (40) +#define PHYS_MASK_SHIFT (48) #define PHYS_MASK ((UL(1) << PHYS_MASK_SHIFT) - 1) /* @@ -122,7 +122,6 @@ #define TCR_SHARED ((UL(3) << 12) | (UL(3) << 28)) #define TCR_TG0_64K (UL(1) << 14) #define TCR_TG1_64K (UL(1) << 30) -#define TCR_IPS_40BIT (UL(2) << 32) #define TCR_ASID16 (UL(1) << 36) #define TCR_TBI0 (UL(1) << 37) diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h index aa3917c8b62318..90c811f05a2e32 100644 --- a/arch/arm64/include/asm/pgtable.h +++ b/arch/arm64/include/asm/pgtable.h @@ -199,7 +199,7 @@ static inline void set_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep, pte_t pte) { if (pte_valid_user(pte)) { - if (pte_exec(pte)) + if (!pte_special(pte) && pte_exec(pte)) __sync_icache_dcache(pte, addr); if (pte_dirty(pte) && pte_write(pte)) pte_val(pte) &= ~PTE_RDONLY; @@ -227,36 +227,36 @@ static inline void set_pte_at(struct mm_struct *mm, unsigned long addr, #define __HAVE_ARCH_PTE_SPECIAL -/* - * Software PMD bits for THP - */ +static inline pte_t pmd_pte(pmd_t pmd) +{ + return __pte(pmd_val(pmd)); +} -#define PMD_SECT_DIRTY (_AT(pmdval_t, 1) << 55) -#define PMD_SECT_SPLITTING (_AT(pmdval_t, 1) << 57) +static inline pmd_t pte_pmd(pte_t pte) +{ + return __pmd(pte_val(pte)); +} /* * THP definitions. */ -#define pmd_young(pmd) (pmd_val(pmd) & PMD_SECT_AF) - -#define __HAVE_ARCH_PMD_WRITE -#define pmd_write(pmd) (!(pmd_val(pmd) & PMD_SECT_RDONLY)) #ifdef CONFIG_TRANSPARENT_HUGEPAGE #define pmd_trans_huge(pmd) (pmd_val(pmd) && !(pmd_val(pmd) & PMD_TABLE_BIT)) -#define pmd_trans_splitting(pmd) (pmd_val(pmd) & PMD_SECT_SPLITTING) +#define pmd_trans_splitting(pmd) pte_special(pmd_pte(pmd)) #endif -#define PMD_BIT_FUNC(fn,op) \ -static inline pmd_t pmd_##fn(pmd_t pmd) { pmd_val(pmd) op; return pmd; } +#define pmd_young(pmd) pte_young(pmd_pte(pmd)) +#define pmd_wrprotect(pmd) pte_pmd(pte_wrprotect(pmd_pte(pmd))) +#define pmd_mksplitting(pmd) pte_pmd(pte_mkspecial(pmd_pte(pmd))) +#define pmd_mkold(pmd) pte_pmd(pte_mkold(pmd_pte(pmd))) +#define pmd_mkwrite(pmd) pte_pmd(pte_mkwrite(pmd_pte(pmd))) +#define pmd_mkdirty(pmd) pte_pmd(pte_mkdirty(pmd_pte(pmd))) +#define pmd_mkyoung(pmd) pte_pmd(pte_mkyoung(pmd_pte(pmd))) +#define pmd_mknotpresent(pmd) (__pmd(pmd_val(pmd) &= ~PMD_TYPE_MASK)) -PMD_BIT_FUNC(wrprotect, |= PMD_SECT_RDONLY); -PMD_BIT_FUNC(mkold, &= ~PMD_SECT_AF); -PMD_BIT_FUNC(mksplitting, |= PMD_SECT_SPLITTING); -PMD_BIT_FUNC(mkwrite, &= ~PMD_SECT_RDONLY); -PMD_BIT_FUNC(mkdirty, |= PMD_SECT_DIRTY); -PMD_BIT_FUNC(mkyoung, |= PMD_SECT_AF); -PMD_BIT_FUNC(mknotpresent, &= ~PMD_TYPE_MASK); +#define __HAVE_ARCH_PMD_WRITE +#define pmd_write(pmd) pte_write(pmd_pte(pmd)) #define pmd_mkhuge(pmd) (__pmd(pmd_val(pmd) & ~PMD_TABLE_BIT)) @@ -266,15 +266,6 @@ PMD_BIT_FUNC(mknotpresent, &= ~PMD_TYPE_MASK); #define pmd_page(pmd) pfn_to_page(__phys_to_pfn(pmd_val(pmd) & PHYS_MASK)) -static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) -{ - const pmdval_t mask = PMD_SECT_USER | PMD_SECT_PXN | PMD_SECT_UXN | - PMD_SECT_RDONLY | PMD_SECT_PROT_NONE | - PMD_SECT_VALID; - pmd_val(pmd) = (pmd_val(pmd) & ~mask) | (pgprot_val(newprot) & mask); - return pmd; -} - #define set_pmd_at(mm, addr, pmdp, pmd) set_pmd(pmdp, pmd) static inline int has_transparent_hugepage(void) @@ -286,11 +277,9 @@ static inline int has_transparent_hugepage(void) * Mark the prot value as uncacheable and unbufferable. */ #define pgprot_noncached(prot) \ - __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRnE)) + __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRnE) | PTE_PXN | PTE_UXN) #define pgprot_writecombine(prot) \ - __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC)) -#define pgprot_dmacoherent(prot) \ - __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC)) + __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN) #define __HAVE_PHYS_MEM_ACCESS_PROT struct file; extern pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn, @@ -383,6 +372,11 @@ static inline pte_t pte_modify(pte_t pte, pgprot_t newprot) return pte; } +static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) +{ + return pte_pmd(pte_modify(pmd_pte(pmd), newprot)); +} + extern pgd_t swapper_pg_dir[PTRS_PER_PGD]; extern pgd_t idmap_pg_dir[PTRS_PER_PGD]; diff --git a/arch/arm64/include/asm/psci.h b/arch/arm64/include/asm/psci.h index e5312ea0ec1a59..d15ab8b4633608 100644 --- a/arch/arm64/include/asm/psci.h +++ b/arch/arm64/include/asm/psci.h @@ -14,6 +14,6 @@ #ifndef __ASM_PSCI_H #define __ASM_PSCI_H -int psci_init(void); +void psci_init(void); #endif /* __ASM_PSCI_H */ diff --git a/arch/arm64/include/asm/ptrace.h b/arch/arm64/include/asm/ptrace.h index 0e7fa49637359a..c7ba261dd4b37b 100644 --- a/arch/arm64/include/asm/ptrace.h +++ b/arch/arm64/include/asm/ptrace.h @@ -68,6 +68,7 @@ /* Architecturally defined mapping between AArch32 and AArch64 registers */ #define compat_usr(x) regs[(x)] +#define compat_fp regs[11] #define compat_sp regs[13] #define compat_lr regs[14] #define compat_sp_hyp regs[15] @@ -132,7 +133,7 @@ struct pt_regs { (!((regs)->pstate & PSR_F_BIT)) #define user_stack_pointer(regs) \ - ((regs)->sp) + (!compat_user_mode(regs)) ? ((regs)->sp) : ((regs)->compat_sp) /* * Are the current registers suitable for user mode? (used to maintain @@ -164,7 +165,7 @@ static inline int valid_user_regs(struct user_pt_regs *regs) return 0; } -#define instruction_pointer(regs) (regs)->pc +#define instruction_pointer(regs) ((unsigned long)(regs)->pc) #ifdef CONFIG_SMP extern unsigned long profile_pc(struct pt_regs *regs); diff --git a/arch/arm64/include/asm/string.h b/arch/arm64/include/asm/string.h index 3ee8b303d9a975..64d2d4884a9db1 100644 --- a/arch/arm64/include/asm/string.h +++ b/arch/arm64/include/asm/string.h @@ -22,6 +22,18 @@ extern char *strrchr(const char *, int c); #define __HAVE_ARCH_STRCHR extern char *strchr(const char *, int c); +#define __HAVE_ARCH_STRCMP +extern int strcmp(const char *, const char *); + +#define __HAVE_ARCH_STRNCMP +extern int strncmp(const char *, const char *, __kernel_size_t); + +#define __HAVE_ARCH_STRLEN +extern __kernel_size_t strlen(const char *); + +#define __HAVE_ARCH_STRNLEN +extern __kernel_size_t strnlen(const char *, __kernel_size_t); + #define __HAVE_ARCH_MEMCPY extern void *memcpy(void *, const void *, __kernel_size_t); @@ -34,4 +46,7 @@ extern void *memchr(const void *, int, __kernel_size_t); #define __HAVE_ARCH_MEMSET extern void *memset(void *, int, __kernel_size_t); +#define __HAVE_ARCH_MEMCMP +extern int memcmp(const void *, const void *, size_t); + #endif diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h index 720e70b66ffdcf..71c77636c2cafb 100644 --- a/arch/arm64/include/asm/thread_info.h +++ b/arch/arm64/include/asm/thread_info.h @@ -68,6 +68,15 @@ struct thread_info { #define init_thread_info (init_thread_union.thread_info) #define init_stack (init_thread_union.stack) +/* + * how to get the current stack pointer from C + */ +#define current_stack_pointer ({ \ + unsigned long current_sp; \ + asm ("mov %0, sp" : "=r" (current_sp)); \ + current_sp; \ +}) + /* * how to get the thread information struct from C */ @@ -75,8 +84,9 @@ static inline struct thread_info *current_thread_info(void) __attribute_const__; static inline struct thread_info *current_thread_info(void) { - register unsigned long sp asm ("sp"); - return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); + return (struct thread_info *) + ((u64)(__builtin_frame_address(0)) + & ~(THREAD_SIZE - 1)); } #define thread_saved_pc(tsk) \ diff --git a/arch/arm64/include/asm/tlb.h b/arch/arm64/include/asm/tlb.h index 717031a762c279..72cadf52ca807f 100644 --- a/arch/arm64/include/asm/tlb.h +++ b/arch/arm64/include/asm/tlb.h @@ -19,115 +19,44 @@ #ifndef __ASM_TLB_H #define __ASM_TLB_H -#include -#include -#include -#include - -#define MMU_GATHER_BUNDLE 8 - -/* - * TLB handling. This allows us to remove pages from the page - * tables, and efficiently handle the TLB issues. - */ -struct mmu_gather { - struct mm_struct *mm; - unsigned int fullmm; - struct vm_area_struct *vma; - unsigned long start, end; - unsigned long range_start; - unsigned long range_end; - unsigned int nr; - unsigned int max; - struct page **pages; - struct page *local[MMU_GATHER_BUNDLE]; -}; +#include /* - * This is unnecessarily complex. There's three ways the TLB shootdown - * code is used: + * There's three ways the TLB shootdown code is used: * 1. Unmapping a range of vmas. See zap_page_range(), unmap_region(). * tlb->fullmm = 0, and tlb_start_vma/tlb_end_vma will be called. - * tlb->vma will be non-NULL. * 2. Unmapping all vmas. See exit_mmap(). * tlb->fullmm = 1, and tlb_start_vma/tlb_end_vma will be called. - * tlb->vma will be non-NULL. Additionally, page tables will be freed. + * Page tables will be freed. * 3. Unmapping argument pages. See shift_arg_pages(). * tlb->fullmm = 0, but tlb_start_vma/tlb_end_vma will not be called. - * tlb->vma will be NULL. */ static inline void tlb_flush(struct mmu_gather *tlb) { - if (tlb->fullmm || !tlb->vma) + if (tlb->fullmm) { flush_tlb_mm(tlb->mm); - else if (tlb->range_end > 0) { - flush_tlb_range(tlb->vma, tlb->range_start, tlb->range_end); - tlb->range_start = TASK_SIZE; - tlb->range_end = 0; + } else if (tlb->end > 0) { + struct vm_area_struct vma = { .vm_mm = tlb->mm, }; + flush_tlb_range(&vma, tlb->start, tlb->end); + tlb->start = TASK_SIZE; + tlb->end = 0; } } static inline void tlb_add_flush(struct mmu_gather *tlb, unsigned long addr) { if (!tlb->fullmm) { - if (addr < tlb->range_start) - tlb->range_start = addr; - if (addr + PAGE_SIZE > tlb->range_end) - tlb->range_end = addr + PAGE_SIZE; - } -} - -static inline void __tlb_alloc_page(struct mmu_gather *tlb) -{ - unsigned long addr = __get_free_pages(GFP_NOWAIT | __GFP_NOWARN, 0); - - if (addr) { - tlb->pages = (void *)addr; - tlb->max = PAGE_SIZE / sizeof(struct page *); + tlb->start = min(tlb->start, addr); + tlb->end = max(tlb->end, addr + PAGE_SIZE); } } -static inline void tlb_flush_mmu(struct mmu_gather *tlb) -{ - tlb_flush(tlb); - free_pages_and_swap_cache(tlb->pages, tlb->nr); - tlb->nr = 0; - if (tlb->pages == tlb->local) - __tlb_alloc_page(tlb); -} - -static inline void -tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, unsigned long start, unsigned long end) -{ - tlb->mm = mm; - tlb->fullmm = !(start | (end+1)); - tlb->start = start; - tlb->end = end; - tlb->vma = NULL; - tlb->max = ARRAY_SIZE(tlb->local); - tlb->pages = tlb->local; - tlb->nr = 0; - __tlb_alloc_page(tlb); -} - -static inline void -tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end) -{ - tlb_flush_mmu(tlb); - - /* keep the page table cache within bounds */ - check_pgt_cache(); - - if (tlb->pages != tlb->local) - free_pages((unsigned long)tlb->pages, 0); -} - /* * Memorize the range for the TLB flush. */ -static inline void -tlb_remove_tlb_entry(struct mmu_gather *tlb, pte_t *ptep, unsigned long addr) +static inline void __tlb_remove_tlb_entry(struct mmu_gather *tlb, pte_t *ptep, + unsigned long addr) { tlb_add_flush(tlb, addr); } @@ -137,38 +66,24 @@ tlb_remove_tlb_entry(struct mmu_gather *tlb, pte_t *ptep, unsigned long addr) * case where we're doing a full MM flush. When we're doing a munmap, * the vmas are adjusted to only cover the region to be torn down. */ -static inline void -tlb_start_vma(struct mmu_gather *tlb, struct vm_area_struct *vma) +static inline void tlb_start_vma(struct mmu_gather *tlb, + struct vm_area_struct *vma) { if (!tlb->fullmm) { - tlb->vma = vma; - tlb->range_start = TASK_SIZE; - tlb->range_end = 0; + tlb->start = TASK_SIZE; + tlb->end = 0; } } -static inline void -tlb_end_vma(struct mmu_gather *tlb, struct vm_area_struct *vma) +static inline void tlb_end_vma(struct mmu_gather *tlb, + struct vm_area_struct *vma) { if (!tlb->fullmm) tlb_flush(tlb); } -static inline int __tlb_remove_page(struct mmu_gather *tlb, struct page *page) -{ - tlb->pages[tlb->nr++] = page; - VM_BUG_ON(tlb->nr > tlb->max); - return tlb->max - tlb->nr; -} - -static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page) -{ - if (!__tlb_remove_page(tlb, page)) - tlb_flush_mmu(tlb); -} - static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pte, - unsigned long addr) + unsigned long addr) { pgtable_page_dtor(pte); tlb_add_flush(tlb, addr); @@ -184,16 +99,5 @@ static inline void __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmdp, } #endif -#define pte_free_tlb(tlb, ptep, addr) __pte_free_tlb(tlb, ptep, addr) -#define pmd_free_tlb(tlb, pmdp, addr) __pmd_free_tlb(tlb, pmdp, addr) -#define pud_free_tlb(tlb, pudp, addr) pud_free((tlb)->mm, pudp) - -#define tlb_migrate_finish(mm) do { } while (0) - -static inline void -tlb_remove_pmd_tlb_entry(struct mmu_gather *tlb, pmd_t *pmdp, unsigned long addr) -{ - tlb_add_flush(tlb, addr); -} #endif diff --git a/arch/arm64/include/asm/topology.h b/arch/arm64/include/asm/topology.h new file mode 100644 index 00000000000000..0172e6d76bf381 --- /dev/null +++ b/arch/arm64/include/asm/topology.h @@ -0,0 +1,39 @@ +#ifndef __ASM_TOPOLOGY_H +#define __ASM_TOPOLOGY_H + +#ifdef CONFIG_SMP + +#include + +struct cpu_topology { + int thread_id; + int core_id; + int cluster_id; + cpumask_t thread_sibling; + cpumask_t core_sibling; +}; + +extern struct cpu_topology cpu_topology[NR_CPUS]; + +#define topology_physical_package_id(cpu) (cpu_topology[cpu].cluster_id) +#define topology_core_id(cpu) (cpu_topology[cpu].core_id) +#define topology_core_cpumask(cpu) (&cpu_topology[cpu].core_sibling) +#define topology_thread_cpumask(cpu) (&cpu_topology[cpu].thread_sibling) + +#define mc_capable() (cpu_topology[0].cluster_id != -1) +#define smt_capable() (cpu_topology[0].thread_id != -1) + +void init_cpu_topology(void); +void store_cpu_topology(unsigned int cpuid); +const struct cpumask *cpu_coregroup_mask(int cpu); + +#else + +static inline void init_cpu_topology(void) { } +static inline void store_cpu_topology(unsigned int cpuid) { } + +#endif + +#include + +#endif /* _ASM_ARM_TOPOLOGY_H */ diff --git a/arch/arm64/include/asm/uaccess.h b/arch/arm64/include/asm/uaccess.h index 6c0f684aca81ce..104719b288fca9 100644 --- a/arch/arm64/include/asm/uaccess.h +++ b/arch/arm64/include/asm/uaccess.h @@ -83,7 +83,7 @@ static inline void set_fs(mm_segment_t fs) * Returns 1 if the range is valid, 0 otherwise. * * This is equivalent to the following test: - * (u65)addr + (u65)size < (u65)current->addr_limit + * (u65)addr + (u65)size <= current->addr_limit * * This needs 65-bit arithmetic. */ @@ -91,9 +91,9 @@ static inline void set_fs(mm_segment_t fs) ({ \ unsigned long flag, roksum; \ __chk_user_ptr(addr); \ - asm("adds %1, %1, %3; ccmp %1, %4, #2, cc; cset %0, cc" \ + asm("adds %1, %1, %3; ccmp %1, %4, #2, cc; cset %0, ls" \ : "=&r" (flag), "=&r" (roksum) \ - : "1" (addr), "Ir" (size), \ + : "1" (addr), "r" ((u64)size), \ "r" (current_thread_info()->addr_limit) \ : "cc"); \ flag; \ diff --git a/arch/arm64/include/uapi/asm/Kbuild b/arch/arm64/include/uapi/asm/Kbuild index e4b78bdca19e81..942376d37d220f 100644 --- a/arch/arm64/include/uapi/asm/Kbuild +++ b/arch/arm64/include/uapi/asm/Kbuild @@ -9,6 +9,7 @@ header-y += byteorder.h header-y += fcntl.h header-y += hwcap.h header-y += kvm_para.h +header-y += perf_regs.h header-y += param.h header-y += ptrace.h header-y += setup.h diff --git a/arch/arm64/include/uapi/asm/perf_regs.h b/arch/arm64/include/uapi/asm/perf_regs.h new file mode 100644 index 00000000000000..172b8317ee49f8 --- /dev/null +++ b/arch/arm64/include/uapi/asm/perf_regs.h @@ -0,0 +1,40 @@ +#ifndef _ASM_ARM64_PERF_REGS_H +#define _ASM_ARM64_PERF_REGS_H + +enum perf_event_arm_regs { + PERF_REG_ARM64_X0, + PERF_REG_ARM64_X1, + PERF_REG_ARM64_X2, + PERF_REG_ARM64_X3, + PERF_REG_ARM64_X4, + PERF_REG_ARM64_X5, + PERF_REG_ARM64_X6, + PERF_REG_ARM64_X7, + PERF_REG_ARM64_X8, + PERF_REG_ARM64_X9, + PERF_REG_ARM64_X10, + PERF_REG_ARM64_X11, + PERF_REG_ARM64_X12, + PERF_REG_ARM64_X13, + PERF_REG_ARM64_X14, + PERF_REG_ARM64_X15, + PERF_REG_ARM64_X16, + PERF_REG_ARM64_X17, + PERF_REG_ARM64_X18, + PERF_REG_ARM64_X19, + PERF_REG_ARM64_X20, + PERF_REG_ARM64_X21, + PERF_REG_ARM64_X22, + PERF_REG_ARM64_X23, + PERF_REG_ARM64_X24, + PERF_REG_ARM64_X25, + PERF_REG_ARM64_X26, + PERF_REG_ARM64_X27, + PERF_REG_ARM64_X28, + PERF_REG_ARM64_X29, + PERF_REG_ARM64_LR, + PERF_REG_ARM64_SP, + PERF_REG_ARM64_PC, + PERF_REG_ARM64_MAX, +}; +#endif /* _ASM_ARM64_PERF_REGS_H */ diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 2d4554b134100a..7d811d9522bc4f 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -14,12 +14,14 @@ arm64-obj-y := cputable.o debug-monitors.o entry.o irq.o fpsimd.o \ arm64-obj-$(CONFIG_COMPAT) += sys32.o kuser32.o signal32.o \ sys_compat.o arm64-obj-$(CONFIG_MODULES) += arm64ksyms.o module.o -arm64-obj-$(CONFIG_SMP) += smp.o smp_spin_table.o +arm64-obj-$(CONFIG_SMP) += smp.o smp_spin_table.o topology.o +arm64-obj-$(CONFIG_PERF_EVENTS) += perf_regs.o arm64-obj-$(CONFIG_HW_PERF_EVENTS) += perf_event.o -arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT)+= hw_breakpoint.o +arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o arm64-obj-$(CONFIG_EARLY_PRINTK) += early_printk.o arm64-obj-$(CONFIG_ARM64_CPU_SUSPEND) += sleep.o suspend.o arm64-obj-$(CONFIG_JUMP_LABEL) += jump_label.o +arm64-obj-$(CONFIG_KGDB) += kgdb.o obj-y += $(arm64-obj-y) vdso/ obj-m += $(arm64-obj-m) diff --git a/arch/arm64/kernel/arm64ksyms.c b/arch/arm64/kernel/arm64ksyms.c index 338b568cd8ae4a..1edc792b4a1f2f 100644 --- a/arch/arm64/kernel/arm64ksyms.c +++ b/arch/arm64/kernel/arm64ksyms.c @@ -44,10 +44,15 @@ EXPORT_SYMBOL(memstart_addr); /* string / mem functions */ EXPORT_SYMBOL(strchr); EXPORT_SYMBOL(strrchr); +EXPORT_SYMBOL(strcmp); +EXPORT_SYMBOL(strncmp); +EXPORT_SYMBOL(strlen); +EXPORT_SYMBOL(strnlen); EXPORT_SYMBOL(memset); EXPORT_SYMBOL(memcpy); EXPORT_SYMBOL(memmove); EXPORT_SYMBOL(memchr); +EXPORT_SYMBOL(memcmp); /* atomic bitops */ EXPORT_SYMBOL(set_bit); diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c index 636ba8b6240b80..3cc73d2f961db8 100644 --- a/arch/arm64/kernel/debug-monitors.c +++ b/arch/arm64/kernel/debug-monitors.c @@ -52,15 +52,15 @@ static void mdscr_write(u32 mdscr) { unsigned long flags; local_dbg_save(flags); - asm volatile("msr mdscr_el1, %0" :: "r" (mdscr)); + asm volatile("msr mdscr_el1, %0" :: "r" ((u64)mdscr)); local_dbg_restore(flags); } static u32 mdscr_read(void) { - u32 mdscr; + u64 mdscr; asm volatile("mrs %0, mdscr_el1" : "=r" (mdscr)); - return mdscr; + return (u32)mdscr; } /* @@ -136,8 +136,7 @@ void disable_debug_monitors(enum debug_el el) */ static void clear_os_lock(void *unused) { - asm volatile("msr oslar_el1, %0" : : "r" (0)); - isb(); + asm volatile("msr oslar_el1, %0" : : "r" ((u64)0)); } static int os_lock_notify(struct notifier_block *self, @@ -156,8 +155,9 @@ static struct notifier_block os_lock_nb = { static int debug_monitors_init(void) { /* Clear the OS lock. */ - smp_call_function(clear_os_lock, NULL, 1); - clear_os_lock(NULL); + on_each_cpu(clear_os_lock, NULL, 1); + isb(); + local_dbg_enable(); /* Register hotplug handler. */ register_cpu_notifier(&os_lock_nb); @@ -189,7 +189,7 @@ static void clear_regs_spsr_ss(struct pt_regs *regs) /* EL1 Single Step Handler hooks */ static LIST_HEAD(step_hook); -DEFINE_RWLOCK(step_hook_lock); +static DEFINE_RWLOCK(step_hook_lock); void register_step_hook(struct step_hook *hook) { @@ -276,7 +276,7 @@ static int single_step_handler(unsigned long addr, unsigned int esr, * Use reader/writer locks instead of plain spinlock. */ static LIST_HEAD(break_hook); -DEFINE_RWLOCK(break_hook_lock); +static DEFINE_RWLOCK(break_hook_lock); void register_break_hook(struct break_hook *hook) { diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S index 0b281fffda5199..61035d6814cbcc 100644 --- a/arch/arm64/kernel/head.S +++ b/arch/arm64/kernel/head.S @@ -384,26 +384,18 @@ ENDPROC(__calc_phys_offset) * Preserves: tbl, flags * Corrupts: phys, start, end, pstate */ - .macro create_block_map, tbl, flags, phys, start, end, idmap=0 + .macro create_block_map, tbl, flags, phys, start, end lsr \phys, \phys, #BLOCK_SHIFT - .if \idmap - and \start, \phys, #PTRS_PER_PTE - 1 // table index - .else lsr \start, \start, #BLOCK_SHIFT and \start, \start, #PTRS_PER_PTE - 1 // table index - .endif orr \phys, \flags, \phys, lsl #BLOCK_SHIFT // table entry - .ifnc \start,\end lsr \end, \end, #BLOCK_SHIFT and \end, \end, #PTRS_PER_PTE - 1 // table end index - .endif 9999: str \phys, [\tbl, \start, lsl #3] // store the entry - .ifnc \start,\end add \start, \start, #1 // next entry add \phys, \phys, #BLOCK_SIZE // next block cmp \start, \end b.ls 9999b - .endif .endm /* @@ -435,9 +427,13 @@ __create_page_tables: * Create the identity mapping. */ add x0, x25, #PAGE_SIZE // section table address - adr x3, __turn_mmu_on // virtual/physical address + ldr x3, =KERNEL_START + add x3, x3, x28 // __pa(KERNEL_START) create_pgd_entry x25, x0, x3, x5, x6 - create_block_map x0, x7, x3, x5, x5, idmap=1 + ldr x6, =KERNEL_END + mov x5, x3 // __pa(KERNEL_START) + add x6, x6, x28 // __pa(KERNEL_END) + create_block_map x0, x7, x3, x5, x6 /* * Map the kernel image (starting with PHYS_OFFSET). @@ -445,7 +441,7 @@ __create_page_tables: add x0, x26, #PAGE_SIZE // section table address mov x5, #PAGE_OFFSET create_pgd_entry x26, x0, x5, x3, x6 - ldr x6, =KERNEL_END - 1 + ldr x6, =KERNEL_END mov x3, x24 // phys offset create_block_map x0, x7, x3, x5, x6 diff --git a/arch/arm64/kernel/kgdb.c b/arch/arm64/kernel/kgdb.c new file mode 100644 index 00000000000000..75c9cf1aafee77 --- /dev/null +++ b/arch/arm64/kernel/kgdb.c @@ -0,0 +1,336 @@ +/* + * AArch64 KGDB support + * + * Based on arch/arm/kernel/kgdb.c + * + * Copyright (C) 2013 Cavium Inc. + * Author: Vijaya Kumar K + * + * 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 . + */ + +#include +#include +#include +#include + +struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = { + { "x0", 8, offsetof(struct pt_regs, regs[0])}, + { "x1", 8, offsetof(struct pt_regs, regs[1])}, + { "x2", 8, offsetof(struct pt_regs, regs[2])}, + { "x3", 8, offsetof(struct pt_regs, regs[3])}, + { "x4", 8, offsetof(struct pt_regs, regs[4])}, + { "x5", 8, offsetof(struct pt_regs, regs[5])}, + { "x6", 8, offsetof(struct pt_regs, regs[6])}, + { "x7", 8, offsetof(struct pt_regs, regs[7])}, + { "x8", 8, offsetof(struct pt_regs, regs[8])}, + { "x9", 8, offsetof(struct pt_regs, regs[9])}, + { "x10", 8, offsetof(struct pt_regs, regs[10])}, + { "x11", 8, offsetof(struct pt_regs, regs[11])}, + { "x12", 8, offsetof(struct pt_regs, regs[12])}, + { "x13", 8, offsetof(struct pt_regs, regs[13])}, + { "x14", 8, offsetof(struct pt_regs, regs[14])}, + { "x15", 8, offsetof(struct pt_regs, regs[15])}, + { "x16", 8, offsetof(struct pt_regs, regs[16])}, + { "x17", 8, offsetof(struct pt_regs, regs[17])}, + { "x18", 8, offsetof(struct pt_regs, regs[18])}, + { "x19", 8, offsetof(struct pt_regs, regs[19])}, + { "x20", 8, offsetof(struct pt_regs, regs[20])}, + { "x21", 8, offsetof(struct pt_regs, regs[21])}, + { "x22", 8, offsetof(struct pt_regs, regs[22])}, + { "x23", 8, offsetof(struct pt_regs, regs[23])}, + { "x24", 8, offsetof(struct pt_regs, regs[24])}, + { "x25", 8, offsetof(struct pt_regs, regs[25])}, + { "x26", 8, offsetof(struct pt_regs, regs[26])}, + { "x27", 8, offsetof(struct pt_regs, regs[27])}, + { "x28", 8, offsetof(struct pt_regs, regs[28])}, + { "x29", 8, offsetof(struct pt_regs, regs[29])}, + { "x30", 8, offsetof(struct pt_regs, regs[30])}, + { "sp", 8, offsetof(struct pt_regs, sp)}, + { "pc", 8, offsetof(struct pt_regs, pc)}, + { "pstate", 8, offsetof(struct pt_regs, pstate)}, + { "v0", 16, -1 }, + { "v1", 16, -1 }, + { "v2", 16, -1 }, + { "v3", 16, -1 }, + { "v4", 16, -1 }, + { "v5", 16, -1 }, + { "v6", 16, -1 }, + { "v7", 16, -1 }, + { "v8", 16, -1 }, + { "v9", 16, -1 }, + { "v10", 16, -1 }, + { "v11", 16, -1 }, + { "v12", 16, -1 }, + { "v13", 16, -1 }, + { "v14", 16, -1 }, + { "v15", 16, -1 }, + { "v16", 16, -1 }, + { "v17", 16, -1 }, + { "v18", 16, -1 }, + { "v19", 16, -1 }, + { "v20", 16, -1 }, + { "v21", 16, -1 }, + { "v22", 16, -1 }, + { "v23", 16, -1 }, + { "v24", 16, -1 }, + { "v25", 16, -1 }, + { "v26", 16, -1 }, + { "v27", 16, -1 }, + { "v28", 16, -1 }, + { "v29", 16, -1 }, + { "v30", 16, -1 }, + { "v31", 16, -1 }, + { "fpsr", 4, -1 }, + { "fpcr", 4, -1 }, +}; + +char *dbg_get_reg(int regno, void *mem, struct pt_regs *regs) +{ + if (regno >= DBG_MAX_REG_NUM || regno < 0) + return NULL; + + if (dbg_reg_def[regno].offset != -1) + memcpy(mem, (void *)regs + dbg_reg_def[regno].offset, + dbg_reg_def[regno].size); + else + memset(mem, 0, dbg_reg_def[regno].size); + return dbg_reg_def[regno].name; +} + +int dbg_set_reg(int regno, void *mem, struct pt_regs *regs) +{ + if (regno >= DBG_MAX_REG_NUM || regno < 0) + return -EINVAL; + + if (dbg_reg_def[regno].offset != -1) + memcpy((void *)regs + dbg_reg_def[regno].offset, mem, + dbg_reg_def[regno].size); + return 0; +} + +void +sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *task) +{ + struct pt_regs *thread_regs; + + /* Initialize to zero */ + memset((char *)gdb_regs, 0, NUMREGBYTES); + thread_regs = task_pt_regs(task); + memcpy((void *)gdb_regs, (void *)thread_regs->regs, GP_REG_BYTES); +} + +void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long pc) +{ + regs->pc = pc; +} + +static int compiled_break; + +static void kgdb_arch_update_addr(struct pt_regs *regs, + char *remcom_in_buffer) +{ + unsigned long addr; + char *ptr; + + ptr = &remcom_in_buffer[1]; + if (kgdb_hex2long(&ptr, &addr)) + kgdb_arch_set_pc(regs, addr); + else if (compiled_break == 1) + kgdb_arch_set_pc(regs, regs->pc + 4); + + compiled_break = 0; +} + +int kgdb_arch_handle_exception(int exception_vector, int signo, + int err_code, char *remcom_in_buffer, + char *remcom_out_buffer, + struct pt_regs *linux_regs) +{ + int err; + + switch (remcom_in_buffer[0]) { + case 'D': + case 'k': + /* + * Packet D (Detach), k (kill). No special handling + * is required here. Handle same as c packet. + */ + case 'c': + /* + * Packet c (Continue) to continue executing. + * Set pc to required address. + * Try to read optional parameter and set pc. + * If this was a compiled breakpoint, we need to move + * to the next instruction else we will just breakpoint + * over and over again. + */ + kgdb_arch_update_addr(linux_regs, remcom_in_buffer); + atomic_set(&kgdb_cpu_doing_single_step, -1); + kgdb_single_step = 0; + + /* + * Received continue command, disable single step + */ + if (kernel_active_single_step()) + kernel_disable_single_step(); + + err = 0; + break; + case 's': + /* + * Update step address value with address passed + * with step packet. + * On debug exception return PC is copied to ELR + * So just update PC. + * If no step address is passed, resume from the address + * pointed by PC. Do not update PC + */ + kgdb_arch_update_addr(linux_regs, remcom_in_buffer); + atomic_set(&kgdb_cpu_doing_single_step, raw_smp_processor_id()); + kgdb_single_step = 1; + + /* + * Enable single step handling + */ + if (!kernel_active_single_step()) + kernel_enable_single_step(linux_regs); + err = 0; + break; + default: + err = -1; + } + return err; +} + +static int kgdb_brk_fn(struct pt_regs *regs, unsigned int esr) +{ + kgdb_handle_exception(1, SIGTRAP, 0, regs); + return 0; +} + +static int kgdb_compiled_brk_fn(struct pt_regs *regs, unsigned int esr) +{ + compiled_break = 1; + kgdb_handle_exception(1, SIGTRAP, 0, regs); + + return 0; +} + +static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned int esr) +{ + kgdb_handle_exception(1, SIGTRAP, 0, regs); + return 0; +} + +static struct break_hook kgdb_brkpt_hook = { + .esr_mask = 0xffffffff, + .esr_val = DBG_ESR_VAL_BRK(KGDB_DYN_DGB_BRK_IMM), + .fn = kgdb_brk_fn +}; + +static struct break_hook kgdb_compiled_brkpt_hook = { + .esr_mask = 0xffffffff, + .esr_val = DBG_ESR_VAL_BRK(KDBG_COMPILED_DBG_BRK_IMM), + .fn = kgdb_compiled_brk_fn +}; + +static struct step_hook kgdb_step_hook = { + .fn = kgdb_step_brk_fn +}; + +static void kgdb_call_nmi_hook(void *ignored) +{ + kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs()); +} + +void kgdb_roundup_cpus(unsigned long flags) +{ + local_irq_enable(); + smp_call_function(kgdb_call_nmi_hook, NULL, 0); + local_irq_disable(); +} + +static int __kgdb_notify(struct die_args *args, unsigned long cmd) +{ + struct pt_regs *regs = args->regs; + + if (kgdb_handle_exception(1, args->signr, cmd, regs)) + return NOTIFY_DONE; + return NOTIFY_STOP; +} + +static int +kgdb_notify(struct notifier_block *self, unsigned long cmd, void *ptr) +{ + unsigned long flags; + int ret; + + local_irq_save(flags); + ret = __kgdb_notify(ptr, cmd); + local_irq_restore(flags); + + return ret; +} + +static struct notifier_block kgdb_notifier = { + .notifier_call = kgdb_notify, + /* + * Want to be lowest priority + */ + .priority = -INT_MAX, +}; + +/* + * kgdb_arch_init - Perform any architecture specific initalization. + * This function will handle the initalization of any architecture + * specific callbacks. + */ +int kgdb_arch_init(void) +{ + int ret = register_die_notifier(&kgdb_notifier); + + if (ret != 0) + return ret; + + register_break_hook(&kgdb_brkpt_hook); + register_break_hook(&kgdb_compiled_brkpt_hook); + register_step_hook(&kgdb_step_hook); + return 0; +} + +/* + * kgdb_arch_exit - Perform any architecture specific uninitalization. + * This function will handle the uninitalization of any architecture + * specific callbacks, for dynamic registration and unregistration. + */ +void kgdb_arch_exit(void) +{ + unregister_break_hook(&kgdb_brkpt_hook); + unregister_break_hook(&kgdb_compiled_brkpt_hook); + unregister_step_hook(&kgdb_step_hook); + unregister_die_notifier(&kgdb_notifier); +} + +/* + * ARM instructions are always in LE. + * Break instruction is encoded in LE format + */ +struct kgdb_arch arch_kgdb_ops = { + .gdb_bpt_instr = { + KGDB_DYN_BRK_INS_BYTE0, + KGDB_DYN_BRK_INS_BYTE1, + KGDB_DYN_BRK_INS_BYTE2, + KGDB_DYN_BRK_INS_BYTE3, + } +}; diff --git a/arch/arm64/kernel/perf_event.c b/arch/arm64/kernel/perf_event.c index 5b1cd792274a34..4139373ccc1f22 100644 --- a/arch/arm64/kernel/perf_event.c +++ b/arch/arm64/kernel/perf_event.c @@ -844,16 +844,16 @@ static const unsigned armv8_pmuv3_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] static inline u32 armv8pmu_pmcr_read(void) { - u32 val; + u64 val; asm volatile("mrs %0, pmcr_el0" : "=r" (val)); - return val; + return (u32)val; } static inline void armv8pmu_pmcr_write(u32 val) { val &= ARMV8_PMCR_MASK; isb(); - asm volatile("msr pmcr_el0, %0" :: "r" (val)); + asm volatile("msr pmcr_el0, %0" :: "r" ((u64)val)); } static inline int armv8pmu_has_overflowed(u32 pmovsr) @@ -893,7 +893,7 @@ static inline int armv8pmu_select_counter(int idx) } counter = ARMV8_IDX_TO_COUNTER(idx); - asm volatile("msr pmselr_el0, %0" :: "r" (counter)); + asm volatile("msr pmselr_el0, %0" :: "r" ((u64)counter)); isb(); return idx; @@ -901,7 +901,7 @@ static inline int armv8pmu_select_counter(int idx) static inline u32 armv8pmu_read_counter(int idx) { - u32 value = 0; + u64 value = 0; if (!armv8pmu_counter_valid(idx)) pr_err("CPU%u reading wrong counter %d\n", @@ -911,7 +911,7 @@ static inline u32 armv8pmu_read_counter(int idx) else if (armv8pmu_select_counter(idx) == idx) asm volatile("mrs %0, pmxevcntr_el0" : "=r" (value)); - return value; + return (u32)value; } static inline void armv8pmu_write_counter(int idx, u32 value) @@ -920,16 +920,16 @@ static inline void armv8pmu_write_counter(int idx, u32 value) pr_err("CPU%u writing wrong counter %d\n", smp_processor_id(), idx); else if (idx == ARMV8_IDX_CYCLE_COUNTER) - asm volatile("msr pmccntr_el0, %0" :: "r" (value)); + asm volatile("msr pmccntr_el0, %0" :: "r" ((u64)value)); else if (armv8pmu_select_counter(idx) == idx) - asm volatile("msr pmxevcntr_el0, %0" :: "r" (value)); + asm volatile("msr pmxevcntr_el0, %0" :: "r" ((u64)value)); } static inline void armv8pmu_write_evtype(int idx, u32 val) { if (armv8pmu_select_counter(idx) == idx) { val &= ARMV8_EVTYPE_MASK; - asm volatile("msr pmxevtyper_el0, %0" :: "r" (val)); + asm volatile("msr pmxevtyper_el0, %0" :: "r" ((u64)val)); } } @@ -944,7 +944,7 @@ static inline int armv8pmu_enable_counter(int idx) } counter = ARMV8_IDX_TO_COUNTER(idx); - asm volatile("msr pmcntenset_el0, %0" :: "r" (BIT(counter))); + asm volatile("msr pmcntenset_el0, %0" :: "r" ((u64)BIT(counter))); return idx; } @@ -959,7 +959,7 @@ static inline int armv8pmu_disable_counter(int idx) } counter = ARMV8_IDX_TO_COUNTER(idx); - asm volatile("msr pmcntenclr_el0, %0" :: "r" (BIT(counter))); + asm volatile("msr pmcntenclr_el0, %0" :: "r" ((u64)BIT(counter))); return idx; } @@ -974,7 +974,7 @@ static inline int armv8pmu_enable_intens(int idx) } counter = ARMV8_IDX_TO_COUNTER(idx); - asm volatile("msr pmintenset_el1, %0" :: "r" (BIT(counter))); + asm volatile("msr pmintenset_el1, %0" :: "r" ((u64)BIT(counter))); return idx; } @@ -989,17 +989,17 @@ static inline int armv8pmu_disable_intens(int idx) } counter = ARMV8_IDX_TO_COUNTER(idx); - asm volatile("msr pmintenclr_el1, %0" :: "r" (BIT(counter))); + asm volatile("msr pmintenclr_el1, %0" :: "r" ((u64)BIT(counter))); isb(); /* Clear the overflow flag in case an interrupt is pending. */ - asm volatile("msr pmovsclr_el0, %0" :: "r" (BIT(counter))); + asm volatile("msr pmovsclr_el0, %0" :: "r" ((u64)BIT(counter))); isb(); return idx; } static inline u32 armv8pmu_getreset_flags(void) { - u32 value; + u64 value; /* Read */ asm volatile("mrs %0, pmovsclr_el0" : "=r" (value)); @@ -1008,7 +1008,7 @@ static inline u32 armv8pmu_getreset_flags(void) value &= ARMV8_OVSR_MASK; asm volatile("msr pmovsclr_el0, %0" :: "r" (value)); - return value; + return (u32)value; } static void armv8pmu_enable_event(struct hw_perf_event *hwc, int idx) @@ -1217,7 +1217,7 @@ static void armv8pmu_reset(void *info) armv8pmu_pmcr_write(ARMV8_PMCR_P | ARMV8_PMCR_C); /* Disable access from userspace. */ - asm volatile("msr pmuserenr_el0, %0" :: "r" (0)); + asm volatile("msr pmuserenr_el0, %0" :: "r" ((u64)0)); } static int armv8_pmuv3_map_event(struct perf_event *event) @@ -1348,8 +1348,8 @@ early_initcall(init_hw_perf_events); * Callchain handling code. */ struct frame_tail { - struct frame_tail __user *fp; - unsigned long lr; + struct frame_tail __user *fp; + unsigned long lr; } __attribute__((packed)); /* @@ -1386,22 +1386,84 @@ user_backtrace(struct frame_tail __user *tail, return buftail.fp; } +#ifdef CONFIG_COMPAT +/* + * The registers we're interested in are at the end of the variable + * length saved register structure. The fp points at the end of this + * structure so the address of this struct is: + * (struct compat_frame_tail *)(xxx->fp)-1 + * + * This code has been adapted from the ARM OProfile support. + */ +struct compat_frame_tail { + compat_uptr_t fp; /* a (struct compat_frame_tail *) in compat mode */ + u32 sp; + u32 lr; +} __attribute__((packed)); + +static struct compat_frame_tail __user * +compat_user_backtrace(struct compat_frame_tail __user *tail, + struct perf_callchain_entry *entry) +{ + struct compat_frame_tail buftail; + unsigned long err; + + /* Also check accessibility of one struct frame_tail beyond */ + if (!access_ok(VERIFY_READ, tail, sizeof(buftail))) + return NULL; + + pagefault_disable(); + err = __copy_from_user_inatomic(&buftail, tail, sizeof(buftail)); + pagefault_enable(); + + if (err) + return NULL; + + perf_callchain_store(entry, buftail.lr); + + /* + * Frame pointers should strictly progress back up the stack + * (towards higher addresses). + */ + if (tail + 1 >= (struct compat_frame_tail __user *) + compat_ptr(buftail.fp)) + return NULL; + + return (struct compat_frame_tail __user *)compat_ptr(buftail.fp) - 1; +} +#endif /* CONFIG_COMPAT */ + void perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs) { - struct frame_tail __user *tail; - if (perf_guest_cbs && perf_guest_cbs->is_in_guest()) { /* We don't support guest os callchain now */ return; } perf_callchain_store(entry, regs->pc); - tail = (struct frame_tail __user *)regs->regs[29]; - while (entry->nr < PERF_MAX_STACK_DEPTH && - tail && !((unsigned long)tail & 0xf)) - tail = user_backtrace(tail, entry); + if (!compat_user_mode(regs)) { + /* AARCH64 mode */ + struct frame_tail __user *tail; + + tail = (struct frame_tail __user *)regs->regs[29]; + + while (entry->nr < PERF_MAX_STACK_DEPTH && + tail && !((unsigned long)tail & 0xf)) + tail = user_backtrace(tail, entry); + } else { +#ifdef CONFIG_COMPAT + /* AARCH32 compat mode */ + struct compat_frame_tail __user *tail; + + tail = (struct compat_frame_tail __user *)regs->compat_fp - 1; + + while ((entry->nr < PERF_MAX_STACK_DEPTH) && + tail && !((unsigned long)tail & 0x3)) + tail = compat_user_backtrace(tail, entry); +#endif + } } /* @@ -1429,6 +1491,7 @@ void perf_callchain_kernel(struct perf_callchain_entry *entry, frame.fp = regs->regs[29]; frame.sp = regs->sp; frame.pc = regs->pc; + walk_stackframe(&frame, callchain_trace, entry); } diff --git a/arch/arm64/kernel/perf_regs.c b/arch/arm64/kernel/perf_regs.c new file mode 100644 index 00000000000000..422ebd63b61925 --- /dev/null +++ b/arch/arm64/kernel/perf_regs.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include +#include +#include + +u64 perf_reg_value(struct pt_regs *regs, int idx) +{ + if (WARN_ON_ONCE((u32)idx >= PERF_REG_ARM64_MAX)) + return 0; + + /* + * Compat (i.e. 32 bit) mode: + * - PC has been set in the pt_regs struct in kernel_entry, + * - Handle SP and LR here. + */ + if (compat_user_mode(regs)) { + if ((u32)idx == PERF_REG_ARM64_SP) + return regs->compat_sp; + if ((u32)idx == PERF_REG_ARM64_LR) + return regs->compat_lr; + } + + return regs->regs[idx]; +} + +#define REG_RESERVED (~((1ULL << PERF_REG_ARM64_MAX) - 1)) + +int perf_reg_validate(u64 mask) +{ + if (!mask || mask & REG_RESERVED) + return -EINVAL; + + return 0; +} + +u64 perf_reg_abi(struct task_struct *task) +{ + if (is_compat_thread(task_thread_info(task))) + return PERF_SAMPLE_REGS_ABI_32; + else + return PERF_SAMPLE_REGS_ABI_64; +} diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index 1c0a9be2ffa85a..fa6b5bba15f624 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -72,8 +72,17 @@ static void setup_restart(void) void soft_restart(unsigned long addr) { + typedef void (*phys_reset_t)(unsigned long); + phys_reset_t phys_reset; + setup_restart(); - cpu_reset(addr); + + /* Switch to the identity mapping */ + phys_reset = (phys_reset_t)virt_to_phys(cpu_reset); + phys_reset(addr); + + /* Should never get here */ + BUG(); } /* diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 4f97db3d736332..ea4828a4aa9672 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -176,22 +176,20 @@ static const struct of_device_id psci_of_match[] __initconst = { {}, }; -int __init psci_init(void) +void __init psci_init(void) { struct device_node *np; const char *method; u32 id; - int err = 0; np = of_find_matching_node(NULL, psci_of_match); if (!np) - return -ENODEV; + return; pr_info("probing function IDs from device-tree\n"); if (of_property_read_string(np, "method", &method)) { pr_warning("missing \"method\" property\n"); - err = -ENXIO; goto out_put_node; } @@ -201,7 +199,6 @@ int __init psci_init(void) invoke_psci_fn = __invoke_psci_fn_smc; } else { pr_warning("invalid \"method\" property: %s\n", method); - err = -EINVAL; goto out_put_node; } @@ -227,7 +224,7 @@ int __init psci_init(void) out_put_node: of_node_put(np); - return err; + return; } #ifdef CONFIG_SMP @@ -251,7 +248,7 @@ static int cpu_psci_cpu_boot(unsigned int cpu) { int err = psci_ops.cpu_on(cpu_logical_map(cpu), __pa(secondary_entry)); if (err) - pr_err("psci: failed to boot CPU%d (%d)\n", cpu, err); + pr_err("failed to boot CPU%d (%d)\n", cpu, err); return err; } @@ -278,7 +275,7 @@ static void cpu_psci_cpu_die(unsigned int cpu) ret = psci_ops.cpu_off(state); - pr_crit("psci: unable to power off CPU%u (%d)\n", cpu, ret); + pr_crit("unable to power off CPU%u (%d)\n", cpu, ret); } #endif diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index c8e9effe52e10f..67da30741a1b64 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -69,6 +69,7 @@ EXPORT_SYMBOL_GPL(elf_hwcap); COMPAT_HWCAP_VFPv3|COMPAT_HWCAP_VFPv4|\ COMPAT_HWCAP_NEON|COMPAT_HWCAP_IDIV) unsigned int compat_elf_hwcap __read_mostly = COMPAT_ELF_HWCAP_DEFAULT; +unsigned int compat_elf_hwcap2 __read_mostly; #endif static const char *cpu_name; @@ -242,6 +243,38 @@ static void __init setup_processor(void) block = (features >> 16) & 0xf; if (block && !(block & 0x8)) elf_hwcap |= HWCAP_CRC32; + +#ifdef CONFIG_COMPAT + /* + * ID_ISAR5_EL1 carries similar information as above, but pertaining to + * the Aarch32 32-bit execution state. + */ + features = read_cpuid(ID_ISAR5_EL1); + block = (features >> 4) & 0xf; + if (!(block & 0x8)) { + switch (block) { + default: + case 2: + compat_elf_hwcap2 |= COMPAT_HWCAP2_PMULL; + case 1: + compat_elf_hwcap2 |= COMPAT_HWCAP2_AES; + case 0: + break; + } + } + + block = (features >> 8) & 0xf; + if (block && !(block & 0x8)) + compat_elf_hwcap2 |= COMPAT_HWCAP2_SHA1; + + block = (features >> 12) & 0xf; + if (block && !(block & 0x8)) + compat_elf_hwcap2 |= COMPAT_HWCAP2_SHA2; + + block = (features >> 16) & 0xf; + if (block && !(block & 0x8)) + compat_elf_hwcap2 |= COMPAT_HWCAP2_CRC32; +#endif } static void __init setup_machine_fdt(phys_addr_t dt_phys) diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 7cfb92a4ab6652..f0a141dd565581 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -114,6 +114,11 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle) return ret; } +static void smp_store_cpu_info(unsigned int cpuid) +{ + store_cpu_topology(cpuid); +} + /* * This is the secondary CPU boot entry. We're using this CPUs * idle thread stack, but a set of temporary page tables. @@ -152,6 +157,8 @@ asmlinkage void secondary_start_kernel(void) */ notify_cpu_starting(cpu); + smp_store_cpu_info(cpu); + /* * OK, now it's safe to let the boot CPU continue. Wait for * the CPU migration code to notice that the CPU is online @@ -160,6 +167,7 @@ asmlinkage void secondary_start_kernel(void) set_cpu_online(cpu, true); complete(&cpu_running); + local_dbg_enable(); local_irq_enable(); local_async_enable(); @@ -390,6 +398,10 @@ void __init smp_prepare_cpus(unsigned int max_cpus) int err; unsigned int cpu, ncores = num_possible_cpus(); + init_cpu_topology(); + + smp_store_cpu_info(smp_processor_id()); + /* * are we trying to boot more cores than exist? */ diff --git a/arch/arm64/kernel/smp_spin_table.c b/arch/arm64/kernel/smp_spin_table.c index 44c22805d2e2ad..7a530d2cc8077e 100644 --- a/arch/arm64/kernel/smp_spin_table.c +++ b/arch/arm64/kernel/smp_spin_table.c @@ -128,7 +128,7 @@ static int smp_spin_table_cpu_boot(unsigned int cpu) return secondary_holding_pen_release != INVALID_HWID ? -ENOSYS : 0; } -void smp_spin_table_cpu_postboot(void) +static void smp_spin_table_cpu_postboot(void) { /* * Let the primary processor know we're out of the pen. diff --git a/arch/arm64/kernel/stacktrace.c b/arch/arm64/kernel/stacktrace.c index 38f0558f0c0a56..6f44bf97e4cfce 100644 --- a/arch/arm64/kernel/stacktrace.c +++ b/arch/arm64/kernel/stacktrace.c @@ -111,10 +111,9 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) frame.sp = thread_saved_sp(tsk); frame.pc = thread_saved_pc(tsk); } else { - register unsigned long current_sp asm("sp"); data.no_sched_functions = 0; frame.fp = (unsigned long)__builtin_frame_address(0); - frame.sp = current_sp; + frame.sp = current_stack_pointer; frame.pc = (unsigned long)save_stack_trace_tsk; } diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c new file mode 100644 index 00000000000000..3e06b0be4ec81e --- /dev/null +++ b/arch/arm64/kernel/topology.c @@ -0,0 +1,95 @@ +/* + * arch/arm64/kernel/topology.c + * + * Copyright (C) 2011,2013,2014 Linaro Limited. + * + * Based on the arm32 version written by Vincent Guittot in turn based on + * arch/sh/kernel/topology.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * cpu topology table + */ +struct cpu_topology cpu_topology[NR_CPUS]; +EXPORT_SYMBOL_GPL(cpu_topology); + +const struct cpumask *cpu_coregroup_mask(int cpu) +{ + return &cpu_topology[cpu].core_sibling; +} + +static void update_siblings_masks(unsigned int cpuid) +{ + struct cpu_topology *cpu_topo, *cpuid_topo = &cpu_topology[cpuid]; + int cpu; + + if (cpuid_topo->cluster_id == -1) { + /* + * DT does not contain topology information for this cpu + * reset it to default behaviour + */ + pr_debug("CPU%u: No topology information configured\n", cpuid); + cpuid_topo->core_id = 0; + cpumask_set_cpu(cpuid, &cpuid_topo->core_sibling); + cpumask_set_cpu(cpuid, &cpuid_topo->thread_sibling); + return; + } + + /* update core and thread sibling masks */ + for_each_possible_cpu(cpu) { + cpu_topo = &cpu_topology[cpu]; + + if (cpuid_topo->cluster_id != cpu_topo->cluster_id) + continue; + + cpumask_set_cpu(cpuid, &cpu_topo->core_sibling); + if (cpu != cpuid) + cpumask_set_cpu(cpu, &cpuid_topo->core_sibling); + + if (cpuid_topo->core_id != cpu_topo->core_id) + continue; + + cpumask_set_cpu(cpuid, &cpu_topo->thread_sibling); + if (cpu != cpuid) + cpumask_set_cpu(cpu, &cpuid_topo->thread_sibling); + } +} + +void store_cpu_topology(unsigned int cpuid) +{ + update_siblings_masks(cpuid); +} + +/* + * init_cpu_topology is called at boot when only one cpu is running + * which prevent simultaneous write access to cpu_topology array + */ +void __init init_cpu_topology(void) +{ + unsigned int cpu; + + /* init core mask and power*/ + for_each_possible_cpu(cpu) { + struct cpu_topology *cpu_topo = &cpu_topology[cpu]; + + cpu_topo->thread_id = -1; + cpu_topo->core_id = -1; + cpu_topo->cluster_id = -1; + cpumask_clear(&cpu_topo->core_sibling); + cpumask_clear(&cpu_topo->thread_sibling); + } +} diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c index 7ffadddb645d32..44676523a879c2 100644 --- a/arch/arm64/kernel/traps.c +++ b/arch/arm64/kernel/traps.c @@ -132,7 +132,6 @@ static void dump_instr(const char *lvl, struct pt_regs *regs) static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk) { struct stackframe frame; - const register unsigned long current_sp asm ("sp"); pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk); @@ -145,7 +144,7 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk) frame.pc = regs->pc; } else if (tsk == current) { frame.fp = (unsigned long)__builtin_frame_address(0); - frame.sp = current_sp; + frame.sp = current_stack_pointer; frame.pc = (unsigned long)dump_backtrace; } else { /* @@ -341,6 +340,15 @@ void __pgd_error(const char *file, int line, unsigned long val) printk("%s:%d: bad pgd %016lx.\n", file, line, val); } +void abort(void) +{ + BUG(); + + /* if that doesn't kill us, halt */ + panic("Oops failed to kill thread"); +} +EXPORT_SYMBOL(abort); + void __init trap_init(void) { return; diff --git a/arch/arm64/kernel/vdso.c b/arch/arm64/kernel/vdso.c index a7149cae16153b..50384fec56c469 100644 --- a/arch/arm64/kernel/vdso.c +++ b/arch/arm64/kernel/vdso.c @@ -106,49 +106,31 @@ int aarch32_setup_vectors_page(struct linux_binprm *bprm, int uses_interp) static int __init vdso_init(void) { - struct page *pg; - char *vbase; - int i, ret = 0; + int i; + + if (memcmp(&vdso_start, "\177ELF", 4)) { + pr_err("vDSO is not a valid ELF object!\n"); + return -EINVAL; + } vdso_pages = (&vdso_end - &vdso_start) >> PAGE_SHIFT; pr_info("vdso: %ld pages (%ld code, %ld data) at base %p\n", vdso_pages + 1, vdso_pages, 1L, &vdso_start); /* Allocate the vDSO pagelist, plus a page for the data. */ - vdso_pagelist = kzalloc(sizeof(struct page *) * (vdso_pages + 1), + vdso_pagelist = kcalloc(vdso_pages + 1, sizeof(struct page *), GFP_KERNEL); - if (vdso_pagelist == NULL) { - pr_err("Failed to allocate vDSO pagelist!\n"); + if (vdso_pagelist == NULL) return -ENOMEM; - } /* Grab the vDSO code pages. */ - for (i = 0; i < vdso_pages; i++) { - pg = virt_to_page(&vdso_start + i*PAGE_SIZE); - ClearPageReserved(pg); - get_page(pg); - vdso_pagelist[i] = pg; - } - - /* Sanity check the shared object header. */ - vbase = vmap(vdso_pagelist, 1, 0, PAGE_KERNEL); - if (vbase == NULL) { - pr_err("Failed to map vDSO pagelist!\n"); - return -ENOMEM; - } else if (memcmp(vbase, "\177ELF", 4)) { - pr_err("vDSO is not a valid ELF object!\n"); - ret = -EINVAL; - goto unmap; - } + for (i = 0; i < vdso_pages; i++) + vdso_pagelist[i] = virt_to_page(&vdso_start + i * PAGE_SIZE); /* Grab the vDSO data page. */ - pg = virt_to_page(vdso_data); - get_page(pg); - vdso_pagelist[i] = pg; + vdso_pagelist[i] = virt_to_page(vdso_data); -unmap: - vunmap(vbase); - return ret; + return 0; } arch_initcall(vdso_init); diff --git a/arch/arm64/kvm/hyp-init.S b/arch/arm64/kvm/hyp-init.S index 2b0244d65c16f5..d968796f4b2d7a 100644 --- a/arch/arm64/kvm/hyp-init.S +++ b/arch/arm64/kvm/hyp-init.S @@ -68,6 +68,12 @@ __do_hyp_init: msr tcr_el2, x4 ldr x4, =VTCR_EL2_FLAGS + /* + * Read the PARange bits from ID_AA64MMFR0_EL1 and set the PS bits in + * VTCR_EL2. + */ + mrs x5, ID_AA64MMFR0_EL1 + bfi x4, x5, #16, #3 msr vtcr_el2, x4 mrs x4, mair_el1 diff --git a/arch/arm64/lib/Makefile b/arch/arm64/lib/Makefile index 328ce1a99daaa4..0d3407c4131aee 100644 --- a/arch/arm64/lib/Makefile +++ b/arch/arm64/lib/Makefile @@ -1,4 +1,9 @@ lib-y := bitops.o clear_user.o delay.o copy_from_user.o \ copy_to_user.o copy_in_user.o copy_page.o \ clear_page.o memchr.o memcpy.o memmove.o memset.o \ + memcmp.o strcmp.o strncmp.o strlen.o strnlen.o \ strchr.o strrchr.o + +ifeq ($(COMPILER),clang) +lib-y += eabi.o +endif diff --git a/arch/arm64/lib/eabi.c b/arch/arm64/lib/eabi.c new file mode 100644 index 00000000000000..41b27b2f49b802 --- /dev/null +++ b/arch/arm64/lib/eabi.c @@ -0,0 +1,32 @@ +/* + * linux/lib/eabi.c + * + * Copyright (C) 2012 Mark Charlebois + */ + +/* + * EABI routines + */ + +#include +#include +#include +#include + +void __aeabi_memcpy(void *dest, const void *src, size_t n) +{ + (void)memcpy(dest, src, n); +} +EXPORT_SYMBOL(__aeabi_memcpy); + +void __aeabi_memmove(void *dest, const void *src, size_t n) +{ + (void)memmove(dest, src, n); +} +EXPORT_SYMBOL(__aeabi_memmove); + +void __aeabi_memset(void *s, size_t n, int c) +{ + (void)memset(s, c, n); +} +EXPORT_SYMBOL(__aeabi_memset); diff --git a/arch/arm64/lib/memcmp.S b/arch/arm64/lib/memcmp.S new file mode 100644 index 00000000000000..c02a442f9d0f85 --- /dev/null +++ b/arch/arm64/lib/memcmp.S @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2013 Linaro. + * + * This code is based on glibc cortex strings work originally authored by Linaro + * and re-licensed under GPLv2 for the Linux kernel. The original code can + * be found @ + * + * http://bazaar.launchpad.net/~linaro-toolchain-dev/cortex-strings/trunk/ + * files/head:/src/aarch64/ + * + * 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 . + */ + +#include +#include + +/* +* compare memory areas(when two memory areas' offset are different, +* alignment handled by the hardware) +* +* Parameters: +* x0 - const memory area 1 pointer +* x1 - const memory area 2 pointer +* x2 - the maximal compare byte length +* Returns: +* x0 - a compare result, maybe less than, equal to, or greater than ZERO +*/ + +/* Parameters and result. */ +src1 .req x0 +src2 .req x1 +limit .req x2 +result .req x0 + +/* Internal variables. */ +data1 .req x3 +data1w .req w3 +data2 .req x4 +data2w .req w4 +has_nul .req x5 +diff .req x6 +endloop .req x7 +tmp1 .req x8 +tmp2 .req x9 +tmp3 .req x10 +pos .req x11 +limit_wd .req x12 +mask .req x13 + +ENTRY(memcmp) + cbz limit, .Lret0 + eor tmp1, src1, src2 + tst tmp1, #7 + b.ne .Lmisaligned8 + ands tmp1, src1, #7 + b.ne .Lmutual_align + sub limit_wd, limit, #1 /* limit != 0, so no underflow. */ + lsr limit_wd, limit_wd, #3 /* Convert to Dwords. */ + /* + * The input source addresses are at alignment boundary. + * Directly compare eight bytes each time. + */ +.Lloop_aligned: + ldr data1, [src1], #8 + ldr data2, [src2], #8 +.Lstart_realigned: + subs limit_wd, limit_wd, #1 + eor diff, data1, data2 /* Non-zero if differences found. */ + csinv endloop, diff, xzr, cs /* Last Dword or differences. */ + cbz endloop, .Lloop_aligned + + /* Not reached the limit, must have found a diff. */ + tbz limit_wd, #63, .Lnot_limit + + /* Limit % 8 == 0 => the diff is in the last 8 bytes. */ + ands limit, limit, #7 + b.eq .Lnot_limit + /* + * The remained bytes less than 8. It is needed to extract valid data + * from last eight bytes of the intended memory range. + */ + lsl limit, limit, #3 /* bytes-> bits. */ + mov mask, #~0 +CPU_BE( lsr mask, mask, limit ) +CPU_LE( lsl mask, mask, limit ) + bic data1, data1, mask + bic data2, data2, mask + + orr diff, diff, mask + b .Lnot_limit + +.Lmutual_align: + /* + * Sources are mutually aligned, but are not currently at an + * alignment boundary. Round down the addresses and then mask off + * the bytes that precede the start point. + */ + bic src1, src1, #7 + bic src2, src2, #7 + ldr data1, [src1], #8 + ldr data2, [src2], #8 + /* + * We can not add limit with tmp1 here.Since the addition probably + * make the limit overflown. + */ + sub limit_wd, limit, #1/*limit != 0, so no underflow.*/ + and tmp3, limit_wd, #7 + lsr limit_wd, limit_wd, #3 + add tmp3, tmp3, tmp1 + add limit_wd, limit_wd, tmp3, lsr #3 + add limit, limit, tmp1/* Adjust the limit for the extra. */ + + lsl tmp1, tmp1, #3/* Bytes beyond alignment -> bits.*/ + neg tmp1, tmp1/* Bits to alignment -64. */ + mov tmp2, #~0 + /*mask off the non-intended bytes before the start address.*/ +CPU_BE( lsl tmp2, tmp2, tmp1 )/*Big-endian.Early bytes are at MSB*/ + /* Little-endian. Early bytes are at LSB. */ +CPU_LE( lsr tmp2, tmp2, tmp1 ) + + orr data1, data1, tmp2 + orr data2, data2, tmp2 + b .Lstart_realigned + + /*src1 and src2 have different alignment offset.*/ +.Lmisaligned8: + cmp limit, #8 + b.lo .Ltiny8proc /*limit < 8: compare byte by byte*/ + + and tmp1, src1, #7 + neg tmp1, tmp1 + add tmp1, tmp1, #8/*valid length in the first 8 bytes of src1*/ + and tmp2, src2, #7 + neg tmp2, tmp2 + add tmp2, tmp2, #8/*valid length in the first 8 bytes of src2*/ + subs tmp3, tmp1, tmp2 + csel pos, tmp1, tmp2, hi /*Choose the maximum.*/ + + sub limit, limit, pos + /*compare the proceeding bytes in the first 8 byte segment.*/ +.Ltinycmp: + ldrb data1w, [src1], #1 + ldrb data2w, [src2], #1 + subs pos, pos, #1 + ccmp data1w, data2w, #0, ne /* NZCV = 0b0000. */ + b.eq .Ltinycmp + cbnz pos, 1f /*diff occurred before the last byte.*/ + cmp data1w, data2w + b.eq .Lstart_align +1: + sub result, data1, data2 + ret + +.Lstart_align: + lsr limit_wd, limit, #3 + cbz limit_wd, .Lremain8 + + ands xzr, src1, #7 + b.eq .Lrecal_offset + /*process more leading bytes to make src1 aligned...*/ + add src1, src1, tmp3 /*backwards src1 to alignment boundary*/ + add src2, src2, tmp3 + sub limit, limit, tmp3 + lsr limit_wd, limit, #3 + cbz limit_wd, .Lremain8 + /*load 8 bytes from aligned SRC1..*/ + ldr data1, [src1], #8 + ldr data2, [src2], #8 + + subs limit_wd, limit_wd, #1 + eor diff, data1, data2 /*Non-zero if differences found.*/ + csinv endloop, diff, xzr, ne + cbnz endloop, .Lunequal_proc + /*How far is the current SRC2 from the alignment boundary...*/ + and tmp3, tmp3, #7 + +.Lrecal_offset:/*src1 is aligned now..*/ + neg pos, tmp3 +.Lloopcmp_proc: + /* + * Divide the eight bytes into two parts. First,backwards the src2 + * to an alignment boundary,load eight bytes and compare from + * the SRC2 alignment boundary. If all 8 bytes are equal,then start + * the second part's comparison. Otherwise finish the comparison. + * This special handle can garantee all the accesses are in the + * thread/task space... + */ + ldr data1, [src1,pos] + ldr data2, [src2,pos] + eor diff, data1, data2 /* Non-zero if differences found. */ + cbnz diff, .Lnot_limit + + /*The second part process*/ + ldr data1, [src1], #8 + ldr data2, [src2], #8 + eor diff, data1, data2 /* Non-zero if differences found. */ + subs limit_wd, limit_wd, #1 + csinv endloop, diff, xzr, ne/*if limit_wd is 0,will finish the cmp*/ + cbz endloop, .Lloopcmp_proc +.Lunequal_proc: + cbz diff, .Lremain8 + +/*There is diff occured in the latest comparison.*/ +.Lnot_limit: +/* +* For little endian,reverse the low significant equal bits into MSB,then +* following CLZ can find how many equal bits exist. +*/ +CPU_LE( rev diff, diff ) +CPU_LE( rev data1, data1 ) +CPU_LE( rev data2, data2 ) + + /* + * The MS-non-zero bit of DIFF marks either the first bit + * that is different, or the end of the significant data. + * Shifting left now will bring the critical information into the + * top bits. + */ + clz pos, diff + lsl data1, data1, pos + lsl data2, data2, pos + /* + * We need to zero-extend (char is unsigned) the value and then + * perform a signed subtraction. + */ + lsr data1, data1, #56 + sub result, data1, data2, lsr #56 + ret + +.Lremain8: + /* Limit % 8 == 0 =>. all data are equal.*/ + ands limit, limit, #7 + b.eq .Lret0 + +.Ltiny8proc: + ldrb data1w, [src1], #1 + ldrb data2w, [src2], #1 + subs limit, limit, #1 + + ccmp data1w, data2w, #0, ne /* NZCV = 0b0000. */ + b.eq .Ltiny8proc + sub result, data1, data2 + ret +.Lret0: + mov result, #0 + ret +ENDPROC(memcmp) diff --git a/arch/arm64/lib/memcpy.S b/arch/arm64/lib/memcpy.S index 27b5003609b662..2f33230d179a20 100644 --- a/arch/arm64/lib/memcpy.S +++ b/arch/arm64/lib/memcpy.S @@ -1,5 +1,13 @@ /* * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2013 Linaro. + * + * This code is based on glibc cortex strings work originally authored by Linaro + * and re-licensed under GPLv2 for the Linux kernel. The original code can + * be found @ + * + * http://bazaar.launchpad.net/~linaro-toolchain-dev/cortex-strings/trunk/ + * files/head:/src/aarch64/ * * 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 @@ -27,27 +35,164 @@ * Returns: * x0 - dest */ +dstin .req x0 +src .req x1 +count .req x2 +tmp1 .req x3 +tmp1w .req w3 +tmp2 .req x4 +tmp2w .req w4 +tmp3 .req x5 +tmp3w .req w5 +dst .req x6 + +A_l .req x7 +A_h .req x8 +B_l .req x9 +B_h .req x10 +C_l .req x11 +C_h .req x12 +D_l .req x13 +D_h .req x14 + ENTRY(memcpy) - mov x4, x0 - subs x2, x2, #8 - b.mi 2f -1: ldr x3, [x1], #8 - subs x2, x2, #8 - str x3, [x4], #8 - b.pl 1b -2: adds x2, x2, #4 - b.mi 3f - ldr w3, [x1], #4 - sub x2, x2, #4 - str w3, [x4], #4 -3: adds x2, x2, #2 - b.mi 4f - ldrh w3, [x1], #2 - sub x2, x2, #2 - strh w3, [x4], #2 -4: adds x2, x2, #1 - b.mi 5f - ldrb w3, [x1] - strb w3, [x4] -5: ret + mov dst, dstin + cmp count, #16 + /*When memory length is less than 16, the accessed are not aligned.*/ + b.lo .Ltiny15 + + neg tmp2, src + ands tmp2, tmp2, #15/* Bytes to reach alignment. */ + b.eq .LSrcAligned + sub count, count, tmp2 + /* + * Copy the leading memory data from src to dst in an increasing + * address order.By this way,the risk of overwritting the source + * memory data is eliminated when the distance between src and + * dst is not less than 16. The memory accesses here are + * alignment. + */ + tbz tmp2, #0, 1f + ldrb tmp1w, [src], #1 + strb tmp1w, [dst], #1 +1: + tbz tmp2, #1, 2f + ldrh tmp1w, [src], #2 + strh tmp1w, [dst], #2 +2: + tbz tmp2, #2, 3f + ldr tmp1w, [src], #4 + str tmp1w, [dst], #4 +3: + tbz tmp2, #3, .LSrcAligned + ldr tmp1, [src],#8 + str tmp1, [dst],#8 + +.LSrcAligned: + cmp count, #64 + b.ge .Lcpy_over64 + /* + * Deal with small copies quickly by dropping straight into the + * exit block. + */ +.Ltail63: + /* + * Copy up to 48 bytes of data. At this point we only need the + * bottom 6 bits of count to be accurate. + */ + ands tmp1, count, #0x30 + b.eq .Ltiny15 + cmp tmp1w, #0x20 + b.eq 1f + b.lt 2f + ldp A_l, A_h, [src], #16 + stp A_l, A_h, [dst], #16 +1: + ldp A_l, A_h, [src], #16 + stp A_l, A_h, [dst], #16 +2: + ldp A_l, A_h, [src], #16 + stp A_l, A_h, [dst], #16 +.Ltiny15: + /* + * To make memmove simpler, we prefer to break one ldp/stp into + * several load/store to access memory in an increasing address order, + * rather than load/store 16 bytes just as the old cortex memcpy. + * The process in original cortex need to satisfy the procondition + * that the src address is at least 16 bytes bigger than dst address, + * otherwise some source data will be overwritten. + */ + tbz count, #3, 1f + ldr tmp1, [src], #8 + str tmp1, [dst], #8 +1: + tbz count, #2, 2f + ldr tmp1w, [src], #4 + str tmp1w, [dst], #4 +2: + tbz count, #1, 3f + ldrh tmp1w, [src], #2 + strh tmp1w, [dst], #2 +3: + tbz count, #0, .Lexitfunc + ldrb tmp1w, [src] + strb tmp1w, [dst] + +.Lexitfunc: + ret + +.Lcpy_over64: + subs count, count, #128 + b.ge .Lcpy_body_large + /* + * Less than 128 bytes to copy, so handle 64 here and then jump + * to the tail. + */ + ldp A_l, A_h, [src],#16 + stp A_l, A_h, [dst],#16 + ldp B_l, B_h, [src],#16 + ldp C_l, C_h, [src],#16 + stp B_l, B_h, [dst],#16 + stp C_l, C_h, [dst],#16 + ldp D_l, D_h, [src],#16 + stp D_l, D_h, [dst],#16 + + tst count, #0x3f + b.ne .Ltail63 + ret + + /* + * Critical loop. Start at a new cache line boundary. Assuming + * 64 bytes per line this ensures the entire loop is in one line. + */ + .p2align 6 +.Lcpy_body_large: + /* pre-get 64 bytes data. */ + ldp A_l, A_h, [src],#16 + ldp B_l, B_h, [src],#16 + ldp C_l, C_h, [src],#16 + ldp D_l, D_h, [src],#16 +1: + /* + * interlace the load of next 64 bytes data block with store of the last + * loaded 64 bytes data. + */ + stp A_l, A_h, [dst],#16 + ldp A_l, A_h, [src],#16 + stp B_l, B_h, [dst],#16 + ldp B_l, B_h, [src],#16 + stp C_l, C_h, [dst],#16 + ldp C_l, C_h, [src],#16 + stp D_l, D_h, [dst],#16 + ldp D_l, D_h, [src],#16 + subs count, count, #64 + b.ge 1b + stp A_l, A_h, [dst],#16 + stp B_l, B_h, [dst],#16 + stp C_l, C_h, [dst],#16 + stp D_l, D_h, [dst],#16 + + tst count, #0x3f + b.ne .Ltail63 + ret ENDPROC(memcpy) diff --git a/arch/arm64/lib/memmove.S b/arch/arm64/lib/memmove.S index b79fdfa42d3979..5a0a440aeccca5 100644 --- a/arch/arm64/lib/memmove.S +++ b/arch/arm64/lib/memmove.S @@ -1,5 +1,13 @@ /* * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2013 Linaro. + * + * This code is based on glibc cortex strings work originally authored by Linaro + * and re-licensed under GPLv2 for the Linux kernel. The original code can + * be found @ + * + * http://bazaar.launchpad.net/~linaro-toolchain-dev/cortex-strings/trunk/ + * files/head:/src/aarch64/ * * 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 @@ -28,30 +36,161 @@ * Returns: * x0 - dest */ +dstin .req x0 +src .req x1 +count .req x2 +tmp1 .req x3 +tmp1w .req w3 +tmp2 .req x4 +tmp2w .req w4 +tmp3 .req x5 +tmp3w .req w5 +dst .req x6 + +A_l .req x7 +A_h .req x8 +B_l .req x9 +B_h .req x10 +C_l .req x11 +C_h .req x12 +D_l .req x13 +D_h .req x14 + ENTRY(memmove) - cmp x0, x1 - b.ls memcpy - add x4, x0, x2 - add x1, x1, x2 - subs x2, x2, #8 - b.mi 2f -1: ldr x3, [x1, #-8]! - subs x2, x2, #8 - str x3, [x4, #-8]! - b.pl 1b -2: adds x2, x2, #4 - b.mi 3f - ldr w3, [x1, #-4]! - sub x2, x2, #4 - str w3, [x4, #-4]! -3: adds x2, x2, #2 - b.mi 4f - ldrh w3, [x1, #-2]! - sub x2, x2, #2 - strh w3, [x4, #-2]! -4: adds x2, x2, #1 - b.mi 5f - ldrb w3, [x1, #-1] - strb w3, [x4, #-1] -5: ret + cmp dstin, src + b.lo memcpy + add tmp1, src, count + cmp dstin, tmp1 + b.hs memcpy /* No overlap. */ + + add dst, dstin, count + add src, src, count + cmp count, #16 + b.lo .Ltail15 /*probably non-alignment accesses.*/ + + ands tmp2, src, #15 /* Bytes to reach alignment. */ + b.eq .LSrcAligned + sub count, count, tmp2 + /* + * process the aligned offset length to make the src aligned firstly. + * those extra instructions' cost is acceptable. It also make the + * coming accesses are based on aligned address. + */ + tbz tmp2, #0, 1f + ldrb tmp1w, [src, #-1]! + strb tmp1w, [dst, #-1]! +1: + tbz tmp2, #1, 2f + ldrh tmp1w, [src, #-2]! + strh tmp1w, [dst, #-2]! +2: + tbz tmp2, #2, 3f + ldr tmp1w, [src, #-4]! + str tmp1w, [dst, #-4]! +3: + tbz tmp2, #3, .LSrcAligned + ldr tmp1, [src, #-8]! + str tmp1, [dst, #-8]! + +.LSrcAligned: + cmp count, #64 + b.ge .Lcpy_over64 + + /* + * Deal with small copies quickly by dropping straight into the + * exit block. + */ +.Ltail63: + /* + * Copy up to 48 bytes of data. At this point we only need the + * bottom 6 bits of count to be accurate. + */ + ands tmp1, count, #0x30 + b.eq .Ltail15 + cmp tmp1w, #0x20 + b.eq 1f + b.lt 2f + ldp A_l, A_h, [src, #-16]! + stp A_l, A_h, [dst, #-16]! +1: + ldp A_l, A_h, [src, #-16]! + stp A_l, A_h, [dst, #-16]! +2: + ldp A_l, A_h, [src, #-16]! + stp A_l, A_h, [dst, #-16]! + +.Ltail15: + tbz count, #3, 1f + ldr tmp1, [src, #-8]! + str tmp1, [dst, #-8]! +1: + tbz count, #2, 2f + ldr tmp1w, [src, #-4]! + str tmp1w, [dst, #-4]! +2: + tbz count, #1, 3f + ldrh tmp1w, [src, #-2]! + strh tmp1w, [dst, #-2]! +3: + tbz count, #0, .Lexitfunc + ldrb tmp1w, [src, #-1] + strb tmp1w, [dst, #-1] + +.Lexitfunc: + ret + +.Lcpy_over64: + subs count, count, #128 + b.ge .Lcpy_body_large + /* + * Less than 128 bytes to copy, so handle 64 here and then jump + * to the tail. + */ + ldp A_l, A_h, [src, #-16] + stp A_l, A_h, [dst, #-16] + ldp B_l, B_h, [src, #-32] + ldp C_l, C_h, [src, #-48] + stp B_l, B_h, [dst, #-32] + stp C_l, C_h, [dst, #-48] + ldp D_l, D_h, [src, #-64]! + stp D_l, D_h, [dst, #-64]! + + tst count, #0x3f + b.ne .Ltail63 + ret + + /* + * Critical loop. Start at a new cache line boundary. Assuming + * 64 bytes per line this ensures the entire loop is in one line. + */ + .p2align 6 +.Lcpy_body_large: + /* pre-load 64 bytes data. */ + ldp A_l, A_h, [src, #-16] + ldp B_l, B_h, [src, #-32] + ldp C_l, C_h, [src, #-48] + ldp D_l, D_h, [src, #-64]! +1: + /* + * interlace the load of next 64 bytes data block with store of the last + * loaded 64 bytes data. + */ + stp A_l, A_h, [dst, #-16] + ldp A_l, A_h, [src, #-16] + stp B_l, B_h, [dst, #-32] + ldp B_l, B_h, [src, #-32] + stp C_l, C_h, [dst, #-48] + ldp C_l, C_h, [src, #-48] + stp D_l, D_h, [dst, #-64]! + ldp D_l, D_h, [src, #-64]! + subs count, count, #64 + b.ge 1b + stp A_l, A_h, [dst, #-16] + stp B_l, B_h, [dst, #-32] + stp C_l, C_h, [dst, #-48] + stp D_l, D_h, [dst, #-64]! + + tst count, #0x3f + b.ne .Ltail63 + ret ENDPROC(memmove) diff --git a/arch/arm64/lib/memset.S b/arch/arm64/lib/memset.S index 87e4a68fbbbcd8..e02f5faadacff2 100644 --- a/arch/arm64/lib/memset.S +++ b/arch/arm64/lib/memset.S @@ -1,5 +1,13 @@ /* * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2013 Linaro. + * + * This code is based on glibc cortex strings work originally authored by Linaro + * and re-licensed under GPLv2 for the Linux kernel. The original code can + * be found @ + * + * http://bazaar.launchpad.net/~linaro-toolchain-dev/cortex-strings/trunk/ + * files/head:/src/aarch64/ * * 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 @@ -27,27 +35,181 @@ * Returns: * x0 - buf */ + +dstin .req x0 +val .req w1 +count .req x2 +tmp1 .req x3 +tmp1w .req w3 +tmp2 .req x4 +tmp2w .req w4 +zva_len_x .req x5 +zva_len .req w5 +zva_bits_x .req x6 + +A_l .req x7 +A_lw .req w7 +dst .req x8 +tmp3w .req w9 +tmp3 .req x9 + ENTRY(memset) - mov x4, x0 - and w1, w1, #0xff - orr w1, w1, w1, lsl #8 - orr w1, w1, w1, lsl #16 - orr x1, x1, x1, lsl #32 - subs x2, x2, #8 - b.mi 2f -1: str x1, [x4], #8 - subs x2, x2, #8 - b.pl 1b -2: adds x2, x2, #4 - b.mi 3f - sub x2, x2, #4 - str w1, [x4], #4 -3: adds x2, x2, #2 - b.mi 4f - sub x2, x2, #2 - strh w1, [x4], #2 -4: adds x2, x2, #1 - b.mi 5f - strb w1, [x4] -5: ret + mov dst, dstin /* Preserve return value. */ + and A_lw, val, #255 + orr A_lw, A_lw, A_lw, lsl #8 + orr A_lw, A_lw, A_lw, lsl #16 + orr A_l, A_l, A_l, lsl #32 + + cmp count, #15 + b.hi .Lover16_proc + /*All store maybe are non-aligned..*/ + tbz count, #3, 1f + str A_l, [dst], #8 +1: + tbz count, #2, 2f + str A_lw, [dst], #4 +2: + tbz count, #1, 3f + strh A_lw, [dst], #2 +3: + tbz count, #0, 4f + strb A_lw, [dst] +4: + ret + +.Lover16_proc: + /*Whether the start address is with 16.*/ + neg tmp2, dst + ands tmp2, tmp2, #15 + b.eq .Laligned +/* +* The count is not less than 16, we can use stp to store the start 16 bytes, +* then adjust the dst aligned with 16.This process will make the current +* memory address at alignment boundary now. +*/ + stp A_l, A_l, [dst] /*non-aligned store..*/ + /*make the dst aligned..*/ + sub count, count, tmp2 + add dst, dst, tmp2 + +.Laligned: + cbz A_l, .Lzero_mem + +.Ltail_maybe_long: + cmp count, #64 + b.ge .Lnot_short +.Ltail63: + ands tmp1, count, #0x30 + b.eq 3f + cmp tmp1w, #0x20 + b.eq 1f + b.lt 2f + stp A_l, A_l, [dst], #16 +1: + stp A_l, A_l, [dst], #16 +2: + stp A_l, A_l, [dst], #16 +/* +* The last store length is less than 16,use stp to write last 16 bytes. +* It will lead some bytes written twice and the access is non-aligned. +*/ +3: + ands count, count, #15 + cbz count, 4f + add dst, dst, count + stp A_l, A_l, [dst, #-16] /* Repeat some/all of last store. */ +4: + ret + + /* + * Critical loop. Start at a new cache line boundary. Assuming + * 64 bytes per line, this ensures the entire loop is in one line. + */ + .p2align 6 +.Lnot_short: + sub dst, dst, #16/* Pre-bias. */ + sub count, count, #64 +1: + stp A_l, A_l, [dst, #16] + stp A_l, A_l, [dst, #32] + stp A_l, A_l, [dst, #48] + stp A_l, A_l, [dst, #64]! + subs count, count, #64 + b.ge 1b + tst count, #0x3f + add dst, dst, #16 + b.ne .Ltail63 +.Lexitfunc: + ret + + /* + * For zeroing memory, check to see if we can use the ZVA feature to + * zero entire 'cache' lines. + */ +.Lzero_mem: + cmp count, #63 + b.le .Ltail63 + /* + * For zeroing small amounts of memory, it's not worth setting up + * the line-clear code. + */ + cmp count, #128 + b.lt .Lnot_short /*count is at least 128 bytes*/ + + mrs tmp1, dczid_el0 + tbnz tmp1, #4, .Lnot_short + mov tmp3w, #4 + and zva_len, tmp1w, #15 /* Safety: other bits reserved. */ + lsl zva_len, tmp3w, zva_len + + ands tmp3w, zva_len, #63 + /* + * ensure the zva_len is not less than 64. + * It is not meaningful to use ZVA if the block size is less than 64. + */ + b.ne .Lnot_short +.Lzero_by_line: + /* + * Compute how far we need to go to become suitably aligned. We're + * already at quad-word alignment. + */ + cmp count, zva_len_x + b.lt .Lnot_short /* Not enough to reach alignment. */ + sub zva_bits_x, zva_len_x, #1 + neg tmp2, dst + ands tmp2, tmp2, zva_bits_x + b.eq 2f /* Already aligned. */ + /* Not aligned, check that there's enough to copy after alignment.*/ + sub tmp1, count, tmp2 + /* + * grantee the remain length to be ZVA is bigger than 64, + * avoid to make the 2f's process over mem range.*/ + cmp tmp1, #64 + ccmp tmp1, zva_len_x, #8, ge /* NZCV=0b1000 */ + b.lt .Lnot_short + /* + * We know that there's at least 64 bytes to zero and that it's safe + * to overrun by 64 bytes. + */ + mov count, tmp1 +1: + stp A_l, A_l, [dst] + stp A_l, A_l, [dst, #16] + stp A_l, A_l, [dst, #32] + subs tmp2, tmp2, #64 + stp A_l, A_l, [dst, #48] + add dst, dst, #64 + b.ge 1b + /* We've overrun a bit, so adjust dst downwards.*/ + add dst, dst, tmp2 +2: + sub count, count, zva_len_x +3: + dc zva, dst + add dst, dst, zva_len_x + subs count, count, zva_len_x + b.ge 3b + ands count, count, zva_bits_x + b.ne .Ltail_maybe_long + ret ENDPROC(memset) diff --git a/arch/arm64/lib/strcmp.S b/arch/arm64/lib/strcmp.S new file mode 100644 index 00000000000000..f50b8f0f7fbf94 --- /dev/null +++ b/arch/arm64/lib/strcmp.S @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2013 Linaro. + * + * This code is based on glibc cortex strings work originally authored by Linaro + * and re-licensed under GPLv2 for the Linux kernel. The original code can + * be found @ + * + * http://bazaar.launchpad.net/~linaro-toolchain-dev/cortex-strings/trunk/ + * files/head:/src/aarch64/ + * + * 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 . + */ + +#include +#include + +/* + * compare two strings + * + * Parameters: + * x0 - const string 1 pointer + * x1 - const string 2 pointer + * Returns: + * x0 - an integer less than, equal to, or greater than zero + * if s1 is found, respectively, to be less than, to match, + * or be greater than s2. + */ + +#define REP8_01 0x0101010101010101 +#define REP8_7f 0x7f7f7f7f7f7f7f7f +#define REP8_80 0x8080808080808080 + +/* Parameters and result. */ +src1 .req x0 +src2 .req x1 +result .req x0 + +/* Internal variables. */ +data1 .req x2 +data1w .req w2 +data2 .req x3 +data2w .req w3 +has_nul .req x4 +diff .req x5 +syndrome .req x6 +tmp1 .req x7 +tmp2 .req x8 +tmp3 .req x9 +zeroones .req x10 +pos .req x11 + +ENTRY(strcmp) + eor tmp1, src1, src2 + mov zeroones, #REP8_01 + tst tmp1, #7 + b.ne .Lmisaligned8 + ands tmp1, src1, #7 + b.ne .Lmutual_align + + /* + * NUL detection works on the principle that (X - 1) & (~X) & 0x80 + * (=> (X - 1) & ~(X | 0x7f)) is non-zero iff a byte is zero, and + * can be done in parallel across the entire word. + */ +.Lloop_aligned: + ldr data1, [src1], #8 + ldr data2, [src2], #8 +.Lstart_realigned: + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + eor diff, data1, data2 /* Non-zero if differences found. */ + bic has_nul, tmp1, tmp2 /* Non-zero if NUL terminator. */ + orr syndrome, diff, has_nul + cbz syndrome, .Lloop_aligned + b .Lcal_cmpresult + +.Lmutual_align: + /* + * Sources are mutually aligned, but are not currently at an + * alignment boundary. Round down the addresses and then mask off + * the bytes that preceed the start point. + */ + bic src1, src1, #7 + bic src2, src2, #7 + lsl tmp1, tmp1, #3 /* Bytes beyond alignment -> bits. */ + ldr data1, [src1], #8 + neg tmp1, tmp1 /* Bits to alignment -64. */ + ldr data2, [src2], #8 + mov tmp2, #~0 + /* Big-endian. Early bytes are at MSB. */ +CPU_BE( lsl tmp2, tmp2, tmp1 ) /* Shift (tmp1 & 63). */ + /* Little-endian. Early bytes are at LSB. */ +CPU_LE( lsr tmp2, tmp2, tmp1 ) /* Shift (tmp1 & 63). */ + + orr data1, data1, tmp2 + orr data2, data2, tmp2 + b .Lstart_realigned + +.Lmisaligned8: + /* + * Get the align offset length to compare per byte first. + * After this process, one string's address will be aligned. + */ + and tmp1, src1, #7 + neg tmp1, tmp1 + add tmp1, tmp1, #8 + and tmp2, src2, #7 + neg tmp2, tmp2 + add tmp2, tmp2, #8 + subs tmp3, tmp1, tmp2 + csel pos, tmp1, tmp2, hi /*Choose the maximum. */ +.Ltinycmp: + ldrb data1w, [src1], #1 + ldrb data2w, [src2], #1 + subs pos, pos, #1 + ccmp data1w, #1, #0, ne /* NZCV = 0b0000. */ + ccmp data1w, data2w, #0, cs /* NZCV = 0b0000. */ + b.eq .Ltinycmp + cbnz pos, 1f /*find the null or unequal...*/ + cmp data1w, #1 + ccmp data1w, data2w, #0, cs + b.eq .Lstart_align /*the last bytes are equal....*/ +1: + sub result, data1, data2 + ret + +.Lstart_align: + ands xzr, src1, #7 + b.eq .Lrecal_offset + /*process more leading bytes to make str1 aligned...*/ + add src1, src1, tmp3 + add src2, src2, tmp3 + /*load 8 bytes from aligned str1 and non-aligned str2..*/ + ldr data1, [src1], #8 + ldr data2, [src2], #8 + + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + bic has_nul, tmp1, tmp2 + eor diff, data1, data2 /* Non-zero if differences found. */ + orr syndrome, diff, has_nul + cbnz syndrome, .Lcal_cmpresult + /*How far is the current str2 from the alignment boundary...*/ + and tmp3, tmp3, #7 +.Lrecal_offset: + neg pos, tmp3 +.Lloopcmp_proc: + /* + * Divide the eight bytes into two parts. First,backwards the src2 + * to an alignment boundary,load eight bytes from the SRC2 alignment + * boundary,then compare with the relative bytes from SRC1. + * If all 8 bytes are equal,then start the second part's comparison. + * Otherwise finish the comparison. + * This special handle can garantee all the accesses are in the + * thread/task space. + */ + ldr data1, [src1,pos] + ldr data2, [src2,pos] + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + bic has_nul, tmp1, tmp2 + eor diff, data1, data2 /* Non-zero if differences found. */ + orr syndrome, diff, has_nul + cbnz syndrome, .Lcal_cmpresult + + /*The second part process*/ + ldr data1, [src1], #8 + ldr data2, [src2], #8 + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + bic has_nul, tmp1, tmp2 + eor diff, data1, data2 /* Non-zero if differences found. */ + orr syndrome, diff, has_nul + cbz syndrome, .Lloopcmp_proc + +.Lcal_cmpresult: + /* + * reversed the byte-order as big-endian,then CLZ can find the most + * significant zero bits. + */ +CPU_LE( rev syndrome, syndrome ) +CPU_LE( rev data1, data1 ) +CPU_LE( rev data2, data2 ) + + /* + * For big-endian we cannot use the trick with the syndrome value + * as carry-propagation can corrupt the upper bits if the trailing + * bytes in the string contain 0x01. + * However, if there is no NUL byte in the dword, we can generate + * the result directly. We ca not just subtract the bytes as the + * MSB might be significant. + */ +CPU_BE( cbnz has_nul, 1f ) +CPU_BE( cmp data1, data2 ) +CPU_BE( cset result, ne ) +CPU_BE( cneg result, result, lo ) +CPU_BE( ret ) +CPU_BE( 1: ) + /*Re-compute the NUL-byte detection, using a byte-reversed value. */ +CPU_BE( rev tmp3, data1 ) +CPU_BE( sub tmp1, tmp3, zeroones ) +CPU_BE( orr tmp2, tmp3, #REP8_7f ) +CPU_BE( bic has_nul, tmp1, tmp2 ) +CPU_BE( rev has_nul, has_nul ) +CPU_BE( orr syndrome, diff, has_nul ) + + clz pos, syndrome + /* + * The MS-non-zero bit of the syndrome marks either the first bit + * that is different, or the top bit of the first zero byte. + * Shifting left now will bring the critical information into the + * top bits. + */ + lsl data1, data1, pos + lsl data2, data2, pos + /* + * But we need to zero-extend (char is unsigned) the value and then + * perform a signed 32-bit subtraction. + */ + lsr data1, data1, #56 + sub result, data1, data2, lsr #56 + ret +ENDPROC(strcmp) diff --git a/arch/arm64/lib/strlen.S b/arch/arm64/lib/strlen.S new file mode 100644 index 00000000000000..987b68b9ce4474 --- /dev/null +++ b/arch/arm64/lib/strlen.S @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2013 Linaro. + * + * This code is based on glibc cortex strings work originally authored by Linaro + * and re-licensed under GPLv2 for the Linux kernel. The original code can + * be found @ + * + * http://bazaar.launchpad.net/~linaro-toolchain-dev/cortex-strings/trunk/ + * files/head:/src/aarch64/ + * + * 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 . + */ + +#include +#include + +/* + * calculate the length of a string + * + * Parameters: + * x0 - const string pointer + * Returns: + * x0 - the return length of specific string + */ + +/* Arguments and results. */ +srcin .req x0 +len .req x0 + +/* Locals and temporaries. */ +src .req x1 +data1 .req x2 +data2 .req x3 +data2a .req x4 +has_nul1 .req x5 +has_nul2 .req x6 +tmp1 .req x7 +tmp2 .req x8 +tmp3 .req x9 +tmp4 .req x10 +zeroones .req x11 +pos .req x12 + +#define REP8_01 0x0101010101010101 +#define REP8_7f 0x7f7f7f7f7f7f7f7f +#define REP8_80 0x8080808080808080 + +ENTRY(strlen) + mov zeroones, #REP8_01 + bic src, srcin, #15 + ands tmp1, srcin, #15 + b.ne .Lmisaligned + /* + * NUL detection works on the principle that (X - 1) & (~X) & 0x80 + * (=> (X - 1) & ~(X | 0x7f)) is non-zero iff a byte is zero, and + * can be done in parallel across the entire word. + */ + /* + * The inner loop deals with two Dwords at a time. This has a + * slightly higher start-up cost, but we should win quite quickly, + * especially on cores with a high number of issue slots per + * cycle, as we get much better parallelism out of the operations. + */ +.Lloop: + ldp data1, data2, [src], #16 +.Lrealigned: + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + sub tmp3, data2, zeroones + orr tmp4, data2, #REP8_7f + bic has_nul1, tmp1, tmp2 + bics has_nul2, tmp3, tmp4 + ccmp has_nul1, #0, #0, eq /* NZCV = 0000 */ + b.eq .Lloop + + sub len, src, srcin + cbz has_nul1, .Lnul_in_data2 +CPU_BE( mov data2, data1 ) /*prepare data to re-calculate the syndrome*/ + sub len, len, #8 + mov has_nul2, has_nul1 +.Lnul_in_data2: + /* + * For big-endian, carry propagation (if the final byte in the + * string is 0x01) means we cannot use has_nul directly. The + * easiest way to get the correct byte is to byte-swap the data + * and calculate the syndrome a second time. + */ +CPU_BE( rev data2, data2 ) +CPU_BE( sub tmp1, data2, zeroones ) +CPU_BE( orr tmp2, data2, #REP8_7f ) +CPU_BE( bic has_nul2, tmp1, tmp2 ) + + sub len, len, #8 + rev has_nul2, has_nul2 + clz pos, has_nul2 + add len, len, pos, lsr #3 /* Bits to bytes. */ + ret + +.Lmisaligned: + cmp tmp1, #8 + neg tmp1, tmp1 + ldp data1, data2, [src], #16 + lsl tmp1, tmp1, #3 /* Bytes beyond alignment -> bits. */ + mov tmp2, #~0 + /* Big-endian. Early bytes are at MSB. */ +CPU_BE( lsl tmp2, tmp2, tmp1 ) /* Shift (tmp1 & 63). */ + /* Little-endian. Early bytes are at LSB. */ +CPU_LE( lsr tmp2, tmp2, tmp1 ) /* Shift (tmp1 & 63). */ + + orr data1, data1, tmp2 + orr data2a, data2, tmp2 + csinv data1, data1, xzr, le + csel data2, data2, data2a, le + b .Lrealigned +ENDPROC(strlen) diff --git a/arch/arm64/lib/strncmp.S b/arch/arm64/lib/strncmp.S new file mode 100644 index 00000000000000..81c8441e9663c4 --- /dev/null +++ b/arch/arm64/lib/strncmp.S @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2013 Linaro. + * + * This code is based on glibc cortex strings work originally authored by Linaro + * and re-licensed under GPLv2 for the Linux kernel. The original code can + * be found @ + * + * http://bazaar.launchpad.net/~linaro-toolchain-dev/cortex-strings/trunk/ + * files/head:/src/aarch64/ + * + * 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 . + */ + +#include +#include + +/* + * compare two strings + * + * Parameters: + * x0 - const string 1 pointer + * x1 - const string 2 pointer + * x2 - the maximal length to be compared + * Returns: + * x0 - an integer less than, equal to, or greater than zero if s1 is found, + * respectively, to be less than, to match, or be greater than s2. + */ + +#define REP8_01 0x0101010101010101 +#define REP8_7f 0x7f7f7f7f7f7f7f7f +#define REP8_80 0x8080808080808080 + +/* Parameters and result. */ +src1 .req x0 +src2 .req x1 +limit .req x2 +result .req x0 + +/* Internal variables. */ +data1 .req x3 +data1w .req w3 +data2 .req x4 +data2w .req w4 +has_nul .req x5 +diff .req x6 +syndrome .req x7 +tmp1 .req x8 +tmp2 .req x9 +tmp3 .req x10 +zeroones .req x11 +pos .req x12 +limit_wd .req x13 +mask .req x14 +endloop .req x15 + +ENTRY(strncmp) + cbz limit, .Lret0 + eor tmp1, src1, src2 + mov zeroones, #REP8_01 + tst tmp1, #7 + b.ne .Lmisaligned8 + ands tmp1, src1, #7 + b.ne .Lmutual_align + /* Calculate the number of full and partial words -1. */ + /* + * when limit is mulitply of 8, if not sub 1, + * the judgement of last dword will wrong. + */ + sub limit_wd, limit, #1 /* limit != 0, so no underflow. */ + lsr limit_wd, limit_wd, #3 /* Convert to Dwords. */ + + /* + * NUL detection works on the principle that (X - 1) & (~X) & 0x80 + * (=> (X - 1) & ~(X | 0x7f)) is non-zero iff a byte is zero, and + * can be done in parallel across the entire word. + */ +.Lloop_aligned: + ldr data1, [src1], #8 + ldr data2, [src2], #8 +.Lstart_realigned: + subs limit_wd, limit_wd, #1 + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + eor diff, data1, data2 /* Non-zero if differences found. */ + csinv endloop, diff, xzr, pl /* Last Dword or differences.*/ + bics has_nul, tmp1, tmp2 /* Non-zero if NUL terminator. */ + ccmp endloop, #0, #0, eq + b.eq .Lloop_aligned + + /*Not reached the limit, must have found the end or a diff. */ + tbz limit_wd, #63, .Lnot_limit + + /* Limit % 8 == 0 => all bytes significant. */ + ands limit, limit, #7 + b.eq .Lnot_limit + + lsl limit, limit, #3 /* Bits -> bytes. */ + mov mask, #~0 +CPU_BE( lsr mask, mask, limit ) +CPU_LE( lsl mask, mask, limit ) + bic data1, data1, mask + bic data2, data2, mask + + /* Make sure that the NUL byte is marked in the syndrome. */ + orr has_nul, has_nul, mask + +.Lnot_limit: + orr syndrome, diff, has_nul + b .Lcal_cmpresult + +.Lmutual_align: + /* + * Sources are mutually aligned, but are not currently at an + * alignment boundary. Round down the addresses and then mask off + * the bytes that precede the start point. + * We also need to adjust the limit calculations, but without + * overflowing if the limit is near ULONG_MAX. + */ + bic src1, src1, #7 + bic src2, src2, #7 + ldr data1, [src1], #8 + neg tmp3, tmp1, lsl #3 /* 64 - bits(bytes beyond align). */ + ldr data2, [src2], #8 + mov tmp2, #~0 + sub limit_wd, limit, #1 /* limit != 0, so no underflow. */ + /* Big-endian. Early bytes are at MSB. */ +CPU_BE( lsl tmp2, tmp2, tmp3 ) /* Shift (tmp1 & 63). */ + /* Little-endian. Early bytes are at LSB. */ +CPU_LE( lsr tmp2, tmp2, tmp3 ) /* Shift (tmp1 & 63). */ + + and tmp3, limit_wd, #7 + lsr limit_wd, limit_wd, #3 + /* Adjust the limit. Only low 3 bits used, so overflow irrelevant.*/ + add limit, limit, tmp1 + add tmp3, tmp3, tmp1 + orr data1, data1, tmp2 + orr data2, data2, tmp2 + add limit_wd, limit_wd, tmp3, lsr #3 + b .Lstart_realigned + +/*when src1 offset is not equal to src2 offset...*/ +.Lmisaligned8: + cmp limit, #8 + b.lo .Ltiny8proc /*limit < 8... */ + /* + * Get the align offset length to compare per byte first. + * After this process, one string's address will be aligned.*/ + and tmp1, src1, #7 + neg tmp1, tmp1 + add tmp1, tmp1, #8 + and tmp2, src2, #7 + neg tmp2, tmp2 + add tmp2, tmp2, #8 + subs tmp3, tmp1, tmp2 + csel pos, tmp1, tmp2, hi /*Choose the maximum. */ + /* + * Here, limit is not less than 8, so directly run .Ltinycmp + * without checking the limit.*/ + sub limit, limit, pos +.Ltinycmp: + ldrb data1w, [src1], #1 + ldrb data2w, [src2], #1 + subs pos, pos, #1 + ccmp data1w, #1, #0, ne /* NZCV = 0b0000. */ + ccmp data1w, data2w, #0, cs /* NZCV = 0b0000. */ + b.eq .Ltinycmp + cbnz pos, 1f /*find the null or unequal...*/ + cmp data1w, #1 + ccmp data1w, data2w, #0, cs + b.eq .Lstart_align /*the last bytes are equal....*/ +1: + sub result, data1, data2 + ret + +.Lstart_align: + lsr limit_wd, limit, #3 + cbz limit_wd, .Lremain8 + /*process more leading bytes to make str1 aligned...*/ + ands xzr, src1, #7 + b.eq .Lrecal_offset + add src1, src1, tmp3 /*tmp3 is positive in this branch.*/ + add src2, src2, tmp3 + ldr data1, [src1], #8 + ldr data2, [src2], #8 + + sub limit, limit, tmp3 + lsr limit_wd, limit, #3 + subs limit_wd, limit_wd, #1 + + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + eor diff, data1, data2 /* Non-zero if differences found. */ + csinv endloop, diff, xzr, ne/*if limit_wd is 0,will finish the cmp*/ + bics has_nul, tmp1, tmp2 + ccmp endloop, #0, #0, eq /*has_null is ZERO: no null byte*/ + b.ne .Lunequal_proc + /*How far is the current str2 from the alignment boundary...*/ + and tmp3, tmp3, #7 +.Lrecal_offset: + neg pos, tmp3 +.Lloopcmp_proc: + /* + * Divide the eight bytes into two parts. First,backwards the src2 + * to an alignment boundary,load eight bytes from the SRC2 alignment + * boundary,then compare with the relative bytes from SRC1. + * If all 8 bytes are equal,then start the second part's comparison. + * Otherwise finish the comparison. + * This special handle can garantee all the accesses are in the + * thread/task space. + */ + ldr data1, [src1,pos] + ldr data2, [src2,pos] + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + bics has_nul, tmp1, tmp2 /* Non-zero if NUL terminator. */ + eor diff, data1, data2 /* Non-zero if differences found. */ + csinv endloop, diff, xzr, eq + cbnz endloop, .Lunequal_proc + + /*The second part process*/ + ldr data1, [src1], #8 + ldr data2, [src2], #8 + subs limit_wd, limit_wd, #1 + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + eor diff, data1, data2 /* Non-zero if differences found. */ + csinv endloop, diff, xzr, ne/*if limit_wd is 0,will finish the cmp*/ + bics has_nul, tmp1, tmp2 + ccmp endloop, #0, #0, eq /*has_null is ZERO: no null byte*/ + b.eq .Lloopcmp_proc + +.Lunequal_proc: + orr syndrome, diff, has_nul + cbz syndrome, .Lremain8 +.Lcal_cmpresult: + /* + * reversed the byte-order as big-endian,then CLZ can find the most + * significant zero bits. + */ +CPU_LE( rev syndrome, syndrome ) +CPU_LE( rev data1, data1 ) +CPU_LE( rev data2, data2 ) + /* + * For big-endian we cannot use the trick with the syndrome value + * as carry-propagation can corrupt the upper bits if the trailing + * bytes in the string contain 0x01. + * However, if there is no NUL byte in the dword, we can generate + * the result directly. We can't just subtract the bytes as the + * MSB might be significant. + */ +CPU_BE( cbnz has_nul, 1f ) +CPU_BE( cmp data1, data2 ) +CPU_BE( cset result, ne ) +CPU_BE( cneg result, result, lo ) +CPU_BE( ret ) +CPU_BE( 1: ) + /* Re-compute the NUL-byte detection, using a byte-reversed value.*/ +CPU_BE( rev tmp3, data1 ) +CPU_BE( sub tmp1, tmp3, zeroones ) +CPU_BE( orr tmp2, tmp3, #REP8_7f ) +CPU_BE( bic has_nul, tmp1, tmp2 ) +CPU_BE( rev has_nul, has_nul ) +CPU_BE( orr syndrome, diff, has_nul ) + /* + * The MS-non-zero bit of the syndrome marks either the first bit + * that is different, or the top bit of the first zero byte. + * Shifting left now will bring the critical information into the + * top bits. + */ + clz pos, syndrome + lsl data1, data1, pos + lsl data2, data2, pos + /* + * But we need to zero-extend (char is unsigned) the value and then + * perform a signed 32-bit subtraction. + */ + lsr data1, data1, #56 + sub result, data1, data2, lsr #56 + ret + +.Lremain8: + /* Limit % 8 == 0 => all bytes significant. */ + ands limit, limit, #7 + b.eq .Lret0 +.Ltiny8proc: + ldrb data1w, [src1], #1 + ldrb data2w, [src2], #1 + subs limit, limit, #1 + + ccmp data1w, #1, #0, ne /* NZCV = 0b0000. */ + ccmp data1w, data2w, #0, cs /* NZCV = 0b0000. */ + b.eq .Ltiny8proc + sub result, data1, data2 + ret + +.Lret0: + mov result, #0 + ret +ENDPROC(strncmp) diff --git a/arch/arm64/lib/strnlen.S b/arch/arm64/lib/strnlen.S new file mode 100644 index 00000000000000..2ca665711bf21e --- /dev/null +++ b/arch/arm64/lib/strnlen.S @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2013 Linaro. + * + * This code is based on glibc cortex strings work originally authored by Linaro + * and re-licensed under GPLv2 for the Linux kernel. The original code can + * be found @ + * + * http://bazaar.launchpad.net/~linaro-toolchain-dev/cortex-strings/trunk/ + * files/head:/src/aarch64/ + * + * 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 . + */ + +#include +#include + +/* + * determine the length of a fixed-size string + * + * Parameters: + * x0 - const string pointer + * x1 - maximal string length + * Returns: + * x0 - the return length of specific string + */ + +/* Arguments and results. */ +srcin .req x0 +len .req x0 +limit .req x1 + +/* Locals and temporaries. */ +src .req x2 +data1 .req x3 +data2 .req x4 +data2a .req x5 +has_nul1 .req x6 +has_nul2 .req x7 +tmp1 .req x8 +tmp2 .req x9 +tmp3 .req x10 +tmp4 .req x11 +zeroones .req x12 +pos .req x13 +limit_wd .req x14 + +#define REP8_01 0x0101010101010101 +#define REP8_7f 0x7f7f7f7f7f7f7f7f +#define REP8_80 0x8080808080808080 + +ENTRY(strnlen) + cbz limit, .Lhit_limit + mov zeroones, #REP8_01 + bic src, srcin, #15 + ands tmp1, srcin, #15 + b.ne .Lmisaligned + /* Calculate the number of full and partial words -1. */ + sub limit_wd, limit, #1 /* Limit != 0, so no underflow. */ + lsr limit_wd, limit_wd, #4 /* Convert to Qwords. */ + + /* + * NUL detection works on the principle that (X - 1) & (~X) & 0x80 + * (=> (X - 1) & ~(X | 0x7f)) is non-zero iff a byte is zero, and + * can be done in parallel across the entire word. + */ + /* + * The inner loop deals with two Dwords at a time. This has a + * slightly higher start-up cost, but we should win quite quickly, + * especially on cores with a high number of issue slots per + * cycle, as we get much better parallelism out of the operations. + */ +.Lloop: + ldp data1, data2, [src], #16 +.Lrealigned: + sub tmp1, data1, zeroones + orr tmp2, data1, #REP8_7f + sub tmp3, data2, zeroones + orr tmp4, data2, #REP8_7f + bic has_nul1, tmp1, tmp2 + bic has_nul2, tmp3, tmp4 + subs limit_wd, limit_wd, #1 + orr tmp1, has_nul1, has_nul2 + ccmp tmp1, #0, #0, pl /* NZCV = 0000 */ + b.eq .Lloop + + cbz tmp1, .Lhit_limit /* No null in final Qword. */ + + /* + * We know there's a null in the final Qword. The easiest thing + * to do now is work out the length of the string and return + * MIN (len, limit). + */ + sub len, src, srcin + cbz has_nul1, .Lnul_in_data2 +CPU_BE( mov data2, data1 ) /*perpare data to re-calculate the syndrome*/ + + sub len, len, #8 + mov has_nul2, has_nul1 +.Lnul_in_data2: + /* + * For big-endian, carry propagation (if the final byte in the + * string is 0x01) means we cannot use has_nul directly. The + * easiest way to get the correct byte is to byte-swap the data + * and calculate the syndrome a second time. + */ +CPU_BE( rev data2, data2 ) +CPU_BE( sub tmp1, data2, zeroones ) +CPU_BE( orr tmp2, data2, #REP8_7f ) +CPU_BE( bic has_nul2, tmp1, tmp2 ) + + sub len, len, #8 + rev has_nul2, has_nul2 + clz pos, has_nul2 + add len, len, pos, lsr #3 /* Bits to bytes. */ + cmp len, limit + csel len, len, limit, ls /* Return the lower value. */ + ret + +.Lmisaligned: + /* + * Deal with a partial first word. + * We're doing two things in parallel here; + * 1) Calculate the number of words (but avoiding overflow if + * limit is near ULONG_MAX) - to do this we need to work out + * limit + tmp1 - 1 as a 65-bit value before shifting it; + * 2) Load and mask the initial data words - we force the bytes + * before the ones we are interested in to 0xff - this ensures + * early bytes will not hit any zero detection. + */ + ldp data1, data2, [src], #16 + + sub limit_wd, limit, #1 + and tmp3, limit_wd, #15 + lsr limit_wd, limit_wd, #4 + + add tmp3, tmp3, tmp1 + add limit_wd, limit_wd, tmp3, lsr #4 + + neg tmp4, tmp1 + lsl tmp4, tmp4, #3 /* Bytes beyond alignment -> bits. */ + + mov tmp2, #~0 + /* Big-endian. Early bytes are at MSB. */ +CPU_BE( lsl tmp2, tmp2, tmp4 ) /* Shift (tmp1 & 63). */ + /* Little-endian. Early bytes are at LSB. */ +CPU_LE( lsr tmp2, tmp2, tmp4 ) /* Shift (tmp1 & 63). */ + + cmp tmp1, #8 + + orr data1, data1, tmp2 + orr data2a, data2, tmp2 + + csinv data1, data1, xzr, le + csel data2, data2, data2a, le + b .Lrealigned + +.Lhit_limit: + mov len, limit + ret +ENDPROC(strnlen) diff --git a/arch/arm64/mm/cache.S b/arch/arm64/mm/cache.S index 1ea9f26d1b7035..c46f48b33c1409 100644 --- a/arch/arm64/mm/cache.S +++ b/arch/arm64/mm/cache.S @@ -30,7 +30,7 @@ * * Corrupted registers: x0-x7, x9-x11 */ -ENTRY(__flush_dcache_all) +__flush_dcache_all: dsb sy // ensure ordering with previous memory accesses mrs x0, clidr_el1 // read clidr and x3, x0, #0x7000000 // extract loc from clidr @@ -166,3 +166,81 @@ ENTRY(__flush_dcache_area) dsb sy ret ENDPROC(__flush_dcache_area) + +/* + * __dma_inv_range(start, end) + * - start - virtual start address of region + * - end - virtual end address of region + */ +__dma_inv_range: + dcache_line_size x2, x3 + sub x3, x2, #1 + bic x0, x0, x3 + bic x1, x1, x3 +1: dc ivac, x0 // invalidate D / U line + add x0, x0, x2 + cmp x0, x1 + b.lo 1b + dsb sy + ret +ENDPROC(__dma_inv_range) + +/* + * __dma_clean_range(start, end) + * - start - virtual start address of region + * - end - virtual end address of region + */ +__dma_clean_range: + dcache_line_size x2, x3 + sub x3, x2, #1 + bic x0, x0, x3 +1: dc cvac, x0 // clean D / U line + add x0, x0, x2 + cmp x0, x1 + b.lo 1b + dsb sy + ret +ENDPROC(__dma_clean_range) + +/* + * __dma_flush_range(start, end) + * - start - virtual start address of region + * - end - virtual end address of region + */ +ENTRY(__dma_flush_range) + dcache_line_size x2, x3 + sub x3, x2, #1 + bic x0, x0, x3 +1: dc civac, x0 // clean & invalidate D / U line + add x0, x0, x2 + cmp x0, x1 + b.lo 1b + dsb sy + ret +ENDPROC(__dma_flush_range) + +/* + * __dma_map_area(start, size, dir) + * - start - kernel virtual start address + * - size - size of region + * - dir - DMA direction + */ +ENTRY(__dma_map_area) + add x1, x1, x0 + cmp w2, #DMA_FROM_DEVICE + b.eq __dma_inv_range + b __dma_clean_range +ENDPROC(__dma_map_area) + +/* + * __dma_unmap_area(start, size, dir) + * - start - kernel virtual start address + * - size - size of region + * - dir - DMA direction + */ +ENTRY(__dma_unmap_area) + add x1, x1, x0 + cmp w2, #DMA_TO_DEVICE + b.ne __dma_inv_range + ret +ENDPROC(__dma_unmap_area) diff --git a/arch/arm64/mm/dma-mapping.c b/arch/arm64/mm/dma-mapping.c index fbd76785c5db64..1f65963a9c0464 100644 --- a/arch/arm64/mm/dma-mapping.c +++ b/arch/arm64/mm/dma-mapping.c @@ -30,18 +30,26 @@ struct dma_map_ops *dma_ops; EXPORT_SYMBOL(dma_ops); -static void *arm64_swiotlb_alloc_coherent(struct device *dev, size_t size, - dma_addr_t *dma_handle, gfp_t flags, - struct dma_attrs *attrs) +static pgprot_t __get_dma_pgprot(struct dma_attrs *attrs, pgprot_t prot, + bool coherent) +{ + if (!coherent || dma_get_attr(DMA_ATTR_WRITE_COMBINE, attrs)) + return pgprot_writecombine(prot); + return prot; +} + +static void *__dma_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t flags, + struct dma_attrs *attrs) { if (dev == NULL) { WARN_ONCE(1, "Use an actual device structure for DMA allocation\n"); return NULL; } - if (IS_ENABLED(CONFIG_ZONE_DMA32) && + if (IS_ENABLED(CONFIG_ZONE_DMA) && dev->coherent_dma_mask <= DMA_BIT_MASK(32)) - flags |= GFP_DMA32; + flags |= GFP_DMA; if (IS_ENABLED(CONFIG_DMA_CMA)) { struct page *page; @@ -58,9 +66,9 @@ static void *arm64_swiotlb_alloc_coherent(struct device *dev, size_t size, } } -static void arm64_swiotlb_free_coherent(struct device *dev, size_t size, - void *vaddr, dma_addr_t dma_handle, - struct dma_attrs *attrs) +static void __dma_free_coherent(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle, + struct dma_attrs *attrs) { if (dev == NULL) { WARN_ONCE(1, "Use an actual device structure for DMA allocation\n"); @@ -78,9 +86,212 @@ static void arm64_swiotlb_free_coherent(struct device *dev, size_t size, } } -static struct dma_map_ops arm64_swiotlb_dma_ops = { - .alloc = arm64_swiotlb_alloc_coherent, - .free = arm64_swiotlb_free_coherent, +static void *__dma_alloc_noncoherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t flags, + struct dma_attrs *attrs) +{ + struct page *page, **map; + void *ptr, *coherent_ptr; + int order, i; + + size = PAGE_ALIGN(size); + order = get_order(size); + + ptr = __dma_alloc_coherent(dev, size, dma_handle, flags, attrs); + if (!ptr) + goto no_mem; + map = kmalloc(sizeof(struct page *) << order, flags & ~GFP_DMA); + if (!map) + goto no_map; + + /* remove any dirty cache lines on the kernel alias */ + __dma_flush_range(ptr, ptr + size); + + /* create a coherent mapping */ + page = virt_to_page(ptr); + for (i = 0; i < (size >> PAGE_SHIFT); i++) + map[i] = page + i; + coherent_ptr = vmap(map, size >> PAGE_SHIFT, VM_MAP, + __get_dma_pgprot(attrs, pgprot_default, false)); + kfree(map); + if (!coherent_ptr) + goto no_map; + + return coherent_ptr; + +no_map: + __dma_free_coherent(dev, size, ptr, *dma_handle, attrs); +no_mem: + *dma_handle = ~0; + return NULL; +} + +static void __dma_free_noncoherent(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle, + struct dma_attrs *attrs) +{ + void *swiotlb_addr = phys_to_virt(dma_to_phys(dev, dma_handle)); + + vunmap(vaddr); + __dma_free_coherent(dev, size, swiotlb_addr, dma_handle, attrs); +} + +static dma_addr_t __swiotlb_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, + enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + dma_addr_t dev_addr; + + dev_addr = swiotlb_map_page(dev, page, offset, size, dir, attrs); + __dma_map_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir); + + return dev_addr; +} + + +static void __swiotlb_unmap_page(struct device *dev, dma_addr_t dev_addr, + size_t size, enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + __dma_unmap_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir); + swiotlb_unmap_page(dev, dev_addr, size, dir, attrs); +} + +static int __swiotlb_map_sg_attrs(struct device *dev, struct scatterlist *sgl, + int nelems, enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + struct scatterlist *sg; + int i, ret; + + ret = swiotlb_map_sg_attrs(dev, sgl, nelems, dir, attrs); + for_each_sg(sgl, sg, ret, i) + __dma_map_area(phys_to_virt(dma_to_phys(dev, sg->dma_address)), + sg->length, dir); + + return ret; +} + +static void __swiotlb_unmap_sg_attrs(struct device *dev, + struct scatterlist *sgl, int nelems, + enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + struct scatterlist *sg; + int i; + + for_each_sg(sgl, sg, nelems, i) + __dma_unmap_area(phys_to_virt(dma_to_phys(dev, sg->dma_address)), + sg->length, dir); + swiotlb_unmap_sg_attrs(dev, sgl, nelems, dir, attrs); +} + +static void __swiotlb_sync_single_for_cpu(struct device *dev, + dma_addr_t dev_addr, size_t size, + enum dma_data_direction dir) +{ + __dma_unmap_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir); + swiotlb_sync_single_for_cpu(dev, dev_addr, size, dir); +} + +static void __swiotlb_sync_single_for_device(struct device *dev, + dma_addr_t dev_addr, size_t size, + enum dma_data_direction dir) +{ + swiotlb_sync_single_for_device(dev, dev_addr, size, dir); + __dma_map_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir); +} + +static void __swiotlb_sync_sg_for_cpu(struct device *dev, + struct scatterlist *sgl, int nelems, + enum dma_data_direction dir) +{ + struct scatterlist *sg; + int i; + + for_each_sg(sgl, sg, nelems, i) + __dma_unmap_area(phys_to_virt(dma_to_phys(dev, sg->dma_address)), + sg->length, dir); + swiotlb_sync_sg_for_cpu(dev, sgl, nelems, dir); +} + +static void __swiotlb_sync_sg_for_device(struct device *dev, + struct scatterlist *sgl, int nelems, + enum dma_data_direction dir) +{ + struct scatterlist *sg; + int i; + + swiotlb_sync_sg_for_device(dev, sgl, nelems, dir); + for_each_sg(sgl, sg, nelems, i) + __dma_map_area(phys_to_virt(dma_to_phys(dev, sg->dma_address)), + sg->length, dir); +} + +/* vma->vm_page_prot must be set appropriately before calling this function */ +static int __dma_common_mmap(struct device *dev, struct vm_area_struct *vma, + void *cpu_addr, dma_addr_t dma_addr, size_t size) +{ + int ret = -ENXIO; + unsigned long nr_vma_pages = (vma->vm_end - vma->vm_start) >> + PAGE_SHIFT; + unsigned long nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; + unsigned long pfn = dma_to_phys(dev, dma_addr) >> PAGE_SHIFT; + unsigned long off = vma->vm_pgoff; + + if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret)) + return ret; + + if (off < nr_pages && nr_vma_pages <= (nr_pages - off)) { + ret = remap_pfn_range(vma, vma->vm_start, + pfn + off, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); + } + + return ret; +} + +static int __swiotlb_mmap_noncoherent(struct device *dev, + struct vm_area_struct *vma, + void *cpu_addr, dma_addr_t dma_addr, size_t size, + struct dma_attrs *attrs) +{ + vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot, false); + return __dma_common_mmap(dev, vma, cpu_addr, dma_addr, size); +} + +static int __swiotlb_mmap_coherent(struct device *dev, + struct vm_area_struct *vma, + void *cpu_addr, dma_addr_t dma_addr, size_t size, + struct dma_attrs *attrs) +{ + /* Just use whatever page_prot attributes were specified */ + return __dma_common_mmap(dev, vma, cpu_addr, dma_addr, size); +} + +struct dma_map_ops noncoherent_swiotlb_dma_ops = { + .alloc = __dma_alloc_noncoherent, + .free = __dma_free_noncoherent, + .mmap = __swiotlb_mmap_noncoherent, + .map_page = __swiotlb_map_page, + .unmap_page = __swiotlb_unmap_page, + .map_sg = __swiotlb_map_sg_attrs, + .unmap_sg = __swiotlb_unmap_sg_attrs, + .sync_single_for_cpu = __swiotlb_sync_single_for_cpu, + .sync_single_for_device = __swiotlb_sync_single_for_device, + .sync_sg_for_cpu = __swiotlb_sync_sg_for_cpu, + .sync_sg_for_device = __swiotlb_sync_sg_for_device, + .dma_supported = swiotlb_dma_supported, + .mapping_error = swiotlb_dma_mapping_error, +}; +EXPORT_SYMBOL(noncoherent_swiotlb_dma_ops); + +struct dma_map_ops coherent_swiotlb_dma_ops = { + .alloc = __dma_alloc_coherent, + .free = __dma_free_coherent, + .mmap = __swiotlb_mmap_coherent, .map_page = swiotlb_map_page, .unmap_page = swiotlb_unmap_page, .map_sg = swiotlb_map_sg_attrs, @@ -92,12 +303,19 @@ static struct dma_map_ops arm64_swiotlb_dma_ops = { .dma_supported = swiotlb_dma_supported, .mapping_error = swiotlb_dma_mapping_error, }; +EXPORT_SYMBOL(coherent_swiotlb_dma_ops); + +extern int swiotlb_late_init_with_default_size(size_t default_size); -void __init arm64_swiotlb_init(void) +static int __init swiotlb_late_init(void) { - dma_ops = &arm64_swiotlb_dma_ops; - swiotlb_init(1); + size_t swiotlb_size = min(SZ_64M, MAX_ORDER_NR_PAGES << PAGE_SHIFT); + + dma_ops = &noncoherent_swiotlb_dma_ops; + + return swiotlb_late_init_with_default_size(swiotlb_size); } +subsys_initcall(swiotlb_late_init); #define PREALLOC_DMA_DEBUG_ENTRIES 4096 diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c index d0b4c2efda90aa..88627c450a6cbd 100644 --- a/arch/arm64/mm/init.c +++ b/arch/arm64/mm/init.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -59,22 +60,22 @@ static int __init early_initrd(char *p) early_param("initrd", early_initrd); #endif -#define MAX_DMA32_PFN ((4UL * 1024 * 1024 * 1024) >> PAGE_SHIFT) - static void __init zone_sizes_init(unsigned long min, unsigned long max) { struct memblock_region *reg; unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES]; - unsigned long max_dma32 = min; + unsigned long max_dma = min; memset(zone_size, 0, sizeof(zone_size)); -#ifdef CONFIG_ZONE_DMA32 /* 4GB maximum for 32-bit only capable devices */ - max_dma32 = max(min, min(max, MAX_DMA32_PFN)); - zone_size[ZONE_DMA32] = max_dma32 - min; -#endif - zone_size[ZONE_NORMAL] = max - max_dma32; + if (IS_ENABLED(CONFIG_ZONE_DMA)) { + unsigned long max_dma_phys = + (unsigned long)dma_to_phys(NULL, DMA_BIT_MASK(32) + 1); + max_dma = max(min, min(max, max_dma_phys >> PAGE_SHIFT)); + zone_size[ZONE_DMA] = max_dma - min; + } + zone_size[ZONE_NORMAL] = max - max_dma; memcpy(zhole_size, zone_size, sizeof(zhole_size)); @@ -84,15 +85,15 @@ static void __init zone_sizes_init(unsigned long min, unsigned long max) if (start >= max) continue; -#ifdef CONFIG_ZONE_DMA32 - if (start < max_dma32) { - unsigned long dma_end = min(end, max_dma32); - zhole_size[ZONE_DMA32] -= dma_end - start; + + if (IS_ENABLED(CONFIG_ZONE_DMA) && start < max_dma) { + unsigned long dma_end = min(end, max_dma); + zhole_size[ZONE_DMA] -= dma_end - start; } -#endif - if (end > max_dma32) { + + if (end > max_dma) { unsigned long normal_end = min(end, max); - unsigned long normal_start = max(start, max_dma32); + unsigned long normal_start = max(start, max_dma); zhole_size[ZONE_NORMAL] -= normal_end - normal_start; } } @@ -261,8 +262,6 @@ static void __init free_unused_memmap(void) */ void __init mem_init(void) { - arm64_swiotlb_init(); - max_mapnr = pfn_to_page(max_pfn + PHYS_PFN_OFFSET) - mem_map; #ifndef CONFIG_SPARSEMEM_VMEMMAP diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c index f8dc7e8fce6fea..6cb136c64f9a88 100644 --- a/arch/arm64/mm/mmu.c +++ b/arch/arm64/mm/mmu.c @@ -98,7 +98,7 @@ static int __init early_cachepolicy(char *p) */ asm volatile( " mrs %0, mair_el1\n" - " bfi %0, %1, #%2, #8\n" + " bfi %0, %1, %2, #8\n" " msr mair_el1, %0\n" " isb\n" : "=&r" (tmp) diff --git a/arch/arm64/mm/proc.S b/arch/arm64/mm/proc.S index 1333e6f9a8e50b..e085ee6ef4e23c 100644 --- a/arch/arm64/mm/proc.S +++ b/arch/arm64/mm/proc.S @@ -173,12 +173,6 @@ ENDPROC(cpu_do_switch_mm) * value of the SCTLR_EL1 register. */ ENTRY(__cpu_setup) - /* - * Preserve the link register across the function call. - */ - mov x28, lr - bl __flush_dcache_all - mov lr, x28 ic iallu // I+BTB cache invalidate tlbi vmalle1is // invalidate I + D TLBs dsb sy @@ -215,8 +209,14 @@ ENTRY(__cpu_setup) * Set/prepare TCR and TTBR. We use 512GB (39-bit) address range for * both user and kernel. */ - ldr x10, =TCR_TxSZ(VA_BITS) | TCR_FLAGS | TCR_IPS_40BIT | \ + ldr x10, =TCR_TxSZ(VA_BITS) | TCR_FLAGS | \ TCR_ASID16 | TCR_TBI0 | (1 << 31) + /* + * Read the PARange bits from ID_AA64MMFR0_EL1 and set the IPS bits in + * TCR_EL1. + */ + mrs x9, ID_AA64MMFR0_EL1 + bfi x10, x9, #32, #3 #ifdef CONFIG_ARM64_64K_PAGES orr x10, x10, TCR_TG0_64K orr x10, x10, TCR_TG1_64K diff --git a/arch/x86/Makefile b/arch/x86/Makefile index eeda43abed6ec8..128b6b77d0d04d 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -19,6 +19,7 @@ endif # attempt to ensure that our asm(".code16gcc") is first in the asm # output. CODE16GCC_CFLAGS := -m32 -include $(srctree)/arch/x86/boot/code16gcc.h \ + $(call cc-option, -no-integrated-as,)\ $(call cc-option, -fno-toplevel-reorder,\ $(call cc-option, -fno-unit-at-a-time)) M16_CFLAGS := $(call cc-option, -m16, $(CODE16GCC_CFLAGS)) @@ -108,7 +109,7 @@ else # this works around some issues with generating unwind tables in older gccs # newer gccs do it by default - KBUILD_CFLAGS += -maccumulate-outgoing-args + KBUILD_CFLAGS += $(call cc-option,-maccumulate-outgoing-args) endif # Make sure compiler does not have buggy stack-protector support. diff --git a/arch/x86/boot/Makefile b/arch/x86/boot/Makefile index 878df7e88cd4d9..a06a21303329c1 100644 --- a/arch/x86/boot/Makefile +++ b/arch/x86/boot/Makefile @@ -55,6 +55,15 @@ KBUILD_CFLAGS := $(USERINCLUDE) $(REALMODE_CFLAGS) -D_SETUP KBUILD_AFLAGS := $(KBUILD_CFLAGS) -D__ASSEMBLY__ GCOV_PROFILE := n + +# For clang we need to rely on no-integrated-as for .code16 . We use gas. +ifeq ($(COMPILER),clang) +KBUILD_CFLAGS += -Wno-unused-value -Wno-unused-parameter -mno-sse \ + $(call cc-option,-no-integrated-as,) +KBUILD_AFLAGS := $(KBUILD_CFLAGS) -D__ASSEMBLY__ +endif + + $(obj)/bzImage: asflags-y := $(SVGA_MODE) quiet_cmd_image = BUILD $@ diff --git a/arch/x86/boot/boot.h b/arch/x86/boot/boot.h index 50f8c5e0f37e1d..756ddb165e0c81 100644 --- a/arch/x86/boot/boot.h +++ b/arch/x86/boot/boot.h @@ -231,8 +231,10 @@ void *copy_from_gs(void *dst, addr_t src, size_t len); void *memcpy(void *dst, void *src, size_t len); void *memset(void *dst, int c, size_t len); +#ifndef __clang__ /* PR18415 */ #define memcpy(d,s,l) __builtin_memcpy(d,s,l) #define memset(d,c,l) __builtin_memset(d,c,l) +#endif /* a20.c */ int enable_a20(void); diff --git a/arch/x86/boot/memory.c b/arch/x86/boot/memory.c index db75d07c3645c0..7af65046dfad53 100644 --- a/arch/x86/boot/memory.c +++ b/arch/x86/boot/memory.c @@ -63,8 +63,13 @@ static int detect_memory_e820(void) count = 0; break; } - +#ifdef __clang__ + /* PR18415 */ + memcpy(desc, &buf, sizeof(*desc)); + desc++; +#else *desc++ = buf; +#endif count++; } while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map)); diff --git a/arch/x86/include/asm/idle.h b/arch/x86/include/asm/idle.h index c5d1785373ed38..02bab09707f28c 100644 --- a/arch/x86/include/asm/idle.h +++ b/arch/x86/include/asm/idle.h @@ -1,13 +1,6 @@ #ifndef _ASM_X86_IDLE_H #define _ASM_X86_IDLE_H -#define IDLE_START 1 -#define IDLE_END 2 - -struct notifier_block; -void idle_notifier_register(struct notifier_block *n); -void idle_notifier_unregister(struct notifier_block *n); - #ifdef CONFIG_X86_64 void enter_idle(void); void exit_idle(void); diff --git a/arch/x86/include/asm/thread_info.h b/arch/x86/include/asm/thread_info.h index e1940c06ed022d..e27ccc17c7d6c4 100644 --- a/arch/x86/include/asm/thread_info.h +++ b/arch/x86/include/asm/thread_info.h @@ -163,10 +163,10 @@ struct thread_info { */ #ifndef __ASSEMBLY__ -#define current_stack_pointer ({ \ - unsigned long sp; \ - asm("mov %%esp,%0" : "=g" (sp)); \ - sp; \ +#define current_stack_pointer ({ \ + register unsigned long sp asm("esp") __used; \ + asm("" : "=r" (sp)); \ + sp; \ }) /* how to get the thread information struct from C */ diff --git a/arch/x86/kernel/process.c b/arch/x86/kernel/process.c index 3fb8d95ab8b5ea..ed8975a3538ecb 100644 --- a/arch/x86/kernel/process.c +++ b/arch/x86/kernel/process.c @@ -40,19 +40,6 @@ __visible DEFINE_PER_CPU_SHARED_ALIGNED(struct tss_struct, init_tss) = INIT_TSS; #ifdef CONFIG_X86_64 static DEFINE_PER_CPU(unsigned char, is_idle); -static ATOMIC_NOTIFIER_HEAD(idle_notifier); - -void idle_notifier_register(struct notifier_block *n) -{ - atomic_notifier_chain_register(&idle_notifier, n); -} -EXPORT_SYMBOL_GPL(idle_notifier_register); - -void idle_notifier_unregister(struct notifier_block *n) -{ - atomic_notifier_chain_unregister(&idle_notifier, n); -} -EXPORT_SYMBOL_GPL(idle_notifier_unregister); #endif struct kmem_cache *task_xstate_cachep; @@ -257,14 +244,14 @@ static inline void play_dead(void) void enter_idle(void) { this_cpu_write(is_idle, 1); - atomic_notifier_call_chain(&idle_notifier, IDLE_START, NULL); + idle_notifier_call_chain(IDLE_START); } static void __exit_idle(void) { if (x86_test_and_clear_bit_percpu(0, is_idle) == 0) return; - atomic_notifier_call_chain(&idle_notifier, IDLE_END, NULL); + idle_notifier_call_chain(IDLE_END); } /* Called from interrupts to signify idle end */ diff --git a/arch/x86/realmode/rm/Makefile b/arch/x86/realmode/rm/Makefile index 3497f14e4dea8a..7e44536d6ab865 100644 --- a/arch/x86/realmode/rm/Makefile +++ b/arch/x86/realmode/rm/Makefile @@ -19,6 +19,14 @@ wakeup-objs += video-vga.o wakeup-objs += video-vesa.o wakeup-objs += video-bios.o +# For clang, we need to enforce not to use the integrated assembler +AFLAGS_wakeup_asm.o = $(call cc-option,-no-integrated-as,) +AFLAGS_bioscall.o = $(call cc-option,-no-integrated-as,) +AFLAGS_copy.o = $(call cc-option,-no-integrated-as,) +AFLAGS_trampoline_32.o = $(call cc-option,-no-integrated-as,) +AFLAGS_trampoline_64.o = $(call cc-option,-no-integrated-as,) +AFLAGS_reboot_32.o = $(call cc-option,-no-integrated-as,) + realmode-y += header.o realmode-y += trampoline_$(BITS).o realmode-y += stack.o diff --git a/arch/x86/um/ksyms.c b/arch/x86/um/ksyms.c index 2e8f43ec621471..04aedcecd8878b 100644 --- a/arch/x86/um/ksyms.c +++ b/arch/x86/um/ksyms.c @@ -4,7 +4,7 @@ #ifndef CONFIG_X86_32 /*XXX: we need them because they would be exported by x86_64 */ -#if (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) || __GNUC__ > 4 +#if (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) || __GNUC__ > 4 || defined(__clang__) EXPORT_SYMBOL(memcpy); #else EXPORT_SYMBOL(__memcpy); diff --git a/block/genhd.c b/block/genhd.c index 791f4194313228..3bceeb40aec21b 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -1107,6 +1107,22 @@ static void disk_release(struct device *dev) blk_put_queue(disk->queue); kfree(disk); } + +static int disk_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct gendisk *disk = dev_to_disk(dev); + struct disk_part_iter piter; + struct hd_struct *part; + int cnt = 0; + + disk_part_iter_init(&piter, disk, 0); + while((part = disk_part_iter_next(&piter))) + cnt++; + disk_part_iter_exit(&piter); + add_uevent_var(env, "NPARTS=%u", cnt); + return 0; +} + struct class block_class = { .name = "block", }; @@ -1126,6 +1142,7 @@ static struct device_type disk_type = { .groups = disk_attr_groups, .release = disk_release, .devnode = block_devnode, + .uevent = disk_uevent, }; #ifdef CONFIG_PROC_FS diff --git a/block/partition-generic.c b/block/partition-generic.c index 789cdea05893bb..c7942acf1379d1 100644 --- a/block/partition-generic.c +++ b/block/partition-generic.c @@ -216,10 +216,21 @@ static void part_release(struct device *dev) kfree(p); } +static int part_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct hd_struct *part = dev_to_part(dev); + + add_uevent_var(env, "PARTN=%u", part->partno); + if (part->info && part->info->volname[0]) + add_uevent_var(env, "PARTNAME=%s", part->info->volname); + return 0; +} + struct device_type part_type = { .name = "partition", .groups = part_attr_groups, .release = part_release, + .uevent = part_uevent, }; static void delete_partition_rcu_cb(struct rcu_head *head) diff --git a/crypto/hmac.c b/crypto/hmac.c index 8d9544cf8169fd..10612555c44aa8 100644 --- a/crypto/hmac.c +++ b/crypto/hmac.c @@ -52,20 +52,19 @@ static int hmac_setkey(struct crypto_shash *parent, struct hmac_ctx *ctx = align_ptr(opad + ss, crypto_tfm_ctx_alignment()); struct crypto_shash *hash = ctx->hash; - struct { - struct shash_desc shash; - char ctx[crypto_shash_descsize(hash)]; - } desc; + char desc[sizeof(struct shash_desc) + crypto_shash_descsize(hash) + + CRYPTO_MINALIGN] CRYPTO_MINALIGN_ATTR; + struct shash_desc *shash = (struct shash_desc *)desc; unsigned int i; - desc.shash.tfm = hash; - desc.shash.flags = crypto_shash_get_flags(parent) & - CRYPTO_TFM_REQ_MAY_SLEEP; + shash->tfm = hash; + shash->flags = crypto_shash_get_flags(parent) + & CRYPTO_TFM_REQ_MAY_SLEEP; if (keylen > bs) { int err; - err = crypto_shash_digest(&desc.shash, inkey, keylen, ipad); + err = crypto_shash_digest(shash, inkey, keylen, ipad); if (err) return err; @@ -81,12 +80,12 @@ static int hmac_setkey(struct crypto_shash *parent, opad[i] ^= 0x5c; } - return crypto_shash_init(&desc.shash) ?: - crypto_shash_update(&desc.shash, ipad, bs) ?: - crypto_shash_export(&desc.shash, ipad) ?: - crypto_shash_init(&desc.shash) ?: - crypto_shash_update(&desc.shash, opad, bs) ?: - crypto_shash_export(&desc.shash, opad); + return crypto_shash_init(shash) ?: + crypto_shash_update(shash, ipad, bs) ?: + crypto_shash_export(shash, ipad) ?: + crypto_shash_init(shash) ?: + crypto_shash_update(shash, opad, bs) ?: + crypto_shash_export(shash, opad); } static int hmac_export(struct shash_desc *pdesc, void *out) diff --git a/crypto/shash.c b/crypto/shash.c index 929058a6856126..47c713954bf30e 100644 --- a/crypto/shash.c +++ b/crypto/shash.c @@ -67,7 +67,8 @@ EXPORT_SYMBOL_GPL(crypto_shash_setkey); static inline unsigned int shash_align_buffer_size(unsigned len, unsigned long mask) { - return len + (mask & ~(__alignof__(u8 __attribute__ ((aligned))) - 1)); + typedef u8 __attribute__ ((aligned)) u8_aligned; + return len + (mask & ~(__alignof__(u8_aligned) - 1)); } static int shash_update_unaligned(struct shash_desc *desc, const u8 *data, diff --git a/crypto/testmgr.c b/crypto/testmgr.c index 77955507f6f1f7..81519ca584735d 100644 --- a/crypto/testmgr.c +++ b/crypto/testmgr.c @@ -1663,16 +1663,17 @@ static int alg_test_crc32c(const struct alg_test_desc *desc, } do { - struct { - struct shash_desc shash; - char ctx[crypto_shash_descsize(tfm)]; - } sdesc; + char sdesc[sizeof(struct shash_desc) + + crypto_shash_descsize(tfm) + + CRYPTO_MINALIGN] CRYPTO_MINALIGN_ATTR; + struct shash_desc *shash = (struct shash_desc *)sdesc; + u32 *ctx = (u32 *)shash_desc_ctx(shash); - sdesc.shash.tfm = tfm; - sdesc.shash.flags = 0; + shash->tfm = tfm; + shash->flags = 0; - *(u32 *)sdesc.ctx = le32_to_cpu(420553207); - err = crypto_shash_final(&sdesc.shash, (u8 *)&val); + *ctx = le32_to_cpu(420553207); + err = crypto_shash_final(shash, (u8 *)&val); if (err) { printk(KERN_ERR "alg: crc32c: Operation failed for " "%s: %d\n", driver, err); diff --git a/drivers/Kconfig b/drivers/Kconfig index b3138fbb46a4bc..8d3ab646f6511a 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -98,6 +98,8 @@ source "drivers/memstick/Kconfig" source "drivers/leds/Kconfig" +source "drivers/switch/Kconfig" + source "drivers/accessibility/Kconfig" source "drivers/infiniband/Kconfig" @@ -170,4 +172,6 @@ source "drivers/phy/Kconfig" source "drivers/powercap/Kconfig" +source "drivers/gator/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 8e3b8b06c0b2e2..9864955ca170c9 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -113,6 +113,7 @@ obj-$(CONFIG_CPU_IDLE) += cpuidle/ obj-y += mmc/ obj-$(CONFIG_MEMSTICK) += memstick/ obj-y += leds/ +obj-$(CONFIG_SWITCH) += switch/ obj-$(CONFIG_INFINIBAND) += infiniband/ obj-$(CONFIG_SGI_SN) += sn/ obj-y += firmware/ @@ -155,3 +156,5 @@ obj-$(CONFIG_IPACK_BUS) += ipack/ obj-$(CONFIG_NTB) += ntb/ obj-$(CONFIG_FMC) += fmc/ obj-$(CONFIG_POWERCAP) += powercap/ + +obj-$(CONFIG_GATOR) += gator/ diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index 868429a47be41a..c6461dfdf3ed7c 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -97,6 +97,28 @@ config SATA_AHCI_PLATFORM If unsure, say N. +config SATA_PHY + bool "SATA PHY Framework" + default n + help + This option enables the SATA PHY utility framework APIs. + The framework acts as an interface between the SATA device + and the PHY device. The SATA PHY device registers itself + with the framework through the APIs provided and the SATA + device finds and requests for an appropriate PHY device. + +config SATA_EXYNOS + bool "Exynos SATA AHCI support" + select I2C + select HAVE_S3C2410_I2C + select I2C_S3C2410 + select SATA_PHY + help + This option enables support for Exynos AHCI Serial ATA + controllers. + + If unsure, say N. + config AHCI_IMX tristate "Freescale i.MX AHCI SATA support" depends on SATA_AHCI_PLATFORM && MFD_SYSCON diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile index 46518c62246094..a4e5cf56d4754d 100644 --- a/drivers/ata/Makefile +++ b/drivers/ata/Makefile @@ -10,6 +10,8 @@ obj-$(CONFIG_SATA_INIC162X) += sata_inic162x.o obj-$(CONFIG_SATA_SIL24) += sata_sil24.o obj-$(CONFIG_SATA_DWC) += sata_dwc_460ex.o obj-$(CONFIG_SATA_HIGHBANK) += sata_highbank.o libahci.o +obj-$(CONFIG_SATA_PHY) += sata_phy.o +obj-$(CONFIG_SATA_EXYNOS) += sata_exynos.o sata_exynos_phy.o libahci.o obj-$(CONFIG_AHCI_IMX) += ahci_imx.o # SFF w/ custom DMA diff --git a/drivers/ata/ahci_platform.c b/drivers/ata/ahci_platform.c index 4b231baceb0995..b62a3abba8e924 100644 --- a/drivers/ata/ahci_platform.c +++ b/drivers/ata/ahci_platform.c @@ -330,6 +330,7 @@ static const struct of_device_id ahci_of_match[] = { { .compatible = "snps,spear-ahci", }, { .compatible = "snps,exynos5440-ahci", }, { .compatible = "ibm,476gtr-ahci", }, + { .compatible = "hisilicon,hisi-ahci", }, {}, }; MODULE_DEVICE_TABLE(of, ahci_of_match); diff --git a/drivers/ata/sata_exynos.c b/drivers/ata/sata_exynos.c new file mode 100644 index 00000000000000..1dcfb2c8d0de29 --- /dev/null +++ b/drivers/ata/sata_exynos.c @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2010-2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS - SATA controller platform driver wrapper + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ahci.h" +#include "sata_phy.h" + +#define MHZ (1000 * 1000) +#define DEFERED 1 +#define NO_PORT 0 + +static const struct ata_port_info ahci_port_info = { + .flags = AHCI_FLAG_COMMON, + .pio_mask = ATA_PIO4, + .udma_mask = ATA_UDMA6, + .port_ops = &ahci_ops, +}; + +static struct scsi_host_template ahci_platform_sht = { + AHCI_SHT("ahci_platform"), +}; + +struct exynos_sata { + struct clk *sclk; + struct clk *clk; + int irq; + unsigned int freq; + struct sata_phy *phy[]; +}; + +static int exynos_sata_parse_dt(struct device_node *np, + struct exynos_sata *sata) +{ + if (!np) + return -EINVAL; + + return of_property_read_u32(np, "samsung,sata-freq", + &sata->freq); +} + +static int exynos_sata_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ata_port_info pi = ahci_port_info; + const struct ata_port_info *ppi[] = { &pi, NULL }; + struct ahci_host_priv *hpriv; + struct exynos_sata *sata; + struct ata_host *host; + struct device_node *of_node_phy = NULL; + static int flag = 0, port_init = NO_PORT; + int n_ports, i, ret; + + sata = devm_kzalloc(dev, sizeof(*sata), GFP_KERNEL); + if (!sata) { + dev_err(dev, "can't alloc sata\n"); + return -ENOMEM; + } + + hpriv = devm_kzalloc(dev, sizeof(*hpriv), GFP_KERNEL); + if (!hpriv) { + dev_err(dev, "can't alloc ahci_host_priv\n"); + return -ENOMEM; + } + + hpriv->flags |= (unsigned long)pi.private_data; + + sata->irq = irq_of_parse_and_map(dev->of_node, 0); + if (sata->irq <= 0) { + dev_err(dev, "irq not specified\n"); + return -EINVAL; + } + + hpriv->mmio = of_iomap(dev->of_node, 0); + if (!hpriv->mmio) { + dev_err(dev, "failed to map IO\n"); + return -ENOMEM; + } + + ret = exynos_sata_parse_dt(dev->of_node, sata); + if (ret < 0) { + dev_err(dev, "failed to get frequency for sata ctrl\n"); + goto err_iomap; + } + + sata->sclk = devm_clk_get(dev, "sclk_sata"); + if (IS_ERR(sata->sclk)) { + dev_err(dev, "failed to get sclk_sata\n"); + ret = PTR_ERR(sata->sclk); + goto err_iomap; + } + + ret = clk_prepare_enable(sata->sclk); + if (ret < 0) { + dev_err(dev, "failed to enable source clk\n"); + goto err_iomap; + } + + ret = clk_set_rate(sata->sclk, sata->freq * MHZ); + if (ret < 0) { + dev_err(dev, "failed to set clk frequency\n"); + goto err_clkstrt; + } + + sata->clk = devm_clk_get(dev, "sata"); + if (IS_ERR(sata->clk)) { + dev_err(dev, "failed to get sata clock\n"); + ret = PTR_ERR(sata->clk); + goto err_clkstrt; + } + + ret = clk_prepare_enable(sata->clk); + if (ret < 0) { + dev_err(dev, "failed to enable source clk\n"); + goto err_clkstrt; + } + + ahci_save_initial_config(dev, hpriv, 0, 0); + + /* prepare host */ + if (hpriv->cap & HOST_CAP_NCQ) + pi.flags |= ATA_FLAG_NCQ; + + if (hpriv->cap & HOST_CAP_PMP) + pi.flags |= ATA_FLAG_PMP; + + ahci_set_em_messages(hpriv, &pi); + + /* CAP.NP sometimes indicate the index of the last enabled + * port, at other times, that of the last possible port, so + * determining the maximum port number requires looking at + * both CAP.NP and port_map. + */ + n_ports = max(ahci_nr_ports(hpriv->cap), fls(hpriv->port_map)); + + host = ata_host_alloc_pinfo(dev, ppi, n_ports); + if (!host) { + ret = -ENOMEM; + goto err_clken; + } + + host->private_data = hpriv; + + if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss) + host->flags |= ATA_HOST_PARALLEL_SCAN; + else + pr_info(KERN_INFO + "ahci: SSS flag set, parallel bus scan disabled\n"); + + if (pi.flags & ATA_FLAG_EM) + ahci_reset_em(host); + + for (i = 0; i < host->n_ports; i++) { + struct ata_port *ap = host->ports[i]; + of_node_phy = of_parse_phandle(dev->of_node, + "samsung,exynos-sata-phy", i); + if (!of_node_phy) { + dev_err(dev, + "phandle of phy not found for port %d\n", i); + break; + } + + sata->phy[i] = sata_get_phy(of_node_phy); + if (IS_ERR(sata->phy[i])) { + if (PTR_ERR(sata->phy[i]) == -EBUSY) + continue; + dev_err(dev, + "failed to get sata phy for port %d\n", i); + if (flag != DEFERED) { + flag = DEFERED ; + return -EPROBE_DEFER; + } else + continue; + + } + /* Initialize the PHY */ + ret = sata_init_phy(sata->phy[i]); + if (ret < 0) { + if (ret == -EPROBE_DEFER) { + if (flag != DEFERED) { + flag = DEFERED ; + sata_put_phy(sata->phy[i]); + return -EPROBE_DEFER; + } else { + continue; + } + } else { + dev_err(dev, + "failed to initialize sata phy for port %d\n", + i); + sata_put_phy(sata->phy[i]); + } + + } + + /* set enclosure management message type */ + if (ap->flags & ATA_FLAG_EM) + ap->em_message_type = hpriv->em_msg_type; + + /* disabled/not-implemented port */ + if (!(hpriv->port_map & (1 << i))) + ap->ops = &ata_dummy_port_ops; + + port_init++; + } + + if (port_init == NO_PORT) + goto err_initphy; + + ret = ahci_reset_controller(host); + if (ret) + goto err_rst; + + ahci_init_controller(host); + ahci_print_info(host, "platform"); + + ret = ata_host_activate(host, sata->irq, ahci_interrupt, IRQF_SHARED, + &ahci_platform_sht); + if (ret) + goto err_rst; + + platform_set_drvdata(pdev, sata); + + return 0; + + err_rst: + for (i = 0; i < host->n_ports; i++) + sata_shutdown_phy(sata->phy[i]); + + err_initphy: + for (i = 0; i < host->n_ports; i++) + sata_put_phy(sata->phy[i]); + + err_clken: + clk_disable_unprepare(sata->clk); + + err_clkstrt: + clk_disable_unprepare(sata->sclk); + + err_iomap: + iounmap(hpriv->mmio); + + return ret; +} + +static int exynos_sata_remove(struct platform_device *pdev) +{ + unsigned int i; + struct device *dev = &pdev->dev; + struct ata_host *host = dev_get_drvdata(dev); + struct exynos_sata *sata = platform_get_drvdata(pdev); + struct ahci_host_priv *hpriv = + (struct ahci_host_priv *)host->private_data; + + ata_host_detach(host); + + for (i = 0; i < host->n_ports; i++) { + sata_shutdown_phy(sata->phy[i]); + sata_put_phy(sata->phy[i]); + } + iounmap(hpriv->mmio); + + return 0; +} + +static const struct of_device_id ahci_of_match[] = { + { .compatible = "samsung,exynos5-sata-ahci", }, +}; + +MODULE_DEVICE_TABLE(of, ahci_of_match); + +static struct platform_driver exynos_sata_driver = { + .probe = exynos_sata_probe, + .remove = exynos_sata_remove, + .driver = { + .name = "exynos-sata", + .owner = THIS_MODULE, + .of_match_table = ahci_of_match, + }, +}; + +module_platform_driver(exynos_sata_driver); + +MODULE_DESCRIPTION("EXYNOS SATA DRIVER"); +MODULE_AUTHOR("Vasanth Ananthan, "); +MODULE_LICENSE("GPL"); diff --git a/drivers/ata/sata_exynos_phy.c b/drivers/ata/sata_exynos_phy.c new file mode 100644 index 00000000000000..052b053bd2668c --- /dev/null +++ b/drivers/ata/sata_exynos_phy.c @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2010-2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS - SATA PHY controller driver + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "sata_phy.h" +#include "sata_exynos_phy.h" + +#define SATA_TIME_LIMIT 1000 + +static struct i2c_client *i2c_client; + +static struct i2c_driver sataphy_i2c_driver; + +struct exynos_sata_phy { + void __iomem *mmio; + void __iomem *pmureg; + struct clk *clk; + struct device *dev; + struct sata_phy phy; +}; + +static bool sata_is_reg(void __iomem *base, u32 reg, u32 checkbit, u32 status) +{ + if ((readl(base + reg) & checkbit) == status) + return true; + else + return false; +} + +static bool wait_for_reg_status(void __iomem *base, u32 reg, u32 checkbit, + u32 status) +{ + unsigned long timeout = jiffies + usecs_to_jiffies(1000); + + while (time_before(jiffies, timeout)) { + if (sata_is_reg(base, reg, checkbit, status)) + return true; + } + + return false; +} + +static int exynos_sataphy_parse_dt(struct device *dev, + struct exynos_sata_phy *phy) +{ + struct device_node *sataphy_pmu; + + sataphy_pmu = of_get_child_by_name(dev->of_node, "sataphy-pmu"); + if (!sataphy_pmu) { + dev_err(dev, + "PMU control register for sata-phy not specified\n"); + return -ENODEV; + } + + phy->pmureg = of_iomap(sataphy_pmu, 0); + + of_node_put(sataphy_pmu); + + if (!phy->pmureg) { + dev_err(dev, "Can't get sata-phy pmu control register\n"); + return -EADDRNOTAVAIL; + } + + return 0; +} + +static int exynos_sataphy_init(struct sata_phy *phy) +{ + int ret; + u32 val; + + /* Values to be written to enable 40 bits interface */ + u8 buf[] = { 0x3A, 0x0B }; + + struct exynos_sata_phy *sata_phy; + + if (!i2c_client) + return -EPROBE_DEFER; + + sata_phy = container_of(phy, struct exynos_sata_phy, phy); + + ret = clk_prepare_enable(sata_phy->clk); + if (ret < 0) { + dev_err(phy->dev, "failed to enable source clk\n"); + return ret; + } + + if (sata_is_reg(sata_phy->mmio , EXYNOS5_SATA_CTRL0, + CTRL0_P0_PHY_CALIBRATED, CTRL0_P0_PHY_CALIBRATED)) + return 0; + + writel(SATA_PHY_PMU_EN, sata_phy->pmureg); + + val = 0; + writel(val, sata_phy->mmio + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->mmio + EXYNOS5_SATA_RESET); + val |= 0xFF; + writel(val, sata_phy->mmio + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->mmio + EXYNOS5_SATA_RESET); + val |= LINK_RESET; + writel(val, sata_phy->mmio + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->mmio + EXYNOS5_SATA_RESET); + val |= RESET_CMN_RST_N; + writel(val, sata_phy->mmio + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->mmio + EXYNOS5_SATA_PHSATA_CTRLM); + val &= ~PHCTRLM_REF_RATE; + writel(val, sata_phy->mmio + EXYNOS5_SATA_PHSATA_CTRLM); + + /* High speed enable for Gen3 */ + val = readl(sata_phy->mmio + EXYNOS5_SATA_PHSATA_CTRLM); + val |= PHCTRLM_HIGH_SPEED; + writel(val, sata_phy->mmio + EXYNOS5_SATA_PHSATA_CTRLM); + + val = readl(sata_phy->mmio + EXYNOS5_SATA_CTRL0); + val |= CTRL0_P0_PHY_CALIBRATED_SEL | CTRL0_P0_PHY_CALIBRATED; + writel(val, sata_phy->mmio + EXYNOS5_SATA_CTRL0); + + writel(0x2, sata_phy->mmio + EXYNOS5_SATA_MODE0); + + ret = i2c_master_send(i2c_client, buf, sizeof(buf)); + if (ret < 0) + return -ENXIO; + + /* release cmu reset */ + val = readl(sata_phy->mmio + EXYNOS5_SATA_RESET); + val &= ~RESET_CMN_RST_N; + writel(val, sata_phy->mmio + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->mmio + EXYNOS5_SATA_RESET); + val |= RESET_CMN_RST_N; + writel(val, sata_phy->mmio + EXYNOS5_SATA_RESET); + + if (wait_for_reg_status(sata_phy->mmio, EXYNOS5_SATA_PHSATA_STATM, + PHSTATM_PLL_LOCKED, 1)) { + return 0; + } + return -EINVAL; +} + +static int exynos_sataphy_shutdown(struct sata_phy *phy) +{ + + struct exynos_sata_phy *sata_phy; + + sata_phy = container_of(phy, struct exynos_sata_phy, phy); + clk_disable_unprepare(sata_phy->clk); + + return 0; +} + +static int exynos_sata_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + i2c_client = client; + return 0; +} + +static int exynos_sata_phy_probe(struct platform_device *pdev) +{ + struct exynos_sata_phy *sataphy; + struct device *dev = &pdev->dev; + int ret = 0; + sataphy = devm_kzalloc(dev, sizeof(struct exynos_sata_phy), GFP_KERNEL); + if (!sataphy) { + dev_err(dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + sataphy->mmio = of_iomap(dev->of_node, 0); + if (!sataphy->mmio) { + dev_err(dev, "failed to remap IO\n"); + return -EADDRNOTAVAIL; + } + + ret = exynos_sataphy_parse_dt(dev, sataphy); + if (ret != 0) + goto err_iomap; + + sataphy->clk = devm_clk_get(dev, "sata_phyctrl"); + if (IS_ERR(sataphy->clk)) { + dev_err(dev, "failed to get clk for PHY\n"); + ret = PTR_ERR(sataphy->clk); + goto err_pmu; + } + + sataphy->phy.init = exynos_sataphy_init; + sataphy->phy.shutdown = exynos_sataphy_shutdown; + sataphy->phy.dev = dev; + + ret = sata_add_phy(&sataphy->phy); + if (ret < 0) { + dev_err(dev, "PHY not registered with framework\n"); + goto err_iomap; + } + + ret = i2c_add_driver(&sataphy_i2c_driver); + if (ret < 0) + goto err_phy; + + platform_set_drvdata(pdev, sataphy); + + return ret; + + err_phy: + sata_remove_phy(&sataphy->phy); + + err_pmu: + iounmap(sataphy->pmureg); + + err_iomap: + iounmap(sataphy->mmio); + + return ret; +} + +static int exynos_sata_phy_remove(struct platform_device *pdev) +{ + struct exynos_sata_phy *sataphy; + + sataphy = platform_get_drvdata(pdev); + iounmap(sataphy->mmio); + i2c_del_driver(&sataphy_i2c_driver); + sata_remove_phy(&sataphy->phy); + + return 0; +} + +static const struct of_device_id sata_phy_of_match[] = { + { .compatible = "samsung,exynos5-sata-phy", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, sata_phy_of_match); + +static const struct i2c_device_id phy_i2c_device_match[] = { + { "sata-phy", 0 }, +}; + +MODULE_DEVICE_TABLE(of, phy_i2c_device_match); + +static struct platform_driver sata_phy_driver = { + .probe = exynos_sata_phy_probe, + .remove = exynos_sata_phy_remove, + .driver = { + .name = "sata-phy", + .owner = THIS_MODULE, + .of_match_table = sata_phy_of_match, + }, +}; + +static struct i2c_driver sataphy_i2c_driver = { + .driver = { + .name = "sata-phy-i2c", + .owner = THIS_MODULE, + .of_match_table = (void *)phy_i2c_device_match, + }, + .probe = exynos_sata_i2c_probe, + .id_table = phy_i2c_device_match, +}; + +module_platform_driver(sata_phy_driver); + +MODULE_DESCRIPTION("EXYNOS SATA PHY DRIVER"); +MODULE_AUTHOR("Vasanth Ananthan, "); +MODULE_LICENSE("GPL"); diff --git a/drivers/ata/sata_exynos_phy.h b/drivers/ata/sata_exynos_phy.h new file mode 100644 index 00000000000000..f9889ccd9abc3d --- /dev/null +++ b/drivers/ata/sata_exynos_phy.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010-2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS - SATA PHY controller definition + * + * 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. +*/ + +#define EXYNOS5_SATA_RESET 0x4 +#define RESET_CMN_RST_N (1 << 1) +#define LINK_RESET 0xF0000 + +#define EXYNOS5_SATA_MODE0 0x10 + +#define EXYNOS5_SATA_CTRL0 0x14 +#define CTRL0_P0_PHY_CALIBRATED_SEL (1 << 9) +#define CTRL0_P0_PHY_CALIBRATED (1 << 8) + +#define EXYNOS5_SATA_PHSATA_CTRLM 0xE0 +#define PHCTRLM_REF_RATE (1 << 1) +#define PHCTRLM_HIGH_SPEED (1 << 0) + +#define EXYNOS5_SATA_PHSATA_STATM 0xF0 +#define PHSTATM_PLL_LOCKED (1 << 0) + +#define SATA_PHY_CON_RESET 0xF003F + +#define SATA_PHY_PMU_EN 0x1 + diff --git a/drivers/ata/sata_phy.c b/drivers/ata/sata_phy.c new file mode 100644 index 00000000000000..53d441775d74e1 --- /dev/null +++ b/drivers/ata/sata_phy.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2010-2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * SATA PHY framework. + * + * This file provides a set of functions/interfaces for establishing + * communication between SATA controller and the PHY controller. A + * PHY controller driver registers call backs for its initialization and + * shutdown. The SATA controller driver finds the appropriate PHYs for + * its implemented ports and initialize/shutdown PHYs through the + * call backs provided. + * + * 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 +#include +#include +#include +#include +#include +#include "sata_phy.h" + +static LIST_HEAD(phy_list); +static DEFINE_SPINLOCK(phy_lock); + +struct sata_phy *sata_get_phy(struct device_node *phy_np) +{ + struct sata_phy *phy; + unsigned long flag; + + spin_lock_irqsave(&phy_lock, flag); + + if (list_empty(&phy_list)) { + spin_unlock_irqrestore(&phy_lock, flag); + return ERR_PTR(-ENODEV); + } + + list_for_each_entry(phy, &phy_list, head) { + if (phy->dev->of_node == phy_np) { + if (phy->status == IN_USE) { + pr_info(KERN_INFO + "PHY already in use\n"); + spin_unlock_irqrestore(&phy_lock, flag); + return ERR_PTR(-EBUSY); + } + + get_device(phy->dev); + phy->status = IN_USE; + spin_unlock_irqrestore(&phy_lock, flag); + return phy; + } + } + + spin_unlock_irqrestore(&phy_lock, flag); + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL(sata_get_phy); + +int sata_add_phy(struct sata_phy *sataphy) +{ + unsigned long flag; + unsigned int ret = -EINVAL; + struct sata_phy *phy; + + if (!sataphy) + return ret; + + spin_lock_irqsave(&phy_lock, flag); + + list_for_each_entry(phy, &phy_list, head) { + if (phy->dev->of_node == sataphy->dev->of_node) { + dev_err(sataphy->dev, "PHY already exists in the list\n"); + goto out; + } + } + + sataphy->status = NOT_IN_USE; + list_add_tail(&sataphy->head, &phy_list); + ret = 0; + + out: + spin_unlock_irqrestore(&phy_lock, flag); + return ret; +} +EXPORT_SYMBOL(sata_add_phy); + +void sata_remove_phy(struct sata_phy *sataphy) +{ + unsigned long flag; + struct sata_phy *phy; + + if (!sataphy) + return; + + if (sataphy->status == IN_USE) { + pr_info(KERN_INFO + "PHY in use, cannot be removed\n"); + return; + } + + spin_lock_irqsave(&phy_lock, flag); + + list_for_each_entry(phy, &phy_list, head) { + if (phy->dev->of_node == sataphy->dev->of_node) + list_del(&phy->head); + } + + spin_unlock_irqrestore(&phy_lock, flag); +} +EXPORT_SYMBOL(sata_remove_phy); + +void sata_put_phy(struct sata_phy *sataphy) +{ + unsigned long flag; + + if (!sataphy) + return; + + spin_lock_irqsave(&phy_lock, flag); + + put_device(sataphy->dev); + sataphy->status = NOT_IN_USE; + + spin_unlock_irqrestore(&phy_lock, flag); +} +EXPORT_SYMBOL(sata_put_phy); + +int sata_init_phy(struct sata_phy *sataphy) +{ + if (sataphy && sataphy->init) + return sataphy->init(sataphy); + + return -EINVAL; +} +EXPORT_SYMBOL(sata_init_phy); + +void sata_shutdown_phy(struct sata_phy *sataphy) +{ + if (sataphy && sataphy->shutdown) + sataphy->shutdown(sataphy); +} +EXPORT_SYMBOL(sata_shutdown_phy); + diff --git a/drivers/ata/sata_phy.h b/drivers/ata/sata_phy.h new file mode 100644 index 00000000000000..9ed1dbed5a11ef --- /dev/null +++ b/drivers/ata/sata_phy.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * SATA utility framework definitions. + * + * 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. +*/ + +#define IN_USE 1 +#define NOT_IN_USE 0 + +struct sata_phy { + int (*init) (struct sata_phy *); + int (*shutdown) (struct sata_phy *); + struct device *dev; + void *priv_data; + struct list_head head; + unsigned char status; +}; + +struct sata_phy *sata_get_phy(struct device_node *); +int sata_add_phy(struct sata_phy *); +void sata_remove_phy(struct sata_phy *); +void sata_put_phy(struct sata_phy *); +int sata_init_phy(struct sata_phy *); +void sata_shutdown_phy(struct sata_phy *); + diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 1b41fca3d65a54..9fe35277c0f2e4 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -57,6 +57,12 @@ struct suspend_stats suspend_stats; static DEFINE_MUTEX(dpm_list_mtx); static pm_message_t pm_transition; +static void dpm_drv_timeout(unsigned long data); +struct dpm_drv_wd_data { + struct device *dev; + struct task_struct *tsk; +}; + static int async_error; static char *pm_verb(int event) @@ -738,6 +744,30 @@ static bool is_async(struct device *dev) && !pm_trace_is_enabled(); } +/** + * dpm_drv_timeout - Driver suspend / resume watchdog handler + * @data: struct device which timed out + * + * Called when a driver has timed out suspending or resuming. + * There's not much we can do here to recover so + * BUG() out for a crash-dump + * + */ +static void dpm_drv_timeout(unsigned long data) +{ + struct dpm_drv_wd_data *wd_data = (void *)data; + struct device *dev = wd_data->dev; + struct task_struct *tsk = wd_data->tsk; + + printk(KERN_EMERG "**** DPM device timeout: %s (%s)\n", dev_name(dev), + (dev->driver ? dev->driver->name : "no driver")); + + printk(KERN_EMERG "dpm suspend stack:\n"); + show_stack(tsk, NULL); + + BUG(); +} + /** * dpm_resume - Execute "resume" callbacks for non-sysdev devices. * @state: PM transition of the system being carried out. @@ -1130,6 +1160,8 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) pm_callback_t callback = NULL; char *info = NULL; int error = 0; + struct timer_list timer; + struct dpm_drv_wd_data data; DECLARE_DPM_WATCHDOG_ON_STACK(wd); dpm_wait_for_children(dev, async); @@ -1153,6 +1185,14 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) if (dev->power.syscore) goto Complete; + + data.dev = dev; + data.tsk = get_current(); + init_timer_on_stack(&timer); + timer.expires = jiffies + HZ * 12; + timer.function = dpm_drv_timeout; + timer.data = (unsigned long)&data; + add_timer(&timer); dpm_watchdog_set(&wd, dev); device_lock(dev); @@ -1213,6 +1253,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) device_unlock(dev); dpm_watchdog_clear(&wd); + del_timer_sync(&timer); + destroy_timer_on_stack(&timer); + Complete: complete_all(&dev->power.completion); if (error) diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c index 962fd35cbd8d24..bc8e1af279c0a4 100644 --- a/drivers/bus/arm-cci.c +++ b/drivers/bus/arm-cci.c @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -544,6 +545,7 @@ static int cci_pmu_init(struct arm_pmu *cci_pmu, struct platform_device *pdev) cci_pmu->plat_device = pdev; cci_pmu->num_events = pmu_get_max_counters(); + cpumask_setall(&cci_pmu->valid_cpus); return armpmu_register(cci_pmu, -1); } @@ -969,6 +971,11 @@ static int cci_probe(void) const char *match_str; bool is_ace; + if (psci_probe() == 0) { + pr_debug("psci found. Aborting cci probe\n"); + return -ENODEV; + } + np = of_find_matching_node(NULL, arm_cci_matches); if (!np) return -ENODEV; diff --git a/drivers/bus/omap_l3_noc.c b/drivers/bus/omap_l3_noc.c index feeecae623f667..82de7a5230f57c 100644 --- a/drivers/bus/omap_l3_noc.c +++ b/drivers/bus/omap_l3_noc.c @@ -72,7 +72,7 @@ static irqreturn_t l3_interrupt_handler(int irq, void *_l3) * to determine the source */ base = l3->l3_base[i]; - err_reg = __raw_readl(base + l3_flagmux[i] + + err_reg = readl_relaxed(base + l3_flagmux[i] + + L3_FLAGMUX_REGERR0 + (inttype << 3)); /* Get the corresponding error and analyse */ @@ -82,9 +82,9 @@ static irqreturn_t l3_interrupt_handler(int irq, void *_l3) /* Read the stderrlog_main_source from clk domain */ l3_targ_base = base + *(l3_targ[i] + err_src); - std_err_main = __raw_readl(l3_targ_base + + std_err_main = readl_relaxed(l3_targ_base + L3_TARG_STDERRLOG_MAIN); - masterid = __raw_readl(l3_targ_base + + masterid = readl_relaxed(l3_targ_base + L3_TARG_STDERRLOG_MSTADDR); switch (std_err_main & CUSTOM_ERROR) { @@ -93,7 +93,7 @@ static irqreturn_t l3_interrupt_handler(int irq, void *_l3) l3_targ_inst_name[i][err_src]; WARN(true, "L3 standard error: TARGET:%s at address 0x%x\n", target_name, - __raw_readl(l3_targ_base + + readl_relaxed(l3_targ_base + L3_TARG_STDERRLOG_SLVOFSLSB)); /* clear the std error log*/ clear = std_err_main | CLEAR_STDERR_LOG; diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 1386749b48ffd6..6ee8a3f6a97f4d 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -6,6 +6,19 @@ menu "Character devices" source "drivers/tty/Kconfig" +config DEVMEM + bool "Memory device driver" + default y + help + The memory driver provides two character devices, mem and kmem, which + provide access to the system's memory. The mem device is a view of + physical memory, and each byte in the device corresponds to the + matching physical address. The kmem device is the same as mem, but + the addresses correspond to the kernel's virtual address space rather + than physical memory. These devices are standard parts of a Linux + system and most users should say Y here. You might say N if very + security conscience or memory is tight. + config DEVKMEM bool "/dev/kmem virtual device support" default y @@ -579,6 +592,10 @@ config DEVPORT depends on ISA || PCI default y +config DCC_TTY + tristate "DCC tty driver" + depends on ARM + source "drivers/s390/char/Kconfig" config MSM_SMD_PKT diff --git a/drivers/char/Makefile b/drivers/char/Makefile index a324f9303e36da..9e4c495d71e5cc 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_PCMCIA) += pcmcia/ obj-$(CONFIG_HANGCHECK_TIMER) += hangcheck-timer.o obj-$(CONFIG_TCG_TPM) += tpm/ +obj-$(CONFIG_DCC_TTY) += dcc_tty.o obj-$(CONFIG_PS3_FLASH) += ps3flash.o obj-$(CONFIG_JS_RTC) += js-rtc.o diff --git a/drivers/char/dcc_tty.c b/drivers/char/dcc_tty.c new file mode 100644 index 00000000000000..0a62d410286faf --- /dev/null +++ b/drivers/char/dcc_tty.c @@ -0,0 +1,326 @@ +/* drivers/char/dcc_tty.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("DCC TTY Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +DEFINE_SPINLOCK(g_dcc_tty_lock); +static struct hrtimer g_dcc_timer; +static char g_dcc_buffer[16]; +static int g_dcc_buffer_head; +static int g_dcc_buffer_count; +static unsigned g_dcc_write_delay_usecs = 1; +static struct tty_driver *g_dcc_tty_driver; +static struct tty_struct *g_dcc_tty; +static int g_dcc_tty_open_count; + +static void dcc_poll_locked(void) +{ + char ch; + int rch; + int written; + + while (g_dcc_buffer_count) { + ch = g_dcc_buffer[g_dcc_buffer_head]; + asm( + "mrc 14, 0, r15, c0, c1, 0\n" + "mcrcc 14, 0, %1, c0, c5, 0\n" + "movcc %0, #1\n" + "movcs %0, #0\n" + : "=r" (written) + : "r" (ch) + ); + if (written) { + if (ch == '\n') + g_dcc_buffer[g_dcc_buffer_head] = '\r'; + else { + g_dcc_buffer_head = (g_dcc_buffer_head + 1) % ARRAY_SIZE(g_dcc_buffer); + g_dcc_buffer_count--; + if (g_dcc_tty) + tty_wakeup(g_dcc_tty); + } + g_dcc_write_delay_usecs = 1; + } else { + if (g_dcc_write_delay_usecs > 0x100) + break; + g_dcc_write_delay_usecs <<= 1; + udelay(g_dcc_write_delay_usecs); + } + } + + if (g_dcc_tty && !test_bit(TTY_THROTTLED, &g_dcc_tty->flags)) { + asm( + "mrc 14, 0, %0, c0, c1, 0\n" + "tst %0, #(1 << 30)\n" + "moveq %0, #-1\n" + "mrcne 14, 0, %0, c0, c5, 0\n" + : "=r" (rch) + ); + if (rch >= 0) { + ch = rch; + tty_insert_flip_string(g_dcc_tty->port, &ch, 1); + tty_flip_buffer_push(g_dcc_tty->port); + } + } + + + if (g_dcc_buffer_count) + hrtimer_start(&g_dcc_timer, ktime_set(0, g_dcc_write_delay_usecs * NSEC_PER_USEC), HRTIMER_MODE_REL); + else + hrtimer_start(&g_dcc_timer, ktime_set(0, 20 * NSEC_PER_MSEC), HRTIMER_MODE_REL); +} + +static int dcc_tty_open(struct tty_struct * tty, struct file * filp) +{ + int ret; + unsigned long irq_flags; + + spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); + if (g_dcc_tty == NULL || g_dcc_tty == tty) { + g_dcc_tty = tty; + g_dcc_tty_open_count++; + ret = 0; + } else + ret = -EBUSY; + spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); + + printk("dcc_tty_open, tty %p, f_flags %x, returned %d\n", tty, filp->f_flags, ret); + + return ret; +} + +static void dcc_tty_close(struct tty_struct * tty, struct file * filp) +{ + printk("dcc_tty_close, tty %p, f_flags %x\n", tty, filp->f_flags); + if (g_dcc_tty == tty) { + if (--g_dcc_tty_open_count == 0) + g_dcc_tty = NULL; + } +} + +static int dcc_write(const unsigned char *buf_start, int count) +{ + const unsigned char *buf = buf_start; + unsigned long irq_flags; + int copy_len; + int space_left; + int tail; + + if (count < 1) + return 0; + + spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); + do { + tail = (g_dcc_buffer_head + g_dcc_buffer_count) % ARRAY_SIZE(g_dcc_buffer); + copy_len = ARRAY_SIZE(g_dcc_buffer) - tail; + space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count; + if (copy_len > space_left) + copy_len = space_left; + if (copy_len > count) + copy_len = count; + memcpy(&g_dcc_buffer[tail], buf, copy_len); + g_dcc_buffer_count += copy_len; + buf += copy_len; + count -= copy_len; + if (copy_len < count && copy_len < space_left) { + space_left -= copy_len; + copy_len = count; + if (copy_len > space_left) { + copy_len = space_left; + } + memcpy(g_dcc_buffer, buf, copy_len); + buf += copy_len; + count -= copy_len; + g_dcc_buffer_count += copy_len; + } + dcc_poll_locked(); + space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count; + } while(count && space_left); + spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); + return buf - buf_start; +} + +static int dcc_tty_write(struct tty_struct * tty, const unsigned char *buf, int count) +{ + int ret; + /* printk("dcc_tty_write %p, %d\n", buf, count); */ + ret = dcc_write(buf, count); + if (ret != count) + printk("dcc_tty_write %p, %d, returned %d\n", buf, count, ret); + return ret; +} + +static int dcc_tty_write_room(struct tty_struct *tty) +{ + int space_left; + unsigned long irq_flags; + + spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); + space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count; + spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); + return space_left; +} + +static int dcc_tty_chars_in_buffer(struct tty_struct *tty) +{ + int ret; + asm( + "mrc 14, 0, %0, c0, c1, 0\n" + "mov %0, %0, LSR #30\n" + "and %0, %0, #1\n" + : "=r" (ret) + ); + return ret; +} + +static void dcc_tty_unthrottle(struct tty_struct * tty) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); + dcc_poll_locked(); + spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); +} + +static enum hrtimer_restart dcc_tty_timer_func(struct hrtimer *timer) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); + dcc_poll_locked(); + spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); + return HRTIMER_NORESTART; +} + +void dcc_console_write(struct console *co, const char *b, unsigned count) +{ +#if 1 + dcc_write(b, count); +#else + /* blocking printk */ + while (count > 0) { + int written; + written = dcc_write(b, count); + if (written) { + b += written; + count -= written; + } + } +#endif +} + +static struct tty_driver *dcc_console_device(struct console *c, int *index) +{ + *index = 0; + return g_dcc_tty_driver; +} + +static int __init dcc_console_setup(struct console *co, char *options) +{ + if (co->index != 0) + return -ENODEV; + return 0; +} + + +static struct console dcc_console = +{ + .name = "ttyDCC", + .write = dcc_console_write, + .device = dcc_console_device, + .setup = dcc_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +static struct tty_operations dcc_tty_ops = { + .open = dcc_tty_open, + .close = dcc_tty_close, + .write = dcc_tty_write, + .write_room = dcc_tty_write_room, + .chars_in_buffer = dcc_tty_chars_in_buffer, + .unthrottle = dcc_tty_unthrottle, +}; + +static int __init dcc_tty_init(void) +{ + int ret; + + hrtimer_init(&g_dcc_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + g_dcc_timer.function = dcc_tty_timer_func; + + g_dcc_tty_driver = alloc_tty_driver(1); + if (!g_dcc_tty_driver) { + printk(KERN_ERR "dcc_tty_probe: alloc_tty_driver failed\n"); + ret = -ENOMEM; + goto err_alloc_tty_driver_failed; + } + g_dcc_tty_driver->owner = THIS_MODULE; + g_dcc_tty_driver->driver_name = "dcc"; + g_dcc_tty_driver->name = "ttyDCC"; + g_dcc_tty_driver->major = 0; // auto assign + g_dcc_tty_driver->minor_start = 0; + g_dcc_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + g_dcc_tty_driver->subtype = SERIAL_TYPE_NORMAL; + g_dcc_tty_driver->init_termios = tty_std_termios; + g_dcc_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + tty_set_operations(g_dcc_tty_driver, &dcc_tty_ops); + ret = tty_register_driver(g_dcc_tty_driver); + if (ret) { + printk(KERN_ERR "dcc_tty_probe: tty_register_driver failed, %d\n", ret); + goto err_tty_register_driver_failed; + } + tty_register_device(g_dcc_tty_driver, 0, NULL); + + register_console(&dcc_console); + hrtimer_start(&g_dcc_timer, ktime_set(0, 0), HRTIMER_MODE_REL); + + return 0; + +err_tty_register_driver_failed: + put_tty_driver(g_dcc_tty_driver); + g_dcc_tty_driver = NULL; +err_alloc_tty_driver_failed: + return ret; +} + +static void __exit dcc_tty_exit(void) +{ + int ret; + + tty_unregister_device(g_dcc_tty_driver, 0); + ret = tty_unregister_driver(g_dcc_tty_driver); + if (ret < 0) { + printk(KERN_ERR "dcc_tty_remove: tty_unregister_driver failed, %d\n", ret); + } else { + put_tty_driver(g_dcc_tty_driver); + } + g_dcc_tty_driver = NULL; +} + +module_init(dcc_tty_init); +module_exit(dcc_tty_exit); + + diff --git a/drivers/char/hw_random/omap-rng.c b/drivers/char/hw_random/omap-rng.c index 9b89ff4881de7c..d68cf8a21091b4 100644 --- a/drivers/char/hw_random/omap-rng.c +++ b/drivers/char/hw_random/omap-rng.c @@ -131,13 +131,13 @@ struct omap_rng_dev { static inline u32 omap_rng_read(struct omap_rng_dev *priv, u16 reg) { - return __raw_readl(priv->base + priv->pdata->regs[reg]); + return readl_relaxed(priv->base + priv->pdata->regs[reg]); } static inline void omap_rng_write(struct omap_rng_dev *priv, u16 reg, u32 val) { - __raw_writel(val, priv->base + priv->pdata->regs[reg]); + writel_relaxed(val, priv->base + priv->pdata->regs[reg]); } static int omap_rng_data_present(struct hwrng *rng, int wait) diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 92c5937f80c33a..fe0f897267471d 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -58,6 +58,7 @@ static inline int valid_mmap_phys_addr_range(unsigned long pfn, size_t size) } #endif +#if defined(CONFIG_DEVMEM) || defined(CONFIG_DEVKMEM) #ifdef CONFIG_STRICT_DEVMEM static inline int range_is_allowed(unsigned long pfn, unsigned long size) { @@ -83,7 +84,9 @@ static inline int range_is_allowed(unsigned long pfn, unsigned long size) return 1; } #endif +#endif +#ifdef CONFIG_DEVMEM void __weak unxlate_dev_mem_ptr(unsigned long phys, void *addr) { } @@ -210,6 +213,9 @@ static ssize_t write_mem(struct file *file, const char __user *buf, *ppos += written; return written; } +#endif /* CONFIG_DEVMEM */ + +#if defined(CONFIG_DEVMEM) || defined(CONFIG_DEVKMEM) int __weak phys_mem_access_prot_allowed(struct file *file, unsigned long pfn, unsigned long size, pgprot_t *vma_prot) @@ -331,6 +337,7 @@ static int mmap_mem(struct file *file, struct vm_area_struct *vma) } return 0; } +#endif /* CONFIG_DEVMEM */ #ifdef CONFIG_DEVKMEM static int mmap_kmem(struct file *file, struct vm_area_struct *vma) @@ -691,6 +698,8 @@ static loff_t null_lseek(struct file *file, loff_t offset, int orig) return file->f_pos = 0; } +#if defined(CONFIG_DEVMEM) || defined(CONFIG_DEVKMEM) || defined(CONFIG_DEVPORT) + /* * The memory devices use the full 32/64 bits of the offset, and so we cannot * check against negative addresses: they are ok. The return value is weird, @@ -724,10 +733,14 @@ static loff_t memory_lseek(struct file *file, loff_t offset, int orig) return ret; } +#endif + +#if defined(CONFIG_DEVMEM) || defined(CONFIG_DEVKMEM) || defined(CONFIG_DEVPORT) static int open_port(struct inode *inode, struct file *filp) { return capable(CAP_SYS_RAWIO) ? 0 : -EPERM; } +#endif #define zero_lseek null_lseek #define full_lseek null_lseek @@ -737,6 +750,7 @@ static int open_port(struct inode *inode, struct file *filp) #define open_mem open_port #define open_kmem open_mem +#ifdef CONFIG_DEVMEM static const struct file_operations mem_fops = { .llseek = memory_lseek, .read = read_mem, @@ -745,6 +759,7 @@ static const struct file_operations mem_fops = { .open = open_mem, .get_unmapped_area = get_unmapped_area_mem, }; +#endif #ifdef CONFIG_DEVKMEM static const struct file_operations kmem_fops = { @@ -806,7 +821,9 @@ static const struct memdev { const struct file_operations *fops; struct backing_dev_info *dev_info; } devlist[] = { +#ifdef CONFIG_DEVMEM [1] = { "mem", 0, &mem_fops, &directly_mappable_cdev_bdi }, +#endif #ifdef CONFIG_DEVKMEM [2] = { "kmem", 0, &kmem_fops, &directly_mappable_cdev_bdi }, #endif diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 7641965d208d62..f7caa2d33f0751 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -45,6 +45,13 @@ config COMMON_CLK_MAX77686 ---help--- This driver supports Maxim 77686 crystal oscillator clock. +config COMMON_CLK_SCPI + tristate "Clock driver for oscillators controlled via SCPI interface" + depends on ARM_SCPI_MHU + ---help--- + This driver provides support for oscillators that are controlled + by firmware that implements the SCPI interface. + config COMMON_CLK_SI5351 tristate "Clock driver for SiLabs 5351A/B/C" depends on I2C @@ -111,4 +118,5 @@ source "drivers/clk/qcom/Kconfig" endmenu +source "drivers/clk/bcm/Kconfig" source "drivers/clk/mvebu/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index a367a98317175f..fcfe62d7cfe2c4 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o obj-$(CONFIG_ARCH_NSPIRE) += clk-nspire.o obj-$(CONFIG_CLK_PPC_CORENET) += clk-ppc-corenet.o obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o +obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o @@ -29,7 +30,9 @@ obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o obj-$(CONFIG_COMMON_CLK_AT91) += at91/ +obj-$(CONFIG_ARCH_BCM_MOBILE) += bcm/ obj-$(CONFIG_ARCH_HI3xxx) += hisilicon/ +obj-$(CONFIG_ARCH_HIP04) += hisilicon/ obj-$(CONFIG_COMMON_CLK_KEYSTONE) += keystone/ ifeq ($(CONFIG_COMMON_CLK), y) obj-$(CONFIG_ARCH_MMP) += mmp/ diff --git a/drivers/clk/bcm/Kconfig b/drivers/clk/bcm/Kconfig new file mode 100644 index 00000000000000..75506e53075b95 --- /dev/null +++ b/drivers/clk/bcm/Kconfig @@ -0,0 +1,9 @@ +config CLK_BCM_KONA + bool "Broadcom Kona CCU clock support" + depends on ARCH_BCM_MOBILE + depends on COMMON_CLK + default y + help + Enable common clock framework support for Broadcom SoCs + using "Kona" style clock control units, including those + in the BCM281xx and BCM21664 families. diff --git a/drivers/clk/bcm/Makefile b/drivers/clk/bcm/Makefile new file mode 100644 index 00000000000000..6297d05a9a1040 --- /dev/null +++ b/drivers/clk/bcm/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_CLK_BCM_KONA) += clk-kona.o +obj-$(CONFIG_CLK_BCM_KONA) += clk-kona-setup.o +obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm281xx.o +obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm21664.o diff --git a/drivers/clk/bcm/clk-bcm21664.c b/drivers/clk/bcm/clk-bcm21664.c new file mode 100644 index 00000000000000..eeae4cad22816d --- /dev/null +++ b/drivers/clk/bcm/clk-bcm21664.c @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * Copyright 2014 Linaro Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "clk-kona.h" +#include "dt-bindings/clock/bcm21664.h" + +#define BCM21664_CCU_COMMON(_name, _capname) \ + KONA_CCU_COMMON(BCM21664, _name, _capname) + +/* Root CCU */ + +static struct peri_clk_data frac_1m_data = { + .gate = HW_SW_GATE(0x214, 16, 0, 1), + .clocks = CLOCKS("ref_crystal"), +}; + +static struct ccu_data root_ccu_data = { + BCM21664_CCU_COMMON(root, ROOT), + /* no policy control */ + .kona_clks = { + [BCM21664_ROOT_CCU_FRAC_1M] = + KONA_CLK(root, frac_1m, peri), + [BCM21664_ROOT_CCU_CLOCK_COUNT] = LAST_KONA_CLK, + }, +}; + +/* AON CCU */ + +static struct peri_clk_data hub_timer_data = { + .gate = HW_SW_GATE(0x0414, 16, 0, 1), + .hyst = HYST(0x0414, 8, 9), + .clocks = CLOCKS("bbl_32k", + "frac_1m", + "dft_19_5m"), + .sel = SELECTOR(0x0a10, 0, 2), + .trig = TRIGGER(0x0a40, 4), +}; + +static struct ccu_data aon_ccu_data = { + BCM21664_CCU_COMMON(aon, AON), + .policy = { + .enable = CCU_LVM_EN(0x0034, 0), + .control = CCU_POLICY_CTL(0x000c, 0, 1, 2), + }, + .kona_clks = { + [BCM21664_AON_CCU_HUB_TIMER] = + KONA_CLK(aon, hub_timer, peri), + [BCM21664_AON_CCU_CLOCK_COUNT] = LAST_KONA_CLK, + }, +}; + +/* Master CCU */ + +static struct peri_clk_data sdio1_data = { + .gate = HW_SW_GATE(0x0358, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_52m", + "ref_52m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a28, 0, 3), + .div = DIVIDER(0x0a28, 4, 14), + .trig = TRIGGER(0x0afc, 9), +}; + +static struct peri_clk_data sdio2_data = { + .gate = HW_SW_GATE(0x035c, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_52m", + "ref_52m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a2c, 0, 3), + .div = DIVIDER(0x0a2c, 4, 14), + .trig = TRIGGER(0x0afc, 10), +}; + +static struct peri_clk_data sdio3_data = { + .gate = HW_SW_GATE(0x0364, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_52m", + "ref_52m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a34, 0, 3), + .div = DIVIDER(0x0a34, 4, 14), + .trig = TRIGGER(0x0afc, 12), +}; + +static struct peri_clk_data sdio4_data = { + .gate = HW_SW_GATE(0x0360, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_52m", + "ref_52m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a30, 0, 3), + .div = DIVIDER(0x0a30, 4, 14), + .trig = TRIGGER(0x0afc, 11), +}; + +static struct peri_clk_data sdio1_sleep_data = { + .clocks = CLOCKS("ref_32k"), /* Verify */ + .gate = HW_SW_GATE(0x0358, 18, 2, 3), +}; + +static struct peri_clk_data sdio2_sleep_data = { + .clocks = CLOCKS("ref_32k"), /* Verify */ + .gate = HW_SW_GATE(0x035c, 18, 2, 3), +}; + +static struct peri_clk_data sdio3_sleep_data = { + .clocks = CLOCKS("ref_32k"), /* Verify */ + .gate = HW_SW_GATE(0x0364, 18, 2, 3), +}; + +static struct peri_clk_data sdio4_sleep_data = { + .clocks = CLOCKS("ref_32k"), /* Verify */ + .gate = HW_SW_GATE(0x0360, 18, 2, 3), +}; + +static struct ccu_data master_ccu_data = { + BCM21664_CCU_COMMON(master, MASTER), + .policy = { + .enable = CCU_LVM_EN(0x0034, 0), + .control = CCU_POLICY_CTL(0x000c, 0, 1, 2), + }, + .kona_clks = { + [BCM21664_MASTER_CCU_SDIO1] = + KONA_CLK(master, sdio1, peri), + [BCM21664_MASTER_CCU_SDIO2] = + KONA_CLK(master, sdio2, peri), + [BCM21664_MASTER_CCU_SDIO3] = + KONA_CLK(master, sdio3, peri), + [BCM21664_MASTER_CCU_SDIO4] = + KONA_CLK(master, sdio4, peri), + [BCM21664_MASTER_CCU_SDIO1_SLEEP] = + KONA_CLK(master, sdio1_sleep, peri), + [BCM21664_MASTER_CCU_SDIO2_SLEEP] = + KONA_CLK(master, sdio2_sleep, peri), + [BCM21664_MASTER_CCU_SDIO3_SLEEP] = + KONA_CLK(master, sdio3_sleep, peri), + [BCM21664_MASTER_CCU_SDIO4_SLEEP] = + KONA_CLK(master, sdio4_sleep, peri), + [BCM21664_MASTER_CCU_CLOCK_COUNT] = LAST_KONA_CLK, + }, +}; + +/* Slave CCU */ + +static struct peri_clk_data uartb_data = { + .gate = HW_SW_GATE(0x0400, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_156m", + "ref_156m"), + .sel = SELECTOR(0x0a10, 0, 2), + .div = FRAC_DIVIDER(0x0a10, 4, 12, 8), + .trig = TRIGGER(0x0afc, 2), +}; + +static struct peri_clk_data uartb2_data = { + .gate = HW_SW_GATE(0x0404, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_156m", + "ref_156m"), + .sel = SELECTOR(0x0a14, 0, 2), + .div = FRAC_DIVIDER(0x0a14, 4, 12, 8), + .trig = TRIGGER(0x0afc, 3), +}; + +static struct peri_clk_data uartb3_data = { + .gate = HW_SW_GATE(0x0408, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_156m", + "ref_156m"), + .sel = SELECTOR(0x0a18, 0, 2), + .div = FRAC_DIVIDER(0x0a18, 4, 12, 8), + .trig = TRIGGER(0x0afc, 4), +}; + +static struct peri_clk_data bsc1_data = { + .gate = HW_SW_GATE(0x0458, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m", + "ref_104m", + "var_13m", + "ref_13m"), + .sel = SELECTOR(0x0a64, 0, 3), + .trig = TRIGGER(0x0afc, 23), +}; + +static struct peri_clk_data bsc2_data = { + .gate = HW_SW_GATE(0x045c, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m", + "ref_104m", + "var_13m", + "ref_13m"), + .sel = SELECTOR(0x0a68, 0, 3), + .trig = TRIGGER(0x0afc, 24), +}; + +static struct peri_clk_data bsc3_data = { + .gate = HW_SW_GATE(0x0470, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m", + "ref_104m", + "var_13m", + "ref_13m"), + .sel = SELECTOR(0x0a7c, 0, 3), + .trig = TRIGGER(0x0afc, 18), +}; + +static struct peri_clk_data bsc4_data = { + .gate = HW_SW_GATE(0x0474, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m", + "ref_104m", + "var_13m", + "ref_13m"), + .sel = SELECTOR(0x0a80, 0, 3), + .trig = TRIGGER(0x0afc, 19), +}; + +static struct ccu_data slave_ccu_data = { + BCM21664_CCU_COMMON(slave, SLAVE), + .policy = { + .enable = CCU_LVM_EN(0x0034, 0), + .control = CCU_POLICY_CTL(0x000c, 0, 1, 2), + }, + .kona_clks = { + [BCM21664_SLAVE_CCU_UARTB] = + KONA_CLK(slave, uartb, peri), + [BCM21664_SLAVE_CCU_UARTB2] = + KONA_CLK(slave, uartb2, peri), + [BCM21664_SLAVE_CCU_UARTB3] = + KONA_CLK(slave, uartb3, peri), + [BCM21664_SLAVE_CCU_BSC1] = + KONA_CLK(slave, bsc1, peri), + [BCM21664_SLAVE_CCU_BSC2] = + KONA_CLK(slave, bsc2, peri), + [BCM21664_SLAVE_CCU_BSC3] = + KONA_CLK(slave, bsc3, peri), + [BCM21664_SLAVE_CCU_BSC4] = + KONA_CLK(slave, bsc4, peri), + [BCM21664_SLAVE_CCU_CLOCK_COUNT] = LAST_KONA_CLK, + }, +}; + +/* Device tree match table callback functions */ + +static void __init kona_dt_root_ccu_setup(struct device_node *node) +{ + kona_dt_ccu_setup(&root_ccu_data, node); +} + +static void __init kona_dt_aon_ccu_setup(struct device_node *node) +{ + kona_dt_ccu_setup(&aon_ccu_data, node); +} + +static void __init kona_dt_master_ccu_setup(struct device_node *node) +{ + kona_dt_ccu_setup(&master_ccu_data, node); +} + +static void __init kona_dt_slave_ccu_setup(struct device_node *node) +{ + kona_dt_ccu_setup(&slave_ccu_data, node); +} + +CLK_OF_DECLARE(bcm21664_root_ccu, BCM21664_DT_ROOT_CCU_COMPAT, + kona_dt_root_ccu_setup); +CLK_OF_DECLARE(bcm21664_aon_ccu, BCM21664_DT_AON_CCU_COMPAT, + kona_dt_aon_ccu_setup); +CLK_OF_DECLARE(bcm21664_master_ccu, BCM21664_DT_MASTER_CCU_COMPAT, + kona_dt_master_ccu_setup); +CLK_OF_DECLARE(bcm21664_slave_ccu, BCM21664_DT_SLAVE_CCU_COMPAT, + kona_dt_slave_ccu_setup); diff --git a/drivers/clk/bcm/clk-bcm281xx.c b/drivers/clk/bcm/clk-bcm281xx.c new file mode 100644 index 00000000000000..8e54a02a18b498 --- /dev/null +++ b/drivers/clk/bcm/clk-bcm281xx.c @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2013 Broadcom Corporation + * Copyright 2013 Linaro Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "clk-kona.h" +#include "dt-bindings/clock/bcm281xx.h" + +#define BCM281XX_CCU_COMMON(_name, _ccuname) \ + KONA_CCU_COMMON(BCM281XX, _name, _ccuname) + +/* Root CCU */ + +static struct peri_clk_data frac_1m_data = { + .gate = HW_SW_GATE(0x214, 16, 0, 1), + .trig = TRIGGER(0x0e04, 0), + .div = FRAC_DIVIDER(0x0e00, 0, 22, 16), + .clocks = CLOCKS("ref_crystal"), +}; + +static struct ccu_data root_ccu_data = { + BCM281XX_CCU_COMMON(root, ROOT), + .kona_clks = { + [BCM281XX_ROOT_CCU_FRAC_1M] = + KONA_CLK(root, frac_1m, peri), + [BCM281XX_ROOT_CCU_CLOCK_COUNT] = LAST_KONA_CLK, + }, +}; + +/* AON CCU */ + +static struct peri_clk_data hub_timer_data = { + .gate = HW_SW_GATE(0x0414, 16, 0, 1), + .clocks = CLOCKS("bbl_32k", + "frac_1m", + "dft_19_5m"), + .sel = SELECTOR(0x0a10, 0, 2), + .trig = TRIGGER(0x0a40, 4), +}; + +static struct peri_clk_data pmu_bsc_data = { + .gate = HW_SW_GATE(0x0418, 16, 0, 1), + .clocks = CLOCKS("ref_crystal", + "pmu_bsc_var", + "bbl_32k"), + .sel = SELECTOR(0x0a04, 0, 2), + .div = DIVIDER(0x0a04, 3, 4), + .trig = TRIGGER(0x0a40, 0), +}; + +static struct peri_clk_data pmu_bsc_var_data = { + .clocks = CLOCKS("var_312m", + "ref_312m"), + .sel = SELECTOR(0x0a00, 0, 2), + .div = DIVIDER(0x0a00, 4, 5), + .trig = TRIGGER(0x0a40, 2), +}; + +static struct ccu_data aon_ccu_data = { + BCM281XX_CCU_COMMON(aon, AON), + .kona_clks = { + [BCM281XX_AON_CCU_HUB_TIMER] = + KONA_CLK(aon, hub_timer, peri), + [BCM281XX_AON_CCU_PMU_BSC] = + KONA_CLK(aon, pmu_bsc, peri), + [BCM281XX_AON_CCU_PMU_BSC_VAR] = + KONA_CLK(aon, pmu_bsc_var, peri), + [BCM281XX_AON_CCU_CLOCK_COUNT] = LAST_KONA_CLK, + }, +}; + +/* Hub CCU */ + +static struct peri_clk_data tmon_1m_data = { + .gate = HW_SW_GATE(0x04a4, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "frac_1m"), + .sel = SELECTOR(0x0e74, 0, 2), + .trig = TRIGGER(0x0e84, 1), +}; + +static struct ccu_data hub_ccu_data = { + BCM281XX_CCU_COMMON(hub, HUB), + .kona_clks = { + [BCM281XX_HUB_CCU_TMON_1M] = + KONA_CLK(hub, tmon_1m, peri), + [BCM281XX_HUB_CCU_CLOCK_COUNT] = LAST_KONA_CLK, + }, +}; + +/* Master CCU */ + +static struct peri_clk_data sdio1_data = { + .gate = HW_SW_GATE(0x0358, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_52m", + "ref_52m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a28, 0, 3), + .div = DIVIDER(0x0a28, 4, 14), + .trig = TRIGGER(0x0afc, 9), +}; + +static struct peri_clk_data sdio2_data = { + .gate = HW_SW_GATE(0x035c, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_52m", + "ref_52m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a2c, 0, 3), + .div = DIVIDER(0x0a2c, 4, 14), + .trig = TRIGGER(0x0afc, 10), +}; + +static struct peri_clk_data sdio3_data = { + .gate = HW_SW_GATE(0x0364, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_52m", + "ref_52m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a34, 0, 3), + .div = DIVIDER(0x0a34, 4, 14), + .trig = TRIGGER(0x0afc, 12), +}; + +static struct peri_clk_data sdio4_data = { + .gate = HW_SW_GATE(0x0360, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_52m", + "ref_52m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a30, 0, 3), + .div = DIVIDER(0x0a30, 4, 14), + .trig = TRIGGER(0x0afc, 11), +}; + +static struct peri_clk_data usb_ic_data = { + .gate = HW_SW_GATE(0x0354, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_96m", + "ref_96m"), + .div = FIXED_DIVIDER(2), + .sel = SELECTOR(0x0a24, 0, 2), + .trig = TRIGGER(0x0afc, 7), +}; + +/* also called usbh_48m */ +static struct peri_clk_data hsic2_48m_data = { + .gate = HW_SW_GATE(0x0370, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a38, 0, 2), + .div = FIXED_DIVIDER(2), + .trig = TRIGGER(0x0afc, 5), +}; + +/* also called usbh_12m */ +static struct peri_clk_data hsic2_12m_data = { + .gate = HW_SW_GATE(0x0370, 20, 4, 5), + .div = DIVIDER(0x0a38, 12, 2), + .clocks = CLOCKS("ref_crystal", + "var_96m", + "ref_96m"), + .pre_div = FIXED_DIVIDER(2), + .sel = SELECTOR(0x0a38, 0, 2), + .trig = TRIGGER(0x0afc, 5), +}; + +static struct ccu_data master_ccu_data = { + BCM281XX_CCU_COMMON(master, MASTER), + .kona_clks = { + [BCM281XX_MASTER_CCU_SDIO1] = + KONA_CLK(master, sdio1, peri), + [BCM281XX_MASTER_CCU_SDIO2] = + KONA_CLK(master, sdio2, peri), + [BCM281XX_MASTER_CCU_SDIO3] = + KONA_CLK(master, sdio3, peri), + [BCM281XX_MASTER_CCU_SDIO4] = + KONA_CLK(master, sdio4, peri), + [BCM281XX_MASTER_CCU_USB_IC] = + KONA_CLK(master, usb_ic, peri), + [BCM281XX_MASTER_CCU_HSIC2_48M] = + KONA_CLK(master, hsic2_48m, peri), + [BCM281XX_MASTER_CCU_HSIC2_12M] = + KONA_CLK(master, hsic2_12m, peri), + [BCM281XX_MASTER_CCU_CLOCK_COUNT] = LAST_KONA_CLK, + }, +}; + +/* Slave CCU */ + +static struct peri_clk_data uartb_data = { + .gate = HW_SW_GATE(0x0400, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_156m", + "ref_156m"), + .sel = SELECTOR(0x0a10, 0, 2), + .div = FRAC_DIVIDER(0x0a10, 4, 12, 8), + .trig = TRIGGER(0x0afc, 2), +}; + +static struct peri_clk_data uartb2_data = { + .gate = HW_SW_GATE(0x0404, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_156m", + "ref_156m"), + .sel = SELECTOR(0x0a14, 0, 2), + .div = FRAC_DIVIDER(0x0a14, 4, 12, 8), + .trig = TRIGGER(0x0afc, 3), +}; + +static struct peri_clk_data uartb3_data = { + .gate = HW_SW_GATE(0x0408, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_156m", + "ref_156m"), + .sel = SELECTOR(0x0a18, 0, 2), + .div = FRAC_DIVIDER(0x0a18, 4, 12, 8), + .trig = TRIGGER(0x0afc, 4), +}; + +static struct peri_clk_data uartb4_data = { + .gate = HW_SW_GATE(0x0408, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_156m", + "ref_156m"), + .sel = SELECTOR(0x0a1c, 0, 2), + .div = FRAC_DIVIDER(0x0a1c, 4, 12, 8), + .trig = TRIGGER(0x0afc, 5), +}; + +static struct peri_clk_data ssp0_data = { + .gate = HW_SW_GATE(0x0410, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m", + "ref_104m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a20, 0, 3), + .div = DIVIDER(0x0a20, 4, 14), + .trig = TRIGGER(0x0afc, 6), +}; + +static struct peri_clk_data ssp2_data = { + .gate = HW_SW_GATE(0x0418, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m", + "ref_104m", + "var_96m", + "ref_96m"), + .sel = SELECTOR(0x0a28, 0, 3), + .div = DIVIDER(0x0a28, 4, 14), + .trig = TRIGGER(0x0afc, 8), +}; + +static struct peri_clk_data bsc1_data = { + .gate = HW_SW_GATE(0x0458, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m", + "ref_104m", + "var_13m", + "ref_13m"), + .sel = SELECTOR(0x0a64, 0, 3), + .trig = TRIGGER(0x0afc, 23), +}; + +static struct peri_clk_data bsc2_data = { + .gate = HW_SW_GATE(0x045c, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m", + "ref_104m", + "var_13m", + "ref_13m"), + .sel = SELECTOR(0x0a68, 0, 3), + .trig = TRIGGER(0x0afc, 24), +}; + +static struct peri_clk_data bsc3_data = { + .gate = HW_SW_GATE(0x0484, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m", + "ref_104m", + "var_13m", + "ref_13m"), + .sel = SELECTOR(0x0a84, 0, 3), + .trig = TRIGGER(0x0b00, 2), +}; + +static struct peri_clk_data pwm_data = { + .gate = HW_SW_GATE(0x0468, 18, 2, 3), + .clocks = CLOCKS("ref_crystal", + "var_104m"), + .sel = SELECTOR(0x0a70, 0, 2), + .div = DIVIDER(0x0a70, 4, 3), + .trig = TRIGGER(0x0afc, 15), +}; + +static struct bus_clk_data bsc3_apb_data = { + .policy = POLICY(0x0048, 4), + .gate = HW_SW_GATE(0x0484, 16, 0, 1), +}; + +static struct ccu_data slave_ccu_data = { + BCM281XX_CCU_COMMON(slave, SLAVE), + .policy = { + .enable = CCU_LVM_EN(0x0034, 0), + .control = CCU_POLICY_CTL(0x000c, 0, 1, 2), + }, + .kona_clks = { + [BCM281XX_SLAVE_CCU_UARTB] = + KONA_CLK(slave, uartb, peri), + [BCM281XX_SLAVE_CCU_UARTB2] = + KONA_CLK(slave, uartb2, peri), + [BCM281XX_SLAVE_CCU_UARTB3] = + KONA_CLK(slave, uartb3, peri), + [BCM281XX_SLAVE_CCU_UARTB4] = + KONA_CLK(slave, uartb4, peri), + [BCM281XX_SLAVE_CCU_SSP0] = + KONA_CLK(slave, ssp0, peri), + [BCM281XX_SLAVE_CCU_SSP2] = + KONA_CLK(slave, ssp2, peri), + [BCM281XX_SLAVE_CCU_BSC1] = + KONA_CLK(slave, bsc1, peri), + [BCM281XX_SLAVE_CCU_BSC2] = + KONA_CLK(slave, bsc2, peri), + [BCM281XX_SLAVE_CCU_BSC3] = + KONA_CLK_PREREQ(slave, bsc3, peri, bsc3_apb), + [BCM281XX_SLAVE_CCU_PWM] = + KONA_CLK(slave, pwm, peri), + [BCM281XX_SLAVE_CCU_BSC3_APB] = + KONA_CLK(slave, bsc3_apb, bus), + [BCM281XX_SLAVE_CCU_CLOCK_COUNT] = LAST_KONA_CLK, + }, +}; + +/* Device tree match table callback functions */ + +static void __init kona_dt_root_ccu_setup(struct device_node *node) +{ + kona_dt_ccu_setup(&root_ccu_data, node); +} + +static void __init kona_dt_aon_ccu_setup(struct device_node *node) +{ + kona_dt_ccu_setup(&aon_ccu_data, node); +} + +static void __init kona_dt_hub_ccu_setup(struct device_node *node) +{ + kona_dt_ccu_setup(&hub_ccu_data, node); +} + +static void __init kona_dt_master_ccu_setup(struct device_node *node) +{ + kona_dt_ccu_setup(&master_ccu_data, node); +} + +static void __init kona_dt_slave_ccu_setup(struct device_node *node) +{ + kona_dt_ccu_setup(&slave_ccu_data, node); +} + +CLK_OF_DECLARE(bcm281xx_root_ccu, BCM281XX_DT_ROOT_CCU_COMPAT, + kona_dt_root_ccu_setup); +CLK_OF_DECLARE(bcm281xx_aon_ccu, BCM281XX_DT_AON_CCU_COMPAT, + kona_dt_aon_ccu_setup); +CLK_OF_DECLARE(bcm281xx_hub_ccu, BCM281XX_DT_HUB_CCU_COMPAT, + kona_dt_hub_ccu_setup); +CLK_OF_DECLARE(bcm281xx_master_ccu, BCM281XX_DT_MASTER_CCU_COMPAT, + kona_dt_master_ccu_setup); +CLK_OF_DECLARE(bcm281xx_slave_ccu, BCM281XX_DT_SLAVE_CCU_COMPAT, + kona_dt_slave_ccu_setup); diff --git a/drivers/clk/bcm/clk-kona-setup.c b/drivers/clk/bcm/clk-kona-setup.c new file mode 100644 index 00000000000000..f8c79546c1288a --- /dev/null +++ b/drivers/clk/bcm/clk-kona-setup.c @@ -0,0 +1,974 @@ +/* + * Copyright (C) 2013 Broadcom Corporation + * Copyright 2013 Linaro Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "clk-kona.h" + +/* These are used when a selector or trigger is found to be unneeded */ +#define selector_clear_exists(sel) ((sel)->width = 0) +#define trigger_clear_exists(trig) FLAG_CLEAR(trig, TRIG, EXISTS) + +LIST_HEAD(ccu_list); /* The list of set up CCUs */ + +/* Validity checking */ + +static bool ccu_data_offsets_valid(struct ccu_data *ccu) +{ + struct ccu_policy *ccu_policy = &ccu->policy; + u32 limit; + + limit = ccu->range - sizeof(u32); + limit = round_down(limit, sizeof(u32)); + if (ccu_policy_exists(ccu_policy)) { + if (ccu_policy->enable.offset > limit) { + pr_err("%s: bad policy enable offset for %s " + "(%u > %u)\n", __func__, + ccu->name, ccu_policy->enable.offset, limit); + return false; + } + if (ccu_policy->control.offset > limit) { + pr_err("%s: bad policy control offset for %s " + "(%u > %u)\n", __func__, + ccu->name, ccu_policy->control.offset, limit); + return false; + } + } + + return true; +} + +static bool clk_requires_trigger(struct kona_clk *bcm_clk) +{ + struct peri_clk_data *peri = bcm_clk->peri; + struct bcm_clk_sel *sel; + struct bcm_clk_div *div; + + if (bcm_clk->type != bcm_clk_peri) + return false; + + sel = &peri->sel; + if (sel->parent_count && selector_exists(sel)) + return true; + + div = &peri->div; + if (!divider_exists(div)) + return false; + + /* Fixed dividers don't need triggers */ + if (!divider_is_fixed(div)) + return true; + + div = &peri->pre_div; + + return divider_exists(div) && !divider_is_fixed(div); +} + +static bool bus_clk_data_offsets_valid(struct kona_clk *bcm_clk) +{ + struct bus_clk_data *bus; + struct bcm_clk_policy *policy; + struct bcm_clk_gate *gate; + struct bcm_clk_hyst *hyst; + const char *name; + u32 limit; + + BUG_ON(bcm_clk->type != bcm_clk_bus); + bus = bcm_clk->bus; + name = bcm_clk->init_data.name; + + limit = bcm_clk->ccu->range - sizeof(u32); + limit = round_down(limit, sizeof(u32)); + + policy = &bus->policy; + if (policy_exists(policy)) { + if (policy->offset > limit) { + pr_err("%s: bad policy offset for %s (%u > %u)\n", + __func__, name, policy->offset, limit); + return false; + } + } + + gate = &bus->gate; + hyst = &bus->hyst; + if (gate_exists(gate)) { + if (gate->offset > limit) { + pr_err("%s: bad gate offset for %s (%u > %u)\n", + __func__, name, gate->offset, limit); + return false; + } + if (hyst_exists(hyst)) { + if (hyst->offset > limit) { + pr_err("%s: bad hysteresis offset for %s " + "(%u > %u)\n", __func__, + name, hyst->offset, limit); + return false; + } + } + } else if (hyst_exists(hyst)) { + pr_err("%s: hysteresis but no gate for %s\n", __func__, name); + return false; + } + + + return true; +} + +static bool peri_clk_data_offsets_valid(struct kona_clk *bcm_clk) +{ + struct peri_clk_data *peri; + struct bcm_clk_policy *policy; + struct bcm_clk_gate *gate; + struct bcm_clk_hyst *hyst; + struct bcm_clk_div *div; + struct bcm_clk_sel *sel; + struct bcm_clk_trig *trig; + const char *name; + u32 limit; + + BUG_ON(bcm_clk->type != bcm_clk_peri); + peri = bcm_clk->peri; + name = bcm_clk->init_data.name; + + limit = bcm_clk->ccu->range - sizeof(u32); + limit = round_down(limit, sizeof(u32)); + + policy = &peri->policy; + if (policy_exists(policy)) { + if (policy->offset > limit) { + pr_err("%s: bad policy offset for %s (%u > %u)\n", + __func__, name, policy->offset, limit); + return false; + } + } + + gate = &peri->gate; + hyst = &peri->hyst; + if (gate_exists(gate)) { + if (gate->offset > limit) { + pr_err("%s: bad gate offset for %s (%u > %u)\n", + __func__, name, gate->offset, limit); + return false; + } + + if (hyst_exists(hyst)) { + if (hyst->offset > limit) { + pr_err("%s: bad hysteresis offset for %s " + "(%u > %u)\n", __func__, + name, hyst->offset, limit); + return false; + } + } + } else if (hyst_exists(hyst)) { + pr_err("%s: hysteresis but no gate for %s\n", __func__, name); + return false; + } + + div = &peri->div; + if (divider_exists(div)) { + if (div->offset > limit) { + pr_err("%s: bad divider offset for %s (%u > %u)\n", + __func__, name, div->offset, limit); + return false; + } + } + + div = &peri->pre_div; + if (divider_exists(div)) { + if (div->offset > limit) { + pr_err("%s: bad pre-divider offset for %s " + "(%u > %u)\n", + __func__, name, div->offset, limit); + return false; + } + } + + sel = &peri->sel; + if (selector_exists(sel)) { + if (sel->offset > limit) { + pr_err("%s: bad selector offset for %s (%u > %u)\n", + __func__, name, sel->offset, limit); + return false; + } + } + + trig = &peri->trig; + if (trigger_exists(trig)) { + if (trig->offset > limit) { + pr_err("%s: bad trigger offset for %s (%u > %u)\n", + __func__, name, trig->offset, limit); + return false; + } + } + + trig = &peri->pre_trig; + if (trigger_exists(trig)) { + if (trig->offset > limit) { + pr_err("%s: bad pre-trigger offset for %s (%u > %u)\n", + __func__, name, trig->offset, limit); + return false; + } + } + + return true; +} + +/* A bit position must be less than the number of bits in a 32-bit register. */ +static bool bit_posn_valid(u32 bit_posn, const char *field_name, + const char *clock_name) +{ + u32 limit = BITS_PER_BYTE * sizeof(u32) - 1; + + if (bit_posn > limit) { + pr_err("%s: bad %s bit for %s (%u > %u)\n", __func__, + field_name, clock_name, bit_posn, limit); + return false; + } + return true; +} + +/* + * A bitfield must be at least 1 bit wide. Both the low-order and + * high-order bits must lie within a 32-bit register. We require + * fields to be less than 32 bits wide, mainly because we use + * shifting to produce field masks, and shifting a full word width + * is not well-defined by the C standard. + */ +static bool bitfield_valid(u32 shift, u32 width, const char *field_name, + const char *clock_name) +{ + u32 limit = BITS_PER_BYTE * sizeof(u32); + + if (!width) { + pr_err("%s: bad %s field width 0 for %s\n", __func__, + field_name, clock_name); + return false; + } + if (shift + width > limit) { + pr_err("%s: bad %s for %s (%u + %u > %u)\n", __func__, + field_name, clock_name, shift, width, limit); + return false; + } + return true; +} + +static bool +ccu_policy_valid(struct ccu_policy *ccu_policy, const char *ccu_name) +{ + struct bcm_lvm_en *enable = &ccu_policy->enable; + struct bcm_policy_ctl *control; + + if (!bit_posn_valid(enable->bit, "policy enable", ccu_name)) + return false; + + control = &ccu_policy->control; + if (!bit_posn_valid(control->go_bit, "policy control GO", ccu_name)) + return false; + + if (!bit_posn_valid(control->atl_bit, "policy control ATL", ccu_name)) + return false; + + if (!bit_posn_valid(control->ac_bit, "policy control AC", ccu_name)) + return false; + + return true; +} + +static bool policy_valid(struct bcm_clk_policy *policy, const char *clock_name) +{ + if (!bit_posn_valid(policy->bit, "policy", clock_name)) + return false; + + return true; +} + +/* + * All gates, if defined, have a status bit, and for hardware-only + * gates, that's it. Gates that can be software controlled also + * have an enable bit. And a gate that can be hardware or software + * controlled will have a hardware/software select bit. + */ +static bool gate_valid(struct bcm_clk_gate *gate, const char *field_name, + const char *clock_name) +{ + if (!bit_posn_valid(gate->status_bit, "gate status", clock_name)) + return false; + + if (gate_is_sw_controllable(gate)) { + if (!bit_posn_valid(gate->en_bit, "gate enable", clock_name)) + return false; + + if (gate_is_hw_controllable(gate)) { + if (!bit_posn_valid(gate->hw_sw_sel_bit, + "gate hw/sw select", + clock_name)) + return false; + } + } else { + BUG_ON(!gate_is_hw_controllable(gate)); + } + + return true; +} + +static bool hyst_valid(struct bcm_clk_hyst *hyst, const char *clock_name) +{ + if (!bit_posn_valid(hyst->en_bit, "hysteresis enable", clock_name)) + return false; + + if (!bit_posn_valid(hyst->val_bit, "hysteresis value", clock_name)) + return false; + + return true; +} + +/* + * A selector bitfield must be valid. Its parent_sel array must + * also be reasonable for the field. + */ +static bool sel_valid(struct bcm_clk_sel *sel, const char *field_name, + const char *clock_name) +{ + if (!bitfield_valid(sel->shift, sel->width, field_name, clock_name)) + return false; + + if (sel->parent_count) { + u32 max_sel; + u32 limit; + + /* + * Make sure the selector field can hold all the + * selector values we expect to be able to use. A + * clock only needs to have a selector defined if it + * has more than one parent. And in that case the + * highest selector value will be in the last entry + * in the array. + */ + max_sel = sel->parent_sel[sel->parent_count - 1]; + limit = (1 << sel->width) - 1; + if (max_sel > limit) { + pr_err("%s: bad selector for %s " + "(%u needs > %u bits)\n", + __func__, clock_name, max_sel, + sel->width); + return false; + } + } else { + pr_warn("%s: ignoring selector for %s (no parents)\n", + __func__, clock_name); + selector_clear_exists(sel); + kfree(sel->parent_sel); + sel->parent_sel = NULL; + } + + return true; +} + +/* + * A fixed divider just needs to be non-zero. A variable divider + * has to have a valid divider bitfield, and if it has a fraction, + * the width of the fraction must not be no more than the width of + * the divider as a whole. + */ +static bool div_valid(struct bcm_clk_div *div, const char *field_name, + const char *clock_name) +{ + if (divider_is_fixed(div)) { + /* Any fixed divider value but 0 is OK */ + if (div->fixed == 0) { + pr_err("%s: bad %s fixed value 0 for %s\n", __func__, + field_name, clock_name); + return false; + } + return true; + } + if (!bitfield_valid(div->shift, div->width, field_name, clock_name)) + return false; + + if (divider_has_fraction(div)) + if (div->frac_width > div->width) { + pr_warn("%s: bad %s fraction width for %s (%u > %u)\n", + __func__, field_name, clock_name, + div->frac_width, div->width); + return false; + } + + return true; +} + +/* + * If a clock has two dividers, the combined number of fractional + * bits must be representable in a 32-bit unsigned value. This + * is because we scale up a dividend using both dividers before + * dividing to improve accuracy, and we need to avoid overflow. + */ +static bool kona_dividers_valid(struct kona_clk *bcm_clk) +{ + struct peri_clk_data *peri = bcm_clk->peri; + struct bcm_clk_div *div; + struct bcm_clk_div *pre_div; + u32 limit; + + BUG_ON(bcm_clk->type != bcm_clk_peri); + + if (!divider_exists(&peri->div) || !divider_exists(&peri->pre_div)) + return true; + + div = &peri->div; + pre_div = &peri->pre_div; + if (divider_is_fixed(div) || divider_is_fixed(pre_div)) + return true; + + limit = BITS_PER_BYTE * sizeof(u32); + + return div->frac_width + pre_div->frac_width <= limit; +} + + +/* A trigger just needs to represent a valid bit position */ +static bool trig_valid(struct bcm_clk_trig *trig, const char *field_name, + const char *clock_name) +{ + return bit_posn_valid(trig->bit, field_name, clock_name); +} + +/* Determine whether the set of bus clock registers are valid. */ +static bool +bus_clk_data_valid(struct kona_clk *bcm_clk) +{ + struct bcm_clk_gate *gate; + + BUG_ON(bcm_clk->type != bcm_clk_bus); + if (!bus_clk_data_offsets_valid(bcm_clk)) + return false; + + gate = &bcm_clk->bus->gate; + if (!gate_exists(gate)) + return true; + + return gate_valid(gate, "gate", bcm_clk->init_data.name); +} + +/* Determine whether the set of peripheral clock registers are valid. */ +static bool +peri_clk_data_valid(struct kona_clk *bcm_clk) +{ + struct peri_clk_data *peri; + struct bcm_clk_policy *policy; + struct bcm_clk_gate *gate; + struct bcm_clk_hyst *hyst; + struct bcm_clk_sel *sel; + struct bcm_clk_div *div; + struct bcm_clk_div *pre_div; + struct bcm_clk_trig *trig; + const char *name; + + BUG_ON(bcm_clk->type != bcm_clk_peri); + + /* + * First validate register offsets. This is the only place + * where we need something from the ccu, so we do these + * together. + */ + if (!peri_clk_data_offsets_valid(bcm_clk)) + return false; + + peri = bcm_clk->peri; + name = bcm_clk->init_data.name; + + policy = &peri->policy; + if (policy_exists(policy) && !policy_valid(policy, name)) + return false; + + gate = &peri->gate; + if (gate_exists(gate) && !gate_valid(gate, "gate", name)) + return false; + + hyst = &peri->hyst; + if (hyst_exists(hyst) && !hyst_valid(hyst, name)) + return false; + + sel = &peri->sel; + if (selector_exists(sel)) { + if (!sel_valid(sel, "selector", name)) + return false; + + } else if (sel->parent_count > 1) { + pr_err("%s: multiple parents but no selector for %s\n", + __func__, name); + + return false; + } + + div = &peri->div; + pre_div = &peri->pre_div; + if (divider_exists(div)) { + if (!div_valid(div, "divider", name)) + return false; + + if (divider_exists(pre_div)) + if (!div_valid(pre_div, "pre-divider", name)) + return false; + } else if (divider_exists(pre_div)) { + pr_err("%s: pre-divider but no divider for %s\n", __func__, + name); + return false; + } + + trig = &peri->trig; + if (trigger_exists(trig)) { + if (!trig_valid(trig, "trigger", name)) + return false; + + if (trigger_exists(&peri->pre_trig)) { + if (!trig_valid(trig, "pre-trigger", name)) { + return false; + } + } + if (!clk_requires_trigger(bcm_clk)) { + pr_warn("%s: ignoring trigger for %s (not needed)\n", + __func__, name); + trigger_clear_exists(trig); + } + } else if (trigger_exists(&peri->pre_trig)) { + pr_err("%s: pre-trigger but no trigger for %s\n", __func__, + name); + return false; + } else if (clk_requires_trigger(bcm_clk)) { + pr_err("%s: required trigger missing for %s\n", __func__, + name); + return false; + } + + return kona_dividers_valid(bcm_clk); +} + +static bool kona_clk_valid(struct kona_clk *bcm_clk) +{ + switch (bcm_clk->type) { + case bcm_clk_bus: + if (!bus_clk_data_valid(bcm_clk)) + return false; + break; + case bcm_clk_peri: + if (!peri_clk_data_valid(bcm_clk)) + return false; + break; + default: + pr_err("%s: unrecognized clock type (%d)\n", __func__, + (int)bcm_clk->type); + return false; + } + return true; +} + +/* + * Scan an array of parent clock names to determine whether there + * are any entries containing BAD_CLK_NAME. Such entries are + * placeholders for non-supported clocks. Keep track of the + * position of each clock name in the original array. + * + * Allocates an array of pointers to to hold the names of all + * non-null entries in the original array, and returns a pointer to + * that array in *names. This will be used for registering the + * clock with the common clock code. On successful return, + * *count indicates how many entries are in that names array. + * + * If there is more than one entry in the resulting names array, + * another array is allocated to record the parent selector value + * for each (defined) parent clock. This is the value that + * represents this parent clock in the clock's source selector + * register. The position of the clock in the original parent array + * defines that selector value. The number of entries in this array + * is the same as the number of entries in the parent names array. + * + * The array of selector values is returned. If the clock has no + * parents, no selector is required and a null pointer is returned. + * + * Returns a null pointer if the clock names array supplied was + * null. (This is not an error.) + * + * Returns a pointer-coded error if an error occurs. + */ +static u32 *parent_process(const char *clocks[], + u32 *count, const char ***names) +{ + static const char **parent_names; + static u32 *parent_sel; + const char **clock; + u32 parent_count; + u32 bad_count = 0; + u32 orig_count; + u32 i; + u32 j; + + *count = 0; /* In case of early return */ + *names = NULL; + if (!clocks) + return NULL; + + /* + * Count the number of names in the null-terminated array, + * and find out how many of those are actually clock names. + */ + for (clock = clocks; *clock; clock++) + if (*clock == BAD_CLK_NAME) + bad_count++; + orig_count = (u32)(clock - clocks); + parent_count = orig_count - bad_count; + + /* If all clocks are unsupported, we treat it as no clock */ + if (!parent_count) + return NULL; + + /* Avoid exceeding our parent clock limit */ + if (parent_count > PARENT_COUNT_MAX) { + pr_err("%s: too many parents (%u > %u)\n", __func__, + parent_count, PARENT_COUNT_MAX); + return ERR_PTR(-EINVAL); + } + + /* + * There is one parent name for each defined parent clock. + * We also maintain an array containing the selector value + * for each defined clock. If there's only one clock, the + * selector is not required, but we allocate space for the + * array anyway to keep things simple. + */ + parent_names = kmalloc(parent_count * sizeof(parent_names), GFP_KERNEL); + if (!parent_names) { + pr_err("%s: error allocating %u parent names\n", __func__, + parent_count); + return ERR_PTR(-ENOMEM); + } + + /* There is at least one parent, so allocate a selector array */ + + parent_sel = kmalloc(parent_count * sizeof(*parent_sel), GFP_KERNEL); + if (!parent_sel) { + pr_err("%s: error allocating %u parent selectors\n", __func__, + parent_count); + kfree(parent_names); + + return ERR_PTR(-ENOMEM); + } + + /* Now fill in the parent names and selector arrays */ + for (i = 0, j = 0; i < orig_count; i++) { + if (clocks[i] != BAD_CLK_NAME) { + parent_names[j] = clocks[i]; + parent_sel[j] = i; + j++; + } + } + *names = parent_names; + *count = parent_count; + + return parent_sel; +} + +static int +clk_sel_setup(const char **clocks, struct bcm_clk_sel *sel, + struct clk_init_data *init_data) +{ + const char **parent_names = NULL; + u32 parent_count = 0; + u32 *parent_sel; + + /* + * If a peripheral clock has multiple parents, the value + * used by the hardware to select that parent is represented + * by the parent clock's position in the "clocks" list. Some + * values don't have defined or supported clocks; these will + * have BAD_CLK_NAME entries in the parents[] array. The + * list is terminated by a NULL entry. + * + * We need to supply (only) the names of defined parent + * clocks when registering a clock though, so we use an + * array of parent selector values to map between the + * indexes the common clock code uses and the selector + * values we need. + */ + parent_sel = parent_process(clocks, &parent_count, &parent_names); + if (IS_ERR(parent_sel)) { + int ret = PTR_ERR(parent_sel); + + pr_err("%s: error processing parent clocks for %s (%d)\n", + __func__, init_data->name, ret); + + return ret; + } + + init_data->parent_names = parent_names; + init_data->num_parents = parent_count; + + sel->parent_count = parent_count; + sel->parent_sel = parent_sel; + + return 0; +} + +static void clk_sel_teardown(struct bcm_clk_sel *sel, + struct clk_init_data *init_data) +{ + kfree(sel->parent_sel); + sel->parent_sel = NULL; + sel->parent_count = 0; + + init_data->num_parents = 0; + kfree(init_data->parent_names); + init_data->parent_names = NULL; +} + +static void bus_clk_teardown(struct bus_clk_data *data, + struct clk_init_data *init_data) +{ + /* Nothing to do */ +} + +static int +bus_clk_setup(struct bus_clk_data *data, struct clk_init_data *init_data) +{ + init_data->flags = CLK_IGNORE_UNUSED; + + return 0; +} + +static void peri_clk_teardown(struct peri_clk_data *data, + struct clk_init_data *init_data) +{ + clk_sel_teardown(&data->sel, init_data); +} + +/* + * Caller is responsible for freeing the parent_names[] and + * parent_sel[] arrays in the peripheral clock's "data" structure + * that can be assigned if the clock has one or more parent clocks + * associated with it. + */ +static int +peri_clk_setup(struct peri_clk_data *data, struct clk_init_data *init_data) +{ + init_data->flags = CLK_IGNORE_UNUSED; + + return clk_sel_setup(data->clocks, &data->sel, init_data); +} + +static void bcm_clk_teardown(struct kona_clk *bcm_clk) +{ + /* There is no function defined for this (yet) */ + /* clkdev_remove(&bcm_clk->cl); */ + + switch (bcm_clk->type) { + case bcm_clk_bus: + bus_clk_teardown(bcm_clk->data, &bcm_clk->init_data); + break; + case bcm_clk_peri: + peri_clk_teardown(bcm_clk->data, &bcm_clk->init_data); + break; + default: + break; + } + bcm_clk->data = NULL; + bcm_clk->type = bcm_clk_none; +} + +static void kona_clk_teardown(struct clk *clk) +{ + struct clk_hw *hw; + struct kona_clk *bcm_clk; + + if (!clk) + return; + + hw = __clk_get_hw(clk); + if (!hw) { + pr_err("%s: clk %p has null hw pointer\n", __func__, clk); + return; + } + clk_unregister(clk); + + bcm_clk = to_kona_clk(hw); + bcm_clk_teardown(bcm_clk); +} + +struct clk *kona_clk_setup(struct kona_clk *bcm_clk) +{ + struct clk_init_data *init_data = &bcm_clk->init_data; + const char *name = init_data->name; + struct clk *clk = NULL; + + switch (bcm_clk->type) { + case bcm_clk_bus: + if (bus_clk_setup(bcm_clk->data, init_data)) + return NULL; + break; + case bcm_clk_peri: + if (peri_clk_setup(bcm_clk->data, init_data)) + return NULL; + break; + default: + pr_err("%s: clock type %d invalid for %s\n", __func__, + (int)bcm_clk->type, name); + return NULL; + } + + /* Make sure everything makes sense before we set it up */ + if (!kona_clk_valid(bcm_clk)) { + pr_err("%s: clock data invalid for %s\n", __func__, name); + goto out_teardown; + } + + bcm_clk->hw.init = init_data; + clk = clk_register(NULL, &bcm_clk->hw); + if (IS_ERR(clk)) { + pr_err("%s: error registering clock %s (%ld)\n", __func__, + name, PTR_ERR(clk)); + goto out_teardown; + } + BUG_ON(!clk); + + /* Make it so we can look the clock up using clk_find() */ + bcm_clk->cl.con_id = name; + bcm_clk->cl.clk = clk; + clkdev_add(&bcm_clk->cl); + + return clk; +out_teardown: + bcm_clk_teardown(bcm_clk); + + return NULL; +} + +static void ccu_clks_teardown(struct ccu_data *ccu) +{ + u32 i; + + for (i = 0; i < ccu->clk_data.clk_num; i++) + kona_clk_teardown(ccu->clk_data.clks[i]); + kfree(ccu->clk_data.clks); +} + +static void kona_ccu_teardown(struct ccu_data *ccu) +{ + kfree(ccu->clk_data.clks); + ccu->clk_data.clks = NULL; + if (!ccu->base) + return; + + of_clk_del_provider(ccu->node); /* safe if never added */ + ccu_clks_teardown(ccu); + list_del(&ccu->links); + of_node_put(ccu->node); + ccu->node = NULL; + iounmap(ccu->base); + ccu->base = NULL; +} + +static bool ccu_data_valid(struct ccu_data *ccu) +{ + struct ccu_policy *ccu_policy; + + if (!ccu_data_offsets_valid(ccu)) + return false; + + ccu_policy = &ccu->policy; + if (ccu_policy_exists(ccu_policy)) + if (!ccu_policy_valid(ccu_policy, ccu->name)) + return false; + + return true; +} + +/* + * Set up a CCU. Call the provided ccu_clks_setup callback to + * initialize the array of clocks provided by the CCU. + */ +void __init kona_dt_ccu_setup(struct ccu_data *ccu, + struct device_node *node) +{ + struct resource res = { 0 }; + resource_size_t range; + unsigned int i; + int ret; + + if (ccu->clk_data.clk_num) { + size_t size; + + size = ccu->clk_data.clk_num * sizeof(*ccu->clk_data.clks); + ccu->clk_data.clks = kzalloc(size, GFP_KERNEL); + if (!ccu->clk_data.clks) { + pr_err("%s: unable to allocate %u clocks for %s\n", + __func__, ccu->clk_data.clk_num, node->name); + return; + } + } + + ret = of_address_to_resource(node, 0, &res); + if (ret) { + pr_err("%s: no valid CCU registers found for %s\n", __func__, + node->name); + goto out_err; + } + + range = resource_size(&res); + if (range > (resource_size_t)U32_MAX) { + pr_err("%s: address range too large for %s\n", __func__, + node->name); + goto out_err; + } + + ccu->range = (u32)range; + + if (!ccu_data_valid(ccu)) { + pr_err("%s: ccu data not valid for %s\n", __func__, node->name); + goto out_err; + } + + ccu->base = ioremap(res.start, ccu->range); + if (!ccu->base) { + pr_err("%s: unable to map CCU registers for %s\n", __func__, + node->name); + goto out_err; + } + ccu->node = of_node_get(node); + list_add_tail(&ccu->links, &ccu_list); + + /* + * Set up each defined kona clock and save the result in + * the clock framework clock array (in ccu->data). Then + * register as a provider for these clocks. + */ + for (i = 0; i < ccu->clk_data.clk_num; i++) { + if (!ccu->kona_clks[i].ccu) + continue; + ccu->clk_data.clks[i] = kona_clk_setup(&ccu->kona_clks[i]); + } + + ret = of_clk_add_provider(node, of_clk_src_onecell_get, &ccu->clk_data); + if (ret) { + pr_err("%s: error adding ccu %s as provider (%d)\n", __func__, + node->name, ret); + goto out_err; + } + + if (!kona_ccu_init(ccu)) + pr_err("Broadcom %s initialization had errors\n", node->name); + + return; +out_err: + kona_ccu_teardown(ccu); + pr_err("Broadcom %s setup aborted\n", node->name); +} diff --git a/drivers/clk/bcm/clk-kona.c b/drivers/clk/bcm/clk-kona.c new file mode 100644 index 00000000000000..00db272607c72d --- /dev/null +++ b/drivers/clk/bcm/clk-kona.c @@ -0,0 +1,1342 @@ +/* + * Copyright (C) 2013 Broadcom Corporation + * Copyright 2013 Linaro Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "clk-kona.h" + +#include + +/* + * "Policies" affect the frequencies of bus clocks provided by a + * CCU. (I believe these polices are named "Deep Sleep", "Economy", + * "Normal", and "Turbo".) A lower policy number has lower power + * consumption, and policy 2 is the default. + */ +#define CCU_POLICY_COUNT 4 + +#define CCU_ACCESS_PASSWORD 0xA5A500 +#define CLK_GATE_DELAY_LOOP 2000 + +#define clk_is_initialized(_clk) FLAG_TEST((_clk), KONA, INITIALIZED) +#define clk_set_initialized(_clk) FLAG_SET((_clk), KONA, INITIALIZED) + +/* Bitfield operations */ + +/* Produces a mask of set bits covering a range of a 32-bit value */ +static inline u32 bitfield_mask(u32 shift, u32 width) +{ + return ((1 << width) - 1) << shift; +} + +/* Extract the value of a bitfield found within a given register value */ +static inline u32 bitfield_extract(u32 reg_val, u32 shift, u32 width) +{ + return (reg_val & bitfield_mask(shift, width)) >> shift; +} + +/* Replace the value of a bitfield found within a given register value */ +static inline u32 bitfield_replace(u32 reg_val, u32 shift, u32 width, u32 val) +{ + u32 mask = bitfield_mask(shift, width); + + return (reg_val & ~mask) | (val << shift); +} + +/* Divider and scaling helpers */ + +/* + * Implement DIV_ROUND_CLOSEST() for 64-bit dividend and both values + * unsigned. Note that unlike do_div(), the remainder is discarded + * and the return value is the quotient (not the remainder). + */ +u64 do_div_round_closest(u64 dividend, unsigned long divisor) +{ + u64 result; + + result = dividend + ((u64)divisor >> 1); + (void)do_div(result, divisor); + + return result; +} + +/* Convert a divider into the scaled divisor value it represents. */ +static inline u64 scaled_div_value(struct bcm_clk_div *div, u32 reg_div) +{ + return (u64)reg_div + ((u64)1 << div->frac_width); +} + +/* + * Build a scaled divider value as close as possible to the + * given whole part (div_value) and fractional part (expressed + * in billionths). + */ +u64 scaled_div_build(struct bcm_clk_div *div, u32 div_value, u32 billionths) +{ + u64 combined; + + BUG_ON(!div_value); + BUG_ON(billionths >= BILLION); + + combined = (u64)div_value * BILLION + billionths; + combined <<= div->frac_width; + + return do_div_round_closest(combined, BILLION); +} + +/* The scaled minimum divisor representable by a divider */ +static inline u64 +scaled_div_min(struct bcm_clk_div *div) +{ + if (divider_is_fixed(div)) + return (u64)div->fixed; + + return scaled_div_value(div, 0); +} + +/* The scaled maximum divisor representable by a divider */ +u64 scaled_div_max(struct bcm_clk_div *div) +{ + u32 reg_div; + + if (divider_is_fixed(div)) + return (u64)div->fixed; + + reg_div = ((u32)1 << div->width) - 1; + + return scaled_div_value(div, reg_div); +} + +/* + * Convert a scaled divisor into its divider representation as + * stored in a divider register field. + */ +static inline u32 +divider(struct bcm_clk_div *div, u64 scaled_div) +{ + BUG_ON(scaled_div < scaled_div_min(div)); + BUG_ON(scaled_div > scaled_div_max(div)); + + return (u32)(scaled_div - ((u64)1 << div->frac_width)); +} + +/* Return a rate scaled for use when dividing by a scaled divisor. */ +static inline u64 +scale_rate(struct bcm_clk_div *div, u32 rate) +{ + if (divider_is_fixed(div)) + return (u64)rate; + + return (u64)rate << div->frac_width; +} + +/* CCU access */ + +/* Read a 32-bit register value from a CCU's address space. */ +static inline u32 __ccu_read(struct ccu_data *ccu, u32 reg_offset) +{ + return readl(ccu->base + reg_offset); +} + +/* Write a 32-bit register value into a CCU's address space. */ +static inline void +__ccu_write(struct ccu_data *ccu, u32 reg_offset, u32 reg_val) +{ + writel(reg_val, ccu->base + reg_offset); +} + +static inline unsigned long ccu_lock(struct ccu_data *ccu) +{ + unsigned long flags; + + spin_lock_irqsave(&ccu->lock, flags); + + return flags; +} +static inline void ccu_unlock(struct ccu_data *ccu, unsigned long flags) +{ + spin_unlock_irqrestore(&ccu->lock, flags); +} + +/* + * Enable/disable write access to CCU protected registers. The + * WR_ACCESS register for all CCUs is at offset 0. + */ +static inline void __ccu_write_enable(struct ccu_data *ccu) +{ + if (ccu->write_enabled) { + pr_err("%s: access already enabled for %s\n", __func__, + ccu->name); + return; + } + ccu->write_enabled = true; + __ccu_write(ccu, 0, CCU_ACCESS_PASSWORD | 1); +} + +static inline void __ccu_write_disable(struct ccu_data *ccu) +{ + if (!ccu->write_enabled) { + pr_err("%s: access wasn't enabled for %s\n", __func__, + ccu->name); + return; + } + + __ccu_write(ccu, 0, CCU_ACCESS_PASSWORD); + ccu->write_enabled = false; +} + +/* + * Poll a register in a CCU's address space, returning when the + * specified bit in that register's value is set (or clear). Delay + * a microsecond after each read of the register. Returns true if + * successful, or false if we gave up trying. + * + * Caller must ensure the CCU lock is held. + */ +static inline bool +__ccu_wait_bit(struct ccu_data *ccu, u32 reg_offset, u32 bit, bool want) +{ + unsigned int tries; + u32 bit_mask = 1 << bit; + + for (tries = 0; tries < CLK_GATE_DELAY_LOOP; tries++) { + u32 val; + bool bit_val; + + val = __ccu_read(ccu, reg_offset); + bit_val = (val & bit_mask) != 0; + if (bit_val == want) + return true; + udelay(1); + } + pr_warn("%s: %s/0x%04x bit %u was never %s\n", __func__, + ccu->name, reg_offset, bit, want ? "set" : "clear"); + + return false; +} + +/* Policy operations */ + +static bool __ccu_policy_engine_start(struct ccu_data *ccu, bool sync) +{ + struct bcm_policy_ctl *control = &ccu->policy.control; + u32 offset; + u32 go_bit; + u32 mask; + bool ret; + + /* If we don't need to control policy for this CCU, we're done. */ + if (!policy_ctl_exists(control)) + return true; + + offset = control->offset; + go_bit = control->go_bit; + + /* Ensure we're not busy before we start */ + ret = __ccu_wait_bit(ccu, offset, go_bit, false); + if (!ret) { + pr_err("%s: ccu %s policy engine wouldn't go idle\n", + __func__, ccu->name); + return false; + } + + /* + * If it's a synchronous request, we'll wait for the voltage + * and frequency of the active load to stabilize before + * returning. To do this we select the active load by + * setting the ATL bit. + * + * An asynchronous request instead ramps the voltage in the + * background, and when that process stabilizes, the target + * load is copied to the active load and the CCU frequency + * is switched. We do this by selecting the target load + * (ATL bit clear) and setting the request auto-copy (AC bit + * set). + * + * Note, we do NOT read-modify-write this register. + */ + mask = (u32)1 << go_bit; + if (sync) + mask |= 1 << control->atl_bit; + else + mask |= 1 << control->ac_bit; + __ccu_write(ccu, offset, mask); + + /* Wait for indication that operation is complete. */ + ret = __ccu_wait_bit(ccu, offset, go_bit, false); + if (!ret) + pr_err("%s: ccu %s policy engine never started\n", + __func__, ccu->name); + + return ret; +} + +static bool __ccu_policy_engine_stop(struct ccu_data *ccu) +{ + struct bcm_lvm_en *enable = &ccu->policy.enable; + u32 offset; + u32 enable_bit; + bool ret; + + /* If we don't need to control policy for this CCU, we're done. */ + if (!policy_lvm_en_exists(enable)) + return true; + + /* Ensure we're not busy before we start */ + offset = enable->offset; + enable_bit = enable->bit; + ret = __ccu_wait_bit(ccu, offset, enable_bit, false); + if (!ret) { + pr_err("%s: ccu %s policy engine already stopped\n", + __func__, ccu->name); + return false; + } + + /* Now set the bit to stop the engine (NO read-modify-write) */ + __ccu_write(ccu, offset, (u32)1 << enable_bit); + + /* Wait for indication that it has stopped. */ + ret = __ccu_wait_bit(ccu, offset, enable_bit, false); + if (!ret) + pr_err("%s: ccu %s policy engine never stopped\n", + __func__, ccu->name); + + return ret; +} + +/* + * A CCU has four operating conditions ("policies"), and some clocks + * can be disabled or enabled based on which policy is currently in + * effect. Such clocks have a bit in a "policy mask" register for + * each policy indicating whether the clock is enabled for that + * policy or not. The bit position for a clock is the same for all + * four registers, and the 32-bit registers are at consecutive + * addresses. + */ +static bool policy_init(struct ccu_data *ccu, struct bcm_clk_policy *policy) +{ + u32 offset; + u32 mask; + int i; + bool ret; + + if (!policy_exists(policy)) + return true; + + /* + * We need to stop the CCU policy engine to allow update + * of our policy bits. + */ + if (!__ccu_policy_engine_stop(ccu)) { + pr_err("%s: unable to stop CCU %s policy engine\n", + __func__, ccu->name); + return false; + } + + /* + * For now, if a clock defines its policy bit we just mark + * it "enabled" for all four policies. + */ + offset = policy->offset; + mask = (u32)1 << policy->bit; + for (i = 0; i < CCU_POLICY_COUNT; i++) { + u32 reg_val; + + reg_val = __ccu_read(ccu, offset); + reg_val |= mask; + __ccu_write(ccu, offset, reg_val); + offset += sizeof(u32); + } + + /* We're done updating; fire up the policy engine again. */ + ret = __ccu_policy_engine_start(ccu, true); + if (!ret) + pr_err("%s: unable to restart CCU %s policy engine\n", + __func__, ccu->name); + + return ret; +} + +/* Gate operations */ + +/* Determine whether a clock is gated. CCU lock must be held. */ +static bool +__is_clk_gate_enabled(struct ccu_data *ccu, struct bcm_clk_gate *gate) +{ + u32 bit_mask; + u32 reg_val; + + /* If there is no gate we can assume it's enabled. */ + if (!gate_exists(gate)) + return true; + + bit_mask = 1 << gate->status_bit; + reg_val = __ccu_read(ccu, gate->offset); + + return (reg_val & bit_mask) != 0; +} + +/* Determine whether a clock is gated. */ +static bool +is_clk_gate_enabled(struct ccu_data *ccu, struct bcm_clk_gate *gate) +{ + long flags; + bool ret; + + /* Avoid taking the lock if we can */ + if (!gate_exists(gate)) + return true; + + flags = ccu_lock(ccu); + ret = __is_clk_gate_enabled(ccu, gate); + ccu_unlock(ccu, flags); + + return ret; +} + +/* + * Commit our desired gate state to the hardware. + * Returns true if successful, false otherwise. + */ +static bool +__gate_commit(struct ccu_data *ccu, struct bcm_clk_gate *gate) +{ + u32 reg_val; + u32 mask; + bool enabled = false; + + BUG_ON(!gate_exists(gate)); + if (!gate_is_sw_controllable(gate)) + return true; /* Nothing we can change */ + + reg_val = __ccu_read(ccu, gate->offset); + + /* For a hardware/software gate, set which is in control */ + if (gate_is_hw_controllable(gate)) { + mask = (u32)1 << gate->hw_sw_sel_bit; + if (gate_is_sw_managed(gate)) + reg_val |= mask; + else + reg_val &= ~mask; + } + + /* + * If software is in control, enable or disable the gate. + * If hardware is, clear the enabled bit for good measure. + * If a software controlled gate can't be disabled, we're + * required to write a 0 into the enable bit (but the gate + * will be enabled). + */ + mask = (u32)1 << gate->en_bit; + if (gate_is_sw_managed(gate) && (enabled = gate_is_enabled(gate)) && + !gate_is_no_disable(gate)) + reg_val |= mask; + else + reg_val &= ~mask; + + __ccu_write(ccu, gate->offset, reg_val); + + /* For a hardware controlled gate, we're done */ + if (!gate_is_sw_managed(gate)) + return true; + + /* Otherwise wait for the gate to be in desired state */ + return __ccu_wait_bit(ccu, gate->offset, gate->status_bit, enabled); +} + +/* + * Initialize a gate. Our desired state (hardware/software select, + * and if software, its enable state) is committed to hardware + * without the usual checks to see if it's already set up that way. + * Returns true if successful, false otherwise. + */ +static bool gate_init(struct ccu_data *ccu, struct bcm_clk_gate *gate) +{ + if (!gate_exists(gate)) + return true; + return __gate_commit(ccu, gate); +} + +/* + * Set a gate to enabled or disabled state. Does nothing if the + * gate is not currently under software control, or if it is already + * in the requested state. Returns true if successful, false + * otherwise. CCU lock must be held. + */ +static bool +__clk_gate(struct ccu_data *ccu, struct bcm_clk_gate *gate, bool enable) +{ + bool ret; + + if (!gate_exists(gate) || !gate_is_sw_managed(gate)) + return true; /* Nothing to do */ + + if (!enable && gate_is_no_disable(gate)) { + pr_warn("%s: invalid gate disable request (ignoring)\n", + __func__); + return true; + } + + if (enable == gate_is_enabled(gate)) + return true; /* No change */ + + gate_flip_enabled(gate); + ret = __gate_commit(ccu, gate); + if (!ret) + gate_flip_enabled(gate); /* Revert the change */ + + return ret; +} + +/* Enable or disable a gate. Returns 0 if successful, -EIO otherwise */ +static int clk_gate(struct ccu_data *ccu, const char *name, + struct bcm_clk_gate *gate, bool enable) +{ + unsigned long flags; + bool success; + + /* + * Avoid taking the lock if we can. We quietly ignore + * requests to change state that don't make sense. + */ + if (!gate_exists(gate) || !gate_is_sw_managed(gate)) + return 0; + if (!enable && gate_is_no_disable(gate)) + return 0; + + flags = ccu_lock(ccu); + __ccu_write_enable(ccu); + + success = __clk_gate(ccu, gate, enable); + + __ccu_write_disable(ccu); + ccu_unlock(ccu, flags); + + if (success) + return 0; + + pr_err("%s: failed to %s gate for %s\n", __func__, + enable ? "enable" : "disable", name); + + return -EIO; +} + +/* Hysteresis operations */ + +/* + * If a clock gate requires a turn-off delay it will have + * "hysteresis" register bits defined. The first, if set, enables + * the delay; and if enabled, the second bit determines whether the + * delay is "low" or "high" (1 means high). For now, if it's + * defined for a clock, we set it. + */ +static bool hyst_init(struct ccu_data *ccu, struct bcm_clk_hyst *hyst) +{ + u32 offset; + u32 reg_val; + u32 mask; + + if (!hyst_exists(hyst)) + return true; + + offset = hyst->offset; + mask = (u32)1 << hyst->en_bit; + mask |= (u32)1 << hyst->val_bit; + + reg_val = __ccu_read(ccu, offset); + reg_val |= mask; + __ccu_write(ccu, offset, reg_val); + + return true; +} + +/* Trigger operations */ + +/* + * Caller must ensure CCU lock is held and access is enabled. + * Returns true if successful, false otherwise. + */ +static bool __clk_trigger(struct ccu_data *ccu, struct bcm_clk_trig *trig) +{ + /* Trigger the clock and wait for it to finish */ + __ccu_write(ccu, trig->offset, 1 << trig->bit); + + return __ccu_wait_bit(ccu, trig->offset, trig->bit, false); +} + +/* Divider operations */ + +/* Read a divider value and return the scaled divisor it represents. */ +static u64 divider_read_scaled(struct ccu_data *ccu, struct bcm_clk_div *div) +{ + unsigned long flags; + u32 reg_val; + u32 reg_div; + + if (divider_is_fixed(div)) + return (u64)div->fixed; + + flags = ccu_lock(ccu); + reg_val = __ccu_read(ccu, div->offset); + ccu_unlock(ccu, flags); + + /* Extract the full divider field from the register value */ + reg_div = bitfield_extract(reg_val, div->shift, div->width); + + /* Return the scaled divisor value it represents */ + return scaled_div_value(div, reg_div); +} + +/* + * Convert a divider's scaled divisor value into its recorded form + * and commit it into the hardware divider register. + * + * Returns 0 on success. Returns -EINVAL for invalid arguments. + * Returns -ENXIO if gating failed, and -EIO if a trigger failed. + */ +static int __div_commit(struct ccu_data *ccu, struct bcm_clk_gate *gate, + struct bcm_clk_div *div, struct bcm_clk_trig *trig) +{ + bool enabled; + u32 reg_div; + u32 reg_val; + int ret = 0; + + BUG_ON(divider_is_fixed(div)); + + /* + * If we're just initializing the divider, and no initial + * state was defined in the device tree, we just find out + * what its current value is rather than updating it. + */ + if (div->scaled_div == BAD_SCALED_DIV_VALUE) { + reg_val = __ccu_read(ccu, div->offset); + reg_div = bitfield_extract(reg_val, div->shift, div->width); + div->scaled_div = scaled_div_value(div, reg_div); + + return 0; + } + + /* Convert the scaled divisor to the value we need to record */ + reg_div = divider(div, div->scaled_div); + + /* Clock needs to be enabled before changing the rate */ + enabled = __is_clk_gate_enabled(ccu, gate); + if (!enabled && !__clk_gate(ccu, gate, true)) { + ret = -ENXIO; + goto out; + } + + /* Replace the divider value and record the result */ + reg_val = __ccu_read(ccu, div->offset); + reg_val = bitfield_replace(reg_val, div->shift, div->width, reg_div); + __ccu_write(ccu, div->offset, reg_val); + + /* If the trigger fails we still want to disable the gate */ + if (!__clk_trigger(ccu, trig)) + ret = -EIO; + + /* Disable the clock again if it was disabled to begin with */ + if (!enabled && !__clk_gate(ccu, gate, false)) + ret = ret ? ret : -ENXIO; /* return first error */ +out: + return ret; +} + +/* + * Initialize a divider by committing our desired state to hardware + * without the usual checks to see if it's already set up that way. + * Returns true if successful, false otherwise. + */ +static bool div_init(struct ccu_data *ccu, struct bcm_clk_gate *gate, + struct bcm_clk_div *div, struct bcm_clk_trig *trig) +{ + if (!divider_exists(div) || divider_is_fixed(div)) + return true; + return !__div_commit(ccu, gate, div, trig); +} + +static int divider_write(struct ccu_data *ccu, struct bcm_clk_gate *gate, + struct bcm_clk_div *div, struct bcm_clk_trig *trig, + u64 scaled_div) +{ + unsigned long flags; + u64 previous; + int ret; + + BUG_ON(divider_is_fixed(div)); + + previous = div->scaled_div; + if (previous == scaled_div) + return 0; /* No change */ + + div->scaled_div = scaled_div; + + flags = ccu_lock(ccu); + __ccu_write_enable(ccu); + + ret = __div_commit(ccu, gate, div, trig); + + __ccu_write_disable(ccu); + ccu_unlock(ccu, flags); + + if (ret) + div->scaled_div = previous; /* Revert the change */ + + return ret; + +} + +/* Common clock rate helpers */ + +/* + * Implement the common clock framework recalc_rate method, taking + * into account a divider and an optional pre-divider. The + * pre-divider register pointer may be NULL. + */ +static unsigned long clk_recalc_rate(struct ccu_data *ccu, + struct bcm_clk_div *div, struct bcm_clk_div *pre_div, + unsigned long parent_rate) +{ + u64 scaled_parent_rate; + u64 scaled_div; + u64 result; + + if (!divider_exists(div)) + return parent_rate; + + if (parent_rate > (unsigned long)LONG_MAX) + return 0; /* actually this would be a caller bug */ + + /* + * If there is a pre-divider, divide the scaled parent rate + * by the pre-divider value first. In this case--to improve + * accuracy--scale the parent rate by *both* the pre-divider + * value and the divider before actually computing the + * result of the pre-divider. + * + * If there's only one divider, just scale the parent rate. + */ + if (pre_div && divider_exists(pre_div)) { + u64 scaled_rate; + + scaled_rate = scale_rate(pre_div, parent_rate); + scaled_rate = scale_rate(div, scaled_rate); + scaled_div = divider_read_scaled(ccu, pre_div); + scaled_parent_rate = do_div_round_closest(scaled_rate, + scaled_div); + } else { + scaled_parent_rate = scale_rate(div, parent_rate); + } + + /* + * Get the scaled divisor value, and divide the scaled + * parent rate by that to determine this clock's resulting + * rate. + */ + scaled_div = divider_read_scaled(ccu, div); + result = do_div_round_closest(scaled_parent_rate, scaled_div); + + return (unsigned long)result; +} + +/* + * Compute the output rate produced when a given parent rate is fed + * into two dividers. The pre-divider can be NULL, and even if it's + * non-null it may be nonexistent. It's also OK for the divider to + * be nonexistent, and in that case the pre-divider is also ignored. + * + * If scaled_div is non-null, it is used to return the scaled divisor + * value used by the (downstream) divider to produce that rate. + */ +static long round_rate(struct ccu_data *ccu, struct bcm_clk_div *div, + struct bcm_clk_div *pre_div, + unsigned long rate, unsigned long parent_rate, + u64 *scaled_div) +{ + u64 scaled_parent_rate; + u64 min_scaled_div; + u64 max_scaled_div; + u64 best_scaled_div; + u64 result; + + BUG_ON(!divider_exists(div)); + BUG_ON(!rate); + BUG_ON(parent_rate > (u64)LONG_MAX); + + /* + * If there is a pre-divider, divide the scaled parent rate + * by the pre-divider value first. In this case--to improve + * accuracy--scale the parent rate by *both* the pre-divider + * value and the divider before actually computing the + * result of the pre-divider. + * + * If there's only one divider, just scale the parent rate. + * + * For simplicity we treat the pre-divider as fixed (for now). + */ + if (divider_exists(pre_div)) { + u64 scaled_rate; + u64 scaled_pre_div; + + scaled_rate = scale_rate(pre_div, parent_rate); + scaled_rate = scale_rate(div, scaled_rate); + scaled_pre_div = divider_read_scaled(ccu, pre_div); + scaled_parent_rate = do_div_round_closest(scaled_rate, + scaled_pre_div); + } else { + scaled_parent_rate = scale_rate(div, parent_rate); + } + + /* + * Compute the best possible divider and ensure it is in + * range. A fixed divider can't be changed, so just report + * the best we can do. + */ + if (!divider_is_fixed(div)) { + best_scaled_div = do_div_round_closest(scaled_parent_rate, + rate); + min_scaled_div = scaled_div_min(div); + max_scaled_div = scaled_div_max(div); + if (best_scaled_div > max_scaled_div) + best_scaled_div = max_scaled_div; + else if (best_scaled_div < min_scaled_div) + best_scaled_div = min_scaled_div; + } else { + best_scaled_div = divider_read_scaled(ccu, div); + } + + /* OK, figure out the resulting rate */ + result = do_div_round_closest(scaled_parent_rate, best_scaled_div); + + if (scaled_div) + *scaled_div = best_scaled_div; + + return (long)result; +} + +/* Common clock parent helpers */ + +/* + * For a given parent selector (register field) value, find the + * index into a selector's parent_sel array that contains it. + * Returns the index, or BAD_CLK_INDEX if it's not found. + */ +static u8 parent_index(struct bcm_clk_sel *sel, u8 parent_sel) +{ + u8 i; + + BUG_ON(sel->parent_count > (u32)U8_MAX); + for (i = 0; i < sel->parent_count; i++) + if (sel->parent_sel[i] == parent_sel) + return i; + return BAD_CLK_INDEX; +} + +/* + * Fetch the current value of the selector, and translate that into + * its corresponding index in the parent array we registered with + * the clock framework. + * + * Returns parent array index that corresponds with the value found, + * or BAD_CLK_INDEX if the found value is out of range. + */ +static u8 selector_read_index(struct ccu_data *ccu, struct bcm_clk_sel *sel) +{ + unsigned long flags; + u32 reg_val; + u32 parent_sel; + u8 index; + + /* If there's no selector, there's only one parent */ + if (!selector_exists(sel)) + return 0; + + /* Get the value in the selector register */ + flags = ccu_lock(ccu); + reg_val = __ccu_read(ccu, sel->offset); + ccu_unlock(ccu, flags); + + parent_sel = bitfield_extract(reg_val, sel->shift, sel->width); + + /* Look up that selector's parent array index and return it */ + index = parent_index(sel, parent_sel); + if (index == BAD_CLK_INDEX) + pr_err("%s: out-of-range parent selector %u (%s 0x%04x)\n", + __func__, parent_sel, ccu->name, sel->offset); + + return index; +} + +/* + * Commit our desired selector value to the hardware. + * + * Returns 0 on success. Returns -EINVAL for invalid arguments. + * Returns -ENXIO if gating failed, and -EIO if a trigger failed. + */ +static int +__sel_commit(struct ccu_data *ccu, struct bcm_clk_gate *gate, + struct bcm_clk_sel *sel, struct bcm_clk_trig *trig) +{ + u32 parent_sel; + u32 reg_val; + bool enabled; + int ret = 0; + + BUG_ON(!selector_exists(sel)); + + /* + * If we're just initializing the selector, and no initial + * state was defined in the device tree, we just find out + * what its current value is rather than updating it. + */ + if (sel->clk_index == BAD_CLK_INDEX) { + u8 index; + + reg_val = __ccu_read(ccu, sel->offset); + parent_sel = bitfield_extract(reg_val, sel->shift, sel->width); + index = parent_index(sel, parent_sel); + if (index == BAD_CLK_INDEX) + return -EINVAL; + sel->clk_index = index; + + return 0; + } + + BUG_ON((u32)sel->clk_index >= sel->parent_count); + parent_sel = sel->parent_sel[sel->clk_index]; + + /* Clock needs to be enabled before changing the parent */ + enabled = __is_clk_gate_enabled(ccu, gate); + if (!enabled && !__clk_gate(ccu, gate, true)) + return -ENXIO; + + /* Replace the selector value and record the result */ + reg_val = __ccu_read(ccu, sel->offset); + reg_val = bitfield_replace(reg_val, sel->shift, sel->width, parent_sel); + __ccu_write(ccu, sel->offset, reg_val); + + /* If the trigger fails we still want to disable the gate */ + if (!__clk_trigger(ccu, trig)) + ret = -EIO; + + /* Disable the clock again if it was disabled to begin with */ + if (!enabled && !__clk_gate(ccu, gate, false)) + ret = ret ? ret : -ENXIO; /* return first error */ + + return ret; +} + +/* + * Initialize a selector by committing our desired state to hardware + * without the usual checks to see if it's already set up that way. + * Returns true if successful, false otherwise. + */ +static bool sel_init(struct ccu_data *ccu, struct bcm_clk_gate *gate, + struct bcm_clk_sel *sel, struct bcm_clk_trig *trig) +{ + if (!selector_exists(sel)) + return true; + return !__sel_commit(ccu, gate, sel, trig); +} + +/* + * Write a new value into a selector register to switch to a + * different parent clock. Returns 0 on success, or an error code + * (from __sel_commit()) otherwise. + */ +static int selector_write(struct ccu_data *ccu, struct bcm_clk_gate *gate, + struct bcm_clk_sel *sel, struct bcm_clk_trig *trig, + u8 index) +{ + unsigned long flags; + u8 previous; + int ret; + + previous = sel->clk_index; + if (previous == index) + return 0; /* No change */ + + sel->clk_index = index; + + flags = ccu_lock(ccu); + __ccu_write_enable(ccu); + + ret = __sel_commit(ccu, gate, sel, trig); + + __ccu_write_disable(ccu); + ccu_unlock(ccu, flags); + + if (ret) + sel->clk_index = previous; /* Revert the change */ + + return ret; +} + +/* Clock operations */ + +static int kona_bus_clk_enable(struct clk_hw *hw) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct bcm_clk_gate *gate = &bcm_clk->bus->gate; + + return clk_gate(bcm_clk->ccu, bcm_clk->init_data.name, gate, true); +} + +static void kona_bus_clk_disable(struct clk_hw *hw) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct bcm_clk_gate *gate = &bcm_clk->bus->gate; + + (void)clk_gate(bcm_clk->ccu, bcm_clk->init_data.name, gate, false); +} + +static int kona_bus_clk_is_enabled(struct clk_hw *hw) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct bcm_clk_gate *gate = &bcm_clk->bus->gate; + + return is_clk_gate_enabled(bcm_clk->ccu, gate) ? 1 : 0; +} + +struct clk_ops kona_bus_clk_ops = { + .enable = kona_bus_clk_enable, + .disable = kona_bus_clk_disable, + .is_enabled = kona_bus_clk_is_enabled, +}; + +static int kona_peri_clk_enable(struct clk_hw *hw) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct bcm_clk_gate *gate = &bcm_clk->peri->gate; + + return clk_gate(bcm_clk->ccu, bcm_clk->init_data.name, gate, true); +} + +static void kona_peri_clk_disable(struct clk_hw *hw) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct bcm_clk_gate *gate = &bcm_clk->peri->gate; + + (void)clk_gate(bcm_clk->ccu, bcm_clk->init_data.name, gate, false); +} + +static int kona_peri_clk_is_enabled(struct clk_hw *hw) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct bcm_clk_gate *gate = &bcm_clk->peri->gate; + + return is_clk_gate_enabled(bcm_clk->ccu, gate) ? 1 : 0; +} + +static unsigned long kona_peri_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct peri_clk_data *data = bcm_clk->peri; + + return clk_recalc_rate(bcm_clk->ccu, &data->div, &data->pre_div, + parent_rate); +} + +static long kona_peri_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct bcm_clk_div *div = &bcm_clk->peri->div; + + if (!divider_exists(div)) + return __clk_get_rate(hw->clk); + + /* Quietly avoid a zero rate */ + return round_rate(bcm_clk->ccu, div, &bcm_clk->peri->pre_div, + rate ? rate : 1, *parent_rate, NULL); +} + +static int kona_peri_clk_set_parent(struct clk_hw *hw, u8 index) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct peri_clk_data *data = bcm_clk->peri; + struct bcm_clk_sel *sel = &data->sel; + struct bcm_clk_trig *trig; + int ret; + + BUG_ON(index >= sel->parent_count); + + /* If there's only one parent we don't require a selector */ + if (!selector_exists(sel)) + return 0; + + /* + * The regular trigger is used by default, but if there's a + * pre-trigger we want to use that instead. + */ + trig = trigger_exists(&data->pre_trig) ? &data->pre_trig + : &data->trig; + + ret = selector_write(bcm_clk->ccu, &data->gate, sel, trig, index); + if (ret == -ENXIO) { + pr_err("%s: gating failure for %s\n", __func__, + bcm_clk->init_data.name); + ret = -EIO; /* Don't proliferate weird errors */ + } else if (ret == -EIO) { + pr_err("%s: %strigger failed for %s\n", __func__, + trig == &data->pre_trig ? "pre-" : "", + bcm_clk->init_data.name); + } + + return ret; +} + +static u8 kona_peri_clk_get_parent(struct clk_hw *hw) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct peri_clk_data *data = bcm_clk->peri; + u8 index; + + index = selector_read_index(bcm_clk->ccu, &data->sel); + + /* Not all callers would handle an out-of-range value gracefully */ + return index == BAD_CLK_INDEX ? 0 : index; +} + +static int kona_peri_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct kona_clk *bcm_clk = to_kona_clk(hw); + struct peri_clk_data *data = bcm_clk->peri; + struct bcm_clk_div *div = &data->div; + u64 scaled_div = 0; + int ret; + + if (parent_rate > (unsigned long)LONG_MAX) + return -EINVAL; + + if (rate == __clk_get_rate(hw->clk)) + return 0; + + if (!divider_exists(div)) + return rate == parent_rate ? 0 : -EINVAL; + + /* + * A fixed divider can't be changed. (Nor can a fixed + * pre-divider be, but for now we never actually try to + * change that.) Tolerate a request for a no-op change. + */ + if (divider_is_fixed(&data->div)) + return rate == parent_rate ? 0 : -EINVAL; + + /* + * Get the scaled divisor value needed to achieve a clock + * rate as close as possible to what was requested, given + * the parent clock rate supplied. + */ + (void)round_rate(bcm_clk->ccu, div, &data->pre_div, + rate ? rate : 1, parent_rate, &scaled_div); + + /* + * We aren't updating any pre-divider at this point, so + * we'll use the regular trigger. + */ + ret = divider_write(bcm_clk->ccu, &data->gate, &data->div, + &data->trig, scaled_div); + if (ret == -ENXIO) { + pr_err("%s: gating failure for %s\n", __func__, + bcm_clk->init_data.name); + ret = -EIO; /* Don't proliferate weird errors */ + } else if (ret == -EIO) { + pr_err("%s: trigger failed for %s\n", __func__, + bcm_clk->init_data.name); + } + + return ret; +} + +struct clk_ops kona_peri_clk_ops = { + .enable = kona_peri_clk_enable, + .disable = kona_peri_clk_disable, + .is_enabled = kona_peri_clk_is_enabled, + .recalc_rate = kona_peri_clk_recalc_rate, + .round_rate = kona_peri_clk_round_rate, + .set_parent = kona_peri_clk_set_parent, + .get_parent = kona_peri_clk_get_parent, + .set_rate = kona_peri_clk_set_rate, +}; + +/* Put a bus clock into its initial state */ +static bool __bus_clk_init(struct kona_clk *bcm_clk) +{ + struct ccu_data *ccu = bcm_clk->ccu; + struct bus_clk_data *bus = bcm_clk->bus; + const char *name = bcm_clk->init_data.name; + + BUG_ON(bcm_clk->type != bcm_clk_bus); + + if (!policy_init(ccu, &bus->policy)) { + pr_err("%s: error initializing policy for %s\n", + __func__, name); + return false; + } + if (!gate_init(ccu, &bus->gate)) { + pr_err("%s: error initializing gate for %s\n", __func__, name); + return false; + } + if (!hyst_init(ccu, &bus->hyst)) { + pr_err("%s: error initializing hyst for %s\n", __func__, name); + return false; + } + return true; +} + +/* Put a peripheral clock into its initial state */ +static bool __peri_clk_init(struct kona_clk *bcm_clk) +{ + struct ccu_data *ccu = bcm_clk->ccu; + struct peri_clk_data *peri = bcm_clk->peri; + const char *name = bcm_clk->init_data.name; + struct bcm_clk_trig *trig; + + BUG_ON(bcm_clk->type != bcm_clk_peri); + + if (!policy_init(ccu, &peri->policy)) { + pr_err("%s: error initializing policy for %s\n", + __func__, name); + return false; + } + if (!gate_init(ccu, &peri->gate)) { + pr_err("%s: error initializing gate for %s\n", __func__, name); + return false; + } + if (!hyst_init(ccu, &peri->hyst)) { + pr_err("%s: error initializing hyst for %s\n", __func__, name); + return false; + } + if (!div_init(ccu, &peri->gate, &peri->div, &peri->trig)) { + pr_err("%s: error initializing divider for %s\n", __func__, + name); + return false; + } + + /* + * For the pre-divider and selector, the pre-trigger is used + * if it's present, otherwise we just use the regular trigger. + */ + trig = trigger_exists(&peri->pre_trig) ? &peri->pre_trig + : &peri->trig; + + if (!div_init(ccu, &peri->gate, &peri->pre_div, trig)) { + pr_err("%s: error initializing pre-divider for %s\n", __func__, + name); + return false; + } + + if (!sel_init(ccu, &peri->gate, &peri->sel, trig)) { + pr_err("%s: error initializing selector for %s\n", __func__, + name); + return false; + } + + return true; +} + +static bool __kona_clk_init(struct kona_clk *bcm_clk); +static bool __kona_prereq_init(struct kona_clk *bcm_clk) +{ + struct clk *clk; + struct clk_hw *hw; + struct kona_clk *prereq; + + BUG_ON(clk_is_initialized(bcm_clk)); + + if (!bcm_clk->prereq) + return true; + + clk = clk_get(NULL, bcm_clk->prereq); + if (IS_ERR(clk)) { + pr_err("%s: unable to get prereq clock %s for %s\n", + __func__, bcm_clk->prereq, bcm_clk->init_data.name); + return false; + } + hw = __clk_get_hw(clk); + if (!hw) { + pr_err("%s: null hw pointer for clock %s\n", __func__, + bcm_clk->init_data.name); + return false; + } + prereq = to_kona_clk(hw); + if (prereq->ccu != bcm_clk->ccu) { + pr_err("%s: prereq clock %s CCU different for clock %s\n", + __func__, bcm_clk->prereq, bcm_clk->init_data.name); + return false; + } + + /* Initialize the prerequisite clock first */ + if (!__kona_clk_init(prereq)) { + pr_err("%s: failed to init prereq %s for clock %s\n", + __func__, bcm_clk->prereq, bcm_clk->init_data.name); + return false; + } + bcm_clk->prereq_clk = clk; + + return true; +} + +static bool __kona_clk_init(struct kona_clk *bcm_clk) +{ + bool ret = false; + + if (clk_is_initialized(bcm_clk)) + return true; + + if (!__kona_prereq_init(bcm_clk)) + return false; + + switch (bcm_clk->type) { + case bcm_clk_bus: + ret = __bus_clk_init(bcm_clk); + break; + case bcm_clk_peri: + ret = __peri_clk_init(bcm_clk); + break; + default: + BUG(); + } + if (ret) + clk_set_initialized(bcm_clk); + + return ret; +} + +/* Set a CCU and all its clocks into their desired initial state */ +bool __init kona_ccu_init(struct ccu_data *ccu) +{ + unsigned long flags; + unsigned int which; + struct clk **clks = ccu->clk_data.clks; + bool success = true; + + flags = ccu_lock(ccu); + __ccu_write_enable(ccu); + + for (which = 0; which < ccu->clk_data.clk_num; which++) { + struct kona_clk *bcm_clk; + + if (!clks[which]) + continue; + bcm_clk = to_kona_clk(__clk_get_hw(clks[which])); + success &= __kona_clk_init(bcm_clk); + } + + __ccu_write_disable(ccu); + ccu_unlock(ccu, flags); + return success; +} diff --git a/drivers/clk/bcm/clk-kona.h b/drivers/clk/bcm/clk-kona.h new file mode 100644 index 00000000000000..fdb25c6f954741 --- /dev/null +++ b/drivers/clk/bcm/clk-kona.h @@ -0,0 +1,545 @@ +/* + * Copyright (C) 2013 Broadcom Corporation + * Copyright 2013 Linaro Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CLK_KONA_H +#define _CLK_KONA_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BILLION 1000000000 + +/* The common clock framework uses u8 to represent a parent index */ +#define PARENT_COUNT_MAX ((u32)U8_MAX) + +#define BAD_CLK_INDEX U8_MAX /* Can't ever be valid */ +#define BAD_CLK_NAME ((const char *)-1) + +#define BAD_SCALED_DIV_VALUE U64_MAX + +/* + * Utility macros for object flag management. If possible, flags + * should be defined such that 0 is the desired default value. + */ +#define FLAG(type, flag) BCM_CLK_ ## type ## _FLAGS_ ## flag +#define FLAG_SET(obj, type, flag) ((obj)->flags |= FLAG(type, flag)) +#define FLAG_CLEAR(obj, type, flag) ((obj)->flags &= ~(FLAG(type, flag))) +#define FLAG_FLIP(obj, type, flag) ((obj)->flags ^= FLAG(type, flag)) +#define FLAG_TEST(obj, type, flag) (!!((obj)->flags & FLAG(type, flag))) + +/* CCU field state tests */ + +#define ccu_policy_exists(ccu_policy) ((ccu_policy)->enable.offset != 0) + +/* Clock field state tests */ + +#define policy_exists(policy) ((policy)->offset != 0) + +#define gate_exists(gate) FLAG_TEST(gate, GATE, EXISTS) +#define gate_is_enabled(gate) FLAG_TEST(gate, GATE, ENABLED) +#define gate_is_hw_controllable(gate) FLAG_TEST(gate, GATE, HW) +#define gate_is_sw_controllable(gate) FLAG_TEST(gate, GATE, SW) +#define gate_is_sw_managed(gate) FLAG_TEST(gate, GATE, SW_MANAGED) +#define gate_is_no_disable(gate) FLAG_TEST(gate, GATE, NO_DISABLE) + +#define gate_flip_enabled(gate) FLAG_FLIP(gate, GATE, ENABLED) + +#define hyst_exists(hyst) ((hyst)->offset != 0) + +#define divider_exists(div) FLAG_TEST(div, DIV, EXISTS) +#define divider_is_fixed(div) FLAG_TEST(div, DIV, FIXED) +#define divider_has_fraction(div) (!divider_is_fixed(div) && \ + (div)->frac_width > 0) + +#define selector_exists(sel) ((sel)->width != 0) +#define trigger_exists(trig) FLAG_TEST(trig, TRIG, EXISTS) + +#define policy_lvm_en_exists(enable) ((enable)->offset != 0) +#define policy_ctl_exists(control) ((control)->offset != 0) + +/* Clock type, used to tell common block what it's part of */ +enum bcm_clk_type { + bcm_clk_none, /* undefined clock type */ + bcm_clk_bus, + bcm_clk_core, + bcm_clk_peri +}; + +/* + * CCU policy control for clocks. Clocks can be enabled or disabled + * based on the CCU policy in effect. One bit in each policy mask + * register (one per CCU policy) represents whether the clock is + * enabled when that policy is effect or not. The CCU policy engine + * must be stopped to update these bits, and must be restarted again + * afterward. + */ +struct bcm_clk_policy { + u32 offset; /* first policy mask register offset */ + u32 bit; /* bit used in all mask registers */ +}; + +/* Policy initialization macro */ + +#define POLICY(_offset, _bit) \ + { \ + .offset = (_offset), \ + .bit = (_bit), \ + } + +/* + * Gating control and status is managed by a 32-bit gate register. + * + * There are several types of gating available: + * - (no gate) + * A clock with no gate is assumed to be always enabled. + * - hardware-only gating (auto-gating) + * Enabling or disabling clocks with this type of gate is + * managed automatically by the hardware. Such clocks can be + * considered by the software to be enabled. The current status + * of auto-gated clocks can be read from the gate status bit. + * - software-only gating + * Auto-gating is not available for this type of clock. + * Instead, software manages whether it's enabled by setting or + * clearing the enable bit. The current gate status of a gate + * under software control can be read from the gate status bit. + * To ensure a change to the gating status is complete, the + * status bit can be polled to verify that the gate has entered + * the desired state. + * - selectable hardware or software gating + * Gating for this type of clock can be configured to be either + * under software or hardware control. Which type is in use is + * determined by the hw_sw_sel bit of the gate register. + */ +struct bcm_clk_gate { + u32 offset; /* gate register offset */ + u32 status_bit; /* 0: gate is disabled; 0: gatge is enabled */ + u32 en_bit; /* 0: disable; 1: enable */ + u32 hw_sw_sel_bit; /* 0: hardware gating; 1: software gating */ + u32 flags; /* BCM_CLK_GATE_FLAGS_* below */ +}; + +/* + * Gate flags: + * HW means this gate can be auto-gated + * SW means the state of this gate can be software controlled + * NO_DISABLE means this gate is (only) enabled if under software control + * SW_MANAGED means the status of this gate is under software control + * ENABLED means this software-managed gate is *supposed* to be enabled + */ +#define BCM_CLK_GATE_FLAGS_EXISTS ((u32)1 << 0) /* Gate is valid */ +#define BCM_CLK_GATE_FLAGS_HW ((u32)1 << 1) /* Can auto-gate */ +#define BCM_CLK_GATE_FLAGS_SW ((u32)1 << 2) /* Software control */ +#define BCM_CLK_GATE_FLAGS_NO_DISABLE ((u32)1 << 3) /* HW or enabled */ +#define BCM_CLK_GATE_FLAGS_SW_MANAGED ((u32)1 << 4) /* SW now in control */ +#define BCM_CLK_GATE_FLAGS_ENABLED ((u32)1 << 5) /* If SW_MANAGED */ + +/* + * Gate initialization macros. + * + * Any gate initially under software control will be enabled. + */ + +/* A hardware/software gate initially under software control */ +#define HW_SW_GATE(_offset, _status_bit, _en_bit, _hw_sw_sel_bit) \ + { \ + .offset = (_offset), \ + .status_bit = (_status_bit), \ + .en_bit = (_en_bit), \ + .hw_sw_sel_bit = (_hw_sw_sel_bit), \ + .flags = FLAG(GATE, HW)|FLAG(GATE, SW)| \ + FLAG(GATE, SW_MANAGED)|FLAG(GATE, ENABLED)| \ + FLAG(GATE, EXISTS), \ + } + +/* A hardware/software gate initially under hardware control */ +#define HW_SW_GATE_AUTO(_offset, _status_bit, _en_bit, _hw_sw_sel_bit) \ + { \ + .offset = (_offset), \ + .status_bit = (_status_bit), \ + .en_bit = (_en_bit), \ + .hw_sw_sel_bit = (_hw_sw_sel_bit), \ + .flags = FLAG(GATE, HW)|FLAG(GATE, SW)| \ + FLAG(GATE, EXISTS), \ + } + +/* A hardware-or-enabled gate (enabled if not under hardware control) */ +#define HW_ENABLE_GATE(_offset, _status_bit, _en_bit, _hw_sw_sel_bit) \ + { \ + .offset = (_offset), \ + .status_bit = (_status_bit), \ + .en_bit = (_en_bit), \ + .hw_sw_sel_bit = (_hw_sw_sel_bit), \ + .flags = FLAG(GATE, HW)|FLAG(GATE, SW)| \ + FLAG(GATE, NO_DISABLE)|FLAG(GATE, EXISTS), \ + } + +/* A software-only gate */ +#define SW_ONLY_GATE(_offset, _status_bit, _en_bit) \ + { \ + .offset = (_offset), \ + .status_bit = (_status_bit), \ + .en_bit = (_en_bit), \ + .flags = FLAG(GATE, SW)|FLAG(GATE, SW_MANAGED)| \ + FLAG(GATE, ENABLED)|FLAG(GATE, EXISTS), \ + } + +/* A hardware-only gate */ +#define HW_ONLY_GATE(_offset, _status_bit) \ + { \ + .offset = (_offset), \ + .status_bit = (_status_bit), \ + .flags = FLAG(GATE, HW)|FLAG(GATE, EXISTS), \ + } + +/* Gate hysteresis for clocks */ +struct bcm_clk_hyst { + u32 offset; /* hyst register offset (normally CLKGATE) */ + u32 en_bit; /* bit used to enable hysteresis */ + u32 val_bit; /* if enabled: 0 = low delay; 1 = high delay */ +}; + +/* Hysteresis initialization macro */ + +#define HYST(_offset, _en_bit, _val_bit) \ + { \ + .offset = (_offset), \ + .en_bit = (_en_bit), \ + .val_bit = (_val_bit), \ + } + +/* + * Each clock can have zero, one, or two dividers which change the + * output rate of the clock. Each divider can be either fixed or + * variable. If there are two dividers, they are the "pre-divider" + * and the "regular" or "downstream" divider. If there is only one, + * there is no pre-divider. + * + * A fixed divider is any non-zero (positive) value, and it + * indicates how the input rate is affected by the divider. + * + * The value of a variable divider is maintained in a sub-field of a + * 32-bit divider register. The position of the field in the + * register is defined by its offset and width. The value recorded + * in this field is always 1 less than the value it represents. + * + * In addition, a variable divider can indicate that some subset + * of its bits represent a "fractional" part of the divider. Such + * bits comprise the low-order portion of the divider field, and can + * be viewed as representing the portion of the divider that lies to + * the right of the decimal point. Most variable dividers have zero + * fractional bits. Variable dividers with non-zero fraction width + * still record a value 1 less than the value they represent; the + * added 1 does *not* affect the low-order bit in this case, it + * affects the bits above the fractional part only. (Often in this + * code a divider field value is distinguished from the value it + * represents by referring to the latter as a "divisor".) + * + * In order to avoid dealing with fractions, divider arithmetic is + * performed using "scaled" values. A scaled value is one that's + * been left-shifted by the fractional width of a divider. Dividing + * a scaled value by a scaled divisor produces the desired quotient + * without loss of precision and without any other special handling + * for fractions. + * + * The recorded value of a variable divider can be modified. To + * modify either divider (or both), a clock must be enabled (i.e., + * using its gate). In addition, a trigger register (described + * below) must be used to commit the change, and polled to verify + * the change is complete. + */ +struct bcm_clk_div { + union { + struct { /* variable divider */ + u32 offset; /* divider register offset */ + u32 shift; /* field shift */ + u32 width; /* field width */ + u32 frac_width; /* field fraction width */ + + u64 scaled_div; /* scaled divider value */ + }; + u32 fixed; /* non-zero fixed divider value */ + }; + u32 flags; /* BCM_CLK_DIV_FLAGS_* below */ +}; + +/* + * Divider flags: + * EXISTS means this divider exists + * FIXED means it is a fixed-rate divider + */ +#define BCM_CLK_DIV_FLAGS_EXISTS ((u32)1 << 0) /* Divider is valid */ +#define BCM_CLK_DIV_FLAGS_FIXED ((u32)1 << 1) /* Fixed-value */ + +/* Divider initialization macros */ + +/* A fixed (non-zero) divider */ +#define FIXED_DIVIDER(_value) \ + { \ + .fixed = (_value), \ + .flags = FLAG(DIV, EXISTS)|FLAG(DIV, FIXED), \ + } + +/* A divider with an integral divisor */ +#define DIVIDER(_offset, _shift, _width) \ + { \ + .offset = (_offset), \ + .shift = (_shift), \ + .width = (_width), \ + .scaled_div = BAD_SCALED_DIV_VALUE, \ + .flags = FLAG(DIV, EXISTS), \ + } + +/* A divider whose divisor has an integer and fractional part */ +#define FRAC_DIVIDER(_offset, _shift, _width, _frac_width) \ + { \ + .offset = (_offset), \ + .shift = (_shift), \ + .width = (_width), \ + .frac_width = (_frac_width), \ + .scaled_div = BAD_SCALED_DIV_VALUE, \ + .flags = FLAG(DIV, EXISTS), \ + } + +/* + * Clocks may have multiple "parent" clocks. If there is more than + * one, a selector must be specified to define which of the parent + * clocks is currently in use. The selected clock is indicated in a + * sub-field of a 32-bit selector register. The range of + * representable selector values typically exceeds the number of + * available parent clocks. Occasionally the reset value of a + * selector field is explicitly set to a (specific) value that does + * not correspond to a defined input clock. + * + * We register all known parent clocks with the common clock code + * using a packed array (i.e., no empty slots) of (parent) clock + * names, and refer to them later using indexes into that array. + * We maintain an array of selector values indexed by common clock + * index values in order to map between these common clock indexes + * and the selector values used by the hardware. + * + * Like dividers, a selector can be modified, but to do so a clock + * must be enabled, and a trigger must be used to commit the change. + */ +struct bcm_clk_sel { + u32 offset; /* selector register offset */ + u32 shift; /* field shift */ + u32 width; /* field width */ + + u32 parent_count; /* number of entries in parent_sel[] */ + u32 *parent_sel; /* array of parent selector values */ + u8 clk_index; /* current selected index in parent_sel[] */ +}; + +/* Selector initialization macro */ +#define SELECTOR(_offset, _shift, _width) \ + { \ + .offset = (_offset), \ + .shift = (_shift), \ + .width = (_width), \ + .clk_index = BAD_CLK_INDEX, \ + } + +/* + * Making changes to a variable divider or a selector for a clock + * requires the use of a trigger. A trigger is defined by a single + * bit within a register. To signal a change, a 1 is written into + * that bit. To determine when the change has been completed, that + * trigger bit is polled; the read value will be 1 while the change + * is in progress, and 0 when it is complete. + * + * Occasionally a clock will have more than one trigger. In this + * case, the "pre-trigger" will be used when changing a clock's + * selector and/or its pre-divider. + */ +struct bcm_clk_trig { + u32 offset; /* trigger register offset */ + u32 bit; /* trigger bit */ + u32 flags; /* BCM_CLK_TRIG_FLAGS_* below */ +}; + +/* + * Trigger flags: + * EXISTS means this trigger exists + */ +#define BCM_CLK_TRIG_FLAGS_EXISTS ((u32)1 << 0) /* Trigger is valid */ + +/* Trigger initialization macro */ +#define TRIGGER(_offset, _bit) \ + { \ + .offset = (_offset), \ + .bit = (_bit), \ + .flags = FLAG(TRIG, EXISTS), \ + } + +struct bus_clk_data { + struct bcm_clk_policy policy; + struct bcm_clk_gate gate; + struct bcm_clk_hyst hyst; +}; + +struct peri_clk_data { + struct bcm_clk_policy policy; + struct bcm_clk_gate gate; + struct bcm_clk_hyst hyst; + struct bcm_clk_trig pre_trig; + struct bcm_clk_div pre_div; + struct bcm_clk_trig trig; + struct bcm_clk_div div; + struct bcm_clk_sel sel; + const char *clocks[]; /* must be last; use CLOCKS() to declare */ +}; +#define CLOCKS(...) { __VA_ARGS__, NULL, } +#define NO_CLOCKS { NULL, } /* Must use of no parent clocks */ + +struct kona_clk { + struct clk_hw hw; + struct clk_init_data init_data; /* includes name of this clock */ + struct ccu_data *ccu; /* ccu this clock is associated with */ + enum bcm_clk_type type; + u32 flags; /* BCM_CLK_KONA_FLAGS_* below */ + struct clk_lookup cl; + union { + const char *prereq; + struct clk *prereq_clk; + }; + union { + void *data; + struct bus_clk_data *bus; + struct peri_clk_data *peri; + }; +}; +#define to_kona_clk(_hw) \ + container_of(_hw, struct kona_clk, hw) + +/* + * Kona clock flags: + * INITIALIZED clock has been initialized already + */ +#define BCM_CLK_KONA_FLAGS_INITIALIZED ((u32)1 << 0) /* Clock initialized */ + +/* Initialization macro for an entry in a CCU's kona_clks[] array. */ +#define ___KONA_CLK_COMMON(_ccu_name, _clk_name, _type) \ + .init_data = { \ + .name = #_clk_name, \ + .ops = &kona_ ## _type ## _clk_ops, \ + }, \ + .ccu = &_ccu_name ## _ccu_data, \ + .type = bcm_clk_ ## _type, \ + .data = &_clk_name ## _data +#define KONA_CLK_PREREQ(_ccu_name, _clk_name, _type, _prereq) \ + { \ + .prereq = #_prereq, \ + ___KONA_CLK_COMMON(_ccu_name, _clk_name, _type), \ + } +#define KONA_CLK(_ccu_name, _clk_name, _type) \ + { \ + ___KONA_CLK_COMMON(_ccu_name, _clk_name, _type), \ + } +#define LAST_KONA_CLK { .type = bcm_clk_none } + +/* + * CCU policy control. To enable software update of the policy + * tables the CCU policy engine must be stopped by setting the + * software update enable bit (LVM_EN). After an update the engine + * is restarted using the GO bit and either the GO_ATL or GO_AC bit. + */ +struct bcm_lvm_en { + u32 offset; /* LVM_EN register offset */ + u32 bit; /* POLICY_CONFIG_EN bit in register */ +}; + +/* Policy enable initialization macro */ +#define CCU_LVM_EN(_offset, _bit) \ + { \ + .offset = (_offset), \ + .bit = (_bit), \ + } + +struct bcm_policy_ctl { + u32 offset; /* POLICY_CTL register offset */ + u32 go_bit; + u32 atl_bit; /* GO, GO_ATL, and GO_AC bits */ + u32 ac_bit; +}; + +/* Policy control initialization macro */ +#define CCU_POLICY_CTL(_offset, _go_bit, _ac_bit, _atl_bit) \ + { \ + .offset = (_offset), \ + .go_bit = (_go_bit), \ + .ac_bit = (_ac_bit), \ + .atl_bit = (_atl_bit), \ + } + +struct ccu_policy { + struct bcm_lvm_en enable; + struct bcm_policy_ctl control; +}; + +/* + * Each CCU defines a mapped area of memory containing registers + * used to manage clocks implemented by the CCU. Access to memory + * within the CCU's space is serialized by a spinlock. Before any + * (other) address can be written, a special access "password" value + * must be written to its WR_ACCESS register (located at the base + * address of the range). We keep track of the name of each CCU as + * it is set up, and maintain them in a list. + */ +struct ccu_data { + void __iomem *base; /* base of mapped address space */ + spinlock_t lock; /* serialization lock */ + bool write_enabled; /* write access is currently enabled */ + struct ccu_policy policy; + struct list_head links; /* for ccu_list */ + struct device_node *node; + struct clk_onecell_data clk_data; + const char *name; + u32 range; /* byte range of address space */ + struct kona_clk kona_clks[]; /* must be last */ +}; + +/* Initialization for common fields in a Kona ccu_data structure */ +#define KONA_CCU_COMMON(_prefix, _name, _ccuname) \ + .name = #_name "_ccu", \ + .lock = __SPIN_LOCK_UNLOCKED(_name ## _ccu_data.lock), \ + .links = LIST_HEAD_INIT(_name ## _ccu_data.links), \ + .clk_data = { \ + .clk_num = _prefix ## _ ## _ccuname ## _CCU_CLOCK_COUNT, \ + } + +/* Exported globals */ + +extern struct clk_ops kona_bus_clk_ops; +extern struct clk_ops kona_peri_clk_ops; + +/* Externally visible functions */ + +extern u64 do_div_round_closest(u64 dividend, unsigned long divisor); +extern u64 scaled_div_max(struct bcm_clk_div *div); +extern u64 scaled_div_build(struct bcm_clk_div *div, u32 div_value, + u32 billionths); + +extern struct clk *kona_clk_setup(struct kona_clk *bcm_clk); +extern void __init kona_dt_ccu_setup(struct ccu_data *ccu, + struct device_node *node); +extern bool __init kona_ccu_init(struct ccu_data *ccu); + +#endif /* _CLK_KONA_H */ diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c new file mode 100644 index 00000000000000..6ad981f66fb735 --- /dev/null +++ b/drivers/clk/clk-scpi.c @@ -0,0 +1,155 @@ +/* + * Clock provider that uses the SCPI interface for clock setting. + * + * Copyright (C) 2014 ARM Ltd. + * Author: Liviu Dudau + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include + +struct scpi_osc { + uint8_t id; + struct clk_hw hw; + unsigned long rate_min; + unsigned long rate_max; + struct scpi_dev *scpi; +}; + +#define to_scpi_osc(osc) container_of(osc, struct scpi_osc, hw) + +static unsigned long scpi_osc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct scpi_osc *osc = to_scpi_osc(hw); + char buf[4]; + int err; + + memset(buf, 0, 4); + buf[0] = osc->id; + err = scpi_exec_command(SCPI_CMD_GET_CLOCK_FREQ, &buf, 2, &buf, 4); + if (err) + return 0; + + return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; +} + +static long scpi_osc_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct scpi_osc *osc = to_scpi_osc(hw); + if (osc->rate_min && rate < osc->rate_min) + rate = osc->rate_min; + if (osc->rate_max && rate > osc->rate_max) + rate = osc->rate_max; + + return rate; +} + +static int scpi_osc_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct scpi_osc *osc = to_scpi_osc(hw); + char buf[6]; + int err; + + memcpy(buf, (char*)&rate, 4); + buf[4] = osc->id; + buf[5] = 0; + err = scpi_exec_command(SCPI_CMD_SET_CLOCK_FREQ, buf, 6, NULL, 0); + if (err) { + pr_info("Failed to set clock frequency: %d\n", err); + } + + return err; +} + +static struct clk_ops scpi_osc_ops = { + .recalc_rate = scpi_osc_recalc_rate, + .round_rate = scpi_osc_round_rate, + .set_rate = scpi_osc_set_rate, +}; + +void __init scpi_osc_setup(struct device_node *node) +{ + struct clk_init_data clk_init; + struct scpi_osc *osc; + struct clk *clk; + int num_parents, i, err; + const char *parent_names[2]; + uint32_t range[2]; + struct platform_device *pdev; + struct of_phandle_args clkspec; + + osc = kzalloc(sizeof(*osc), GFP_KERNEL); + if (!osc) + return; + + if (of_property_read_u32_array(node, "freq-range", range, + ARRAY_SIZE(range)) == 0) { + osc->rate_min = range[0]; + osc->rate_max = range[1]; + } + + of_property_read_string(node, "clock-output-names", &clk_init.name); + if (!clk_init.name) + clk_init.name = node->full_name; + + num_parents = of_clk_get_parent_count(node); + if (num_parents < 0 || num_parents > 2) + goto cleanup; + + for (i = 0; i < num_parents; i++) { + parent_names[i] = of_clk_get_parent_name(node, i); + if (!parent_names[i]) + goto cleanup; + else + pr_info("Found parent clock %s\n", parent_names[i]); + + err = of_parse_phandle_with_args(node, "clocks", + "#clock-cells", i, &clkspec); + if (!err && clkspec.args_count) + osc->id = clkspec.args[0]; + else + osc->id = -1; + } + + request_module("scpi-mhu"); + + pdev = of_find_device_by_node(node); + if (!pdev) { + pr_info("Failed to find platform device\n"); + } else { + pr_info("Found platform device %s\n", pdev->name); + } + + clk_init.ops = &scpi_osc_ops; + clk_init.flags = CLK_IS_BASIC; + clk_init.num_parents = num_parents; + clk_init.parent_names = parent_names; + osc->hw.init = &clk_init; + + clk = clk_register(NULL, &osc->hw); + if (IS_ERR(clk)) { + pr_err("Failed to register clock '%s'!\n", clk_init.name); + goto cleanup; + } + + of_clk_add_provider(node, of_clk_src_simple_get, clk); + pr_info("Registered clock '%s'\n", clk_init.name); + + return; + +cleanup: + if (osc) + kfree(osc); +} +CLK_OF_DECLARE(scpi_clk, "arm,scpi-osc", scpi_osc_setup); diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index c42e608af6bbe0..92760b127694ec 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -277,6 +277,10 @@ static int clk_debug_create_one(struct clk *clk, struct dentry *pdentry) if (!d) goto err_out; + if (clk->ops->debug_init) + if (clk->ops->debug_init(clk->hw, clk->dentry)) + goto err_out; + ret = 0; goto out; diff --git a/drivers/clk/hisilicon/Makefile b/drivers/clk/hisilicon/Makefile index a049108341fc60..352269bd414571 100644 --- a/drivers/clk/hisilicon/Makefile +++ b/drivers/clk/hisilicon/Makefile @@ -2,4 +2,7 @@ # Hisilicon Clock specific Makefile # -obj-y += clk.o clkgate-separated.o clk-hi3620.o +obj-y += clk.o clkgate-separated.o + +obj-$(CONFIG_ARCH_HI3xxx) += clk-hi3620.o clk-hi3716.o +obj-$(CONFIG_ARCH_HIP04) += clk-hip04.o diff --git a/drivers/clk/hisilicon/clk-hi3620.c b/drivers/clk/hisilicon/clk-hi3620.c index f24ad6a3a7970c..233eba22187a90 100644 --- a/drivers/clk/hisilicon/clk-hi3620.c +++ b/drivers/clk/hisilicon/clk-hi3620.c @@ -210,33 +210,298 @@ static struct hisi_gate_clock hi3620_seperated_gate_clks[] __initdata = { static void __init hi3620_clk_init(struct device_node *np) { - void __iomem *base; + struct hisi_clock_data *clk_data; - if (np) { - base = of_iomap(np, 0); - if (!base) { - pr_err("failed to map Hi3620 clock registers\n"); - return; - } - } else { - pr_err("failed to find Hi3620 clock node in DTS\n"); + clk_data = hisi_clk_init(np, HI3620_NR_CLKS); + if (!clk_data) return; - } - - hisi_clk_init(np, HI3620_NR_CLKS); hisi_clk_register_fixed_rate(hi3620_fixed_rate_clks, ARRAY_SIZE(hi3620_fixed_rate_clks), - base); + clk_data); hisi_clk_register_fixed_factor(hi3620_fixed_factor_clks, ARRAY_SIZE(hi3620_fixed_factor_clks), - base); + clk_data); hisi_clk_register_mux(hi3620_mux_clks, ARRAY_SIZE(hi3620_mux_clks), - base); + clk_data); hisi_clk_register_divider(hi3620_div_clks, ARRAY_SIZE(hi3620_div_clks), - base); + clk_data); hisi_clk_register_gate_sep(hi3620_seperated_gate_clks, ARRAY_SIZE(hi3620_seperated_gate_clks), - base); + clk_data); } CLK_OF_DECLARE(hi3620_clk, "hisilicon,hi3620-clock", hi3620_clk_init); + +struct hisi_mmc_clock { + unsigned int id; + const char *name; + const char *parent_name; + unsigned long flags; + u32 clken_reg; + u32 clken_bit; + u32 div_reg; + u32 div_off; + u32 div_bits; + u32 drv_reg; + u32 drv_off; + u32 drv_bits; + u32 sam_reg; + u32 sam_off; + u32 sam_bits; +}; + +struct clk_mmc { + struct clk_hw hw; + u32 id; + void __iomem *clken_reg; + u32 clken_bit; + void __iomem *div_reg; + u32 div_off; + u32 div_bits; + void __iomem *drv_reg; + u32 drv_off; + u32 drv_bits; + void __iomem *sam_reg; + u32 sam_off; + u32 sam_bits; +}; + +#define to_mmc(_hw) container_of(_hw, struct clk_mmc, hw) + +static struct hisi_mmc_clock hi3620_mmc_clks[] __initdata = { + { HI3620_SD_CIUCLK, "sd_bclk1", "sd_clk", CLK_SET_RATE_PARENT, 0x1f8, 0, 0x1f8, 1, 3, 0x1f8, 4, 4, 0x1f8, 8, 4}, + { HI3620_MMC_CIUCLK1, "mmc_bclk1", "mmc_clk1", CLK_SET_RATE_PARENT, 0x1f8, 12, 0x1f8, 13, 3, 0x1f8, 16, 4, 0x1f8, 20, 4}, + { HI3620_MMC_CIUCLK2, "mmc_bclk2", "mmc_clk2", CLK_SET_RATE_PARENT, 0x1f8, 24, 0x1f8, 25, 3, 0x1f8, 28, 4, 0x1fc, 0, 4}, + { HI3620_MMC_CIUCLK3, "mmc_bclk3", "mmc_clk3", CLK_SET_RATE_PARENT, 0x1fc, 4, 0x1fc, 5, 3, 0x1fc, 8, 4, 0x1fc, 12, 4}, +}; + +static unsigned long mmc_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + switch (parent_rate) { + case 26000000: + return 13000000; + case 180000000: + return 25000000; + case 360000000: + return 50000000; + case 720000000: + return 100000000; + case 1440000000: + return 180000000; + default: + return parent_rate; + } +} + +static long mmc_clk_determine_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *best_parent_rate, + struct clk **best_parent_p) +{ + struct clk_mmc *mclk = to_mmc(hw); + unsigned long best = 0; + + if ((rate <= 13000000) && (mclk->id == HI3620_MMC_CIUCLK1)) { + rate = 13000000; + best = 26000000; + } else if (rate <= 26000000) { + rate = 25000000; + best = 180000000; + } else if (rate <= 52000000) { + rate = 50000000; + best = 360000000; + } else if (rate <= 100000000) { + rate = 100000000; + best = 720000000; + } else { + /* max is 180M */ + rate = 180000000; + best = 1440000000; + } + *best_parent_rate = best; + return rate; +} + +static u32 mmc_clk_delay(u32 val, u32 para, u32 off, u32 len) +{ + u32 i; + + if (para >= 0) { + for (i = 0; i < len; i++) { + if (para % 2) + val |= 1 << (off + i); + else + val &= ~(1 << (off + i)); + para = para >> 1; + } + } + return val; +} + +static int mmc_clk_set_timing(struct clk_hw *hw, unsigned long rate) +{ + struct clk_mmc *mclk = to_mmc(hw); + unsigned long flags; + u32 sam, drv, div, val; + static DEFINE_SPINLOCK(mmc_clk_lock); + + switch (rate) { + case 13000000: + sam = 3; + drv = 1; + div = 1; + break; + case 25000000: + sam = 13; + drv = 6; + div = 6; + break; + case 50000000: + sam = 3; + drv = 6; + div = 6; + break; + case 100000000: + sam = 6; + drv = 4; + div = 6; + break; + case 180000000: + sam = 6; + drv = 4; + div = 7; + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&mmc_clk_lock, flags); + + val = readl_relaxed(mclk->clken_reg); + val &= ~(1 << mclk->clken_bit); + writel_relaxed(val, mclk->clken_reg); + + val = readl_relaxed(mclk->sam_reg); + val = mmc_clk_delay(val, sam, mclk->sam_off, mclk->sam_bits); + writel_relaxed(val, mclk->sam_reg); + + val = readl_relaxed(mclk->drv_reg); + val = mmc_clk_delay(val, drv, mclk->drv_off, mclk->drv_bits); + writel_relaxed(val, mclk->drv_reg); + + val = readl_relaxed(mclk->div_reg); + val = mmc_clk_delay(val, div, mclk->div_off, mclk->div_bits); + writel_relaxed(val, mclk->div_reg); + + val = readl_relaxed(mclk->clken_reg); + val |= 1 << mclk->clken_bit; + writel_relaxed(val, mclk->clken_reg); + + spin_unlock_irqrestore(&mmc_clk_lock, flags); + + return 0; +} + +static int mmc_clk_prepare(struct clk_hw *hw) +{ + struct clk_mmc *mclk = to_mmc(hw); + unsigned long rate; + + if (mclk->id == HI3620_MMC_CIUCLK1) + rate = 13000000; + else + rate = 25000000; + + return mmc_clk_set_timing(hw, rate); +} + +static int mmc_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return mmc_clk_set_timing(hw, rate); +} + +static struct clk_ops clk_mmc_ops = { + .prepare = mmc_clk_prepare, + .determine_rate = mmc_clk_determine_rate, + .set_rate = mmc_clk_set_rate, + .recalc_rate = mmc_clk_recalc_rate, +}; + +static struct clk *hisi_register_clk_mmc(struct hisi_mmc_clock *mmc_clk, + void __iomem *base, struct device_node *np) +{ + struct clk_mmc *mclk; + struct clk *clk; + struct clk_init_data init; + + mclk = kzalloc(sizeof(*mclk), GFP_KERNEL); + if (!mclk) { + pr_err("%s: fail to allocate mmc clk\n", __func__); + return ERR_PTR(-ENOMEM); + } + + init.name = mmc_clk->name; + init.ops = &clk_mmc_ops; + init.flags = mmc_clk->flags | CLK_IS_BASIC; + init.parent_names = (mmc_clk->parent_name ? &mmc_clk->parent_name : NULL); + init.num_parents = (mmc_clk->parent_name ? 1 : 0); + mclk->hw.init = &init; + + mclk->id = mmc_clk->id; + mclk->clken_reg = base + mmc_clk->clken_reg; + mclk->clken_bit = mmc_clk->clken_bit; + mclk->div_reg = base + mmc_clk->div_reg; + mclk->div_off = mmc_clk->div_off; + mclk->div_bits = mmc_clk->div_bits; + mclk->drv_reg = base + mmc_clk->drv_reg; + mclk->drv_off = mmc_clk->drv_off; + mclk->drv_bits = mmc_clk->drv_bits; + mclk->sam_reg = base + mmc_clk->sam_reg; + mclk->sam_off = mmc_clk->sam_off; + mclk->sam_bits = mmc_clk->sam_bits; + + clk = clk_register(NULL, &mclk->hw); + if (WARN_ON(IS_ERR(clk))) + kfree(mclk); + return clk; +} + +static void __init hi3620_mmc_clk_init(struct device_node *node) +{ + void __iomem *base; + int i, num = ARRAY_SIZE(hi3620_mmc_clks); + struct clk_onecell_data *clk_data; + + if (!node) { + pr_err("failed to find pctrl node in DTS\n"); + return; + } + + base = of_iomap(node, 0); + if (!base) { + pr_err("failed to map pctrl\n"); + return; + } + + clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL); + if (WARN_ON(!clk_data)) + return; + + clk_data->clks = kzalloc(sizeof(struct clk *) * num, GFP_KERNEL); + if (!clk_data->clks) { + pr_err("%s: fail to allocate mmc clk\n", __func__); + return; + } + + for (i = 0; i < num; i++) { + struct hisi_mmc_clock *mmc_clk = &hi3620_mmc_clks[i]; + clk_data->clks[mmc_clk->id] = + hisi_register_clk_mmc(mmc_clk, base, node); + } + + clk_data->clk_num = num; + of_clk_add_provider(node, of_clk_src_onecell_get, clk_data); +} + +CLK_OF_DECLARE(hi3620_mmc_clk, "hisilicon,hi3620-mmc-clock", hi3620_mmc_clk_init); diff --git a/drivers/clk/hisilicon/clk-hi3716.c b/drivers/clk/hisilicon/clk-hi3716.c new file mode 100644 index 00000000000000..887ffe90fba5b3 --- /dev/null +++ b/drivers/clk/hisilicon/clk-hi3716.c @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2013 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(_lock); + +static void __iomem *clk_base; + +struct hi3716_clk { + struct clk_gate gate; + void __iomem *reg; + u8 reset_bit; +}; + +#define MAX_NUMS 10 + +static struct hi3716_clk *to_clk_hi3716(struct clk_hw *hw) +{ + return container_of(hw, struct hi3716_clk, gate.hw); +} + +static void __init hi3716_map_io(void) +{ + struct device_node *node; + + if (clk_base) + return; + + node = of_find_compatible_node(NULL, NULL, "hisilicon,clkbase"); + if (node) + clk_base = of_iomap(node, 0); + WARN_ON(!clk_base); +} + +static int hi3716_clkgate_prepare(struct clk_hw *hw) +{ + struct hi3716_clk *clk = to_clk_hi3716(hw); + unsigned long flags = 0; + u32 reg; + + spin_lock_irqsave(&_lock, flags); + + reg = readl_relaxed(clk->reg); + reg &= ~BIT(clk->reset_bit); + writel_relaxed(reg, clk->reg); + + spin_unlock_irqrestore(&_lock, flags); + + return 0; +} + +static void hi3716_clkgate_unprepare(struct clk_hw *hw) +{ + struct hi3716_clk *clk = to_clk_hi3716(hw); + unsigned long flags = 0; + u32 reg; + + spin_lock_irqsave(&_lock, flags); + + reg = readl_relaxed(clk->reg); + reg |= BIT(clk->reset_bit); + writel_relaxed(reg, clk->reg); + + spin_unlock_irqrestore(&_lock, flags); +} + +static struct clk_ops hi3716_clkgate_ops = { + .prepare = hi3716_clkgate_prepare, + .unprepare = hi3716_clkgate_unprepare, +}; + +void __init hi3716_clkgate_setup(struct device_node *node) +{ + struct clk *clk; + struct hi3716_clk *p_clk; + struct clk_init_data init; + const char *parent_name; + u32 array[3]; /* reg, enable_bit, reset_bit */ + int err; + + hi3716_map_io(); + err = of_property_read_u32_array(node, "gate-reg", &array[0], 3); + if (WARN_ON(err)) + return; + + err = of_property_read_string(node, "clock-output-names", &init.name); + if (WARN_ON(err)) + return; + + p_clk = kzalloc(sizeof(*p_clk), GFP_KERNEL); + if (WARN_ON(!p_clk)) + return; + + hi3716_clkgate_ops.enable = clk_gate_ops.enable; + hi3716_clkgate_ops.disable = clk_gate_ops.disable; + hi3716_clkgate_ops.is_enabled = clk_gate_ops.is_enabled; + + init.ops = &hi3716_clkgate_ops; + init.flags = CLK_SET_RATE_PARENT; + parent_name = of_clk_get_parent_name(node, 0); + init.parent_names = &parent_name; + init.num_parents = 1; + + p_clk->reg = p_clk->gate.reg = clk_base + array[0]; + p_clk->gate.bit_idx = array[1]; + p_clk->gate.flags = 0; + p_clk->gate.lock = &_lock; + p_clk->gate.hw.init = &init; + p_clk->reset_bit = array[2]; + + clk = clk_register(NULL, &p_clk->gate.hw); + if (WARN_ON(IS_ERR(clk))) { + kfree(p_clk); + return; + } + + of_clk_add_provider(node, of_clk_src_simple_get, clk); +} + +static void __init hi3716_clkmux_setup(struct device_node *node) +{ + int num = 0, err; + void __iomem *reg; + unsigned int shift, width; + u32 array[3]; /* reg, mux_shift, mux_width */ + u32 *table = NULL; + const char *clk_name = node->name; + const char *parents[MAX_NUMS]; + struct clk *clk; + + hi3716_map_io(); + err = of_property_read_string(node, "clock-output-names", &clk_name); + if (WARN_ON(err)) + return; + + err = of_property_read_u32_array(node, "mux-reg", &array[0], 3); + if (WARN_ON(err)) + return; + + reg = clk_base + array[0]; + shift = array[1]; + width = array[2]; + + while ((num < MAX_NUMS) && + ((parents[num] = of_clk_get_parent_name(node, num)) != NULL)) + num++; + if (!num) + return; + + table = kzalloc(sizeof(u32 *) * num, GFP_KERNEL); + if (WARN_ON(!table)) + return; + + err = of_property_read_u32_array(node, "mux-table", table, num); + if (WARN_ON(err)) + goto err; + + clk = clk_register_mux_table(NULL, clk_name, parents, num, + CLK_SET_RATE_PARENT, reg, shift, BIT(width) - 1, + 0, table, &_lock); + if (WARN_ON(IS_ERR(clk))) + goto err; + + clk_register_clkdev(clk, clk_name, NULL); + of_clk_add_provider(node, of_clk_src_simple_get, clk); + return; + +err: + kfree(table); + return; +} + +static void __init hi3716_fixed_pll_setup(struct device_node *node) +{ + const char *clk_name, *parent_name; + struct clk *clks[MAX_NUMS]; + u32 rate[MAX_NUMS]; + struct clk_onecell_data *clk_data; + int i, err, nums = 0; + + nums = of_property_count_strings(node, "clock-output-names"); + if (WARN_ON((nums < 0) || (nums > MAX_NUMS))) + return; + + err = of_property_read_u32_array(node, "clock-frequency", + &rate[0], nums); + WARN_ON(err); + + parent_name = of_clk_get_parent_name(node, 0); + + for (i = 0; i < nums; i++) { + err = of_property_read_string_index(node, "clock-output-names", + i, &clk_name); + WARN_ON(err); + + clks[i] = clk_register_fixed_rate(NULL, clk_name, + parent_name, 0, rate[i]); + WARN_ON(IS_ERR(clks[i])); + } + + clk_data = kzalloc(sizeof(*clk_data) + nums * sizeof(struct clk *), + GFP_KERNEL); + if (WARN_ON(!clk_data)) + return; + + memcpy(&clk_data[1], clks, nums * sizeof(struct clk *)); + clk_data->clks = (struct clk **)&clk_data[1]; + clk_data->clk_num = nums; + of_clk_add_provider(node, of_clk_src_onecell_get, clk_data); +} + +void __init hi3716_fixed_divider_setup(struct device_node *node) +{ + const char *clk_parent; + const char *clk_name; + u32 div[MAX_NUMS]; + struct clk *clks[MAX_NUMS]; + struct clk_onecell_data *clk_data; + int err, i, nums = 0; + + clk_parent = of_clk_get_parent_name(node, 0); + + nums = of_property_count_strings(node, "clock-output-names"); + if (WARN_ON((nums < 0) || (nums > MAX_NUMS))) + return; + + err = of_property_read_u32_array(node, "div-table", &div[0], nums); + WARN_ON(err); + + for (i = 0; i < nums; i++) { + err = of_property_read_string_index(node, + "clock-output-names", i, &clk_name); + WARN_ON(err); + + clks[i] = clk_register_fixed_factor(NULL, clk_name, + clk_parent, CLK_SET_RATE_PARENT, 1, div[i]); + WARN_ON(IS_ERR(clks[i])); + } + + clk_data = kzalloc(sizeof(*clk_data) + nums * sizeof(struct clk *), + GFP_KERNEL); + if (WARN_ON(!clk_data)) + return; + + memcpy(&clk_data[1], clks, nums * sizeof(struct clk *)); + clk_data->clks = (struct clk **)&clk_data[1]; + clk_data->clk_num = nums; + of_clk_add_provider(node, of_clk_src_onecell_get, clk_data); +} + +CLK_OF_DECLARE(hi3716_fixed_rate, "fixed-clock", of_fixed_clk_setup) +CLK_OF_DECLARE(hi3716_fixed_pll, "hisilicon,hi3716-fixed-pll", hi3716_fixed_pll_setup) +CLK_OF_DECLARE(hi3716_divider, "hisilicon,hi3716-fixed-divider", hi3716_fixed_divider_setup) +CLK_OF_DECLARE(hi3716_mux, "hisilicon,hi3716-clk-mux", hi3716_clkmux_setup) +CLK_OF_DECLARE(hi3716_gate, "hisilicon,hi3716-clk-gate", hi3716_clkgate_setup) diff --git a/drivers/clk/hisilicon/clk-hip04.c b/drivers/clk/hisilicon/clk-hip04.c new file mode 100644 index 00000000000000..132b57a0ce0947 --- /dev/null +++ b/drivers/clk/hisilicon/clk-hip04.c @@ -0,0 +1,58 @@ +/* + * Hisilicon HiP04 clock driver + * + * Copyright (c) 2013-2014 Hisilicon Limited. + * Copyright (c) 2013-2014 Linaro Limited. + * + * Author: Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "clk.h" + +/* fixed rate clocks */ +static struct hisi_fixed_rate_clock hip04_fixed_rate_clks[] __initdata = { + { HIP04_OSC50M, "osc50m", NULL, CLK_IS_ROOT, 50000000, }, + { HIP04_CLK_50M, "clk50m", NULL, CLK_IS_ROOT, 50000000, }, + { HIP04_CLK_168M, "clk168m", NULL, CLK_IS_ROOT, 168750000, }, +}; + +static void __init hip04_clk_init(struct device_node *np) +{ + struct hisi_clock_data *clk_data; + + clk_data = hisi_clk_init(np, HIP04_NR_CLKS); + if (!clk_data) + return; + + hisi_clk_register_fixed_rate(hip04_fixed_rate_clks, + ARRAY_SIZE(hip04_fixed_rate_clks), + clk_data); +} +CLK_OF_DECLARE(hip04_clk, "hisilicon,hip04-clock", hip04_clk_init); diff --git a/drivers/clk/hisilicon/clk.c b/drivers/clk/hisilicon/clk.c index a3a7152c92d970..276f672e7b1aff 100644 --- a/drivers/clk/hisilicon/clk.c +++ b/drivers/clk/hisilicon/clk.c @@ -37,23 +37,49 @@ #include "clk.h" static DEFINE_SPINLOCK(hisi_clk_lock); -static struct clk **clk_table; -static struct clk_onecell_data clk_data; -void __init hisi_clk_init(struct device_node *np, int nr_clks) +struct hisi_clock_data __init *hisi_clk_init(struct device_node *np, + int nr_clks) { + struct hisi_clock_data *clk_data; + struct clk **clk_table; + void __iomem *base; + + if (np) { + base = of_iomap(np, 0); + if (!base) { + pr_err("failed to map Hisilicon clock registers\n"); + goto err; + } + } else { + pr_err("failed to find Hisilicon clock node in DTS\n"); + goto err; + } + + clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL); + if (!clk_data) { + pr_err("%s: could not allocate clock data\n", __func__); + goto err; + } + clk_data->base = base; + clk_table = kzalloc(sizeof(struct clk *) * nr_clks, GFP_KERNEL); if (!clk_table) { pr_err("%s: could not allocate clock lookup table\n", __func__); - return; + goto err_data; } - clk_data.clks = clk_table; - clk_data.clk_num = nr_clks; - of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); + clk_data->clk_data.clks = clk_table; + clk_data->clk_data.clk_num = nr_clks; + of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data->clk_data); + return clk_data; +err_data: + kfree(clk_data); +err: + return NULL; } void __init hisi_clk_register_fixed_rate(struct hisi_fixed_rate_clock *clks, - int nums, void __iomem *base) + int nums, struct hisi_clock_data *data) { struct clk *clk; int i; @@ -68,11 +94,13 @@ void __init hisi_clk_register_fixed_rate(struct hisi_fixed_rate_clock *clks, __func__, clks[i].name); continue; } + data->clk_data.clks[clks[i].id] = clk; } } void __init hisi_clk_register_fixed_factor(struct hisi_fixed_factor_clock *clks, - int nums, void __iomem *base) + int nums, + struct hisi_clock_data *data) { struct clk *clk; int i; @@ -87,13 +115,15 @@ void __init hisi_clk_register_fixed_factor(struct hisi_fixed_factor_clock *clks, __func__, clks[i].name); continue; } + data->clk_data.clks[clks[i].id] = clk; } } void __init hisi_clk_register_mux(struct hisi_mux_clock *clks, - int nums, void __iomem *base) + int nums, struct hisi_clock_data *data) { struct clk *clk; + void __iomem *base = data->base; int i; for (i = 0; i < nums; i++) { @@ -111,14 +141,15 @@ void __init hisi_clk_register_mux(struct hisi_mux_clock *clks, if (clks[i].alias) clk_register_clkdev(clk, clks[i].alias, NULL); - clk_table[clks[i].id] = clk; + data->clk_data.clks[clks[i].id] = clk; } } void __init hisi_clk_register_divider(struct hisi_divider_clock *clks, - int nums, void __iomem *base) + int nums, struct hisi_clock_data *data) { struct clk *clk; + void __iomem *base = data->base; int i; for (i = 0; i < nums; i++) { @@ -139,14 +170,15 @@ void __init hisi_clk_register_divider(struct hisi_divider_clock *clks, if (clks[i].alias) clk_register_clkdev(clk, clks[i].alias, NULL); - clk_table[clks[i].id] = clk; + data->clk_data.clks[clks[i].id] = clk; } } void __init hisi_clk_register_gate_sep(struct hisi_gate_clock *clks, - int nums, void __iomem *base) + int nums, struct hisi_clock_data *data) { struct clk *clk; + void __iomem *base = data->base; int i; for (i = 0; i < nums; i++) { @@ -166,6 +198,6 @@ void __init hisi_clk_register_gate_sep(struct hisi_gate_clock *clks, if (clks[i].alias) clk_register_clkdev(clk, clks[i].alias, NULL); - clk_table[clks[i].id] = clk; + data->clk_data.clks[clks[i].id] = clk; } } diff --git a/drivers/clk/hisilicon/clk.h b/drivers/clk/hisilicon/clk.h index 4a6beebefb7a69..43fa5da88f0209 100644 --- a/drivers/clk/hisilicon/clk.h +++ b/drivers/clk/hisilicon/clk.h @@ -30,6 +30,11 @@ #include #include +struct hisi_clock_data { + struct clk_onecell_data clk_data; + void __iomem *base; +}; + struct hisi_fixed_rate_clock { unsigned int id; char *name; @@ -89,15 +94,15 @@ struct clk *hisi_register_clkgate_sep(struct device *, const char *, void __iomem *, u8, u8, spinlock_t *); -void __init hisi_clk_init(struct device_node *, int); +struct hisi_clock_data __init *hisi_clk_init(struct device_node *, int); void __init hisi_clk_register_fixed_rate(struct hisi_fixed_rate_clock *, - int, void __iomem *); + int, struct hisi_clock_data *); void __init hisi_clk_register_fixed_factor(struct hisi_fixed_factor_clock *, - int, void __iomem *); + int, struct hisi_clock_data *); void __init hisi_clk_register_mux(struct hisi_mux_clock *, int, - void __iomem *); + struct hisi_clock_data *); void __init hisi_clk_register_divider(struct hisi_divider_clock *, - int, void __iomem *); + int, struct hisi_clock_data *); void __init hisi_clk_register_gate_sep(struct hisi_gate_clock *, - int, void __iomem *); + int, struct hisi_clock_data *); #endif /* __HISI_CLK_H */ diff --git a/drivers/clk/samsung/Makefile b/drivers/clk/samsung/Makefile index 8eb4799237f03d..b572dd73f8a3d1 100644 --- a/drivers/clk/samsung/Makefile +++ b/drivers/clk/samsung/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_COMMON_CLK) += clk.o clk-pll.o obj-$(CONFIG_ARCH_EXYNOS4) += clk-exynos4.o obj-$(CONFIG_SOC_EXYNOS5250) += clk-exynos5250.o +obj-$(CONFIG_SOC_EXYNOS5410) += clk-exynos5410.o obj-$(CONFIG_SOC_EXYNOS5420) += clk-exynos5420.o obj-$(CONFIG_SOC_EXYNOS5440) += clk-exynos5440.o obj-$(CONFIG_ARCH_EXYNOS) += clk-exynos-audss.o diff --git a/drivers/clk/samsung/clk-exynos5410.c b/drivers/clk/samsung/clk-exynos5410.c new file mode 100644 index 00000000000000..eaeaf73799810c --- /dev/null +++ b/drivers/clk/samsung/clk-exynos5410.c @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * Author: Tarek Dakhran + * + * Copyright (c) 2014 Hardkernel Co., Ltd. + * Author: Hakjoo Kim + * + * 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. + * + * Common Clock Framework support for Exynos5410 SoC. +*/ + +#include + +#include +#include +#include +#include +#include + +#include "clk.h" + +#define APLL_LOCK 0x0 +#define APLL_CON0 0x100 +#define CPLL_LOCK 0x10020 +#define CPLL_CON0 0x10120 +#define MPLL_LOCK 0x4000 +#define MPLL_CON0 0x4100 +#define BPLL_LOCK 0x20010 +#define BPLL_CON0 0x20110 +#define KPLL_LOCK 0x28000 +#define KPLL_CON0 0x28100 +#define VPLL_LOCK 0x10050 +#define VPLL_CON0 0x10140 +#define DPLL_LOCK 0x10030 +#define DPLL_CON0 0x10128 + +#define SRC_CPU 0x200 +#define DIV_CPU0 0x500 +#define SRC_CPERI1 0x4204 +#define DIV_TOP0 0x10510 +#define DIV_TOP1 0x10514 +#define DIV_TOP2 0x10518 +#define DIV_FSYS1 0x1054c +#define DIV_FSYS2 0x10550 +#define DIV_PERIC0 0x10558 +#define DIV_DISP1_0 0x1052c +#define DIV_FSYS0 0x10548 +#define SRC_TOP0 0x10210 +#define SRC_TOP1 0x10214 +#define SRC_TOP2 0x10218 +#define SRC_TOP3 0x1021c +#define SRC_FSYS 0x10244 +#define SRC_DISP1_0 0x1022c +#define SRC_PERIC0 0x10250 +#define SRC_MASK_DISP1_0 0x1032c +#define SRC_MASK_FSYS 0x10340 +#define SRC_MASK_PERIC0 0x10350 +#define GATE_BUS_FSYS0 0x10740 +#define GATE_TOP_SCLK_GSCL 0x10820 +#define GATE_TOP_SCLK_DISP1 0x10828 +#define GATE_TOP_SCLK_FSYS 0x10840 +#define GATE_IP_FSYS 0x10944 +#define GATE_IP_PERIC 0x10950 +#define GATE_IP_PERIS 0x10960 +#define GATE_IP_DISP1 0x10928 +#define GATE_IP_GSCL0 0x10910 +#define GATE_IP_GSCL1 0x10920 +#define GATE_IP_MFC 0x1092c +#define SRC_CDREX 0x20200 +#define SRC_KFC 0x28200 +#define DIV_KFC0 0x28500 + +/* list of PLLs */ +enum exynos5410_plls { + apll, cpll, mpll, + bpll, kpll, vpll, + dpll, + nr_plls /* number of PLLs */ +}; + +/* + * list of controller registers to be saved and restored during a + * suspend/resume cycle. + */ +static unsigned long exynos5410_clk_regs[] __initdata = { + SRC_CPU, + DIV_CPU0, + GATE_BUS_FSYS0, + SRC_CPERI1, + DIV_PERIC0, + SRC_TOP0, + SRC_TOP1, + SRC_TOP2, + SRC_TOP3, + SRC_FSYS, + DIV_TOP0, + DIV_TOP1, + DIV_TOP2, + DIV_FSYS0, + DIV_FSYS1, + DIV_FSYS2, + DIV_PERIC0, + SRC_MASK_FSYS, + SRC_MASK_PERIC0, + GATE_IP_FSYS, + GATE_IP_PERIS, + GATE_IP_GSCL0, + GATE_IP_GSCL1, + GATE_IP_MFC, + GATE_TOP_SCLK_GSCL, + GATE_TOP_SCLK_DISP1, + GATE_TOP_SCLK_FSYS, + SRC_CDREX, + SRC_KFC, + DIV_KFC0, +}; + +/* list of all parent clocks */ +PNAME(apll_p) = { "fin_pll", "fout_apll", }; +PNAME(bpll_p) = { "fin_pll", "fout_bpll", }; +PNAME(cpll_p) = { "fin_pll", "fout_cpll" }; +PNAME(mpll_p) = { "fin_pll", "fout_mpll", }; +PNAME(kpll_p) = { "fin_pll", "fout_kpll", }; +PNAME(dpll_p) = { "fin_pll", "fout_dpll", }; + +PNAME(mout_cpu_p) = { "mout_apll", "sclk_mpll", }; +PNAME(mout_kfc_p) = { "mout_kpll", "sclk_mpll", }; +PNAME(mout_vpllsrc_p) = { "fin_pll", "sclk_hdmi27m" }; +PNAME(mout_vpll_p) = { "mout_vpllsrc", "fout_vpll" }; +PNAME(mout_hdmi_p) = { "div_hdmi_pixel", "sclk_hdmiphy" }; +PNAME(mout_usbd3_p) = { "sclk_mpll_bpll", "fin_pll" }; + +PNAME(mpll_user_p) = { "fin_pll", "sclk_mpll", }; +PNAME(bpll_user_p) = { "fin_pll", "sclk_bpll", }; +PNAME(mpll_bpll_p) = { "sclk_mpll_muxed", "sclk_bpll_muxed", }; +PNAME(cpll_mpll_p) = { "sclk_cpll", "sclk_mpll_muxed", }; +PNAME(aclk200_disp1_p) = { "fin_pll", "aclk200", }; +PNAME(aclk300_disp0_p) = { "fin_pll", "div_aclk300_disp0", }; +PNAME(aclk300_disp1_p) = { "fin_pll", "div_aclk300_disp1", }; +PNAME(aclk300_gscl_p) = { "fin_pll", "div_aclk300_gscl", }; +PNAME(aclk300_jpeg_p) = { "fin_pll", "div_aclk300_jpeg", }; +PNAME(aclk333_sub_p) = { "fin_pll", "div_aclk333", }; + +PNAME(group2_p) = { "fin_pll", "fin_pll", "sclk_hdmi27m", "sclk_dptxphy", + "sclk_uhostphy", "sclk_hdmiphy", "sclk_mpll_bpll", + "sclk_dpll", "sclk_vpll", "sclk_cpll" }; + +static const struct samsung_pll_rate_table apll_tbl[] = { + /* sorted in descending order */ + /* PLL_35XX_RATE(rate, m, p, s) */ + PLL_35XX_RATE(2100000000, 175, 2, 0), + PLL_35XX_RATE(2000000000, 250, 3, 0), + PLL_35XX_RATE(1900000000, 475, 6, 0), + PLL_35XX_RATE(1800000000, 225, 3, 0), + PLL_35XX_RATE(1700000000, 425, 6, 0), + PLL_35XX_RATE(1600000000, 200, 3, 0), + PLL_35XX_RATE(1500000000, 250, 4, 0), + PLL_35XX_RATE(1400000000, 175, 3, 0), + PLL_35XX_RATE(1300000000, 325, 6, 0), + PLL_35XX_RATE(1200000000, 100, 2, 0), + PLL_35XX_RATE(1100000000, 275, 3, 1), + PLL_35XX_RATE(1000000000, 250, 3, 1), + PLL_35XX_RATE(900000000, 150, 2, 1), + PLL_35XX_RATE(800000000, 200, 3, 1), + PLL_35XX_RATE(700000000, 175, 3, 1), + PLL_35XX_RATE(600000000, 100, 2, 1), + PLL_35XX_RATE(500000000, 250, 3, 2), + PLL_35XX_RATE(400000000, 200, 3, 2), + PLL_35XX_RATE(300000000, 100, 2, 2), + PLL_35XX_RATE(200000000, 200, 3, 3), +}; + +static const struct samsung_pll_rate_table kpll_tbl[] = { + /* sorted in descending order */ + /* PLL_35XX_RATE(rate, m, p, s) */ + PLL_35XX_RATE(1500000000, 250, 4, 0), + PLL_35XX_RATE(1400000000, 175, 3, 0), + PLL_35XX_RATE(1300000000, 325, 6, 0), + PLL_35XX_RATE(1200000000, 100, 2, 0), + PLL_35XX_RATE(1100000000, 275, 3, 1), + PLL_35XX_RATE(1000000000, 250, 3, 1), + PLL_35XX_RATE(900000000, 150, 2, 1), + PLL_35XX_RATE(800000000, 200, 3, 1), + PLL_35XX_RATE(700000000, 175, 3, 1), + PLL_35XX_RATE(600000000, 100, 2, 1), + PLL_35XX_RATE(500000000, 250, 3, 2), + PLL_35XX_RATE(400000000, 200, 3, 2), + PLL_35XX_RATE(300000000, 100, 2, 2), + PLL_35XX_RATE(200000000, 200, 3, 3), +}; + +/* fixed rate clocks generated inside the soc */ +static struct samsung_fixed_rate_clock exynos5410_fixed_rate_clks[] __initdata = { + FRATE(CLK_SCLK_HDMIPHY, "sclk_hdmiphy", NULL, CLK_IS_ROOT, 24000000), + FRATE(0, "sclk_hdmi27m", NULL, CLK_IS_ROOT, 24000000), + FRATE(0, "sclk_dptxphy", NULL, CLK_IS_ROOT, 24000000), + FRATE(0, "sclk_uhostphy", NULL, CLK_IS_ROOT, 48000000), +}; + +static struct samsung_mux_clock exynos5410_pll_pmux_clks[] __initdata = { + MUX(0, "mout_vpllsrc", mout_vpllsrc_p, SRC_TOP2, 0, 1), +}; + +static struct samsung_mux_clock exynos5410_mux_clks[] __initdata = { + MUX_A(0, "mout_apll", apll_p, SRC_CPU, 0, 1, "mout_apll"), + MUX_A(0, "mout_cpu", mout_cpu_p, SRC_CPU, 16, 1, "mout_cpu"), + + MUX_A(0, "mout_kpll", kpll_p, SRC_KFC, 0, 1, "mout_kpll"), + MUX_A(0, "mout_kfc", mout_kfc_p, SRC_KFC, 16, 1, "mout_kfc"), + + MUX_A(0, "sclk_mpll", mpll_p, SRC_CPERI1, 8, 1, "mout_mpll"), + MUX(0, "sclk_mpll_muxed", mpll_user_p, SRC_TOP2, 20, 1), + + MUX(0, "sclk_bpll", bpll_p, SRC_CDREX, 0, 1), + MUX(0, "sclk_bpll_muxed", bpll_user_p, SRC_TOP2, 24, 1), + MUX(0, "sclk_vpll", mout_vpll_p, SRC_TOP2, 16, 1), + + MUX(0, "sclk_cpll", cpll_p, SRC_TOP2, 8, 1), + MUX(0, "sclk_dpll", dpll_p, SRC_TOP2, 10, 1), + + MUX(0, "sclk_mpll_bpll", mpll_bpll_p, SRC_TOP1, 20, 1), + + + MUX(0, "mout_mmc0", group2_p, SRC_FSYS, 0, 4), + MUX(0, "mout_mmc1", group2_p, SRC_FSYS, 4, 4), + MUX(0, "mout_mmc2", group2_p, SRC_FSYS, 8, 4), + + MUX(0, "mout_usbd300", mout_usbd3_p, SRC_FSYS, 28, 1), + MUX(0, "mout_usbd301", mout_usbd3_p, SRC_FSYS, 29, 1), + + MUX(0, "mout_uart0", group2_p, SRC_PERIC0, 0, 4), + MUX(0, "mout_uart1", group2_p, SRC_PERIC0, 4, 4), + MUX(0, "mout_uart2", group2_p, SRC_PERIC0, 8, 4), + + MUX(0, "mout_aclk166", mpll_bpll_p, SRC_TOP0, 8, 1), + MUX(0, "mout_aclk200", mpll_bpll_p, SRC_TOP0, 12, 1), + MUX(0, "mout_aclk333", cpll_mpll_p, SRC_TOP0, 16, 1), + MUX(0, "mout_aclk400", mpll_bpll_p, SRC_TOP0, 20, 1), + MUX(0, "mout_aclk266_gscl", mpll_bpll_p, SRC_TOP1, 28, 1), + + MUX_A(0, "mout_aclk200_disp1", aclk200_disp1_p, + SRC_TOP3, 4, 1, "aclk200_disp1"), + MUX(CLK_MOUT_HDMI, "mout_hdmi", mout_hdmi_p, SRC_DISP1_0, 20, 1), + MUX(0, "mout_fimd1", group2_p, SRC_DISP1_0, 0, 4), + MUX(0, "mout_aclk300_gscl", aclk300_gscl_p, SRC_TOP3, 17, 1), + MUX(0, "mout_aclk300_disp0", aclk300_disp0_p, SRC_TOP3, 18, 1), + MUX(0, "mout_aclk300_disp1", aclk300_disp1_p, SRC_TOP3, 19, 1), + MUX(0, "mout_aclk400_isp", mpll_bpll_p, SRC_TOP3, 20, 1), + MUX(0, "mout_aclk333_sub", aclk333_sub_p, SRC_TOP3, 24, 1), + MUX(0, "mout_aclk300_jpeg", aclk300_jpeg_p, SRC_TOP3, 28, 1), +}; + +static struct samsung_div_clock exynos5410_div_clks[] __initdata = { + DIV(0, "div_arm", "mout_cpu", DIV_CPU0, 0, 3), + DIV(0, "div_arm2", "div_arm", DIV_CPU0, 28, 3), + + DIV(0, "div_acp", "div_arm2", DIV_CPU0, 8, 3), + DIV(0, "div_cpud", "div_arm2", DIV_CPU0, 4, 3), + DIV(0, "div_atb", "div_arm2", DIV_CPU0, 16, 3), + DIV(0, "pclk_dbg", "div_arm2", DIV_CPU0, 20, 3), + + DIV(0, "div_kfc", "mout_kfc", DIV_KFC0, 0, 3), + DIV(0, "div_aclk", "div_kfc", DIV_KFC0, 4, 3), + DIV(0, "div_pclk", "div_kfc", DIV_KFC0, 20, 3), + DIV(0, "sclk_kpll", "mout_kpll", DIV_KFC0, 24, 3), + + DIV(0, "aclk66_pre", "sclk_mpll_muxed", DIV_TOP1, 24, 3), + DIV(0, "aclk66", "aclk66_pre", DIV_TOP0, 0, 3), + + DIV(CLK_SCLK_USBPHY300, "sclk_usbphy300", "mout_usbd300", DIV_FSYS0, 16, 4), + DIV(CLK_SCLK_USBPHY301, "sclk_usbphy301", "mout_usbd301", DIV_FSYS0, 20, 4), + DIV(0, "div_usbd300", "mout_usbd300", DIV_FSYS0, 24, 4), + DIV(0, "div_usbd301", "mout_usbd301", DIV_FSYS0, 28, 4), + + DIV(0, "div_mmc0", "mout_mmc0", DIV_FSYS1, 0, 4), + DIV(0, "div_mmc1", "mout_mmc1", DIV_FSYS1, 16, 4), + DIV(0, "div_mmc2", "mout_mmc2", DIV_FSYS2, 0, 4), + + DIV_F(0, "div_mmc_pre0", "div_mmc0", + DIV_FSYS1, 8, 8, CLK_SET_RATE_PARENT, 0), + DIV_F(0, "div_mmc_pre1", "div_mmc1", + DIV_FSYS1, 24, 8, CLK_SET_RATE_PARENT, 0), + DIV_F(0, "div_mmc_pre2", "div_mmc2", + DIV_FSYS2, 8, 8, CLK_SET_RATE_PARENT, 0), + + DIV(0, "div_uart0", "mout_uart0", DIV_PERIC0, 0, 4), + DIV(0, "div_uart1", "mout_uart1", DIV_PERIC0, 4, 4), + DIV(0, "div_uart2", "mout_uart2", DIV_PERIC0, 8, 4), + DIV(0, "div_uart3", "mout_uart3", DIV_PERIC0, 12, 4), + + DIV(0, "aclk166", "mout_aclk166", DIV_TOP0, 8, 3), + DIV(0, "aclk200", "mout_aclk200", DIV_TOP0, 12, 3), + DIV(0, "aclk266", "sclk_mpll_muxed", DIV_TOP0, 16, 3), + DIV(0, "div_aclk333", "mout_aclk333", DIV_TOP0, 20, 3), + DIV(0, "aclk400", "mout_aclk400", DIV_TOP0, 24, 3), + DIV(0, "div_aclk300_gscl", "sclk_dpll", DIV_TOP2, 8, 3), + DIV(0, "div_aclk300_disp0", "sclk_dpll", DIV_TOP2, 12, 3), + DIV(0, "div_aclk300_disp1", "sclk_dpll", DIV_TOP2, 16, 3), + DIV(0, "div_aclk300_jpeg", "sclk_dpll", DIV_TOP2, 17, 3), + DIV(0, "div_hdmi_pixel", "sclk_vpll", DIV_DISP1_0, 28, 4), + DIV(0, "div_fimd1", "mout_fimd1", DIV_DISP1_0, 0, 4), +}; + +static struct samsung_gate_clock exynos5410_gate_clks[] __initdata = { + GATE(CLK_MCT, "mct", "aclk66", GATE_IP_PERIS, 18, 0, 0), + + GATE(CLK_SCLK_MMC0, "sclk_mmc0", "div_mmc_pre0", + SRC_MASK_FSYS, 0, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_MMC1, "sclk_mmc1", "div_mmc_pre1", + SRC_MASK_FSYS, 4, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_MMC2, "sclk_mmc2", "div_mmc_pre2", + SRC_MASK_FSYS, 8, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_USBD300, "sclk_usbd300", "div_usbd300", + GATE_TOP_SCLK_FSYS, 9, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_USBD301, "sclk_usbd301", "div_usbd301", + GATE_TOP_SCLK_FSYS, 10, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_FIMD1, "sclk_fimd1", "div_fimd1", + GATE_TOP_SCLK_DISP1, 0, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_HDMI, "sclk_hdmi", "mout_hdmi", + GATE_TOP_SCLK_DISP1, 9, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_PIXEL, "sclk_pixel", "div_hdmi_pixel", + GATE_TOP_SCLK_DISP1, 10, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_DP1, "sclk_dp1", "mout_aclk300_disp0", + GATE_TOP_SCLK_DISP1, 20, CLK_SET_RATE_PARENT, 0), + + GATE(CLK_MMC0, "sdmmc0", "aclk200", GATE_BUS_FSYS0, 12, 0, 0), + GATE(CLK_MMC1, "sdmmc1", "aclk200", GATE_BUS_FSYS0, 13, 0, 0), + GATE(CLK_MMC2, "sdmmc2", "aclk200", GATE_BUS_FSYS0, 14, 0, 0), + + GATE(CLK_UART0, "uart0", "aclk66", GATE_IP_PERIC, 0, 0, 0), + GATE(CLK_UART1, "uart1", "aclk66", GATE_IP_PERIC, 1, 0, 0), + GATE(CLK_UART2, "uart2", "aclk66", GATE_IP_PERIC, 2, 0, 0), + + GATE(CLK_I2C0, "i2c0", "aclk66", GATE_IP_PERIC, 6, 0, 0), + GATE(CLK_I2C1, "i2c1", "aclk66", GATE_IP_PERIC, 7, 0, 0), + GATE(CLK_I2C2, "i2c2", "aclk66", GATE_IP_PERIC, 8, 0, 0), + GATE(CLK_I2C3, "i2c3", "aclk66", GATE_IP_PERIC, 9, 0, 0), + GATE(CLK_I2C4, "i2c4", "aclk66", GATE_IP_PERIC, 10, 0, 0), + GATE(CLK_I2C5, "i2c5", "aclk66", GATE_IP_PERIC, 11, 0, 0), + GATE(CLK_I2C6, "i2c6", "aclk66", GATE_IP_PERIC, 12, 0, 0), + GATE(CLK_I2C7, "i2c7", "aclk66", GATE_IP_PERIC, 13, 0, 0), + GATE(CLK_I2C_HDMI, "i2c_hdmi", "aclk66", GATE_IP_PERIC, 14, 0, 0), + + GATE(CLK_SCLK_UART0, "sclk_uart0", "div_uart0", + SRC_MASK_PERIC0, 0, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_UART1, "sclk_uart1", "div_uart1", + SRC_MASK_PERIC0, 4, CLK_SET_RATE_PARENT, 0), + GATE(CLK_SCLK_UART2, "sclk_uart2", "div_uart2", + SRC_MASK_PERIC0, 8, CLK_SET_RATE_PARENT, 0), + GATE(CLK_FIMD1, "fimd1", "mout_aclk300_disp1", GATE_IP_DISP1, 0, 0, 0), + GATE(CLK_MIE1, "mie1", "mout_aclk300_disp1", GATE_IP_DISP1, 1, 0, 0), + GATE(CLK_DSIM1, "dsim1", "mout_aclk300_disp1", GATE_IP_DISP1, 3, 0, 0), + GATE(CLK_DP, "dp", "aclk200", GATE_IP_DISP1, 4, 0, 0), + GATE(CLK_MIXER, "mixer", "mout_aclk200_disp1", GATE_IP_DISP1, 5, 0, 0), + GATE(CLK_HDMI, "hdmi", "aclk66", GATE_IP_DISP1, 6, 0, 0), + GATE(CLK_GSCL0, "gscl0", "mout_aclk300_disp1", GATE_IP_GSCL0, 0, 0, 0), + GATE(CLK_GSCL1, "gscl1", "mout_aclk300_disp1", GATE_IP_GSCL0, 1, 0, 0), + GATE(CLK_GSCL2, "gscl2", "mout_aclk300_disp1", GATE_IP_GSCL0, 1, 0, 0), + GATE(CLK_GSCL3, "gscl3", "mout_aclk300_disp1", GATE_IP_GSCL0, 1, 0, 0), + GATE(CLK_GSCL4, "gscl4", "mout_aclk300_disp1", GATE_IP_GSCL1, 15, 0, 0), + GATE(CLK_MFC, "mfc", "mout_aclk333_sub", GATE_IP_MFC, 0, 0, 0), + GATE(CLK_SMMU_MFCL, "smmu_mfcl", "mout_aclk333_sub", GATE_IP_MFC, 1, 0, 0), + GATE(CLK_SMMU_MFCR, "smmu_mfcr", "mout_aclk333_sub", GATE_IP_MFC, 2, 0, 0), +}; + +static struct samsung_pll_clock exynos5410_plls[nr_plls] __initdata = { + [apll] = PLL(pll_35xx, CLK_FOUT_APLL, "fout_apll", "fin_pll", APLL_LOCK, + APLL_CON0, apll_tbl), + [cpll] = PLL(pll_35xx, CLK_FOUT_CPLL, "fout_cpll", "fin_pll", CPLL_LOCK, + CPLL_CON0, NULL), + [mpll] = PLL(pll_35xx, CLK_FOUT_MPLL, "fout_mpll", "fin_pll", MPLL_LOCK, + MPLL_CON0, NULL), + [bpll] = PLL(pll_35xx, CLK_FOUT_BPLL, "fout_bpll", "fin_pll", BPLL_LOCK, + BPLL_CON0, NULL), + [kpll] = PLL(pll_35xx, CLK_FOUT_KPLL, "fout_kpll", "fin_pll", KPLL_LOCK, + KPLL_CON0, kpll_tbl), + [vpll] = PLL(pll_35xx, CLK_FOUT_VPLL, "fout_vpll", "mout_vpllsrc", + VPLL_LOCK, VPLL_CON0, NULL), + [dpll] = PLL(pll_35xx, CLK_FOUT_DPLL, "fout_dpll", "fin_pll", DPLL_LOCK, + DPLL_CON0, NULL), +}; + +/* register exynos5410 clocks */ +static void __init exynos5410_clk_init(struct device_node *np) +{ + void __iomem *reg_base; + + reg_base = of_iomap(np, 0); + if (!reg_base) + panic("%s: failed to map registers\n", __func__); + + samsung_clk_init(np, reg_base, CLK_NR_CLKS, + exynos5410_clk_regs, ARRAY_SIZE(exynos5410_clk_regs), + NULL, 0); + samsung_clk_register_mux(exynos5410_pll_pmux_clks, + ARRAY_SIZE(exynos5410_pll_pmux_clks)); + samsung_clk_register_pll(exynos5410_plls, ARRAY_SIZE(exynos5410_plls), + reg_base); + + samsung_clk_register_fixed_rate(exynos5410_fixed_rate_clks, + ARRAY_SIZE(exynos5410_fixed_rate_clks)); + samsung_clk_register_mux(exynos5410_mux_clks, + ARRAY_SIZE(exynos5410_mux_clks)); + samsung_clk_register_div(exynos5410_div_clks, + ARRAY_SIZE(exynos5410_div_clks)); + samsung_clk_register_gate(exynos5410_gate_clks, + ARRAY_SIZE(exynos5410_gate_clks)); + + pr_debug("Exynos5410: clock setup completed.\n"); +} +CLK_OF_DECLARE(exynos5410_clk, "samsung,exynos5410-clock", exynos5410_clk_init); diff --git a/drivers/clk/samsung/clk-exynos5420.c b/drivers/clk/samsung/clk-exynos5420.c index ab4f2f7d88ef5e..79254587988f49 100644 --- a/drivers/clk/samsung/clk-exynos5420.c +++ b/drivers/clk/samsung/clk-exynos5420.c @@ -539,9 +539,9 @@ static struct samsung_gate_clock exynos5420_gate_clks[] __initdata = { GATE(CLK_SCLK_MMC2, "sclk_mmc2", "dout_mmc2", GATE_TOP_SCLK_FSYS, 2, CLK_SET_RATE_PARENT, 0), GATE(CLK_SCLK_USBPHY301, "sclk_usbphy301", "dout_usbphy301", - GATE_TOP_SCLK_FSYS, 7, CLK_SET_RATE_PARENT, 0), + GATE_TOP_SCLK_FSYS, 7, CLK_IGNORE_UNUSED, 0), GATE(CLK_SCLK_USBPHY300, "sclk_usbphy300", "dout_usbphy300", - GATE_TOP_SCLK_FSYS, 8, CLK_SET_RATE_PARENT, 0), + GATE_TOP_SCLK_FSYS, 8, CLK_IGNORE_UNUSED, 0), GATE(CLK_SCLK_USBD300, "sclk_usbd300", "dout_usbd300", GATE_TOP_SCLK_FSYS, 9, CLK_SET_RATE_PARENT, 0), GATE(CLK_SCLK_USBD301, "sclk_usbd301", "dout_usbd301", diff --git a/drivers/clk/samsung/clk-pll.c b/drivers/clk/samsung/clk-pll.c index 81e6d2f49aa001..cc64478f8bbc20 100644 --- a/drivers/clk/samsung/clk-pll.c +++ b/drivers/clk/samsung/clk-pll.c @@ -80,7 +80,7 @@ static unsigned long samsung_pll35xx_recalc_rate(struct clk_hw *hw, u32 mdiv, pdiv, sdiv, pll_con; u64 fvco = parent_rate; - pll_con = __raw_readl(pll->con_reg); + pll_con = readl_relaxed(pll->con_reg); mdiv = (pll_con >> PLL35XX_MDIV_SHIFT) & PLL35XX_MDIV_MASK; pdiv = (pll_con >> PLL35XX_PDIV_SHIFT) & PLL35XX_PDIV_MASK; sdiv = (pll_con >> PLL35XX_SDIV_SHIFT) & PLL35XX_SDIV_MASK; @@ -117,19 +117,19 @@ static int samsung_pll35xx_set_rate(struct clk_hw *hw, unsigned long drate, return -EINVAL; } - tmp = __raw_readl(pll->con_reg); + tmp = readl_relaxed(pll->con_reg); if (!(samsung_pll35xx_mp_change(rate, tmp))) { /* If only s change, change just s value only*/ tmp &= ~(PLL35XX_SDIV_MASK << PLL35XX_SDIV_SHIFT); tmp |= rate->sdiv << PLL35XX_SDIV_SHIFT; - __raw_writel(tmp, pll->con_reg); + writel_relaxed(tmp, pll->con_reg); return 0; } /* Set PLL lock time. */ - __raw_writel(rate->pdiv * PLL35XX_LOCK_FACTOR, + writel_relaxed(rate->pdiv * PLL35XX_LOCK_FACTOR, pll->lock_reg); /* Change PLL PMS values */ @@ -139,12 +139,12 @@ static int samsung_pll35xx_set_rate(struct clk_hw *hw, unsigned long drate, tmp |= (rate->mdiv << PLL35XX_MDIV_SHIFT) | (rate->pdiv << PLL35XX_PDIV_SHIFT) | (rate->sdiv << PLL35XX_SDIV_SHIFT); - __raw_writel(tmp, pll->con_reg); + writel_relaxed(tmp, pll->con_reg); /* wait_lock_time */ do { cpu_relax(); - tmp = __raw_readl(pll->con_reg); + tmp = readl_relaxed(pll->con_reg); } while (!(tmp & (PLL35XX_LOCK_STAT_MASK << PLL35XX_LOCK_STAT_SHIFT))); return 0; @@ -184,8 +184,8 @@ static unsigned long samsung_pll36xx_recalc_rate(struct clk_hw *hw, s16 kdiv; u64 fvco = parent_rate; - pll_con0 = __raw_readl(pll->con_reg); - pll_con1 = __raw_readl(pll->con_reg + 4); + pll_con0 = readl_relaxed(pll->con_reg); + pll_con1 = readl_relaxed(pll->con_reg + 4); mdiv = (pll_con0 >> PLL36XX_MDIV_SHIFT) & PLL36XX_MDIV_MASK; pdiv = (pll_con0 >> PLL36XX_PDIV_SHIFT) & PLL36XX_PDIV_MASK; sdiv = (pll_con0 >> PLL36XX_SDIV_SHIFT) & PLL36XX_SDIV_MASK; @@ -225,20 +225,20 @@ static int samsung_pll36xx_set_rate(struct clk_hw *hw, unsigned long drate, return -EINVAL; } - pll_con0 = __raw_readl(pll->con_reg); - pll_con1 = __raw_readl(pll->con_reg + 4); + pll_con0 = readl_relaxed(pll->con_reg); + pll_con1 = readl_relaxed(pll->con_reg + 4); if (!(samsung_pll36xx_mpk_change(rate, pll_con0, pll_con1))) { /* If only s change, change just s value only*/ pll_con0 &= ~(PLL36XX_SDIV_MASK << PLL36XX_SDIV_SHIFT); pll_con0 |= (rate->sdiv << PLL36XX_SDIV_SHIFT); - __raw_writel(pll_con0, pll->con_reg); + writel_relaxed(pll_con0, pll->con_reg); return 0; } /* Set PLL lock time. */ - __raw_writel(rate->pdiv * PLL36XX_LOCK_FACTOR, pll->lock_reg); + writel_relaxed(rate->pdiv * PLL36XX_LOCK_FACTOR, pll->lock_reg); /* Change PLL PMS values */ pll_con0 &= ~((PLL36XX_MDIV_MASK << PLL36XX_MDIV_SHIFT) | @@ -247,16 +247,16 @@ static int samsung_pll36xx_set_rate(struct clk_hw *hw, unsigned long drate, pll_con0 |= (rate->mdiv << PLL36XX_MDIV_SHIFT) | (rate->pdiv << PLL36XX_PDIV_SHIFT) | (rate->sdiv << PLL36XX_SDIV_SHIFT); - __raw_writel(pll_con0, pll->con_reg); + writel_relaxed(pll_con0, pll->con_reg); pll_con1 &= ~(PLL36XX_KDIV_MASK << PLL36XX_KDIV_SHIFT); pll_con1 |= rate->kdiv << PLL36XX_KDIV_SHIFT; - __raw_writel(pll_con1, pll->con_reg + 4); + writel_relaxed(pll_con1, pll->con_reg + 4); /* wait_lock_time */ do { cpu_relax(); - tmp = __raw_readl(pll->con_reg); + tmp = readl_relaxed(pll->con_reg); } while (!(tmp & (1 << PLL36XX_LOCK_STAT_SHIFT))); return 0; @@ -297,7 +297,7 @@ static unsigned long samsung_pll45xx_recalc_rate(struct clk_hw *hw, u32 mdiv, pdiv, sdiv, pll_con; u64 fvco = parent_rate; - pll_con = __raw_readl(pll->con_reg); + pll_con = readl_relaxed(pll->con_reg); mdiv = (pll_con >> PLL45XX_MDIV_SHIFT) & PLL45XX_MDIV_MASK; pdiv = (pll_con >> PLL45XX_PDIV_SHIFT) & PLL45XX_PDIV_MASK; sdiv = (pll_con >> PLL45XX_SDIV_SHIFT) & PLL45XX_SDIV_MASK; @@ -340,14 +340,14 @@ static int samsung_pll45xx_set_rate(struct clk_hw *hw, unsigned long drate, return -EINVAL; } - con0 = __raw_readl(pll->con_reg); - con1 = __raw_readl(pll->con_reg + 0x4); + con0 = readl_relaxed(pll->con_reg); + con1 = readl_relaxed(pll->con_reg + 0x4); if (!(samsung_pll45xx_mp_change(con0, con1, rate))) { /* If only s change, change just s value only*/ con0 &= ~(PLL45XX_SDIV_MASK << PLL45XX_SDIV_SHIFT); con0 |= rate->sdiv << PLL45XX_SDIV_SHIFT; - __raw_writel(con0, pll->con_reg); + writel_relaxed(con0, pll->con_reg); return 0; } @@ -361,29 +361,29 @@ static int samsung_pll45xx_set_rate(struct clk_hw *hw, unsigned long drate, (rate->sdiv << PLL45XX_SDIV_SHIFT); /* Set PLL AFC value. */ - con1 = __raw_readl(pll->con_reg + 0x4); + con1 = readl_relaxed(pll->con_reg + 0x4); con1 &= ~(PLL45XX_AFC_MASK << PLL45XX_AFC_SHIFT); con1 |= (rate->afc << PLL45XX_AFC_SHIFT); /* Set PLL lock time. */ switch (pll->type) { case pll_4502: - __raw_writel(rate->pdiv * PLL4502_LOCK_FACTOR, pll->lock_reg); + writel_relaxed(rate->pdiv * PLL4502_LOCK_FACTOR, pll->lock_reg); break; case pll_4508: - __raw_writel(rate->pdiv * PLL4508_LOCK_FACTOR, pll->lock_reg); + writel_relaxed(rate->pdiv * PLL4508_LOCK_FACTOR, pll->lock_reg); break; default: break; } /* Set new configuration. */ - __raw_writel(con1, pll->con_reg + 0x4); - __raw_writel(con0, pll->con_reg); + writel_relaxed(con1, pll->con_reg + 0x4); + writel_relaxed(con0, pll->con_reg); /* Wait for locking. */ start = ktime_get(); - while (!(__raw_readl(pll->con_reg) & PLL45XX_LOCKED)) { + while (!(readl_relaxed(pll->con_reg) & PLL45XX_LOCKED)) { ktime_t delta = ktime_sub(ktime_get(), start); if (ktime_to_ms(delta) > PLL_TIMEOUT_MS) { @@ -442,8 +442,8 @@ static unsigned long samsung_pll46xx_recalc_rate(struct clk_hw *hw, u32 mdiv, pdiv, sdiv, kdiv, pll_con0, pll_con1, shift; u64 fvco = parent_rate; - pll_con0 = __raw_readl(pll->con_reg); - pll_con1 = __raw_readl(pll->con_reg + 4); + pll_con0 = readl_relaxed(pll->con_reg); + pll_con1 = readl_relaxed(pll->con_reg + 4); mdiv = (pll_con0 >> PLL46XX_MDIV_SHIFT) & PLL46XX_MDIV_MASK; pdiv = (pll_con0 >> PLL46XX_PDIV_SHIFT) & PLL46XX_PDIV_MASK; sdiv = (pll_con0 >> PLL46XX_SDIV_SHIFT) & PLL46XX_SDIV_MASK; @@ -487,14 +487,14 @@ static int samsung_pll46xx_set_rate(struct clk_hw *hw, unsigned long drate, return -EINVAL; } - con0 = __raw_readl(pll->con_reg); - con1 = __raw_readl(pll->con_reg + 0x4); + con0 = readl_relaxed(pll->con_reg); + con1 = readl_relaxed(pll->con_reg + 0x4); if (!(samsung_pll46xx_mpk_change(con0, con1, rate))) { /* If only s change, change just s value only*/ con0 &= ~(PLL46XX_SDIV_MASK << PLL46XX_SDIV_SHIFT); con0 |= rate->sdiv << PLL46XX_SDIV_SHIFT; - __raw_writel(con0, pll->con_reg); + writel_relaxed(con0, pll->con_reg); return 0; } @@ -516,7 +516,7 @@ static int samsung_pll46xx_set_rate(struct clk_hw *hw, unsigned long drate, (rate->vsel << PLL46XX_VSEL_SHIFT); /* Set PLL K, MFR and MRR values. */ - con1 = __raw_readl(pll->con_reg + 0x4); + con1 = readl_relaxed(pll->con_reg + 0x4); con1 &= ~((PLL46XX_KDIV_MASK << PLL46XX_KDIV_SHIFT) | (PLL46XX_MFR_MASK << PLL46XX_MFR_SHIFT) | (PLL46XX_MRR_MASK << PLL46XX_MRR_SHIFT)); @@ -525,13 +525,13 @@ static int samsung_pll46xx_set_rate(struct clk_hw *hw, unsigned long drate, (rate->mrr << PLL46XX_MRR_SHIFT); /* Write configuration to PLL */ - __raw_writel(lock, pll->lock_reg); - __raw_writel(con0, pll->con_reg); - __raw_writel(con1, pll->con_reg + 0x4); + writel_relaxed(lock, pll->lock_reg); + writel_relaxed(con0, pll->con_reg); + writel_relaxed(con1, pll->con_reg + 0x4); /* Wait for locking. */ start = ktime_get(); - while (!(__raw_readl(pll->con_reg) & PLL46XX_LOCKED)) { + while (!(readl_relaxed(pll->con_reg) & PLL46XX_LOCKED)) { ktime_t delta = ktime_sub(ktime_get(), start); if (ktime_to_ms(delta) > PLL_TIMEOUT_MS) { @@ -574,7 +574,7 @@ static unsigned long samsung_pll6552_recalc_rate(struct clk_hw *hw, u32 mdiv, pdiv, sdiv, pll_con; u64 fvco = parent_rate; - pll_con = __raw_readl(pll->con_reg); + pll_con = readl_relaxed(pll->con_reg); mdiv = (pll_con >> PLL6552_MDIV_SHIFT) & PLL6552_MDIV_MASK; pdiv = (pll_con >> PLL6552_PDIV_SHIFT) & PLL6552_PDIV_MASK; sdiv = (pll_con >> PLL6552_SDIV_SHIFT) & PLL6552_SDIV_MASK; @@ -609,8 +609,8 @@ static unsigned long samsung_pll6553_recalc_rate(struct clk_hw *hw, u32 mdiv, pdiv, sdiv, kdiv, pll_con0, pll_con1; u64 fvco = parent_rate; - pll_con0 = __raw_readl(pll->con_reg); - pll_con1 = __raw_readl(pll->con_reg + 0x4); + pll_con0 = readl_relaxed(pll->con_reg); + pll_con1 = readl_relaxed(pll->con_reg + 0x4); mdiv = (pll_con0 >> PLL6553_MDIV_SHIFT) & PLL6553_MDIV_MASK; pdiv = (pll_con0 >> PLL6553_PDIV_SHIFT) & PLL6553_PDIV_MASK; sdiv = (pll_con0 >> PLL6553_SDIV_SHIFT) & PLL6553_SDIV_MASK; @@ -655,7 +655,7 @@ static unsigned long samsung_pll2550x_recalc_rate(struct clk_hw *hw, u32 r, p, m, s, pll_stat; u64 fvco = parent_rate; - pll_stat = __raw_readl(pll->reg_base + pll->offset * 3); + pll_stat = readl_relaxed(pll->reg_base + pll->offset * 3); r = (pll_stat >> PLL2550X_R_SHIFT) & PLL2550X_R_MASK; if (!r) return 0; diff --git a/drivers/clk/samsung/clk.c b/drivers/clk/samsung/clk.c index f503f32e2f80b2..afad1c80f36845 100644 --- a/drivers/clk/samsung/clk.c +++ b/drivers/clk/samsung/clk.c @@ -31,7 +31,7 @@ static int samsung_clk_suspend(void) unsigned long i; for (i = 0; i < nr_reg_dump; i++, rd++) - rd->value = __raw_readl(reg_base + rd->offset); + rd->value = readl_relaxed(reg_base + rd->offset); return 0; } @@ -42,7 +42,7 @@ static void samsung_clk_resume(void) unsigned long i; for (i = 0; i < nr_reg_dump; i++, rd++) - __raw_writel(rd->value, reg_base + rd->offset); + writel_relaxed(rd->value, reg_base + rd->offset); } static struct syscore_ops samsung_clk_syscore_ops = { diff --git a/drivers/clocksource/exynos_mct.c b/drivers/clocksource/exynos_mct.c index 48f76bc05da0d8..128326263d3d2e 100644 --- a/drivers/clocksource/exynos_mct.c +++ b/drivers/clocksource/exynos_mct.c @@ -27,6 +27,8 @@ #include +#include + #define EXYNOS4_MCTREG(x) (x) #define EXYNOS4_MCT_G_CNT_L EXYNOS4_MCTREG(0x100) #define EXYNOS4_MCT_G_CNT_U EXYNOS4_MCTREG(0x104) @@ -95,7 +97,7 @@ static void exynos4_mct_write(unsigned int value, unsigned long offset) u32 mask; u32 i; - __raw_writel(value, reg_base + offset); + writel_relaxed(value, reg_base + offset); if (likely(offset >= EXYNOS4_MCT_L_BASE(0))) { stat_addr = (offset & ~EXYNOS4_MCT_L_MASK) + MCT_L_WSTAT_OFFSET; @@ -145,8 +147,8 @@ static void exynos4_mct_write(unsigned int value, unsigned long offset) /* Wait maximum 1 ms until written values are applied */ for (i = 0; i < loops_per_jiffy / 1000 * HZ; i++) - if (__raw_readl(reg_base + stat_addr) & mask) { - __raw_writel(mask, reg_base + stat_addr); + if (readl_relaxed(reg_base + stat_addr) & mask) { + writel_relaxed(mask, reg_base + stat_addr); return; } @@ -161,7 +163,7 @@ static void exynos4_mct_frc_start(u32 hi, u32 lo) exynos4_mct_write(lo, EXYNOS4_MCT_G_CNT_L); exynos4_mct_write(hi, EXYNOS4_MCT_G_CNT_U); - reg = __raw_readl(reg_base + EXYNOS4_MCT_G_TCON); + reg = readl_relaxed(reg_base + EXYNOS4_MCT_G_TCON); reg |= MCT_G_TCON_START; exynos4_mct_write(reg, EXYNOS4_MCT_G_TCON); } @@ -169,12 +171,12 @@ static void exynos4_mct_frc_start(u32 hi, u32 lo) static cycle_t exynos4_frc_read(struct clocksource *cs) { unsigned int lo, hi; - u32 hi2 = __raw_readl(reg_base + EXYNOS4_MCT_G_CNT_U); + u32 hi2 = readl_relaxed(reg_base + EXYNOS4_MCT_G_CNT_U); do { hi = hi2; - lo = __raw_readl(reg_base + EXYNOS4_MCT_G_CNT_L); - hi2 = __raw_readl(reg_base + EXYNOS4_MCT_G_CNT_U); + lo = readl_relaxed(reg_base + EXYNOS4_MCT_G_CNT_L); + hi2 = readl_relaxed(reg_base + EXYNOS4_MCT_G_CNT_U); } while (hi != hi2); return ((cycle_t)hi << 32) | lo; @@ -198,6 +200,10 @@ static void __init exynos4_clocksource_init(void) { exynos4_mct_frc_start(0, 0); + if (soc_is_exynos5250()) { + mct_frc.rating = 399; + } + if (clocksource_register_hz(&mct_frc, clk_rate)) panic("%s: can't register clocksource\n", mct_frc.name); } @@ -206,7 +212,7 @@ static void exynos4_mct_comp0_stop(void) { unsigned int tcon; - tcon = __raw_readl(reg_base + EXYNOS4_MCT_G_TCON); + tcon = readl_relaxed(reg_base + EXYNOS4_MCT_G_TCON); tcon &= ~(MCT_G_TCON_COMP0_ENABLE | MCT_G_TCON_COMP0_AUTO_INC); exynos4_mct_write(tcon, EXYNOS4_MCT_G_TCON); @@ -219,7 +225,7 @@ static void exynos4_mct_comp0_start(enum clock_event_mode mode, unsigned int tcon; cycle_t comp_cycle; - tcon = __raw_readl(reg_base + EXYNOS4_MCT_G_TCON); + tcon = readl_relaxed(reg_base + EXYNOS4_MCT_G_TCON); if (mode == CLOCK_EVT_MODE_PERIODIC) { tcon |= MCT_G_TCON_COMP0_AUTO_INC; @@ -308,7 +314,7 @@ static void exynos4_mct_tick_stop(struct mct_clock_event_device *mevt) unsigned long mask = MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START; unsigned long offset = mevt->base + MCT_L_TCON_OFFSET; - tmp = __raw_readl(reg_base + offset); + tmp = readl_relaxed(reg_base + offset); if (tmp & mask) { tmp &= ~mask; exynos4_mct_write(tmp, offset); @@ -330,7 +336,7 @@ static void exynos4_mct_tick_start(unsigned long cycles, /* enable MCT tick interrupt */ exynos4_mct_write(0x1, mevt->base + MCT_L_INT_ENB_OFFSET); - tmp = __raw_readl(reg_base + mevt->base + MCT_L_TCON_OFFSET); + tmp = readl_relaxed(reg_base + mevt->base + MCT_L_TCON_OFFSET); tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START | MCT_L_TCON_INTERVAL_MODE; exynos4_mct_write(tmp, mevt->base + MCT_L_TCON_OFFSET); @@ -382,7 +388,7 @@ static int exynos4_mct_tick_clear(struct mct_clock_event_device *mevt) exynos4_mct_tick_stop(mevt); /* Clear the MCT tick interrupt */ - if (__raw_readl(reg_base + mevt->base + MCT_L_INT_CSTAT_OFFSET) & 1) { + if (readl_relaxed(reg_base + mevt->base + MCT_L_INT_CSTAT_OFFSET) & 1) { exynos4_mct_write(0x1, mevt->base + MCT_L_INT_CSTAT_OFFSET); return 1; } else { diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index 5645cfc90c415e..19aaddd247d3b7 100644 --- a/drivers/clocksource/samsung_pwm_timer.c +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -130,9 +130,9 @@ static void samsung_time_stop(unsigned int channel) spin_lock_irqsave(&samsung_pwm_lock, flags); - tcon = __raw_readl(pwm.base + REG_TCON); + tcon = readl_relaxed(pwm.base + REG_TCON); tcon &= ~TCON_START(channel); - __raw_writel(tcon, pwm.base + REG_TCON); + writel_relaxed(tcon, pwm.base + REG_TCON); spin_unlock_irqrestore(&samsung_pwm_lock, flags); } @@ -148,14 +148,14 @@ static void samsung_time_setup(unsigned int channel, unsigned long tcnt) spin_lock_irqsave(&samsung_pwm_lock, flags); - tcon = __raw_readl(pwm.base + REG_TCON); + tcon = readl_relaxed(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); + writel_relaxed(tcnt, pwm.base + REG_TCNTB(channel)); + writel_relaxed(tcnt, pwm.base + REG_TCMPB(channel)); + writel_relaxed(tcon, pwm.base + REG_TCON); spin_unlock_irqrestore(&samsung_pwm_lock, flags); } @@ -170,7 +170,7 @@ static void samsung_time_start(unsigned int channel, bool periodic) spin_lock_irqsave(&samsung_pwm_lock, flags); - tcon = __raw_readl(pwm.base + REG_TCON); + tcon = readl_relaxed(pwm.base + REG_TCON); tcon &= ~TCON_MANUALUPDATE(channel); tcon |= TCON_START(channel); @@ -180,7 +180,7 @@ static void samsung_time_start(unsigned int channel, bool periodic) else tcon &= ~TCON_AUTORELOAD(channel); - __raw_writel(tcon, pwm.base + REG_TCON); + writel_relaxed(tcon, pwm.base + REG_TCON); spin_unlock_irqrestore(&samsung_pwm_lock, flags); } diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index 4b029c0944af5e..cb5b9364e56720 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -102,6 +102,16 @@ config CPU_FREQ_DEFAULT_GOV_CONSERVATIVE Be aware that not all cpufreq drivers support the conservative governor. If unsure have a look at the help section of the driver. Fallback governor will be the performance governor. + +config CPU_FREQ_DEFAULT_GOV_INTERACTIVE + bool "interactive" + select CPU_FREQ_GOV_INTERACTIVE + help + Use the CPUFreq governor 'interactive' as default. This allows + you to get a full dynamic cpu frequency capable system by simply + loading your cpufreq low-level hardware driver, using the + 'interactive' governor for latency-sensitive workloads. + endchoice config CPU_FREQ_GOV_PERFORMANCE @@ -159,6 +169,24 @@ config CPU_FREQ_GOV_ONDEMAND If in doubt, say N. +config CPU_FREQ_GOV_INTERACTIVE + tristate "'interactive' cpufreq policy governor" + default n + help + 'interactive' - This driver adds a dynamic cpufreq policy governor + designed for latency-sensitive workloads. + + This governor attempts to reduce the latency of clock + increases so that the system is more responsive to + interactive workloads. + + To compile this driver as a module, choose M here: the + module will be called cpufreq_interactive. + + For details, take a look at linux/Documentation/cpu-freq. + + If in doubt, say N. + config CPU_FREQ_GOV_CONSERVATIVE tristate "'conservative' cpufreq governor" depends on CPU_FREQ @@ -200,7 +228,7 @@ source "drivers/cpufreq/Kconfig.x86" endmenu menu "ARM CPU frequency scaling drivers" -depends on ARM +depends on ARM || ARM64 source "drivers/cpufreq/Kconfig.arm" endmenu diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 74945652dd7af5..0fd80cbcd730e9 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_POWERSAVE) += cpufreq_powersave.o obj-$(CONFIG_CPU_FREQ_GOV_USERSPACE) += cpufreq_userspace.o obj-$(CONFIG_CPU_FREQ_GOV_ONDEMAND) += cpufreq_ondemand.o obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o +obj-$(CONFIG_CPU_FREQ_GOV_INTERACTIVE) += cpufreq_interactive.o obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o obj-$(CONFIG_GENERIC_CPUFREQ_CPU0) += cpufreq-cpu0.o diff --git a/drivers/cpufreq/cpufreq_interactive.c b/drivers/cpufreq/cpufreq_interactive.c new file mode 100644 index 00000000000000..ff77b300df88b3 --- /dev/null +++ b/drivers/cpufreq/cpufreq_interactive.c @@ -0,0 +1,1343 @@ +/* + * drivers/cpufreq/cpufreq_interactive.c + * + * Copyright (C) 2010 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + * Author: Mike Chan (mike@android.com) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CREATE_TRACE_POINTS +#include + +struct cpufreq_interactive_cpuinfo { + struct timer_list cpu_timer; + struct timer_list cpu_slack_timer; + spinlock_t load_lock; /* protects the next 4 fields */ + u64 time_in_idle; + u64 time_in_idle_timestamp; + u64 cputime_speedadj; + u64 cputime_speedadj_timestamp; + struct cpufreq_policy *policy; + struct cpufreq_frequency_table *freq_table; + unsigned int target_freq; + unsigned int floor_freq; + u64 floor_validate_time; + u64 hispeed_validate_time; + struct rw_semaphore enable_sem; + int governor_enabled; +}; + +static DEFINE_PER_CPU(struct cpufreq_interactive_cpuinfo, cpuinfo); + +/* realtime thread handles frequency scaling */ +static struct task_struct *speedchange_task; +static cpumask_t speedchange_cpumask; +static spinlock_t speedchange_cpumask_lock; +static struct mutex gov_lock; + +/* Target load. Lower values result in higher CPU speeds. */ +#define DEFAULT_TARGET_LOAD 90 +static unsigned int default_target_loads[] = {DEFAULT_TARGET_LOAD}; + +#define DEFAULT_TIMER_RATE (20 * USEC_PER_MSEC) +#define DEFAULT_ABOVE_HISPEED_DELAY DEFAULT_TIMER_RATE +static unsigned int default_above_hispeed_delay[] = { + DEFAULT_ABOVE_HISPEED_DELAY }; + +struct cpufreq_interactive_tunables { + int usage_count; + /* Hi speed to bump to from lo speed when load burst (default max) */ + unsigned int hispeed_freq; + /* Go to hi speed when CPU load at or above this value. */ +#define DEFAULT_GO_HISPEED_LOAD 99 + unsigned long go_hispeed_load; + /* Target load. Lower values result in higher CPU speeds. */ + spinlock_t target_loads_lock; + unsigned int *target_loads; + int ntarget_loads; + /* + * The minimum amount of time to spend at a frequency before we can ramp + * down. + */ +#define DEFAULT_MIN_SAMPLE_TIME (80 * USEC_PER_MSEC) + unsigned long min_sample_time; + /* + * The sample rate of the timer used to increase frequency + */ + unsigned long timer_rate; + /* + * Wait this long before raising speed above hispeed, by default a + * single timer interval. + */ + spinlock_t above_hispeed_delay_lock; + unsigned int *above_hispeed_delay; + int nabove_hispeed_delay; + /* Non-zero means indefinite speed boost active */ + int boost_val; + /* Duration of a boot pulse in usecs */ + int boostpulse_duration_val; + /* End time of boost pulse in ktime converted to usecs */ + u64 boostpulse_endtime; + /* + * Max additional time to wait in idle, beyond timer_rate, at speeds + * above minimum before wakeup to reduce speed, or -1 if unnecessary. + */ +#define DEFAULT_TIMER_SLACK (4 * DEFAULT_TIMER_RATE) + int timer_slack_val; + bool io_is_busy; +}; + +/* For cases where we have single governor instance for system */ +struct cpufreq_interactive_tunables *common_tunables; + +static struct attribute_group *get_sysfs_attr(void); + +static void cpufreq_interactive_timer_resched( + struct cpufreq_interactive_cpuinfo *pcpu) +{ + struct cpufreq_interactive_tunables *tunables = + pcpu->policy->governor_data; + unsigned long expires; + unsigned long flags; + + spin_lock_irqsave(&pcpu->load_lock, flags); + pcpu->time_in_idle = + get_cpu_idle_time(smp_processor_id(), + &pcpu->time_in_idle_timestamp, + tunables->io_is_busy); + pcpu->cputime_speedadj = 0; + pcpu->cputime_speedadj_timestamp = pcpu->time_in_idle_timestamp; + expires = jiffies + usecs_to_jiffies(tunables->timer_rate); + mod_timer_pinned(&pcpu->cpu_timer, expires); + + if (tunables->timer_slack_val >= 0 && + pcpu->target_freq > pcpu->policy->min) { + expires += usecs_to_jiffies(tunables->timer_slack_val); + mod_timer_pinned(&pcpu->cpu_slack_timer, expires); + } + + spin_unlock_irqrestore(&pcpu->load_lock, flags); +} + +/* The caller shall take enable_sem write semaphore to avoid any timer race. + * The cpu_timer and cpu_slack_timer must be deactivated when calling this + * function. + */ +static void cpufreq_interactive_timer_start( + struct cpufreq_interactive_tunables *tunables, int cpu) +{ + struct cpufreq_interactive_cpuinfo *pcpu = &per_cpu(cpuinfo, cpu); + unsigned long expires = jiffies + + usecs_to_jiffies(tunables->timer_rate); + unsigned long flags; + + pcpu->cpu_timer.expires = expires; + add_timer_on(&pcpu->cpu_timer, cpu); + if (tunables->timer_slack_val >= 0 && + pcpu->target_freq > pcpu->policy->min) { + expires += usecs_to_jiffies(tunables->timer_slack_val); + pcpu->cpu_slack_timer.expires = expires; + add_timer_on(&pcpu->cpu_slack_timer, cpu); + } + + spin_lock_irqsave(&pcpu->load_lock, flags); + pcpu->time_in_idle = + get_cpu_idle_time(cpu, &pcpu->time_in_idle_timestamp, + tunables->io_is_busy); + pcpu->cputime_speedadj = 0; + pcpu->cputime_speedadj_timestamp = pcpu->time_in_idle_timestamp; + spin_unlock_irqrestore(&pcpu->load_lock, flags); +} + +static unsigned int freq_to_above_hispeed_delay( + struct cpufreq_interactive_tunables *tunables, + unsigned int freq) +{ + int i; + unsigned int ret; + unsigned long flags; + + spin_lock_irqsave(&tunables->above_hispeed_delay_lock, flags); + + for (i = 0; i < tunables->nabove_hispeed_delay - 1 && + freq >= tunables->above_hispeed_delay[i+1]; i += 2) + ; + + ret = tunables->above_hispeed_delay[i]; + spin_unlock_irqrestore(&tunables->above_hispeed_delay_lock, flags); + return ret; +} + +static unsigned int freq_to_targetload( + struct cpufreq_interactive_tunables *tunables, unsigned int freq) +{ + int i; + unsigned int ret; + unsigned long flags; + + spin_lock_irqsave(&tunables->target_loads_lock, flags); + + for (i = 0; i < tunables->ntarget_loads - 1 && + freq >= tunables->target_loads[i+1]; i += 2) + ; + + ret = tunables->target_loads[i]; + spin_unlock_irqrestore(&tunables->target_loads_lock, flags); + return ret; +} + +/* + * If increasing frequencies never map to a lower target load then + * choose_freq() will find the minimum frequency that does not exceed its + * target load given the current load. + */ +static unsigned int choose_freq(struct cpufreq_interactive_cpuinfo *pcpu, + unsigned int loadadjfreq) +{ + unsigned int freq = pcpu->policy->cur; + unsigned int prevfreq, freqmin, freqmax; + unsigned int tl; + int index; + + freqmin = 0; + freqmax = UINT_MAX; + + do { + prevfreq = freq; + tl = freq_to_targetload(pcpu->policy->governor_data, freq); + + /* + * Find the lowest frequency where the computed load is less + * than or equal to the target load. + */ + + if (cpufreq_frequency_table_target( + pcpu->policy, pcpu->freq_table, loadadjfreq / tl, + CPUFREQ_RELATION_L, &index)) + break; + freq = pcpu->freq_table[index].frequency; + + if (freq > prevfreq) { + /* The previous frequency is too low. */ + freqmin = prevfreq; + + if (freq >= freqmax) { + /* + * Find the highest frequency that is less + * than freqmax. + */ + if (cpufreq_frequency_table_target( + pcpu->policy, pcpu->freq_table, + freqmax - 1, CPUFREQ_RELATION_H, + &index)) + break; + freq = pcpu->freq_table[index].frequency; + + if (freq == freqmin) { + /* + * The first frequency below freqmax + * has already been found to be too + * low. freqmax is the lowest speed + * we found that is fast enough. + */ + freq = freqmax; + break; + } + } + } else if (freq < prevfreq) { + /* The previous frequency is high enough. */ + freqmax = prevfreq; + + if (freq <= freqmin) { + /* + * Find the lowest frequency that is higher + * than freqmin. + */ + if (cpufreq_frequency_table_target( + pcpu->policy, pcpu->freq_table, + freqmin + 1, CPUFREQ_RELATION_L, + &index)) + break; + freq = pcpu->freq_table[index].frequency; + + /* + * If freqmax is the first frequency above + * freqmin then we have already found that + * this speed is fast enough. + */ + if (freq == freqmax) + break; + } + } + + /* If same frequency chosen as previous then done. */ + } while (freq != prevfreq); + + return freq; +} + +static u64 update_load(int cpu) +{ + struct cpufreq_interactive_cpuinfo *pcpu = &per_cpu(cpuinfo, cpu); + struct cpufreq_interactive_tunables *tunables = + pcpu->policy->governor_data; + u64 now; + u64 now_idle; + unsigned int delta_idle; + unsigned int delta_time; + u64 active_time; + + now_idle = get_cpu_idle_time(cpu, &now, tunables->io_is_busy); + delta_idle = (unsigned int)(now_idle - pcpu->time_in_idle); + delta_time = (unsigned int)(now - pcpu->time_in_idle_timestamp); + + if (delta_time <= delta_idle) + active_time = 0; + else + active_time = delta_time - delta_idle; + + pcpu->cputime_speedadj += active_time * pcpu->policy->cur; + + pcpu->time_in_idle = now_idle; + pcpu->time_in_idle_timestamp = now; + return now; +} + +static void cpufreq_interactive_timer(unsigned long data) +{ + u64 now; + unsigned int delta_time; + u64 cputime_speedadj; + int cpu_load; + struct cpufreq_interactive_cpuinfo *pcpu = + &per_cpu(cpuinfo, data); + struct cpufreq_interactive_tunables *tunables = + pcpu->policy->governor_data; + unsigned int new_freq; + unsigned int loadadjfreq; + unsigned int index; + unsigned long flags; + bool boosted; + + if (!down_read_trylock(&pcpu->enable_sem)) + return; + if (!pcpu->governor_enabled) + goto exit; + + spin_lock_irqsave(&pcpu->load_lock, flags); + now = update_load(data); + delta_time = (unsigned int)(now - pcpu->cputime_speedadj_timestamp); + cputime_speedadj = pcpu->cputime_speedadj; + spin_unlock_irqrestore(&pcpu->load_lock, flags); + + if (WARN_ON_ONCE(!delta_time)) + goto rearm; + + do_div(cputime_speedadj, delta_time); + loadadjfreq = (unsigned int)cputime_speedadj * 100; + cpu_load = loadadjfreq / pcpu->target_freq; + boosted = tunables->boost_val || now < tunables->boostpulse_endtime; + + if (cpu_load >= tunables->go_hispeed_load || boosted) { + if (pcpu->target_freq < tunables->hispeed_freq) { + new_freq = tunables->hispeed_freq; + } else { + new_freq = choose_freq(pcpu, loadadjfreq); + + if (new_freq < tunables->hispeed_freq) + new_freq = tunables->hispeed_freq; + } + } else { + new_freq = choose_freq(pcpu, loadadjfreq); + } + + if (pcpu->target_freq >= tunables->hispeed_freq && + new_freq > pcpu->target_freq && + now - pcpu->hispeed_validate_time < + freq_to_above_hispeed_delay(tunables, pcpu->target_freq)) { + trace_cpufreq_interactive_notyet( + data, cpu_load, pcpu->target_freq, + pcpu->policy->cur, new_freq); + goto rearm; + } + + pcpu->hispeed_validate_time = now; + + if (cpufreq_frequency_table_target(pcpu->policy, pcpu->freq_table, + new_freq, CPUFREQ_RELATION_L, + &index)) + goto rearm; + + new_freq = pcpu->freq_table[index].frequency; + + /* + * Do not scale below floor_freq unless we have been at or above the + * floor frequency for the minimum sample time since last validated. + */ + if (new_freq < pcpu->floor_freq) { + if (now - pcpu->floor_validate_time < + tunables->min_sample_time) { + trace_cpufreq_interactive_notyet( + data, cpu_load, pcpu->target_freq, + pcpu->policy->cur, new_freq); + goto rearm; + } + } + + /* + * Update the timestamp for checking whether speed has been held at + * or above the selected frequency for a minimum of min_sample_time, + * if not boosted to hispeed_freq. If boosted to hispeed_freq then we + * allow the speed to drop as soon as the boostpulse duration expires + * (or the indefinite boost is turned off). + */ + + if (!boosted || new_freq > tunables->hispeed_freq) { + pcpu->floor_freq = new_freq; + pcpu->floor_validate_time = now; + } + + if (pcpu->target_freq == new_freq) { + trace_cpufreq_interactive_already( + data, cpu_load, pcpu->target_freq, + pcpu->policy->cur, new_freq); + goto rearm_if_notmax; + } + + trace_cpufreq_interactive_target(data, cpu_load, pcpu->target_freq, + pcpu->policy->cur, new_freq); + + pcpu->target_freq = new_freq; + spin_lock_irqsave(&speedchange_cpumask_lock, flags); + cpumask_set_cpu(data, &speedchange_cpumask); + spin_unlock_irqrestore(&speedchange_cpumask_lock, flags); + wake_up_process(speedchange_task); + +rearm_if_notmax: + /* + * Already set max speed and don't see a need to change that, + * wait until next idle to re-evaluate, don't need timer. + */ + if (pcpu->target_freq == pcpu->policy->max) + goto exit; + +rearm: + if (!timer_pending(&pcpu->cpu_timer)) + cpufreq_interactive_timer_resched(pcpu); + +exit: + up_read(&pcpu->enable_sem); + return; +} + +static void cpufreq_interactive_idle_start(void) +{ + struct cpufreq_interactive_cpuinfo *pcpu = + &per_cpu(cpuinfo, smp_processor_id()); + int pending; + + if (!down_read_trylock(&pcpu->enable_sem)) + return; + if (!pcpu->governor_enabled) { + up_read(&pcpu->enable_sem); + return; + } + + pending = timer_pending(&pcpu->cpu_timer); + + if (pcpu->target_freq != pcpu->policy->min) { + /* + * Entering idle while not at lowest speed. On some + * platforms this can hold the other CPU(s) at that speed + * even though the CPU is idle. Set a timer to re-evaluate + * speed so this idle CPU doesn't hold the other CPUs above + * min indefinitely. This should probably be a quirk of + * the CPUFreq driver. + */ + if (!pending) + cpufreq_interactive_timer_resched(pcpu); + } + + up_read(&pcpu->enable_sem); +} + +static void cpufreq_interactive_idle_end(void) +{ + struct cpufreq_interactive_cpuinfo *pcpu = + &per_cpu(cpuinfo, smp_processor_id()); + + if (!down_read_trylock(&pcpu->enable_sem)) + return; + if (!pcpu->governor_enabled) { + up_read(&pcpu->enable_sem); + return; + } + + /* Arm the timer for 1-2 ticks later if not already. */ + if (!timer_pending(&pcpu->cpu_timer)) { + cpufreq_interactive_timer_resched(pcpu); + } else if (time_after_eq(jiffies, pcpu->cpu_timer.expires)) { + del_timer(&pcpu->cpu_timer); + del_timer(&pcpu->cpu_slack_timer); + cpufreq_interactive_timer(smp_processor_id()); + } + + up_read(&pcpu->enable_sem); +} + +static int cpufreq_interactive_speedchange_task(void *data) +{ + unsigned int cpu; + cpumask_t tmp_mask; + unsigned long flags; + struct cpufreq_interactive_cpuinfo *pcpu; + + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&speedchange_cpumask_lock, flags); + + if (cpumask_empty(&speedchange_cpumask)) { + spin_unlock_irqrestore(&speedchange_cpumask_lock, + flags); + schedule(); + + if (kthread_should_stop()) + break; + + spin_lock_irqsave(&speedchange_cpumask_lock, flags); + } + + set_current_state(TASK_RUNNING); + tmp_mask = speedchange_cpumask; + cpumask_clear(&speedchange_cpumask); + spin_unlock_irqrestore(&speedchange_cpumask_lock, flags); + + for_each_cpu(cpu, &tmp_mask) { + unsigned int j; + unsigned int max_freq = 0; + + pcpu = &per_cpu(cpuinfo, cpu); + if (!down_read_trylock(&pcpu->enable_sem)) + continue; + if (!pcpu->governor_enabled) { + up_read(&pcpu->enable_sem); + continue; + } + + for_each_cpu(j, pcpu->policy->cpus) { + struct cpufreq_interactive_cpuinfo *pjcpu = + &per_cpu(cpuinfo, j); + + if (pjcpu->target_freq > max_freq) + max_freq = pjcpu->target_freq; + } + + if (max_freq != pcpu->policy->cur) + __cpufreq_driver_target(pcpu->policy, + max_freq, + CPUFREQ_RELATION_H); + trace_cpufreq_interactive_setspeed(cpu, + pcpu->target_freq, + pcpu->policy->cur); + + up_read(&pcpu->enable_sem); + } + } + + return 0; +} + +static void cpufreq_interactive_boost(void) +{ + int i; + int anyboost = 0; + unsigned long flags; + struct cpufreq_interactive_cpuinfo *pcpu; + struct cpufreq_interactive_tunables *tunables; + + spin_lock_irqsave(&speedchange_cpumask_lock, flags); + + for_each_online_cpu(i) { + pcpu = &per_cpu(cpuinfo, i); + tunables = pcpu->policy->governor_data; + + if (pcpu->target_freq < tunables->hispeed_freq) { + pcpu->target_freq = tunables->hispeed_freq; + cpumask_set_cpu(i, &speedchange_cpumask); + pcpu->hispeed_validate_time = + ktime_to_us(ktime_get()); + anyboost = 1; + } + + /* + * Set floor freq and (re)start timer for when last + * validated. + */ + + pcpu->floor_freq = tunables->hispeed_freq; + pcpu->floor_validate_time = ktime_to_us(ktime_get()); + } + + spin_unlock_irqrestore(&speedchange_cpumask_lock, flags); + + if (anyboost) + wake_up_process(speedchange_task); +} + +static int cpufreq_interactive_notifier( + struct notifier_block *nb, unsigned long val, void *data) +{ + struct cpufreq_freqs *freq = data; + struct cpufreq_interactive_cpuinfo *pcpu; + int cpu; + unsigned long flags; + + if (val == CPUFREQ_POSTCHANGE) { + pcpu = &per_cpu(cpuinfo, freq->cpu); + if (!down_read_trylock(&pcpu->enable_sem)) + return 0; + if (!pcpu->governor_enabled) { + up_read(&pcpu->enable_sem); + return 0; + } + + for_each_cpu(cpu, pcpu->policy->cpus) { + struct cpufreq_interactive_cpuinfo *pjcpu = + &per_cpu(cpuinfo, cpu); + if (cpu != freq->cpu) { + if (!down_read_trylock(&pjcpu->enable_sem)) + continue; + if (!pjcpu->governor_enabled) { + up_read(&pjcpu->enable_sem); + continue; + } + } + spin_lock_irqsave(&pjcpu->load_lock, flags); + update_load(cpu); + spin_unlock_irqrestore(&pjcpu->load_lock, flags); + if (cpu != freq->cpu) + up_read(&pjcpu->enable_sem); + } + + up_read(&pcpu->enable_sem); + } + return 0; +} + +static struct notifier_block cpufreq_notifier_block = { + .notifier_call = cpufreq_interactive_notifier, +}; + +static unsigned int *get_tokenized_data(const char *buf, int *num_tokens) +{ + const char *cp; + int i; + int ntokens = 1; + unsigned int *tokenized_data; + int err = -EINVAL; + + cp = buf; + while ((cp = strpbrk(cp + 1, " :"))) + ntokens++; + + if (!(ntokens & 0x1)) + goto err; + + tokenized_data = kmalloc(ntokens * sizeof(unsigned int), GFP_KERNEL); + if (!tokenized_data) { + err = -ENOMEM; + goto err; + } + + cp = buf; + i = 0; + while (i < ntokens) { + if (sscanf(cp, "%u", &tokenized_data[i++]) != 1) + goto err_kfree; + + cp = strpbrk(cp, " :"); + if (!cp) + break; + cp++; + } + + if (i != ntokens) + goto err_kfree; + + *num_tokens = ntokens; + return tokenized_data; + +err_kfree: + kfree(tokenized_data); +err: + return ERR_PTR(err); +} + +static ssize_t show_target_loads( + struct cpufreq_interactive_tunables *tunables, + char *buf) +{ + int i; + ssize_t ret = 0; + unsigned long flags; + + spin_lock_irqsave(&tunables->target_loads_lock, flags); + + for (i = 0; i < tunables->ntarget_loads; i++) + ret += sprintf(buf + ret, "%u%s", tunables->target_loads[i], + i & 0x1 ? ":" : " "); + + sprintf(buf + ret - 1, "\n"); + spin_unlock_irqrestore(&tunables->target_loads_lock, flags); + return ret; +} + +static ssize_t store_target_loads( + struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) +{ + int ntokens; + unsigned int *new_target_loads = NULL; + unsigned long flags; + + new_target_loads = get_tokenized_data(buf, &ntokens); + if (IS_ERR(new_target_loads)) + return PTR_RET(new_target_loads); + + spin_lock_irqsave(&tunables->target_loads_lock, flags); + if (tunables->target_loads != default_target_loads) + kfree(tunables->target_loads); + tunables->target_loads = new_target_loads; + tunables->ntarget_loads = ntokens; + spin_unlock_irqrestore(&tunables->target_loads_lock, flags); + return count; +} + +static ssize_t show_above_hispeed_delay( + struct cpufreq_interactive_tunables *tunables, char *buf) +{ + int i; + ssize_t ret = 0; + unsigned long flags; + + spin_lock_irqsave(&tunables->above_hispeed_delay_lock, flags); + + for (i = 0; i < tunables->nabove_hispeed_delay; i++) + ret += sprintf(buf + ret, "%u%s", + tunables->above_hispeed_delay[i], + i & 0x1 ? ":" : " "); + + sprintf(buf + ret - 1, "\n"); + spin_unlock_irqrestore(&tunables->above_hispeed_delay_lock, flags); + return ret; +} + +static ssize_t store_above_hispeed_delay( + struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) +{ + int ntokens; + unsigned int *new_above_hispeed_delay = NULL; + unsigned long flags; + + new_above_hispeed_delay = get_tokenized_data(buf, &ntokens); + if (IS_ERR(new_above_hispeed_delay)) + return PTR_RET(new_above_hispeed_delay); + + spin_lock_irqsave(&tunables->above_hispeed_delay_lock, flags); + if (tunables->above_hispeed_delay != default_above_hispeed_delay) + kfree(tunables->above_hispeed_delay); + tunables->above_hispeed_delay = new_above_hispeed_delay; + tunables->nabove_hispeed_delay = ntokens; + spin_unlock_irqrestore(&tunables->above_hispeed_delay_lock, flags); + return count; + +} + +static ssize_t show_hispeed_freq(struct cpufreq_interactive_tunables *tunables, + char *buf) +{ + return sprintf(buf, "%u\n", tunables->hispeed_freq); +} + +static ssize_t store_hispeed_freq(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) +{ + int ret; + long unsigned int val; + + ret = strict_strtoul(buf, 0, &val); + if (ret < 0) + return ret; + tunables->hispeed_freq = val; + return count; +} + +static ssize_t show_go_hispeed_load(struct cpufreq_interactive_tunables + *tunables, char *buf) +{ + return sprintf(buf, "%lu\n", tunables->go_hispeed_load); +} + +static ssize_t store_go_hispeed_load(struct cpufreq_interactive_tunables + *tunables, const char *buf, size_t count) +{ + int ret; + unsigned long val; + + ret = strict_strtoul(buf, 0, &val); + if (ret < 0) + return ret; + tunables->go_hispeed_load = val; + return count; +} + +static ssize_t show_min_sample_time(struct cpufreq_interactive_tunables + *tunables, char *buf) +{ + return sprintf(buf, "%lu\n", tunables->min_sample_time); +} + +static ssize_t store_min_sample_time(struct cpufreq_interactive_tunables + *tunables, const char *buf, size_t count) +{ + int ret; + unsigned long val; + + ret = strict_strtoul(buf, 0, &val); + if (ret < 0) + return ret; + tunables->min_sample_time = val; + return count; +} + +static ssize_t show_timer_rate(struct cpufreq_interactive_tunables *tunables, + char *buf) +{ + return sprintf(buf, "%lu\n", tunables->timer_rate); +} + +static ssize_t store_timer_rate(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) +{ + int ret; + unsigned long val; + + ret = strict_strtoul(buf, 0, &val); + if (ret < 0) + return ret; + tunables->timer_rate = val; + return count; +} + +static ssize_t show_timer_slack(struct cpufreq_interactive_tunables *tunables, + char *buf) +{ + return sprintf(buf, "%d\n", tunables->timer_slack_val); +} + +static ssize_t store_timer_slack(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) +{ + int ret; + unsigned long val; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + tunables->timer_slack_val = val; + return count; +} + +static ssize_t show_boost(struct cpufreq_interactive_tunables *tunables, + char *buf) +{ + return sprintf(buf, "%d\n", tunables->boost_val); +} + +static ssize_t store_boost(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) +{ + int ret; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + + tunables->boost_val = val; + + if (tunables->boost_val) { + trace_cpufreq_interactive_boost("on"); + cpufreq_interactive_boost(); + } else { + trace_cpufreq_interactive_unboost("off"); + } + + return count; +} + +static ssize_t store_boostpulse(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) +{ + int ret; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + + tunables->boostpulse_endtime = ktime_to_us(ktime_get()) + + tunables->boostpulse_duration_val; + trace_cpufreq_interactive_boost("pulse"); + cpufreq_interactive_boost(); + return count; +} + +static ssize_t show_boostpulse_duration(struct cpufreq_interactive_tunables + *tunables, char *buf) +{ + return sprintf(buf, "%d\n", tunables->boostpulse_duration_val); +} + +static ssize_t store_boostpulse_duration(struct cpufreq_interactive_tunables + *tunables, const char *buf, size_t count) +{ + int ret; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + + tunables->boostpulse_duration_val = val; + return count; +} + +static ssize_t show_io_is_busy(struct cpufreq_interactive_tunables *tunables, + char *buf) +{ + return sprintf(buf, "%u\n", tunables->io_is_busy); +} + +static ssize_t store_io_is_busy(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) +{ + int ret; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + tunables->io_is_busy = val; + return count; +} + +/* + * Create show/store routines + * - sys: One governor instance for complete SYSTEM + * - pol: One governor instance per struct cpufreq_policy + */ +#define show_gov_pol_sys(file_name) \ +static ssize_t show_##file_name##_gov_sys \ +(struct kobject *kobj, struct attribute *attr, char *buf) \ +{ \ + return show_##file_name(common_tunables, buf); \ +} \ + \ +static ssize_t show_##file_name##_gov_pol \ +(struct cpufreq_policy *policy, char *buf) \ +{ \ + return show_##file_name(policy->governor_data, buf); \ +} + +#define store_gov_pol_sys(file_name) \ +static ssize_t store_##file_name##_gov_sys \ +(struct kobject *kobj, struct attribute *attr, const char *buf, \ + size_t count) \ +{ \ + return store_##file_name(common_tunables, buf, count); \ +} \ + \ +static ssize_t store_##file_name##_gov_pol \ +(struct cpufreq_policy *policy, const char *buf, size_t count) \ +{ \ + return store_##file_name(policy->governor_data, buf, count); \ +} + +#define show_store_gov_pol_sys(file_name) \ +show_gov_pol_sys(file_name); \ +store_gov_pol_sys(file_name) + +show_store_gov_pol_sys(target_loads); +show_store_gov_pol_sys(above_hispeed_delay); +show_store_gov_pol_sys(hispeed_freq); +show_store_gov_pol_sys(go_hispeed_load); +show_store_gov_pol_sys(min_sample_time); +show_store_gov_pol_sys(timer_rate); +show_store_gov_pol_sys(timer_slack); +show_store_gov_pol_sys(boost); +store_gov_pol_sys(boostpulse); +show_store_gov_pol_sys(boostpulse_duration); +show_store_gov_pol_sys(io_is_busy); + +#define gov_sys_attr_rw(_name) \ +static struct global_attr _name##_gov_sys = \ +__ATTR(_name, 0644, show_##_name##_gov_sys, store_##_name##_gov_sys) + +#define gov_pol_attr_rw(_name) \ +static struct freq_attr _name##_gov_pol = \ +__ATTR(_name, 0644, show_##_name##_gov_pol, store_##_name##_gov_pol) + +#define gov_sys_pol_attr_rw(_name) \ + gov_sys_attr_rw(_name); \ + gov_pol_attr_rw(_name) + +gov_sys_pol_attr_rw(target_loads); +gov_sys_pol_attr_rw(above_hispeed_delay); +gov_sys_pol_attr_rw(hispeed_freq); +gov_sys_pol_attr_rw(go_hispeed_load); +gov_sys_pol_attr_rw(min_sample_time); +gov_sys_pol_attr_rw(timer_rate); +gov_sys_pol_attr_rw(timer_slack); +gov_sys_pol_attr_rw(boost); +gov_sys_pol_attr_rw(boostpulse_duration); +gov_sys_pol_attr_rw(io_is_busy); + +static struct global_attr boostpulse_gov_sys = + __ATTR(boostpulse, 0200, NULL, store_boostpulse_gov_sys); + +static struct freq_attr boostpulse_gov_pol = + __ATTR(boostpulse, 0200, NULL, store_boostpulse_gov_pol); + +/* One Governor instance for entire system */ +static struct attribute *interactive_attributes_gov_sys[] = { + &target_loads_gov_sys.attr, + &above_hispeed_delay_gov_sys.attr, + &hispeed_freq_gov_sys.attr, + &go_hispeed_load_gov_sys.attr, + &min_sample_time_gov_sys.attr, + &timer_rate_gov_sys.attr, + &timer_slack_gov_sys.attr, + &boost_gov_sys.attr, + &boostpulse_gov_sys.attr, + &boostpulse_duration_gov_sys.attr, + &io_is_busy_gov_sys.attr, + NULL, +}; + +static struct attribute_group interactive_attr_group_gov_sys = { + .attrs = interactive_attributes_gov_sys, + .name = "interactive", +}; + +/* Per policy governor instance */ +static struct attribute *interactive_attributes_gov_pol[] = { + &target_loads_gov_pol.attr, + &above_hispeed_delay_gov_pol.attr, + &hispeed_freq_gov_pol.attr, + &go_hispeed_load_gov_pol.attr, + &min_sample_time_gov_pol.attr, + &timer_rate_gov_pol.attr, + &timer_slack_gov_pol.attr, + &boost_gov_pol.attr, + &boostpulse_gov_pol.attr, + &boostpulse_duration_gov_pol.attr, + &io_is_busy_gov_pol.attr, + NULL, +}; + +static struct attribute_group interactive_attr_group_gov_pol = { + .attrs = interactive_attributes_gov_pol, + .name = "interactive", +}; + +static struct attribute_group *get_sysfs_attr(void) +{ + if (have_governor_per_policy()) + return &interactive_attr_group_gov_pol; + else + return &interactive_attr_group_gov_sys; +} + +static int cpufreq_interactive_idle_notifier(struct notifier_block *nb, + unsigned long val, + void *data) +{ + switch (val) { + case IDLE_START: + cpufreq_interactive_idle_start(); + break; + case IDLE_END: + cpufreq_interactive_idle_end(); + break; + } + + return 0; +} + +static struct notifier_block cpufreq_interactive_idle_nb = { + .notifier_call = cpufreq_interactive_idle_notifier, +}; + +static int cpufreq_governor_interactive(struct cpufreq_policy *policy, + unsigned int event) +{ + int rc; + unsigned int j; + struct cpufreq_interactive_cpuinfo *pcpu; + struct cpufreq_frequency_table *freq_table; + struct cpufreq_interactive_tunables *tunables; + + if (have_governor_per_policy()) + tunables = policy->governor_data; + else + tunables = common_tunables; + + WARN_ON(!tunables && (event != CPUFREQ_GOV_POLICY_INIT)); + + switch (event) { + case CPUFREQ_GOV_POLICY_INIT: + if (have_governor_per_policy()) { + WARN_ON(tunables); + } else if (tunables) { + tunables->usage_count++; + policy->governor_data = tunables; + return 0; + } + + tunables = kzalloc(sizeof(*tunables), GFP_KERNEL); + if (!tunables) { + pr_err("%s: POLICY_INIT: kzalloc failed\n", __func__); + return -ENOMEM; + } + + tunables->usage_count = 1; + tunables->above_hispeed_delay = default_above_hispeed_delay; + tunables->nabove_hispeed_delay = + ARRAY_SIZE(default_above_hispeed_delay); + tunables->go_hispeed_load = DEFAULT_GO_HISPEED_LOAD; + tunables->target_loads = default_target_loads; + tunables->ntarget_loads = ARRAY_SIZE(default_target_loads); + tunables->min_sample_time = DEFAULT_MIN_SAMPLE_TIME; + tunables->timer_rate = DEFAULT_TIMER_RATE; + tunables->boostpulse_duration_val = DEFAULT_MIN_SAMPLE_TIME; + tunables->timer_slack_val = DEFAULT_TIMER_SLACK; + + spin_lock_init(&tunables->target_loads_lock); + spin_lock_init(&tunables->above_hispeed_delay_lock); + + policy->governor_data = tunables; + if (!have_governor_per_policy()) + common_tunables = tunables; + + rc = sysfs_create_group(get_governor_parent_kobj(policy), + get_sysfs_attr()); + if (rc) { + kfree(tunables); + policy->governor_data = NULL; + if (!have_governor_per_policy()) + common_tunables = NULL; + return rc; + } + + if (!policy->governor->initialized) { + idle_notifier_register(&cpufreq_interactive_idle_nb); + cpufreq_register_notifier(&cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + } + + break; + + case CPUFREQ_GOV_POLICY_EXIT: + if (!--tunables->usage_count) { + if (policy->governor->initialized == 1) { + cpufreq_unregister_notifier(&cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + idle_notifier_unregister(&cpufreq_interactive_idle_nb); + } + + sysfs_remove_group(get_governor_parent_kobj(policy), + get_sysfs_attr()); + kfree(tunables); + common_tunables = NULL; + } + + policy->governor_data = NULL; + break; + + case CPUFREQ_GOV_START: + mutex_lock(&gov_lock); + + freq_table = cpufreq_frequency_get_table(policy->cpu); + if (!tunables->hispeed_freq) + tunables->hispeed_freq = policy->max; + + for_each_cpu(j, policy->cpus) { + pcpu = &per_cpu(cpuinfo, j); + pcpu->policy = policy; + pcpu->target_freq = policy->cur; + pcpu->freq_table = freq_table; + pcpu->floor_freq = pcpu->target_freq; + pcpu->floor_validate_time = + ktime_to_us(ktime_get()); + pcpu->hispeed_validate_time = + pcpu->floor_validate_time; + down_write(&pcpu->enable_sem); + del_timer_sync(&pcpu->cpu_timer); + del_timer_sync(&pcpu->cpu_slack_timer); + cpufreq_interactive_timer_start(tunables, j); + pcpu->governor_enabled = 1; + up_write(&pcpu->enable_sem); + } + + mutex_unlock(&gov_lock); + break; + + case CPUFREQ_GOV_STOP: + mutex_lock(&gov_lock); + for_each_cpu(j, policy->cpus) { + pcpu = &per_cpu(cpuinfo, j); + down_write(&pcpu->enable_sem); + pcpu->governor_enabled = 0; + del_timer_sync(&pcpu->cpu_timer); + del_timer_sync(&pcpu->cpu_slack_timer); + up_write(&pcpu->enable_sem); + } + + mutex_unlock(&gov_lock); + break; + + case CPUFREQ_GOV_LIMITS: + if (policy->max < policy->cur) + __cpufreq_driver_target(policy, + policy->max, CPUFREQ_RELATION_H); + else if (policy->min > policy->cur) + __cpufreq_driver_target(policy, + policy->min, CPUFREQ_RELATION_L); + for_each_cpu(j, policy->cpus) { + pcpu = &per_cpu(cpuinfo, j); + + /* hold write semaphore to avoid race */ + down_write(&pcpu->enable_sem); + if (pcpu->governor_enabled == 0) { + up_write(&pcpu->enable_sem); + continue; + } + + /* update target_freq firstly */ + if (policy->max < pcpu->target_freq) + pcpu->target_freq = policy->max; + else if (policy->min > pcpu->target_freq) + pcpu->target_freq = policy->min; + + /* Reschedule timer. + * Delete the timers, else the timer callback may + * return without re-arm the timer when failed + * acquire the semaphore. This race may cause timer + * stopped unexpectedly. + */ + del_timer_sync(&pcpu->cpu_timer); + del_timer_sync(&pcpu->cpu_slack_timer); + cpufreq_interactive_timer_start(tunables, j); + up_write(&pcpu->enable_sem); + } + break; + } + return 0; +} + +#ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE +static +#endif +struct cpufreq_governor cpufreq_gov_interactive = { + .name = "interactive", + .governor = cpufreq_governor_interactive, + .max_transition_latency = 10000000, + .owner = THIS_MODULE, +}; + +static void cpufreq_interactive_nop_timer(unsigned long data) +{ +} + +static int __init cpufreq_interactive_init(void) +{ + unsigned int i; + struct cpufreq_interactive_cpuinfo *pcpu; + struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 }; + + /* Initalize per-cpu timers */ + for_each_possible_cpu(i) { + pcpu = &per_cpu(cpuinfo, i); + init_timer_deferrable(&pcpu->cpu_timer); + pcpu->cpu_timer.function = cpufreq_interactive_timer; + pcpu->cpu_timer.data = i; + init_timer(&pcpu->cpu_slack_timer); + pcpu->cpu_slack_timer.function = cpufreq_interactive_nop_timer; + spin_lock_init(&pcpu->load_lock); + init_rwsem(&pcpu->enable_sem); + } + + spin_lock_init(&speedchange_cpumask_lock); + mutex_init(&gov_lock); + speedchange_task = + kthread_create(cpufreq_interactive_speedchange_task, NULL, + "cfinteractive"); + if (IS_ERR(speedchange_task)) + return PTR_ERR(speedchange_task); + + sched_setscheduler_nocheck(speedchange_task, SCHED_FIFO, ¶m); + get_task_struct(speedchange_task); + + /* NB: wake up so the thread does not look hung to the freezer */ + wake_up_process(speedchange_task); + + return cpufreq_register_governor(&cpufreq_gov_interactive); +} + +#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE +fs_initcall(cpufreq_interactive_init); +#else +module_init(cpufreq_interactive_init); +#endif + +static void __exit cpufreq_interactive_exit(void) +{ + cpufreq_unregister_governor(&cpufreq_gov_interactive); + kthread_stop(speedchange_task); + put_task_struct(speedchange_task); +} + +module_exit(cpufreq_interactive_exit); + +MODULE_AUTHOR("Mike Chan "); +MODULE_DESCRIPTION("'cpufreq_interactive' - A cpufreq governor for " + "Latency sensitive workloads"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/exynos-cpufreq.c b/drivers/cpufreq/exynos-cpufreq.c index fcd2914d081a88..ac07e5f0a2ca22 100644 --- a/drivers/cpufreq/exynos-cpufreq.c +++ b/drivers/cpufreq/exynos-cpufreq.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include @@ -208,6 +210,32 @@ static struct notifier_block exynos_cpufreq_nb = { .notifier_call = exynos_cpufreq_pm_notifier, }; +static int exynos_cpufreq_reboot_notifier(struct notifier_block *this, + unsigned long code, void *_cmd) +{ + struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */ + mutex_lock(&cpufreq_lock); + + if (frequency_locked) + goto out; + frequency_locked = true; + + if (locking_frequency) { + mutex_unlock(&cpufreq_lock); + exynos_target(policy, + exynos_cpufreq_get_index(locking_frequency)); + mutex_lock(&cpufreq_lock); + } + +out: + mutex_unlock(&cpufreq_lock); + return NOTIFY_DONE; +} + +static struct notifier_block exynos_cpufreq_reboot_nb = { + .notifier_call = exynos_cpufreq_reboot_notifier, +}; + static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy) { policy->clk = exynos_info->cpu_clk; @@ -266,6 +294,7 @@ static int exynos_cpufreq_probe(struct platform_device *pdev) locking_frequency = clk_get_rate(exynos_info->cpu_clk) / 1000; register_pm_notifier(&exynos_cpufreq_nb); + register_reboot_notifier(&exynos_cpufreq_reboot_nb); if (cpufreq_register_driver(&exynos_driver)) { pr_err("%s: failed to register cpufreq driver\n", __func__); diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c index 40d84c43d8f46c..df4f5cfeb15540 100644 --- a/drivers/cpufreq/exynos4210-cpufreq.c +++ b/drivers/cpufreq/exynos4210-cpufreq.c @@ -60,20 +60,20 @@ static void exynos4210_set_clkdiv(unsigned int div_index) tmp = apll_freq_4210[div_index].clk_div_cpu0; - __raw_writel(tmp, EXYNOS4_CLKDIV_CPU); + writel_relaxed(tmp, EXYNOS4_CLKDIV_CPU); do { - tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU); + tmp = readl_relaxed(EXYNOS4_CLKDIV_STATCPU); } while (tmp & 0x1111111); /* Change Divider - CPU1 */ tmp = apll_freq_4210[div_index].clk_div_cpu1; - __raw_writel(tmp, EXYNOS4_CLKDIV_CPU1); + writel_relaxed(tmp, EXYNOS4_CLKDIV_CPU1); do { - tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU1); + tmp = readl_relaxed(EXYNOS4_CLKDIV_STATCPU1); } while (tmp & 0x11); } @@ -85,7 +85,7 @@ static void exynos4210_set_apll(unsigned int index) clk_set_parent(moutcore, mout_mpll); do { - tmp = (__raw_readl(EXYNOS4_CLKMUX_STATCPU) + tmp = (readl_relaxed(EXYNOS4_CLKMUX_STATCPU) >> EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT); tmp &= 0x7; } while (tmp != 0x2); @@ -96,7 +96,7 @@ static void exynos4210_set_apll(unsigned int index) clk_set_parent(moutcore, mout_apll); do { - tmp = __raw_readl(EXYNOS4_CLKMUX_STATCPU); + tmp = readl_relaxed(EXYNOS4_CLKMUX_STATCPU); tmp &= EXYNOS4_CLKMUX_STATCPU_MUXCORE_MASK; } while (tmp != (0x1 << EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT)); } diff --git a/drivers/cpufreq/exynos4x12-cpufreq.c b/drivers/cpufreq/exynos4x12-cpufreq.c index 7c11ace3b3fc15..972d0b1165fba2 100644 --- a/drivers/cpufreq/exynos4x12-cpufreq.c +++ b/drivers/cpufreq/exynos4x12-cpufreq.c @@ -106,21 +106,21 @@ static void exynos4x12_set_clkdiv(unsigned int div_index) tmp = apll_freq_4x12[div_index].clk_div_cpu0; - __raw_writel(tmp, EXYNOS4_CLKDIV_CPU); + writel_relaxed(tmp, EXYNOS4_CLKDIV_CPU); - while (__raw_readl(EXYNOS4_CLKDIV_STATCPU) & 0x11111111) + while (readl_relaxed(EXYNOS4_CLKDIV_STATCPU) & 0x11111111) cpu_relax(); /* Change Divider - CPU1 */ tmp = apll_freq_4x12[div_index].clk_div_cpu1; - __raw_writel(tmp, EXYNOS4_CLKDIV_CPU1); + writel_relaxed(tmp, EXYNOS4_CLKDIV_CPU1); if (soc_is_exynos4212()) stat_cpu1 = 0x11; else stat_cpu1 = 0x111; - while (__raw_readl(EXYNOS4_CLKDIV_STATCPU1) & stat_cpu1) + while (readl_relaxed(EXYNOS4_CLKDIV_STATCPU1) & stat_cpu1) cpu_relax(); } @@ -133,7 +133,7 @@ static void exynos4x12_set_apll(unsigned int index) do { cpu_relax(); - tmp = (__raw_readl(EXYNOS4_CLKMUX_STATCPU) + tmp = (readl_relaxed(EXYNOS4_CLKMUX_STATCPU) >> EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT); tmp &= 0x7; } while (tmp != 0x2); @@ -145,7 +145,7 @@ static void exynos4x12_set_apll(unsigned int index) do { cpu_relax(); - tmp = __raw_readl(EXYNOS4_CLKMUX_STATCPU); + tmp = readl_relaxed(EXYNOS4_CLKMUX_STATCPU); tmp &= EXYNOS4_CLKMUX_STATCPU_MUXCORE_MASK; } while (tmp != (0x1 << EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT)); } diff --git a/drivers/cpufreq/exynos5250-cpufreq.c b/drivers/cpufreq/exynos5250-cpufreq.c index 5f90b82a40825d..d212a7262acfbf 100644 --- a/drivers/cpufreq/exynos5250-cpufreq.c +++ b/drivers/cpufreq/exynos5250-cpufreq.c @@ -87,17 +87,17 @@ static void set_clkdiv(unsigned int div_index) tmp = apll_freq_5250[div_index].clk_div_cpu0; - __raw_writel(tmp, EXYNOS5_CLKDIV_CPU0); + writel_relaxed(tmp, EXYNOS5_CLKDIV_CPU0); - while (__raw_readl(EXYNOS5_CLKDIV_STATCPU0) & 0x11111111) + while (readl_relaxed(EXYNOS5_CLKDIV_STATCPU0) & 0x11111111) cpu_relax(); /* Change Divider - CPU1 */ tmp = apll_freq_5250[div_index].clk_div_cpu1; - __raw_writel(tmp, EXYNOS5_CLKDIV_CPU1); + writel_relaxed(tmp, EXYNOS5_CLKDIV_CPU1); - while (__raw_readl(EXYNOS5_CLKDIV_STATCPU1) & 0x11) + while (readl_relaxed(EXYNOS5_CLKDIV_STATCPU1) & 0x11) cpu_relax(); } @@ -111,7 +111,7 @@ static void set_apll(unsigned int index) do { cpu_relax(); - tmp = (__raw_readl(EXYNOS5_CLKMUX_STATCPU) >> 16); + tmp = (readl_relaxed(EXYNOS5_CLKMUX_STATCPU) >> 16); tmp &= 0x7; } while (tmp != 0x2); @@ -122,7 +122,7 @@ static void set_apll(unsigned int index) do { cpu_relax(); - tmp = __raw_readl(EXYNOS5_CLKMUX_STATCPU); + tmp = readl_relaxed(EXYNOS5_CLKMUX_STATCPU); tmp &= (0x7 << 16); } while (tmp != (0x1 << 16)); } diff --git a/drivers/cpufreq/exynos5440-cpufreq.c b/drivers/cpufreq/exynos5440-cpufreq.c index 49b75601531694..8478ca76685803 100644 --- a/drivers/cpufreq/exynos5440-cpufreq.c +++ b/drivers/cpufreq/exynos5440-cpufreq.c @@ -157,7 +157,7 @@ static int init_div_table(void) tmp = (clk_div | ema_div | (volt_id << P0_7_VDD_SHIFT) | ((freq / FREQ_UNIT) << P0_7_FREQ_SHIFT)); - __raw_writel(tmp, dvfs_info->base + XMU_PMU_P0_7 + 4 * i); + writel_relaxed(tmp, dvfs_info->base + XMU_PMU_P0_7 + 4 * i); } rcu_read_unlock(); @@ -169,17 +169,17 @@ static void exynos_enable_dvfs(unsigned int cur_frequency) unsigned int tmp, i, cpu; struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table; /* Disable DVFS */ - __raw_writel(0, dvfs_info->base + XMU_DVFS_CTRL); + writel_relaxed(0, dvfs_info->base + XMU_DVFS_CTRL); /* Enable PSTATE Change Event */ - tmp = __raw_readl(dvfs_info->base + XMU_PMUEVTEN); + tmp = readl_relaxed(dvfs_info->base + XMU_PMUEVTEN); tmp |= (1 << PSTATE_CHANGED_EVTEN_SHIFT); - __raw_writel(tmp, dvfs_info->base + XMU_PMUEVTEN); + writel_relaxed(tmp, dvfs_info->base + XMU_PMUEVTEN); /* Enable PSTATE Change IRQ */ - tmp = __raw_readl(dvfs_info->base + XMU_PMUIRQEN); + tmp = readl_relaxed(dvfs_info->base + XMU_PMUIRQEN); tmp |= (1 << PSTATE_CHANGED_IRQEN_SHIFT); - __raw_writel(tmp, dvfs_info->base + XMU_PMUIRQEN); + writel_relaxed(tmp, dvfs_info->base + XMU_PMUIRQEN); /* Set initial performance index */ for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) @@ -197,14 +197,14 @@ static void exynos_enable_dvfs(unsigned int cur_frequency) cur_frequency); for (cpu = 0; cpu < CONFIG_NR_CPUS; cpu++) { - tmp = __raw_readl(dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4); + tmp = readl_relaxed(dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4); tmp &= ~(P_VALUE_MASK << C0_3_PSTATE_NEW_SHIFT); tmp |= (i << C0_3_PSTATE_NEW_SHIFT); - __raw_writel(tmp, dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4); + writel_relaxed(tmp, dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4); } /* Enable DVFS */ - __raw_writel(1 << XMU_DVFS_CTRL_EN_SHIFT, + writel_relaxed(1 << XMU_DVFS_CTRL_EN_SHIFT, dvfs_info->base + XMU_DVFS_CTRL); } @@ -223,11 +223,11 @@ static int exynos_target(struct cpufreq_policy *policy, unsigned int index) /* Set the target frequency in all C0_3_PSTATE register */ for_each_cpu(i, policy->cpus) { - tmp = __raw_readl(dvfs_info->base + XMU_C0_3_PSTATE + i * 4); + tmp = readl_relaxed(dvfs_info->base + XMU_C0_3_PSTATE + i * 4); tmp &= ~(P_VALUE_MASK << C0_3_PSTATE_NEW_SHIFT); tmp |= (index << C0_3_PSTATE_NEW_SHIFT); - __raw_writel(tmp, dvfs_info->base + XMU_C0_3_PSTATE + i * 4); + writel_relaxed(tmp, dvfs_info->base + XMU_C0_3_PSTATE + i * 4); } mutex_unlock(&cpufreq_lock); return 0; @@ -246,7 +246,7 @@ static void exynos_cpufreq_work(struct work_struct *work) mutex_lock(&cpufreq_lock); freqs.old = policy->cur; - cur_pstate = __raw_readl(dvfs_info->base + XMU_P_STATUS); + cur_pstate = readl_relaxed(dvfs_info->base + XMU_P_STATUS); if (cur_pstate >> C0_3_PSTATE_VALID_SHIFT & 0x1) index = (cur_pstate >> C0_3_PSTATE_CURR_SHIFT) & P_VALUE_MASK; else @@ -270,9 +270,9 @@ static irqreturn_t exynos_cpufreq_irq(int irq, void *id) { unsigned int tmp; - tmp = __raw_readl(dvfs_info->base + XMU_PMUIRQ); + tmp = readl_relaxed(dvfs_info->base + XMU_PMUIRQ); if (tmp >> PSTATE_CHANGED_SHIFT & 0x1) { - __raw_writel(tmp, dvfs_info->base + XMU_PMUIRQ); + writel_relaxed(tmp, dvfs_info->base + XMU_PMUIRQ); disable_irq_nosync(irq); schedule_work(&dvfs_info->irq_work); } diff --git a/drivers/cpuidle/governors/menu.c b/drivers/cpuidle/governors/menu.c index cf7f2f0e4ef54d..954a30e58f3266 100644 --- a/drivers/cpuidle/governors/menu.c +++ b/drivers/cpuidle/governors/menu.c @@ -182,7 +182,12 @@ static inline int performance_multiplier(void) /* for higher loadavg, we are more reluctant */ - mult += 2 * get_loadavg(); + /* + * this doesn't work as intended - it is almost always 0, but can + * sometimes, depending on workload, spike very high into the hundreds + * even when the average cpu load is under 10%. + */ + /* mult += 2 * get_loadavg(); */ /* for IO wait tasks (per cpu!) we add 5x each */ mult += 10 * nr_iowait_cpu(smp_processor_id()); diff --git a/drivers/crypto/omap-aes.c b/drivers/crypto/omap-aes.c index dde41f1df6080a..084622287893ec 100644 --- a/drivers/crypto/omap-aes.c +++ b/drivers/crypto/omap-aes.c @@ -195,7 +195,7 @@ static DEFINE_SPINLOCK(list_lock); #define omap_aes_read(dd, offset) \ ({ \ int _read_ret; \ - _read_ret = __raw_readl(dd->io_base + offset); \ + _read_ret = readl_relaxed(dd->io_base + offset); \ pr_debug("omap_aes_read(" #offset "=%#x)= %#x\n", \ offset, _read_ret); \ _read_ret; \ @@ -203,7 +203,7 @@ static DEFINE_SPINLOCK(list_lock); #else static inline u32 omap_aes_read(struct omap_aes_dev *dd, u32 offset) { - return __raw_readl(dd->io_base + offset); + return readl_relaxed(dd->io_base + offset); } #endif @@ -212,13 +212,13 @@ static inline u32 omap_aes_read(struct omap_aes_dev *dd, u32 offset) do { \ pr_debug("omap_aes_write(" #offset "=%#x) value=%#x\n", \ offset, value); \ - __raw_writel(value, dd->io_base + offset); \ + writel_relaxed(value, dd->io_base + offset); \ } while (0) #else static inline void omap_aes_write(struct omap_aes_dev *dd, u32 offset, u32 value) { - __raw_writel(value, dd->io_base + offset); + writel_relaxed(value, dd->io_base + offset); } #endif diff --git a/drivers/crypto/omap-sham.c b/drivers/crypto/omap-sham.c index a727a6a5965370..25bde7cc7d2c53 100644 --- a/drivers/crypto/omap-sham.c +++ b/drivers/crypto/omap-sham.c @@ -244,13 +244,13 @@ static struct omap_sham_drv sham = { static inline u32 omap_sham_read(struct omap_sham_dev *dd, u32 offset) { - return __raw_readl(dd->io_base + offset); + return readl_relaxed(dd->io_base + offset); } static inline void omap_sham_write(struct omap_sham_dev *dd, u32 offset, u32 value) { - __raw_writel(value, dd->io_base + offset); + writel_relaxed(value, dd->io_base + offset); } static inline void omap_sham_write_mask(struct omap_sham_dev *dd, u32 address, diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c index 73fa9b7a10ab36..2e147cb75dd9e5 100644 --- a/drivers/dma/pl330.c +++ b/drivers/dma/pl330.c @@ -292,7 +292,7 @@ struct pl330_config { #define DMAC_MODE_NS (1 << 0) unsigned int mode; unsigned int data_bus_width:10; /* In number of bits */ - unsigned int data_buf_dep:10; + unsigned int data_buf_dep:11; unsigned int num_chan:4; unsigned int num_peri:6; u32 peri_ns; @@ -3034,7 +3034,7 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id) dev_info(&adev->dev, - "Loaded driver for PL330 DMAC-%d\n", adev->periphid); + "Loaded driver for PL330 DMAC-%x\n", adev->periphid); dev_info(&adev->dev, "\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u\n", pi->pcfg.data_buf_dep, diff --git a/drivers/gator/Kconfig b/drivers/gator/Kconfig new file mode 100644 index 00000000000000..856aa1a15ca280 --- /dev/null +++ b/drivers/gator/Kconfig @@ -0,0 +1,33 @@ +config GATOR + tristate "Gator module for ARM's Streamline Performance Analyzer" + default m if (ARM || ARM64) + depends on PROFILING + depends on HIGH_RES_TIMERS + depends on (PERF_EVENTS && HW_PERF_EVENTS) || ARM64 + select TRACING + +config GATOR_WITH_MALI_SUPPORT + bool + +choice + prompt "Enable Mali GPU support in Gator" + depends on GATOR + optional + +config GATOR_MALI_400MP + bool "Mali-400MP" + select GATOR_WITH_MALI_SUPPORT + +config GATOR_MALI_T6XX + bool "Mali-T604 or Mali-T658" + select GATOR_WITH_MALI_SUPPORT + +endchoice + +config GATOR_MALI_PATH + string "Path to Mali driver" + depends on GATOR_WITH_MALI_SUPPORT + default "drivers/gpu/arm/mali400mp" + help + The gator code adds this to its include path so it can get the Mali + trace headers with: #include "linux/mali_linux_trace.h" diff --git a/drivers/gator/LICENSE b/drivers/gator/LICENSE new file mode 100644 index 00000000000000..d159169d105089 --- /dev/null +++ b/drivers/gator/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/drivers/gator/Makefile b/drivers/gator/Makefile new file mode 100644 index 00000000000000..3dc9d059a4b409 --- /dev/null +++ b/drivers/gator/Makefile @@ -0,0 +1,76 @@ +ifneq ($(KERNELRELEASE),) + +# Uncomment the following line to enable kernel stack unwinding within gator, or update gator_backtrace.c +# EXTRA_CFLAGS += -DGATOR_KERNEL_STACK_UNWINDING + +CONFIG_GATOR ?= m +obj-$(CONFIG_GATOR) := gator.o + +gator-y := gator_main.o \ + gator_events_irq.o \ + gator_events_sched.o \ + gator_events_net.o \ + gator_events_block.o \ + gator_events_meminfo.o \ + gator_events_perf_pmu.o \ + gator_events_mmapped.o \ + +# Convert the old GATOR_WITH_MALI_SUPPORT to the new kernel flags +ifneq ($(GATOR_WITH_MALI_SUPPORT),) + CONFIG_GATOR_WITH_MALI_SUPPORT := y + ifeq ($(GATOR_WITH_MALI_SUPPORT),MALI_T6xx) + CONFIG_GATOR_MALI_4XXMP := n + CONFIG_GATOR_MALI_T6XX := y + else + CONFIG_GATOR_MALI_4XXMP := y + CONFIG_GATOR_MALI_T6XX := n + endif + EXTRA_CFLAGS += -DMALI_SUPPORT=$(GATOR_WITH_MALI_SUPPORT) + ifneq ($(GATOR_MALI_INTERFACE_STYLE),) + EXTRA_CFLAGS += -DGATOR_MALI_INTERFACE_STYLE=$(GATOR_MALI_INTERFACE_STYLE) + endif +endif + +ifeq ($(CONFIG_GATOR_WITH_MALI_SUPPORT),y) + ifeq ($(CONFIG_GATOR_MALI_T6XX),y) + gator-y += gator_events_mali_t6xx.o \ + gator_events_mali_t6xx_hw.o + include $(src)/mali_t6xx.mk + else + gator-y += gator_events_mali_4xx.o + endif + gator-y += gator_events_mali_common.o + + ifneq ($(CONFIG_GATOR_MALI_PATH),) + ccflags-y += -I$(CONFIG_GATOR_MALI_PATH) + endif + ccflags-$(CONFIG_GATOR_MALI_4XXMP) += -DMALI_SUPPORT=MALI_4xx + ccflags-$(CONFIG_GATOR_MALI_T6XX) += -DMALI_SUPPORT=MALI_T6xx +endif + +# GATOR_TEST controls whether to include (=1) or exclude (=0) test code. +GATOR_TEST ?= 0 +EXTRA_CFLAGS += -DGATOR_TEST=$(GATOR_TEST) + +gator-$(CONFIG_ARM) += gator_events_armv6.o \ + gator_events_armv7.o \ + gator_events_ccn-504.o \ + gator_events_l2c-310.o \ + gator_events_scorpion.o + +gator-$(CONFIG_ARM64) += gator_events_ccn-504.o + +else + +all: + @echo + @echo "usage:" + @echo " make -C M=\`pwd\` ARCH=arm CROSS_COMPILE=<...> modules" + @echo + $(error) + +clean: + rm -f *.o .*.cmd modules.order Module.symvers gator.ko gator.mod.c + rm -rf .tmp_versions + +endif diff --git a/drivers/gator/gator.h b/drivers/gator/gator.h new file mode 100644 index 00000000000000..d8981ed85a6a10 --- /dev/null +++ b/drivers/gator/gator.h @@ -0,0 +1,142 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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. + */ + +#ifndef GATOR_H_ +#define GATOR_H_ + +#include +#include +#include +#include + +#define GATOR_PERF_SUPPORT LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 0) +#define GATOR_PERF_PMU_SUPPORT GATOR_PERF_SUPPORT && defined(CONFIG_PERF_EVENTS) && (!(defined(__arm__) || defined(__aarch64__)) || defined(CONFIG_HW_PERF_EVENTS)) +#define GATOR_NO_PERF_SUPPORT (!(GATOR_PERF_SUPPORT)) +#define GATOR_CPU_FREQ_SUPPORT (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38)) && defined(CONFIG_CPU_FREQ) +#define GATOR_IKS_SUPPORT defined(CONFIG_BL_SWITCHER) + +// cpu ids +#define ARM1136 0xb36 +#define ARM1156 0xb56 +#define ARM1176 0xb76 +#define ARM11MPCORE 0xb02 +#define CORTEX_A5 0xc05 +#define CORTEX_A7 0xc07 +#define CORTEX_A8 0xc08 +#define CORTEX_A9 0xc09 +#define CORTEX_A12 0xc0d +#define CORTEX_A15 0xc0f +#define SCORPION 0x00f +#define SCORPIONMP 0x02d +#define KRAITSIM 0x049 +#define KRAIT 0x04d +#define KRAIT_S4_PRO 0x06f +#define CORTEX_A53 0xd03 +#define CORTEX_A57 0xd07 +#define AARCH64 0xd0f +#define OTHER 0xfff + +#define MAXSIZE_CORE_NAME 32 + +struct gator_cpu { + const int cpuid; + // Human readable name + const char core_name[MAXSIZE_CORE_NAME]; + // Perf PMU name + const char * const pmu_name; + // gatorfs event name + const char * const pmnc_name; + // compatible from Documentation/devicetree/bindings/arm/cpus.txt + const char * const dt_name; + const int pmnc_counters; +}; + +const struct gator_cpu *gator_find_cpu_by_cpuid(const u32 cpuid); +const struct gator_cpu *gator_find_cpu_by_pmu_name(const char *const name); + +/****************************************************************************** + * Filesystem + ******************************************************************************/ +int gatorfs_create_file_perm(struct super_block *sb, struct dentry *root, + char const *name, + const struct file_operations *fops, int perm); + +struct dentry *gatorfs_mkdir(struct super_block *sb, struct dentry *root, + char const *name); + +int gatorfs_create_ulong(struct super_block *sb, struct dentry *root, + char const *name, unsigned long *val); + +int gatorfs_create_ro_ulong(struct super_block *sb, struct dentry *root, + char const *name, unsigned long *val); + +void gator_op_create_files(struct super_block *sb, struct dentry *root); + +/****************************************************************************** + * Tracepoints + ******************************************************************************/ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) +# error Kernels prior to 2.6.32 not supported +#elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +# define GATOR_DEFINE_PROBE(probe_name, proto) \ + static void probe_##probe_name(PARAMS(proto)) +# define GATOR_REGISTER_TRACE(probe_name) \ + register_trace_##probe_name(probe_##probe_name) +# define GATOR_UNREGISTER_TRACE(probe_name) \ + unregister_trace_##probe_name(probe_##probe_name) +#else +# define GATOR_DEFINE_PROBE(probe_name, proto) \ + static void probe_##probe_name(void *data, PARAMS(proto)) +# define GATOR_REGISTER_TRACE(probe_name) \ + register_trace_##probe_name(probe_##probe_name, NULL) +# define GATOR_UNREGISTER_TRACE(probe_name) \ + unregister_trace_##probe_name(probe_##probe_name, NULL) +#endif + +/****************************************************************************** + * Events + ******************************************************************************/ +struct gator_interface { + void (*shutdown)(void); // Complementary function to init + int (*create_files)(struct super_block *sb, struct dentry *root); + int (*start)(void); + void (*stop)(void); // Complementary function to start + int (*online)(int **buffer, bool migrate); + int (*offline)(int **buffer, bool migrate); + void (*online_dispatch)(int cpu, bool migrate); // called in process context but may not be running on core 'cpu' + void (*offline_dispatch)(int cpu, bool migrate); // called in process context but may not be running on core 'cpu' + int (*read)(int **buffer); + int (*read64)(long long **buffer); + int (*read_proc)(long long **buffer, struct task_struct *); + struct list_head list; +}; + +int gator_events_install(struct gator_interface *interface); +int gator_events_get_key(void); +u32 gator_cpuid(void); + +void gator_backtrace_handler(struct pt_regs *const regs); + +#if !GATOR_IKS_SUPPORT + +#define get_physical_cpu() smp_processor_id() +#define lcpu_to_pcpu(lcpu) lcpu +#define pcpu_to_lcpu(pcpu) pcpu + +#else + +#define get_physical_cpu() lcpu_to_pcpu(get_logical_cpu()) +int lcpu_to_pcpu(const int lcpu); +int pcpu_to_lcpu(const int pcpu); + +#endif + +#define get_logical_cpu() smp_processor_id() +#define on_primary_core() (get_logical_cpu() == 0) + +#endif // GATOR_H_ diff --git a/drivers/gator/gator_annotate.c b/drivers/gator/gator_annotate.c new file mode 100644 index 00000000000000..5b9399bea230c7 --- /dev/null +++ b/drivers/gator/gator_annotate.c @@ -0,0 +1,186 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 +#include +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(annotate_lock); +static bool collect_annotations = false; + +static int annotate_copy(struct file *file, char const __user *buf, size_t count) +{ + int cpu = 0; + int write = per_cpu(gator_buffer_write, cpu)[ANNOTATE_BUF]; + + if (file == NULL) { + // copy from kernel + memcpy(&per_cpu(gator_buffer, cpu)[ANNOTATE_BUF][write], buf, count); + } else { + // copy from user space + if (copy_from_user(&per_cpu(gator_buffer, cpu)[ANNOTATE_BUF][write], buf, count) != 0) + return -1; + } + per_cpu(gator_buffer_write, cpu)[ANNOTATE_BUF] = (write + count) & gator_buffer_mask[ANNOTATE_BUF]; + + return 0; +} + +static ssize_t annotate_write(struct file *file, char const __user *buf, size_t count_orig, loff_t *offset) +{ + int pid, cpu, header_size, available, contiguous, length1, length2, size, count = count_orig & 0x7fffffff; + bool interrupt_context; + + if (*offset) { + return -EINVAL; + } + + interrupt_context = in_interrupt(); + // Annotations are not supported in interrupt context, but may work if you comment out the the next four lines of code. + // By doing so, annotations in interrupt context can result in deadlocks and lost data. + if (interrupt_context) { + printk(KERN_WARNING "gator: Annotations are not supported in interrupt context. Edit gator_annotate.c in the gator driver to enable annotations in interrupt context.\n"); + return -EINVAL; + } + + retry: + // synchronize between cores and with collect_annotations + spin_lock(&annotate_lock); + + if (!collect_annotations) { + // Not collecting annotations, tell the caller everything was written + size = count_orig; + goto annotate_write_out; + } + + // Annotation only uses a single per-cpu buffer as the data must be in order to the engine + cpu = 0; + + if (current == NULL) { + pid = 0; + } else { + pid = current->pid; + } + + // determine total size of the payload + header_size = MAXSIZE_PACK32 * 3 + MAXSIZE_PACK64; + available = buffer_bytes_available(cpu, ANNOTATE_BUF) - header_size; + size = count < available ? count : available; + + if (size <= 0) { + // Buffer is full, wait until space is available + spin_unlock(&annotate_lock); + + // Drop the annotation as blocking is not allowed in interrupt context + if (interrupt_context) { + return -EINVAL; + } + + wait_event_interruptible(gator_annotate_wait, buffer_bytes_available(cpu, ANNOTATE_BUF) > header_size || !collect_annotations); + + // Check to see if a signal is pending + if (signal_pending(current)) { + return -EINTR; + } + + goto retry; + } + + // synchronize shared variables annotateBuf and annotatePos + if (per_cpu(gator_buffer, cpu)[ANNOTATE_BUF]) { + u64 time = gator_get_time(); + gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, get_physical_cpu()); + gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, pid); + gator_buffer_write_packed_int64(cpu, ANNOTATE_BUF, time); + gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, size); + + // determine the sizes to capture, length1 + length2 will equal size + contiguous = contiguous_space_available(cpu, ANNOTATE_BUF); + if (size < contiguous) { + length1 = size; + length2 = 0; + } else { + length1 = contiguous; + length2 = size - contiguous; + } + + if (annotate_copy(file, buf, length1) != 0) { + size = -EINVAL; + goto annotate_write_out; + } + + if (length2 > 0 && annotate_copy(file, &buf[length1], length2) != 0) { + size = -EINVAL; + goto annotate_write_out; + } + + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, ANNOTATE_BUF, time); + } + +annotate_write_out: + spin_unlock(&annotate_lock); + + // return the number of bytes written + return size; +} + +#include "gator_annotate_kernel.c" + +static int annotate_release(struct inode *inode, struct file *file) +{ + int cpu = 0; + + // synchronize between cores + spin_lock(&annotate_lock); + + if (per_cpu(gator_buffer, cpu)[ANNOTATE_BUF] && buffer_check_space(cpu, ANNOTATE_BUF, MAXSIZE_PACK64 + 3 * MAXSIZE_PACK32)) { + uint32_t pid = current->pid; + gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, get_physical_cpu()); + gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, pid); + gator_buffer_write_packed_int64(cpu, ANNOTATE_BUF, 0); // time + gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, 0); // size + } + + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, ANNOTATE_BUF, gator_get_time()); + + spin_unlock(&annotate_lock); + + return 0; +} + +static const struct file_operations annotate_fops = { + .write = annotate_write, + .release = annotate_release +}; + +static int gator_annotate_create_files(struct super_block *sb, struct dentry *root) +{ + return gatorfs_create_file_perm(sb, root, "annotate", &annotate_fops, 0666); +} + +static int gator_annotate_start(void) +{ + collect_annotations = true; + return 0; +} + +static void gator_annotate_stop(void) +{ + // the spinlock here will ensure that when this function exits, we are not in the middle of an annotation + spin_lock(&annotate_lock); + collect_annotations = false; + wake_up(&gator_annotate_wait); + spin_unlock(&annotate_lock); +} diff --git a/drivers/gator/gator_annotate_kernel.c b/drivers/gator/gator_annotate_kernel.c new file mode 100644 index 00000000000000..a406e4882974ba --- /dev/null +++ b/drivers/gator/gator_annotate_kernel.c @@ -0,0 +1,200 @@ +/** + * Copyright (C) ARM Limited 2012-2013. All rights reserved. + * + * 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. + * + */ + +#define ESCAPE_CODE 0x1c +#define STRING_ANNOTATION 0x06 +#define NAME_CHANNEL_ANNOTATION 0x07 +#define NAME_GROUP_ANNOTATION 0x08 +#define VISUAL_ANNOTATION 0x04 +#define MARKER_ANNOTATION 0x05 + +static void kannotate_write(const char *ptr, unsigned int size) +{ + int retval; + int pos = 0; + loff_t offset = 0; + while (pos < size) { + retval = annotate_write(NULL, &ptr[pos], size - pos, &offset); + if (retval < 0) { + printk(KERN_WARNING "gator: kannotate_write failed with return value %d\n", retval); + return; + } + pos += retval; + } +} + +static void marshal_u16(char *buf, u16 val) { + buf[0] = val & 0xff; + buf[1] = (val >> 8) & 0xff; +} + +static void marshal_u32(char *buf, u32 val) { + buf[0] = val & 0xff; + buf[1] = (val >> 8) & 0xff; + buf[2] = (val >> 16) & 0xff; + buf[3] = (val >> 24) & 0xff; +} + +void gator_annotate_channel(int channel, const char *str) +{ + const u16 str_size = strlen(str) & 0xffff; + char header[8]; + header[0] = ESCAPE_CODE; + header[1] = STRING_ANNOTATION; + marshal_u32(header + 2, channel); + marshal_u16(header + 6, str_size); + kannotate_write(header, sizeof(header)); + kannotate_write(str, str_size); +} + +EXPORT_SYMBOL(gator_annotate_channel); + +void gator_annotate(const char *str) +{ + gator_annotate_channel(0, str); +} + +EXPORT_SYMBOL(gator_annotate); + +void gator_annotate_channel_color(int channel, int color, const char *str) +{ + const u16 str_size = (strlen(str) + 4) & 0xffff; + char header[12]; + header[0] = ESCAPE_CODE; + header[1] = STRING_ANNOTATION; + marshal_u32(header + 2, channel); + marshal_u16(header + 6, str_size); + marshal_u32(header + 8, color); + kannotate_write(header, sizeof(header)); + kannotate_write(str, str_size - 4); +} + +EXPORT_SYMBOL(gator_annotate_channel_color); + +void gator_annotate_color(int color, const char *str) +{ + gator_annotate_channel_color(0, color, str); +} + +EXPORT_SYMBOL(gator_annotate_color); + +void gator_annotate_channel_end(int channel) +{ + char header[8]; + header[0] = ESCAPE_CODE; + header[1] = STRING_ANNOTATION; + marshal_u32(header + 2, channel); + marshal_u16(header + 6, 0); + kannotate_write(header, sizeof(header)); +} + +EXPORT_SYMBOL(gator_annotate_channel_end); + +void gator_annotate_end(void) +{ + gator_annotate_channel_end(0); +} + +EXPORT_SYMBOL(gator_annotate_end); + +void gator_annotate_name_channel(int channel, int group, const char* str) +{ + const u16 str_size = strlen(str) & 0xffff; + char header[12]; + header[0] = ESCAPE_CODE; + header[1] = NAME_CHANNEL_ANNOTATION; + marshal_u32(header + 2, channel); + marshal_u32(header + 6, group); + marshal_u16(header + 10, str_size); + kannotate_write(header, sizeof(header)); + kannotate_write(str, str_size); +} + +EXPORT_SYMBOL(gator_annotate_name_channel); + +void gator_annotate_name_group(int group, const char* str) +{ + const u16 str_size = strlen(str) & 0xffff; + char header[8]; + header[0] = ESCAPE_CODE; + header[1] = NAME_GROUP_ANNOTATION; + marshal_u32(header + 2, group); + marshal_u16(header + 6, str_size); + kannotate_write(header, sizeof(header)); + kannotate_write(str, str_size); +} + +EXPORT_SYMBOL(gator_annotate_name_group); + +void gator_annotate_visual(const char *data, unsigned int length, const char *str) +{ + const u16 str_size = strlen(str) & 0xffff; + char header[4]; + char header_length[4]; + header[0] = ESCAPE_CODE; + header[1] = VISUAL_ANNOTATION; + marshal_u16(header + 2, str_size); + marshal_u32(header_length, length); + kannotate_write(header, sizeof(header)); + kannotate_write(str, str_size); + kannotate_write(header_length, sizeof(header_length)); + kannotate_write(data, length); +} + +EXPORT_SYMBOL(gator_annotate_visual); + +void gator_annotate_marker(void) +{ + char header[4]; + header[0] = ESCAPE_CODE; + header[1] = MARKER_ANNOTATION; + marshal_u16(header + 2, 0); + kannotate_write(header, sizeof(header)); +} + +EXPORT_SYMBOL(gator_annotate_marker); + +void gator_annotate_marker_str(const char *str) +{ + const u16 str_size = strlen(str) & 0xffff; + char header[4]; + header[0] = ESCAPE_CODE; + header[1] = MARKER_ANNOTATION; + marshal_u16(header + 2, str_size); + kannotate_write(header, sizeof(header)); + kannotate_write(str, str_size); +} + +EXPORT_SYMBOL(gator_annotate_marker_str); + +void gator_annotate_marker_color(int color) +{ + char header[8]; + header[0] = ESCAPE_CODE; + header[1] = MARKER_ANNOTATION; + marshal_u16(header + 2, 4); + marshal_u32(header + 4, color); + kannotate_write(header, sizeof(header)); +} + +EXPORT_SYMBOL(gator_annotate_marker_color); + +void gator_annotate_marker_color_str(int color, const char *str) +{ + const u16 str_size = (strlen(str) + 4) & 0xffff; + char header[8]; + header[0] = ESCAPE_CODE; + header[1] = MARKER_ANNOTATION; + marshal_u16(header + 2, str_size); + marshal_u32(header + 4, color); + kannotate_write(header, sizeof(header)); + kannotate_write(str, str_size - 4); +} + +EXPORT_SYMBOL(gator_annotate_marker_color_str); diff --git a/drivers/gator/gator_backtrace.c b/drivers/gator/gator_backtrace.c new file mode 100644 index 00000000000000..ffacb490194cb6 --- /dev/null +++ b/drivers/gator/gator_backtrace.c @@ -0,0 +1,168 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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. + * + */ + +/* + * EABI backtrace stores {fp,lr} on the stack. + */ +struct stack_frame_eabi { + union { + struct { + unsigned long fp; + // May be the fp in the case of a leaf function or clang + unsigned long lr; + // If lr is really the fp, lr2 is the corresponding lr + unsigned long lr2; + }; + // Used to read 32 bit fp/lr from a 64 bit kernel + struct { + u32 fp_32; + // same as lr above + u32 lr_32; + // same as lr2 above + u32 lr2_32; + }; + }; +}; + +static void arm_backtrace_eabi(int cpu, struct pt_regs *const regs, unsigned int depth) +{ +#if defined(__arm__) || defined(__aarch64__) + struct stack_frame_eabi *curr; + struct stack_frame_eabi bufcurr; +#if defined(__arm__) + const bool is_compat = false; + unsigned long fp = regs->ARM_fp; + unsigned long sp = regs->ARM_sp; + unsigned long lr = regs->ARM_lr; + const int gcc_frame_offset = sizeof(unsigned long); +#else + // Is userspace aarch32 (32 bit) + const bool is_compat = compat_user_mode(regs); + unsigned long fp = (is_compat ? regs->regs[11] : regs->regs[29]); + unsigned long sp = (is_compat ? regs->compat_sp : regs->sp); + unsigned long lr = (is_compat ? regs->compat_lr : regs->regs[30]); + const int gcc_frame_offset = (is_compat ? sizeof(u32) : 0); +#endif + // clang frame offset is always zero + int is_user_mode = user_mode(regs); + + // pc (current function) has already been added + + if (!is_user_mode) { + return; + } + + // Add the lr (parent function) + // entry preamble may not have executed + gator_add_trace(cpu, lr); + + // check fp is valid + if (fp == 0 || fp < sp) { + return; + } + + // Get the current stack frame + curr = (struct stack_frame_eabi *)(fp - gcc_frame_offset); + if ((unsigned long)curr & 3) { + return; + } + + while (depth-- && curr) { + if (!access_ok(VERIFY_READ, curr, sizeof(struct stack_frame_eabi)) || + __copy_from_user_inatomic(&bufcurr, curr, sizeof(struct stack_frame_eabi))) { + return; + } + + fp = (is_compat ? bufcurr.fp_32 : bufcurr.fp); + lr = (is_compat ? bufcurr.lr_32 : bufcurr.lr); + +#define calc_next(reg) ((reg) - gcc_frame_offset) + // Returns true if reg is a valid fp +#define validate_next(reg, curr) \ + ((reg) != 0 && (calc_next(reg) & 3) == 0 && (unsigned long)(curr) < calc_next(reg)) + + // Try lr from the stack as the fp because gcc leaf functions do not push lr + // If gcc_frame_offset is non-zero, the lr will also be the clang fp + // This assumes code is at a lower address than the stack + if (validate_next(lr, curr)) { + fp = lr; + lr = (is_compat ? bufcurr.lr2_32 : bufcurr.lr2); + } + + gator_add_trace(cpu, lr); + + if (!validate_next(fp, curr)) { + return; + } + + // Move to the next stack frame + curr = (struct stack_frame_eabi *)calc_next(fp); + } +#endif +} + +#if defined(__arm__) || defined(__aarch64__) +static int report_trace(struct stackframe *frame, void *d) +{ + unsigned int *depth = d, cookie = NO_COOKIE; + unsigned long addr = frame->pc; + + if (*depth) { +#if defined(MODULE) + unsigned int cpu = get_physical_cpu(); + struct module *mod = __module_address(addr); + if (mod) { + cookie = get_cookie(cpu, current, mod->name, false); + addr = addr - (unsigned long)mod->module_core; + } +#endif + marshal_backtrace(addr & ~1, cookie); + (*depth)--; + } + + return *depth == 0; +} +#endif + +// Uncomment the following line to enable kernel stack unwinding within gator, note it can also be defined from the Makefile +// #define GATOR_KERNEL_STACK_UNWINDING + +#if (defined(__arm__) || defined(__aarch64__)) && !defined(GATOR_KERNEL_STACK_UNWINDING) +// Disabled by default +MODULE_PARM_DESC(kernel_stack_unwinding, "Allow kernel stack unwinding."); +bool kernel_stack_unwinding = 0; +module_param(kernel_stack_unwinding, bool, 0644); +#endif + +static void kernel_backtrace(int cpu, struct pt_regs *const regs) +{ +#if defined(__arm__) || defined(__aarch64__) +#ifdef GATOR_KERNEL_STACK_UNWINDING + int depth = gator_backtrace_depth; +#else + int depth = (kernel_stack_unwinding ? gator_backtrace_depth : 1); +#endif + struct stackframe frame; + if (depth == 0) + depth = 1; +#if defined(__arm__) + frame.fp = regs->ARM_fp; + frame.sp = regs->ARM_sp; + frame.lr = regs->ARM_lr; + frame.pc = regs->ARM_pc; +#else + frame.fp = regs->regs[29]; + frame.sp = regs->sp; + frame.pc = regs->pc; +#endif + walk_stackframe(&frame, report_trace, &depth); +#else + marshal_backtrace(PC_REG & ~1, NO_COOKIE); +#endif +} diff --git a/drivers/gator/gator_cookies.c b/drivers/gator/gator_cookies.c new file mode 100644 index 00000000000000..eb9b946170c1cd --- /dev/null +++ b/drivers/gator/gator_cookies.c @@ -0,0 +1,433 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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. + * + */ + +#define COOKIEMAP_ENTRIES 1024 /* must be power of 2 */ +#define TRANSLATE_BUFFER_SIZE 512 // must be a power of 2 - 512/4 = 128 entries +#define TRANSLATE_TEXT_SIZE 256 +#define MAX_COLLISIONS 2 + +static uint32_t *gator_crc32_table; +static unsigned int translate_buffer_mask; + +struct cookie_args { + struct task_struct *task; + const char *text; +}; + +static DEFINE_PER_CPU(char *, translate_text); +static DEFINE_PER_CPU(uint32_t, cookie_next_key); +static DEFINE_PER_CPU(uint64_t *, cookie_keys); +static DEFINE_PER_CPU(uint32_t *, cookie_values); +static DEFINE_PER_CPU(int, translate_buffer_read); +static DEFINE_PER_CPU(int, translate_buffer_write); +static DEFINE_PER_CPU(struct cookie_args *, translate_buffer); + +static uint32_t get_cookie(int cpu, struct task_struct *task, const char *text, bool from_wq); +static void wq_cookie_handler(struct work_struct *unused); +DECLARE_WORK(cookie_work, wq_cookie_handler); +static struct timer_list app_process_wake_up_timer; +static void app_process_wake_up_handler(unsigned long unused_data); + +static uint32_t cookiemap_code(uint64_t value64) +{ + uint32_t value = (uint32_t)((value64 >> 32) + value64); + uint32_t cookiecode = (value >> 24) & 0xff; + cookiecode = cookiecode * 31 + ((value >> 16) & 0xff); + cookiecode = cookiecode * 31 + ((value >> 8) & 0xff); + cookiecode = cookiecode * 31 + ((value >> 0) & 0xff); + cookiecode &= (COOKIEMAP_ENTRIES - 1); + return cookiecode * MAX_COLLISIONS; +} + +static uint32_t gator_chksum_crc32(const char *data) +{ + register unsigned long crc; + const unsigned char *block = data; + int i, length = strlen(data); + + crc = 0xFFFFFFFF; + for (i = 0; i < length; i++) { + crc = ((crc >> 8) & 0x00FFFFFF) ^ gator_crc32_table[(crc ^ *block++) & 0xFF]; + } + + return (crc ^ 0xFFFFFFFF); +} + +/* + * Exists + * Pre: [0][1][v][3]..[n-1] + * Post: [v][0][1][3]..[n-1] + */ +static uint32_t cookiemap_exists(uint64_t key) +{ + unsigned long x, flags, retval = 0; + int cpu = get_physical_cpu(); + uint32_t cookiecode = cookiemap_code(key); + uint64_t *keys = &(per_cpu(cookie_keys, cpu)[cookiecode]); + uint32_t *values = &(per_cpu(cookie_values, cpu)[cookiecode]); + + // Can be called from interrupt handler or from work queue + local_irq_save(flags); + for (x = 0; x < MAX_COLLISIONS; x++) { + if (keys[x] == key) { + uint32_t value = values[x]; + for (; x > 0; x--) { + keys[x] = keys[x - 1]; + values[x] = values[x - 1]; + } + keys[0] = key; + values[0] = value; + retval = value; + break; + } + } + local_irq_restore(flags); + + return retval; +} + +/* + * Add + * Pre: [0][1][2][3]..[n-1] + * Post: [v][0][1][2]..[n-2] + */ +static void cookiemap_add(uint64_t key, uint32_t value) +{ + int cpu = get_physical_cpu(); + int cookiecode = cookiemap_code(key); + uint64_t *keys = &(per_cpu(cookie_keys, cpu)[cookiecode]); + uint32_t *values = &(per_cpu(cookie_values, cpu)[cookiecode]); + int x; + + for (x = MAX_COLLISIONS - 1; x > 0; x--) { + keys[x] = keys[x - 1]; + values[x] = values[x - 1]; + } + keys[0] = key; + values[0] = value; +} + +#ifndef CONFIG_PREEMPT_RT_FULL +static void translate_buffer_write_args(int cpu, struct task_struct *task, const char *text) +{ + unsigned long flags; + int write; + int next_write; + struct cookie_args *args; + + local_irq_save(flags); + + write = per_cpu(translate_buffer_write, cpu); + next_write = (write + 1) & translate_buffer_mask; + + // At least one entry must always remain available as when read == write, the queue is empty not full + if (next_write != per_cpu(translate_buffer_read, cpu)) { + args = &per_cpu(translate_buffer, cpu)[write]; + args->task = task; + args->text = text; + get_task_struct(task); + per_cpu(translate_buffer_write, cpu) = next_write; + } + + local_irq_restore(flags); +} +#endif + +static void translate_buffer_read_args(int cpu, struct cookie_args *args) +{ + unsigned long flags; + int read; + + local_irq_save(flags); + + read = per_cpu(translate_buffer_read, cpu); + *args = per_cpu(translate_buffer, cpu)[read]; + per_cpu(translate_buffer_read, cpu) = (read + 1) & translate_buffer_mask; + + local_irq_restore(flags); +} + +static void wq_cookie_handler(struct work_struct *unused) +{ + struct cookie_args args; + int cpu = get_physical_cpu(), cookie; + + mutex_lock(&start_mutex); + + if (gator_started != 0) { + while (per_cpu(translate_buffer_read, cpu) != per_cpu(translate_buffer_write, cpu)) { + translate_buffer_read_args(cpu, &args); + cookie = get_cookie(cpu, args.task, args.text, true); + marshal_link(cookie, args.task->tgid, args.task->pid); + put_task_struct(args.task); + } + } + + mutex_unlock(&start_mutex); +} + +static void app_process_wake_up_handler(unsigned long unused_data) +{ + // had to delay scheduling work as attempting to schedule work during the context switch is illegal in kernel versions 3.5 and greater + schedule_work(&cookie_work); +} + +// Retrieve full name from proc/pid/cmdline for java processes on Android +static int translate_app_process(const char **text, int cpu, struct task_struct *task, bool from_wq) +{ + void *maddr; + unsigned int len; + unsigned long addr; + struct mm_struct *mm; + struct page *page = NULL; + struct vm_area_struct *page_vma; + int bytes, offset, retval = 0; + char *buf = per_cpu(translate_text, cpu); + +#ifndef CONFIG_PREEMPT_RT_FULL + // Push work into a work queue if in atomic context as the kernel functions below might sleep + // Rely on the in_interrupt variable rather than in_irq() or in_interrupt() kernel functions, as the value of these functions seems + // inconsistent during a context switch between android/linux versions + if (!from_wq) { + // Check if already in buffer + int pos = per_cpu(translate_buffer_read, cpu); + while (pos != per_cpu(translate_buffer_write, cpu)) { + if (per_cpu(translate_buffer, cpu)[pos].task == task) + goto out; + pos = (pos + 1) & translate_buffer_mask; + } + + translate_buffer_write_args(cpu, task, *text); + + // Not safe to call in RT-Preempt full in schedule switch context + mod_timer(&app_process_wake_up_timer, jiffies + 1); + goto out; + } +#endif + + mm = get_task_mm(task); + if (!mm) + goto out; + if (!mm->arg_end) + goto outmm; + addr = mm->arg_start; + len = mm->arg_end - mm->arg_start; + + if (len > TRANSLATE_TEXT_SIZE) + len = TRANSLATE_TEXT_SIZE; + + down_read(&mm->mmap_sem); + while (len) { + if (get_user_pages(task, mm, addr, 1, 0, 1, &page, &page_vma) <= 0) + goto outsem; + + maddr = kmap(page); + offset = addr & (PAGE_SIZE - 1); + bytes = len; + if (bytes > PAGE_SIZE - offset) + bytes = PAGE_SIZE - offset; + + copy_from_user_page(page_vma, page, addr, buf, maddr + offset, bytes); + + kunmap(page); // release page allocated by get_user_pages() + page_cache_release(page); + + len -= bytes; + buf += bytes; + addr += bytes; + + *text = per_cpu(translate_text, cpu); + retval = 1; + } + + // On app_process startup, /proc/pid/cmdline is initially "zygote" then "" but changes after an initial startup period + if (strcmp(*text, "zygote") == 0 || strcmp(*text, "") == 0) + retval = 0; + +outsem: + up_read(&mm->mmap_sem); +outmm: + mmput(mm); +out: + return retval; +} + +static uint32_t get_cookie(int cpu, struct task_struct *task, const char *text, bool from_wq) +{ + unsigned long flags, cookie; + uint64_t key; + + key = gator_chksum_crc32(text); + key = (key << 32) | (uint32_t)task->tgid; + + cookie = cookiemap_exists(key); + if (cookie) { + return cookie; + } + + if (strcmp(text, "app_process") == 0) { + if (!translate_app_process(&text, cpu, task, from_wq)) + return UNRESOLVED_COOKIE; + } + + // Can be called from interrupt handler or from work queue or from scheduler trace + local_irq_save(flags); + + cookie = UNRESOLVED_COOKIE; + if (marshal_cookie_header(text)) { + cookie = per_cpu(cookie_next_key, cpu) += nr_cpu_ids; + cookiemap_add(key, cookie); + marshal_cookie(cookie, text); + } + + local_irq_restore(flags); + + return cookie; +} + +static int get_exec_cookie(int cpu, struct task_struct *task) +{ + struct mm_struct *mm = task->mm; + const char *text; + + // kernel threads have no address space + if (!mm) + return NO_COOKIE; + + if (task && task->mm && task->mm->exe_file) { + text = task->mm->exe_file->f_path.dentry->d_name.name; + return get_cookie(cpu, task, text, false); + } + + return UNRESOLVED_COOKIE; +} + +static unsigned long get_address_cookie(int cpu, struct task_struct *task, unsigned long addr, off_t *offset) +{ + unsigned long cookie = NO_COOKIE; + struct mm_struct *mm = task->mm; + struct vm_area_struct *vma; + const char *text; + + if (!mm) + return cookie; + + for (vma = find_vma(mm, addr); vma; vma = vma->vm_next) { + if (addr < vma->vm_start || addr >= vma->vm_end) + continue; + + if (vma->vm_file) { + text = vma->vm_file->f_path.dentry->d_name.name; + cookie = get_cookie(cpu, task, text, false); + *offset = (vma->vm_pgoff << PAGE_SHIFT) + addr - vma->vm_start; + } else { + /* must be an anonymous map */ + *offset = addr; + } + + break; + } + + if (!vma) + cookie = UNRESOLVED_COOKIE; + + return cookie; +} + +static int cookies_initialize(void) +{ + uint32_t crc, poly; + int i, j, cpu, size, err = 0; + + translate_buffer_mask = TRANSLATE_BUFFER_SIZE / sizeof(per_cpu(translate_buffer, 0)[0]) - 1; + + for_each_present_cpu(cpu) { + per_cpu(cookie_next_key, cpu) = nr_cpu_ids + cpu; + + size = COOKIEMAP_ENTRIES * MAX_COLLISIONS * sizeof(uint64_t); + per_cpu(cookie_keys, cpu) = (uint64_t *)kmalloc(size, GFP_KERNEL); + if (!per_cpu(cookie_keys, cpu)) { + err = -ENOMEM; + goto cookie_setup_error; + } + memset(per_cpu(cookie_keys, cpu), 0, size); + + size = COOKIEMAP_ENTRIES * MAX_COLLISIONS * sizeof(uint32_t); + per_cpu(cookie_values, cpu) = (uint32_t *)kmalloc(size, GFP_KERNEL); + if (!per_cpu(cookie_values, cpu)) { + err = -ENOMEM; + goto cookie_setup_error; + } + memset(per_cpu(cookie_values, cpu), 0, size); + + per_cpu(translate_buffer, cpu) = (struct cookie_args *)kmalloc(TRANSLATE_BUFFER_SIZE, GFP_KERNEL); + if (!per_cpu(translate_buffer, cpu)) { + err = -ENOMEM; + goto cookie_setup_error; + } + + per_cpu(translate_buffer_write, cpu) = 0; + per_cpu(translate_buffer_read, cpu) = 0; + + per_cpu(translate_text, cpu) = (char *)kmalloc(TRANSLATE_TEXT_SIZE, GFP_KERNEL); + if (!per_cpu(translate_text, cpu)) { + err = -ENOMEM; + goto cookie_setup_error; + } + } + + // build CRC32 table + poly = 0x04c11db7; + gator_crc32_table = (uint32_t *)kmalloc(256 * sizeof(uint32_t), GFP_KERNEL); + if (!gator_crc32_table) { + err = -ENOMEM; + goto cookie_setup_error; + } + for (i = 0; i < 256; i++) { + crc = i; + for (j = 8; j > 0; j--) { + if (crc & 1) { + crc = (crc >> 1) ^ poly; + } else { + crc >>= 1; + } + } + gator_crc32_table[i] = crc; + } + + setup_timer(&app_process_wake_up_timer, app_process_wake_up_handler, 0); + +cookie_setup_error: + return err; +} + +static void cookies_release(void) +{ + int cpu; + + for_each_present_cpu(cpu) { + kfree(per_cpu(cookie_keys, cpu)); + per_cpu(cookie_keys, cpu) = NULL; + + kfree(per_cpu(cookie_values, cpu)); + per_cpu(cookie_values, cpu) = NULL; + + kfree(per_cpu(translate_buffer, cpu)); + per_cpu(translate_buffer, cpu) = NULL; + per_cpu(translate_buffer_read, cpu) = 0; + per_cpu(translate_buffer_write, cpu) = 0; + + kfree(per_cpu(translate_text, cpu)); + per_cpu(translate_text, cpu) = NULL; + } + + del_timer_sync(&app_process_wake_up_timer); + kfree(gator_crc32_table); + gator_crc32_table = NULL; +} diff --git a/drivers/gator/gator_events_armv6.c b/drivers/gator/gator_events_armv6.c new file mode 100644 index 00000000000000..dd7974090b821d --- /dev/null +++ b/drivers/gator/gator_events_armv6.c @@ -0,0 +1,237 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 "gator.h" + +// gator_events_perf_pmu.c is used if perf is supported +#if GATOR_NO_PERF_SUPPORT + +static const char *pmnc_name; + +/* + * Per-CPU PMCR + */ +#define PMCR_E (1 << 0) /* Enable */ +#define PMCR_P (1 << 1) /* Count reset */ +#define PMCR_C (1 << 2) /* Cycle counter reset */ +#define PMCR_OFL_PMN0 (1 << 8) /* Count reg 0 overflow */ +#define PMCR_OFL_PMN1 (1 << 9) /* Count reg 1 overflow */ +#define PMCR_OFL_CCNT (1 << 10) /* Cycle counter overflow */ + +#define PMN0 0 +#define PMN1 1 +#define CCNT 2 +#define CNTMAX (CCNT+1) + +static int pmnc_counters = 0; +static unsigned long pmnc_enabled[CNTMAX]; +static unsigned long pmnc_event[CNTMAX]; +static unsigned long pmnc_key[CNTMAX]; + +static DEFINE_PER_CPU(int[CNTMAX * 2], perfCnt); + +static inline void armv6_pmnc_write(u32 val) +{ + /* upper 4bits and 7, 11 are write-as-0 */ + val &= 0x0ffff77f; + asm volatile("mcr p15, 0, %0, c15, c12, 0" : : "r" (val)); +} + +static inline u32 armv6_pmnc_read(void) +{ + u32 val; + asm volatile("mrc p15, 0, %0, c15, c12, 0" : "=r" (val)); + return val; +} + +static void armv6_pmnc_reset_counter(unsigned int cnt) +{ + u32 val = 0; + switch (cnt) { + case CCNT: + asm volatile("mcr p15, 0, %0, c15, c12, 1" : : "r" (val)); + break; + case PMN0: + asm volatile("mcr p15, 0, %0, c15, c12, 2" : : "r" (val)); + break; + case PMN1: + asm volatile("mcr p15, 0, %0, c15, c12, 3" : : "r" (val)); + break; + } +} + +int gator_events_armv6_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + int i; + + pmnc_counters = 3; + + for (i = PMN0; i <= CCNT; i++) { + char buf[40]; + if (i == CCNT) { + snprintf(buf, sizeof buf, "ARM_%s_ccnt", pmnc_name); + } else { + snprintf(buf, sizeof buf, "ARM_%s_cnt%d", pmnc_name, i); + } + dir = gatorfs_mkdir(sb, root, buf); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &pmnc_enabled[i]); + gatorfs_create_ro_ulong(sb, dir, "key", &pmnc_key[i]); + if (i != CCNT) { + gatorfs_create_ulong(sb, dir, "event", &pmnc_event[i]); + } + } + + return 0; +} + +static int gator_events_armv6_online(int **buffer, bool migrate) +{ + unsigned int cnt, len = 0, cpu = smp_processor_id(); + u32 pmnc; + + if (armv6_pmnc_read() & PMCR_E) { + armv6_pmnc_write(armv6_pmnc_read() & ~PMCR_E); + } + + /* initialize PMNC, reset overflow, D bit, C bit and P bit. */ + armv6_pmnc_write(PMCR_OFL_PMN0 | PMCR_OFL_PMN1 | PMCR_OFL_CCNT | + PMCR_C | PMCR_P); + + /* configure control register */ + for (pmnc = 0, cnt = PMN0; cnt <= CCNT; cnt++) { + unsigned long event; + + if (!pmnc_enabled[cnt]) + continue; + + event = pmnc_event[cnt] & 255; + + // Set event (if destined for PMNx counters) + if (cnt == PMN0) { + pmnc |= event << 20; + } else if (cnt == PMN1) { + pmnc |= event << 12; + } + + // Reset counter + armv6_pmnc_reset_counter(cnt); + } + armv6_pmnc_write(pmnc | PMCR_E); + + // return zero values, no need to read as the counters were just reset + for (cnt = PMN0; cnt <= CCNT; cnt++) { + if (pmnc_enabled[cnt]) { + per_cpu(perfCnt, cpu)[len++] = pmnc_key[cnt]; + per_cpu(perfCnt, cpu)[len++] = 0; + } + } + + if (buffer) + *buffer = per_cpu(perfCnt, cpu); + + return len; +} + +static int gator_events_armv6_offline(int **buffer, bool migrate) +{ + unsigned int cnt; + + armv6_pmnc_write(armv6_pmnc_read() & ~PMCR_E); + for (cnt = PMN0; cnt <= CCNT; cnt++) { + armv6_pmnc_reset_counter(cnt); + } + + return 0; +} + +static void gator_events_armv6_stop(void) +{ + unsigned int cnt; + + for (cnt = PMN0; cnt <= CCNT; cnt++) { + pmnc_enabled[cnt] = 0; + pmnc_event[cnt] = 0; + } +} + +static int gator_events_armv6_read(int **buffer) +{ + int cnt, len = 0; + int cpu = smp_processor_id(); + + // a context switch may occur before the online hotplug event, thus need to check that the pmu is enabled + if (!(armv6_pmnc_read() & PMCR_E)) { + return 0; + } + + for (cnt = PMN0; cnt <= CCNT; cnt++) { + if (pmnc_enabled[cnt]) { + u32 value = 0; + switch (cnt) { + case CCNT: + asm volatile("mrc p15, 0, %0, c15, c12, 1" : "=r" (value)); + break; + case PMN0: + asm volatile("mrc p15, 0, %0, c15, c12, 2" : "=r" (value)); + break; + case PMN1: + asm volatile("mrc p15, 0, %0, c15, c12, 3" : "=r" (value)); + break; + } + armv6_pmnc_reset_counter(cnt); + + per_cpu(perfCnt, cpu)[len++] = pmnc_key[cnt]; + per_cpu(perfCnt, cpu)[len++] = value; + } + } + + if (buffer) + *buffer = per_cpu(perfCnt, cpu); + + return len; +} + +static struct gator_interface gator_events_armv6_interface = { + .create_files = gator_events_armv6_create_files, + .stop = gator_events_armv6_stop, + .online = gator_events_armv6_online, + .offline = gator_events_armv6_offline, + .read = gator_events_armv6_read, +}; + +int gator_events_armv6_init(void) +{ + unsigned int cnt; + + switch (gator_cpuid()) { + case ARM1136: + case ARM1156: + case ARM1176: + pmnc_name = "ARM11"; + break; + case ARM11MPCORE: + pmnc_name = "ARM11MPCore"; + break; + default: + return -1; + } + + for (cnt = PMN0; cnt <= CCNT; cnt++) { + pmnc_enabled[cnt] = 0; + pmnc_event[cnt] = 0; + pmnc_key[cnt] = gator_events_get_key(); + } + + return gator_events_install(&gator_events_armv6_interface); +} + +#endif diff --git a/drivers/gator/gator_events_armv7.c b/drivers/gator/gator_events_armv7.c new file mode 100644 index 00000000000000..30881c8fd3fd94 --- /dev/null +++ b/drivers/gator/gator_events_armv7.c @@ -0,0 +1,312 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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. + */ + +/* Disabling interrupts + * Many of the functions below disable interrupts via local_irq_save(). This disabling of interrupts is done to prevent any race conditions + * between multiple entities (e.g. hrtimer interrupts and event based interrupts) calling the same functions. As accessing the pmu involves + * several steps (disable, select, read, enable), these steps must be performed atomically. Normal synchronization routines cannot be used + * as these functions are being called from interrupt context. + */ + +#include "gator.h" + +// gator_events_perf_pmu.c is used if perf is supported +#if GATOR_NO_PERF_SUPPORT + +// Per-CPU PMNC: config reg +#define PMNC_E (1 << 0) /* Enable all counters */ +#define PMNC_P (1 << 1) /* Reset all counters */ +#define PMNC_C (1 << 2) /* Cycle counter reset */ +#define PMNC_MASK 0x3f /* Mask for writable bits */ + +// ccnt reg +#define CCNT_REG (1 << 31) + +#define CCNT 0 +#define CNT0 1 +#define CNTMAX (6+1) + +static const char *pmnc_name; +static int pmnc_counters; + +static unsigned long pmnc_enabled[CNTMAX]; +static unsigned long pmnc_event[CNTMAX]; +static unsigned long pmnc_key[CNTMAX]; + +static DEFINE_PER_CPU(int[CNTMAX * 2], perfCnt); + +inline void armv7_pmnc_write(u32 val) +{ + val &= PMNC_MASK; + asm volatile("mcr p15, 0, %0, c9, c12, 0" : : "r" (val)); +} + +inline u32 armv7_pmnc_read(void) +{ + u32 val; + asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val)); + return val; +} + +inline u32 armv7_ccnt_read(u32 reset_value) +{ + unsigned long flags; + u32 newval = -reset_value; + u32 den = CCNT_REG; + u32 val; + + local_irq_save(flags); + asm volatile("mcr p15, 0, %0, c9, c12, 2" : : "r" (den)); // disable + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r" (val)); // read + asm volatile("mcr p15, 0, %0, c9, c13, 0" : : "r" (newval)); // new value + asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (den)); // enable + local_irq_restore(flags); + + return val; +} + +inline u32 armv7_cntn_read(unsigned int cnt, u32 reset_value) +{ + unsigned long flags; + u32 newval = -reset_value; + u32 sel = (cnt - CNT0); + u32 den = 1 << sel; + u32 oldval; + + local_irq_save(flags); + asm volatile("mcr p15, 0, %0, c9, c12, 2" : : "r" (den)); // disable + asm volatile("mcr p15, 0, %0, c9, c12, 5" : : "r" (sel)); // select + asm volatile("mrc p15, 0, %0, c9, c13, 2" : "=r" (oldval)); // read + asm volatile("mcr p15, 0, %0, c9, c13, 2" : : "r" (newval)); // new value + asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (den)); // enable + local_irq_restore(flags); + + return oldval; +} + +static inline void armv7_pmnc_disable_interrupt(unsigned int cnt) +{ + u32 val = cnt ? (1 << (cnt - CNT0)) : (1 << 31); + asm volatile("mcr p15, 0, %0, c9, c14, 2" : : "r" (val)); +} + +inline u32 armv7_pmnc_reset_interrupt(void) +{ + // Get and reset overflow status flags + u32 flags; + asm volatile("mrc p15, 0, %0, c9, c12, 3" : "=r" (flags)); + flags &= 0x8000003f; + asm volatile("mcr p15, 0, %0, c9, c12, 3" : : "r" (flags)); + return flags; +} + +static inline u32 armv7_pmnc_enable_counter(unsigned int cnt) +{ + u32 val = cnt ? (1 << (cnt - CNT0)) : CCNT_REG; + asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (val)); + return cnt; +} + +static inline u32 armv7_pmnc_disable_counter(unsigned int cnt) +{ + u32 val = cnt ? (1 << (cnt - CNT0)) : CCNT_REG; + asm volatile("mcr p15, 0, %0, c9, c12, 2" : : "r" (val)); + return cnt; +} + +static inline int armv7_pmnc_select_counter(unsigned int cnt) +{ + u32 val = (cnt - CNT0); + asm volatile("mcr p15, 0, %0, c9, c12, 5" : : "r" (val)); + return cnt; +} + +static inline void armv7_pmnc_write_evtsel(unsigned int cnt, u32 val) +{ + if (armv7_pmnc_select_counter(cnt) == cnt) { + asm volatile("mcr p15, 0, %0, c9, c13, 1" : : "r" (val)); + } +} + +static int gator_events_armv7_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + int i; + + for (i = 0; i < pmnc_counters; i++) { + char buf[40]; + if (i == 0) { + snprintf(buf, sizeof buf, "ARM_%s_ccnt", pmnc_name); + } else { + snprintf(buf, sizeof buf, "ARM_%s_cnt%d", pmnc_name, i - 1); + } + dir = gatorfs_mkdir(sb, root, buf); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &pmnc_enabled[i]); + gatorfs_create_ro_ulong(sb, dir, "key", &pmnc_key[i]); + if (i > 0) { + gatorfs_create_ulong(sb, dir, "event", &pmnc_event[i]); + } + } + + return 0; +} + +static int gator_events_armv7_online(int **buffer, bool migrate) +{ + unsigned int cnt, len = 0, cpu = smp_processor_id(); + + if (armv7_pmnc_read() & PMNC_E) { + armv7_pmnc_write(armv7_pmnc_read() & ~PMNC_E); + } + + // Initialize & Reset PMNC: C bit and P bit + armv7_pmnc_write(PMNC_P | PMNC_C); + + // Reset overflow flags + armv7_pmnc_reset_interrupt(); + + for (cnt = CCNT; cnt < CNTMAX; cnt++) { + unsigned long event; + + if (!pmnc_enabled[cnt]) + continue; + + // Disable counter + armv7_pmnc_disable_counter(cnt); + + event = pmnc_event[cnt] & 255; + + // Set event (if destined for PMNx counters), we don't need to set the event if it's a cycle count + if (cnt != CCNT) + armv7_pmnc_write_evtsel(cnt, event); + + armv7_pmnc_disable_interrupt(cnt); + + // Reset counter + cnt ? armv7_cntn_read(cnt, 0) : armv7_ccnt_read(0); + + // Enable counter + armv7_pmnc_enable_counter(cnt); + } + + // enable + armv7_pmnc_write(armv7_pmnc_read() | PMNC_E); + + // return zero values, no need to read as the counters were just reset + for (cnt = 0; cnt < pmnc_counters; cnt++) { + if (pmnc_enabled[cnt]) { + per_cpu(perfCnt, cpu)[len++] = pmnc_key[cnt]; + per_cpu(perfCnt, cpu)[len++] = 0; + } + } + + if (buffer) + *buffer = per_cpu(perfCnt, cpu); + + return len; +} + +static int gator_events_armv7_offline(int **buffer, bool migrate) +{ + // disable all counters, including PMCCNTR; overflow IRQs will not be signaled + armv7_pmnc_write(armv7_pmnc_read() & ~PMNC_E); + + return 0; +} + +static void gator_events_armv7_stop(void) +{ + unsigned int cnt; + + for (cnt = CCNT; cnt < CNTMAX; cnt++) { + pmnc_enabled[cnt] = 0; + pmnc_event[cnt] = 0; + } +} + +static int gator_events_armv7_read(int **buffer) +{ + int cnt, len = 0; + int cpu = smp_processor_id(); + + // a context switch may occur before the online hotplug event, thus need to check that the pmu is enabled + if (!(armv7_pmnc_read() & PMNC_E)) { + return 0; + } + + for (cnt = 0; cnt < pmnc_counters; cnt++) { + if (pmnc_enabled[cnt]) { + int value; + if (cnt == CCNT) { + value = armv7_ccnt_read(0); + } else { + value = armv7_cntn_read(cnt, 0); + } + per_cpu(perfCnt, cpu)[len++] = pmnc_key[cnt]; + per_cpu(perfCnt, cpu)[len++] = value; + } + } + + if (buffer) + *buffer = per_cpu(perfCnt, cpu); + + return len; +} + +static struct gator_interface gator_events_armv7_interface = { + .create_files = gator_events_armv7_create_files, + .stop = gator_events_armv7_stop, + .online = gator_events_armv7_online, + .offline = gator_events_armv7_offline, + .read = gator_events_armv7_read, +}; + +int gator_events_armv7_init(void) +{ + unsigned int cnt; + + switch (gator_cpuid()) { + case CORTEX_A5: + pmnc_name = "Cortex-A5"; + pmnc_counters = 2; + break; + case CORTEX_A7: + pmnc_name = "Cortex-A7"; + pmnc_counters = 4; + break; + case CORTEX_A8: + pmnc_name = "Cortex-A8"; + pmnc_counters = 4; + break; + case CORTEX_A9: + pmnc_name = "Cortex-A9"; + pmnc_counters = 6; + break; + case CORTEX_A15: + pmnc_name = "Cortex-A15"; + pmnc_counters = 6; + break; + default: + return -1; + } + + pmnc_counters++; // CNT[n] + CCNT + + for (cnt = CCNT; cnt < CNTMAX; cnt++) { + pmnc_enabled[cnt] = 0; + pmnc_event[cnt] = 0; + pmnc_key[cnt] = gator_events_get_key(); + } + + return gator_events_install(&gator_events_armv7_interface); +} + +#endif diff --git a/drivers/gator/gator_events_block.c b/drivers/gator/gator_events_block.c new file mode 100644 index 00000000000000..691ef2574536e5 --- /dev/null +++ b/drivers/gator/gator_events_block.c @@ -0,0 +1,153 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 "gator.h" +#include + +#define BLOCK_RQ_WR 0 +#define BLOCK_RQ_RD 1 + +#define BLOCK_TOTAL (BLOCK_RQ_RD+1) + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) +#define EVENTWRITE REQ_RW +#else +#define EVENTWRITE REQ_WRITE +#endif + +static ulong block_rq_wr_enabled; +static ulong block_rq_rd_enabled; +static ulong block_rq_wr_key; +static ulong block_rq_rd_key; +static atomic_t blockCnt[BLOCK_TOTAL]; +static int blockGet[BLOCK_TOTAL * 4]; + +GATOR_DEFINE_PROBE(block_rq_complete, TP_PROTO(struct request_queue *q, struct request *rq)) +{ + int write, size; + + if (!rq) + return; + + write = rq->cmd_flags & EVENTWRITE; + size = rq->resid_len; + + if (!size) + return; + + if (write) { + if (block_rq_wr_enabled) { + atomic_add(size, &blockCnt[BLOCK_RQ_WR]); + } + } else { + if (block_rq_rd_enabled) { + atomic_add(size, &blockCnt[BLOCK_RQ_RD]); + } + } +} + +static int gator_events_block_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + + /* block_complete_wr */ + dir = gatorfs_mkdir(sb, root, "Linux_block_rq_wr"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &block_rq_wr_enabled); + gatorfs_create_ro_ulong(sb, dir, "key", &block_rq_wr_key); + + /* block_complete_rd */ + dir = gatorfs_mkdir(sb, root, "Linux_block_rq_rd"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &block_rq_rd_enabled); + gatorfs_create_ro_ulong(sb, dir, "key", &block_rq_rd_key); + + return 0; +} + +static int gator_events_block_start(void) +{ + // register tracepoints + if (block_rq_wr_enabled || block_rq_rd_enabled) + if (GATOR_REGISTER_TRACE(block_rq_complete)) + goto fail_block_rq_exit; + pr_debug("gator: registered block event tracepoints\n"); + + return 0; + + // unregister tracepoints on error +fail_block_rq_exit: + pr_err("gator: block event tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n"); + + return -1; +} + +static void gator_events_block_stop(void) +{ + if (block_rq_wr_enabled || block_rq_rd_enabled) + GATOR_UNREGISTER_TRACE(block_rq_complete); + pr_debug("gator: unregistered block event tracepoints\n"); + + block_rq_wr_enabled = 0; + block_rq_rd_enabled = 0; +} + +static int gator_events_block_read(int **buffer) +{ + int len, value, data = 0; + + if (!on_primary_core()) { + return 0; + } + + len = 0; + if (block_rq_wr_enabled && (value = atomic_read(&blockCnt[BLOCK_RQ_WR])) > 0) { + atomic_sub(value, &blockCnt[BLOCK_RQ_WR]); + blockGet[len++] = block_rq_wr_key; + blockGet[len++] = 0; // indicates to Streamline that value bytes were written now, not since the last message + blockGet[len++] = block_rq_wr_key; + blockGet[len++] = value; + data += value; + } + if (block_rq_rd_enabled && (value = atomic_read(&blockCnt[BLOCK_RQ_RD])) > 0) { + atomic_sub(value, &blockCnt[BLOCK_RQ_RD]); + blockGet[len++] = block_rq_rd_key; + blockGet[len++] = 0; // indicates to Streamline that value bytes were read now, not since the last message + blockGet[len++] = block_rq_rd_key; + blockGet[len++] = value; + data += value; + } + + if (buffer) + *buffer = blockGet; + + return len; +} + +static struct gator_interface gator_events_block_interface = { + .create_files = gator_events_block_create_files, + .start = gator_events_block_start, + .stop = gator_events_block_stop, + .read = gator_events_block_read, +}; + +int gator_events_block_init(void) +{ + block_rq_wr_enabled = 0; + block_rq_rd_enabled = 0; + + block_rq_wr_key = gator_events_get_key(); + block_rq_rd_key = gator_events_get_key(); + + return gator_events_install(&gator_events_block_interface); +} diff --git a/drivers/gator/gator_events_ccn-504.c b/drivers/gator/gator_events_ccn-504.c new file mode 100644 index 00000000000000..b89231967c75c7 --- /dev/null +++ b/drivers/gator/gator_events_ccn-504.c @@ -0,0 +1,346 @@ +/** + * Copyright (C) ARM Limited 2013. All rights reserved. + * + * 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 +#include + +#include "gator.h" + +#define NUM_REGIONS 256 +#define REGION_SIZE (64*1024) +#define REGION_DEBUG 1 +#define REGION_XP 64 +#define NUM_XPS 11 + +// DT (Debug) region +#define PMEVCNTSR0 0x0150 +#define PMCCNTRSR 0x0190 +#define PMCR 0x01A8 +#define PMSR 0x01B0 +#define PMSR_REQ 0x01B8 +#define PMSR_CLR 0x01C0 + +// XP region +#define DT_CONFIG 0x0300 +#define DT_CONTROL 0x0370 + +// Multiple +#define PMU_EVENT_SEL 0x0600 +#define OLY_ID 0xFF00 + +#define CCNT 4 +#define CNTMAX (CCNT + 1) + +#define get_pmu_event_id(event) (((event) >> 0) & 0xFF) +#define get_node_type(event) (((event) >> 8) & 0xFF) +#define get_region(event) (((event) >> 16) & 0xFF) + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) + +// From kernel/params.c +#define STANDARD_PARAM_DEF(name, type, format, tmptype, strtolfn) \ + int param_set_##name(const char *val, struct kernel_param *kp) \ + { \ + tmptype l; \ + int ret; \ + \ + if (!val) return -EINVAL; \ + ret = strtolfn(val, 0, &l); \ + if (ret == -EINVAL || ((type)l != l)) \ + return -EINVAL; \ + *((type *)kp->arg) = l; \ + return 0; \ + } \ + int param_get_##name(char *buffer, struct kernel_param *kp) \ + { \ + return sprintf(buffer, format, *((type *)kp->arg)); \ + } + +#else + +// From kernel/params.c +#define STANDARD_PARAM_DEF(name, type, format, tmptype, strtolfn) \ + int param_set_##name(const char *val, const struct kernel_param *kp) \ + { \ + tmptype l; \ + int ret; \ + \ + ret = strtolfn(val, 0, &l); \ + if (ret < 0 || ((type)l != l)) \ + return ret < 0 ? ret : -EINVAL; \ + *((type *)kp->arg) = l; \ + return 0; \ + } \ + int param_get_##name(char *buffer, const struct kernel_param *kp) \ + { \ + return scnprintf(buffer, PAGE_SIZE, format, \ + *((type *)kp->arg)); \ + } \ + struct kernel_param_ops param_ops_##name = { \ + .set = param_set_##name, \ + .get = param_get_##name, \ + }; \ + EXPORT_SYMBOL(param_set_##name); \ + EXPORT_SYMBOL(param_get_##name); \ + EXPORT_SYMBOL(param_ops_##name) + +#endif + +STANDARD_PARAM_DEF(u64, u64, "%llu", u64, strict_strtoull); + +// From include/linux/moduleparam.h +#define param_check_u64(name, p) __param_check(name, p, u64) + +MODULE_PARM_DESC(ccn504_addr, "CCN-504 physical base address"); +static u64 ccn504_addr = 0; +module_param(ccn504_addr, u64, 0444); + +static void __iomem *gator_events_ccn504_base; +static bool gator_events_ccn504_global_enabled; +static unsigned long gator_events_ccn504_enabled[CNTMAX]; +static unsigned long gator_events_ccn504_event[CNTMAX]; +static unsigned long gator_events_ccn504_key[CNTMAX]; +static int gator_events_ccn504_buffer[2*CNTMAX]; +static int gator_events_ccn504_prev[CNTMAX]; + +static void gator_events_ccn504_create_shutdown(void) +{ + if (gator_events_ccn504_base != NULL) { + iounmap(gator_events_ccn504_base); + } +} + +static int gator_events_ccn504_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + int i; + char buf[32]; + + for (i = 0; i < CNTMAX; ++i) { + if (i == CCNT) { + snprintf(buf, sizeof(buf), "CCN-504_ccnt"); + } else { + snprintf(buf, sizeof(buf), "CCN-504_cnt%i", i); + } + dir = gatorfs_mkdir(sb, root, buf); + if (!dir) { + return -1; + } + + gatorfs_create_ulong(sb, dir, "enabled", &gator_events_ccn504_enabled[i]); + if (i != CCNT) { + gatorfs_create_ulong(sb, dir, "event", &gator_events_ccn504_event[i]); + } + gatorfs_create_ro_ulong(sb, dir, "key", &gator_events_ccn504_key[i]); + } + + return 0; +} + +static void gator_events_ccn504_set_dt_config(int xp_node_id, int event_num, int value) +{ + u32 dt_config; + + dt_config = readl(gator_events_ccn504_base + (REGION_XP + xp_node_id)*REGION_SIZE + DT_CONFIG); + dt_config |= (value + event_num) << (4*event_num); + writel(dt_config, gator_events_ccn504_base + (REGION_XP + xp_node_id)*REGION_SIZE + DT_CONFIG); +} + +static int gator_events_ccn504_start(void) +{ + int i; + + gator_events_ccn504_global_enabled = 0; + for (i = 0; i < CNTMAX; ++i) { + if (gator_events_ccn504_enabled[i]) { + gator_events_ccn504_global_enabled = 1; + break; + } + } + + if (!gator_events_ccn504_global_enabled) { + return 0; + } + + memset(&gator_events_ccn504_prev, 0x80, sizeof(gator_events_ccn504_prev)); + + // Disable INTREQ on overflow + // [6] ovfl_intr_en = 0 + // perhaps set to 1? + // [5] cntr_rst = 0 + // No register paring + // [4:1] cntcfg = 0 + // Enable PMU features + // [0] pmu_en = 1 + writel(0x1, gator_events_ccn504_base + REGION_DEBUG*REGION_SIZE + PMCR); + + // Configure the XPs + for (i = 0; i < NUM_XPS; ++i) { + int dt_control; + + // Pass on all events + writel(0, gator_events_ccn504_base + (REGION_XP + i)*REGION_SIZE + DT_CONFIG); + + // Enable PMU capability + // [0] dt_enable = 1 + dt_control = readl(gator_events_ccn504_base + (REGION_XP + i)*REGION_SIZE + DT_CONTROL); + dt_control |= 0x1; + writel(dt_control, gator_events_ccn504_base + (REGION_XP + i)*REGION_SIZE + DT_CONTROL); + } + + // Assume no other pmu_event_sel registers are set + + // cycle counter does not need to be enabled + for (i = 0; i < CCNT; ++i) { + int pmu_event_id; + int node_type; + int region; + u32 pmu_event_sel; + u32 oly_id_whole; + u32 oly_id; + u32 node_id; + + if (!gator_events_ccn504_enabled[i]) { + continue; + } + + pmu_event_id = get_pmu_event_id(gator_events_ccn504_event[i]); + node_type = get_node_type(gator_events_ccn504_event[i]); + region = get_region(gator_events_ccn504_event[i]); + + // Verify the node_type + oly_id_whole = readl(gator_events_ccn504_base + region*REGION_SIZE + OLY_ID); + oly_id = oly_id_whole & 0x1F; + node_id = (oly_id_whole >> 8) & 0x7F; + if ((oly_id != node_type) || + ((node_type == 0x16) && ((oly_id != 0x14) && (oly_id != 0x15) && (oly_id != 0x16) && (oly_id != 0x18) && (oly_id != 0x19) && (oly_id != 0x1A)))) { + printk(KERN_ERR "gator: oly_id is 0x%x expected 0x%x\n", oly_id, node_type); + return -1; + } + + // Set the control register + pmu_event_sel = readl(gator_events_ccn504_base + region*REGION_SIZE + PMU_EVENT_SEL); + switch (node_type) { + case 0x08: // XP + pmu_event_sel |= pmu_event_id << (7*i); + gator_events_ccn504_set_dt_config(node_id, i, 0x4); + break; + case 0x04: // HN-F + case 0x16: // RN-I + case 0x10: // SBAS + pmu_event_sel |= pmu_event_id << (4*i); + gator_events_ccn504_set_dt_config(node_id/2, i, (node_id & 1) == 0 ? 0x8 : 0xC); + break; + } + writel(pmu_event_sel, gator_events_ccn504_base + region*REGION_SIZE + PMU_EVENT_SEL); + } + + return 0; +} + +static void gator_events_ccn504_stop(void) +{ + int i; + + if (!gator_events_ccn504_global_enabled) { + return; + } + + // cycle counter does not need to be disabled + for (i = 0; i < CCNT; ++i) { + int region; + + if (!gator_events_ccn504_enabled[i]) { + continue; + } + + region = get_region(gator_events_ccn504_event[i]); + + writel(0, gator_events_ccn504_base + region*REGION_SIZE + PMU_EVENT_SEL); + } + + // Clear dt_config + for (i = 0; i < NUM_XPS; ++i) { + writel(0, gator_events_ccn504_base + (REGION_XP + i)*REGION_SIZE + DT_CONFIG); + } +} + +static int gator_events_ccn504_read(int **buffer) +{ + int i; + int len = 0; + int value; + + if (!on_primary_core() || !gator_events_ccn504_global_enabled) { + return 0; + } + + // Verify the pmsr register is zero + while (readl(gator_events_ccn504_base + REGION_DEBUG*REGION_SIZE + PMSR) != 0); + + // Request a PMU snapshot + writel(1, gator_events_ccn504_base + REGION_DEBUG*REGION_SIZE + PMSR_REQ); + + // Wait for the snapshot + while (readl(gator_events_ccn504_base + REGION_DEBUG*REGION_SIZE + PMSR) == 0); + + // Read the shadow registers + for (i = 0; i < CNTMAX; ++i) { + if (!gator_events_ccn504_enabled[i]) { + continue; + } + + value = readl(gator_events_ccn504_base + REGION_DEBUG*REGION_SIZE + (i == CCNT ? PMCCNTRSR : PMEVCNTSR0 + 8*i)); + if (gator_events_ccn504_prev[i] != 0x80808080) { + gator_events_ccn504_buffer[len++] = gator_events_ccn504_key[i]; + gator_events_ccn504_buffer[len++] = value - gator_events_ccn504_prev[i]; + } + gator_events_ccn504_prev[i] = value; + + // Are the counters registers cleared when read? Is that what the cntr_rst bit on the pmcr register does? + } + + // Clear the PMU snapshot status + writel(1, gator_events_ccn504_base + REGION_DEBUG*REGION_SIZE + PMSR_CLR); + + if (buffer) + *buffer = gator_events_ccn504_buffer; + + return len; +} + +static struct gator_interface gator_events_ccn504_interface = { + .shutdown = gator_events_ccn504_create_shutdown, + .create_files = gator_events_ccn504_create_files, + .start = gator_events_ccn504_start, + .stop = gator_events_ccn504_stop, + .read = gator_events_ccn504_read, +}; + +int gator_events_ccn504_init(void) +{ + int i; + + if (ccn504_addr == 0) { + return -1; + } + + gator_events_ccn504_base = ioremap(ccn504_addr, NUM_REGIONS*REGION_SIZE); + if (gator_events_ccn504_base == NULL) { + printk(KERN_ERR "gator: ioremap returned NULL\n"); + return -1; + } + + for (i = 0; i < CNTMAX; ++i) { + gator_events_ccn504_enabled[i] = 0; + gator_events_ccn504_event[i] = 0; + gator_events_ccn504_key[i] = gator_events_get_key(); + } + + return gator_events_install(&gator_events_ccn504_interface); +} diff --git a/drivers/gator/gator_events_irq.c b/drivers/gator/gator_events_irq.c new file mode 100644 index 00000000000000..b11879a248f831 --- /dev/null +++ b/drivers/gator/gator_events_irq.c @@ -0,0 +1,165 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 "gator.h" +#include + +#define HARDIRQ 0 +#define SOFTIRQ 1 +#define TOTALIRQ (SOFTIRQ+1) + +static ulong hardirq_enabled; +static ulong softirq_enabled; +static ulong hardirq_key; +static ulong softirq_key; +static DEFINE_PER_CPU(atomic_t[TOTALIRQ], irqCnt); +static DEFINE_PER_CPU(int[TOTALIRQ * 2], irqGet); + +GATOR_DEFINE_PROBE(irq_handler_exit, + TP_PROTO(int irq, struct irqaction *action, int ret)) +{ + atomic_inc(&per_cpu(irqCnt, get_physical_cpu())[HARDIRQ]); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37) +GATOR_DEFINE_PROBE(softirq_exit, TP_PROTO(struct softirq_action *h, struct softirq_action *vec)) +#else +GATOR_DEFINE_PROBE(softirq_exit, TP_PROTO(unsigned int vec_nr)) +#endif +{ + atomic_inc(&per_cpu(irqCnt, get_physical_cpu())[SOFTIRQ]); +} + +static int gator_events_irq_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + + /* irq */ + dir = gatorfs_mkdir(sb, root, "Linux_irq_irq"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &hardirq_enabled); + gatorfs_create_ro_ulong(sb, dir, "key", &hardirq_key); + + /* soft irq */ + dir = gatorfs_mkdir(sb, root, "Linux_irq_softirq"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &softirq_enabled); + gatorfs_create_ro_ulong(sb, dir, "key", &softirq_key); + + return 0; +} + +static int gator_events_irq_online(int **buffer, bool migrate) +{ + int len = 0, cpu = get_physical_cpu(); + + // synchronization with the irq_exit functions is not necessary as the values are being reset + if (hardirq_enabled) { + atomic_set(&per_cpu(irqCnt, cpu)[HARDIRQ], 0); + per_cpu(irqGet, cpu)[len++] = hardirq_key; + per_cpu(irqGet, cpu)[len++] = 0; + } + + if (softirq_enabled) { + atomic_set(&per_cpu(irqCnt, cpu)[SOFTIRQ], 0); + per_cpu(irqGet, cpu)[len++] = softirq_key; + per_cpu(irqGet, cpu)[len++] = 0; + } + + if (buffer) + *buffer = per_cpu(irqGet, cpu); + + return len; +} + +static int gator_events_irq_start(void) +{ + // register tracepoints + if (hardirq_enabled) + if (GATOR_REGISTER_TRACE(irq_handler_exit)) + goto fail_hardirq_exit; + if (softirq_enabled) + if (GATOR_REGISTER_TRACE(softirq_exit)) + goto fail_softirq_exit; + pr_debug("gator: registered irq tracepoints\n"); + + return 0; + + // unregister tracepoints on error +fail_softirq_exit: + if (hardirq_enabled) + GATOR_UNREGISTER_TRACE(irq_handler_exit); +fail_hardirq_exit: + pr_err("gator: irq tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n"); + + return -1; +} + +static void gator_events_irq_stop(void) +{ + if (hardirq_enabled) + GATOR_UNREGISTER_TRACE(irq_handler_exit); + if (softirq_enabled) + GATOR_UNREGISTER_TRACE(softirq_exit); + pr_debug("gator: unregistered irq tracepoints\n"); + + hardirq_enabled = 0; + softirq_enabled = 0; +} + +static int gator_events_irq_read(int **buffer) +{ + int len, value; + int cpu = get_physical_cpu(); + + len = 0; + if (hardirq_enabled) { + value = atomic_read(&per_cpu(irqCnt, cpu)[HARDIRQ]); + atomic_sub(value, &per_cpu(irqCnt, cpu)[HARDIRQ]); + + per_cpu(irqGet, cpu)[len++] = hardirq_key; + per_cpu(irqGet, cpu)[len++] = value; + } + + if (softirq_enabled) { + value = atomic_read(&per_cpu(irqCnt, cpu)[SOFTIRQ]); + atomic_sub(value, &per_cpu(irqCnt, cpu)[SOFTIRQ]); + + per_cpu(irqGet, cpu)[len++] = softirq_key; + per_cpu(irqGet, cpu)[len++] = value; + } + + if (buffer) + *buffer = per_cpu(irqGet, cpu); + + return len; +} + +static struct gator_interface gator_events_irq_interface = { + .create_files = gator_events_irq_create_files, + .online = gator_events_irq_online, + .start = gator_events_irq_start, + .stop = gator_events_irq_stop, + .read = gator_events_irq_read, +}; + +int gator_events_irq_init(void) +{ + hardirq_key = gator_events_get_key(); + softirq_key = gator_events_get_key(); + + hardirq_enabled = 0; + softirq_enabled = 0; + + return gator_events_install(&gator_events_irq_interface); +} diff --git a/drivers/gator/gator_events_l2c-310.c b/drivers/gator/gator_events_l2c-310.c new file mode 100644 index 00000000000000..ee521af22517a0 --- /dev/null +++ b/drivers/gator/gator_events_l2c-310.c @@ -0,0 +1,208 @@ +/** + * l2c310 (L2 Cache Controller) event counters for gator + * + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 +#include +#include +#if defined(CONFIG_OF) +#include +#include +#endif +#include + +#include "gator.h" + +#define L2C310_COUNTERS_NUM 2 + +static struct { + unsigned long enabled; + unsigned long event; + unsigned long key; +} l2c310_counters[L2C310_COUNTERS_NUM]; + +static int l2c310_buffer[L2C310_COUNTERS_NUM * 2]; + +static void __iomem *l2c310_base; + +static void gator_events_l2c310_reset_counters(void) +{ + u32 val = readl(l2c310_base + L2X0_EVENT_CNT_CTRL); + + val |= ((1 << L2C310_COUNTERS_NUM) - 1) << 1; + + writel(val, l2c310_base + L2X0_EVENT_CNT_CTRL); +} + +static int gator_events_l2c310_create_files(struct super_block *sb, + struct dentry *root) +{ + int i; + + for (i = 0; i < L2C310_COUNTERS_NUM; i++) { + char buf[16]; + struct dentry *dir; + + snprintf(buf, sizeof(buf), "L2C-310_cnt%d", i); + dir = gatorfs_mkdir(sb, root, buf); + if (WARN_ON(!dir)) + return -1; + gatorfs_create_ulong(sb, dir, "enabled", + &l2c310_counters[i].enabled); + gatorfs_create_ulong(sb, dir, "event", + &l2c310_counters[i].event); + gatorfs_create_ro_ulong(sb, dir, "key", + &l2c310_counters[i].key); + } + + return 0; +} + +static int gator_events_l2c310_start(void) +{ + static const unsigned long l2x0_event_cntx_cfg[L2C310_COUNTERS_NUM] = { + L2X0_EVENT_CNT0_CFG, + L2X0_EVENT_CNT1_CFG, + }; + int i; + + /* Counter event sources */ + for (i = 0; i < L2C310_COUNTERS_NUM; i++) + writel((l2c310_counters[i].event & 0xf) << 2, + l2c310_base + l2x0_event_cntx_cfg[i]); + + gator_events_l2c310_reset_counters(); + + /* Event counter enable */ + writel(1, l2c310_base + L2X0_EVENT_CNT_CTRL); + + return 0; +} + +static void gator_events_l2c310_stop(void) +{ + /* Event counter disable */ + writel(0, l2c310_base + L2X0_EVENT_CNT_CTRL); +} + +static int gator_events_l2c310_read(int **buffer) +{ + static const unsigned long l2x0_event_cntx_val[L2C310_COUNTERS_NUM] = { + L2X0_EVENT_CNT0_VAL, + L2X0_EVENT_CNT1_VAL, + }; + int i; + int len = 0; + + if (!on_primary_core()) + return 0; + + for (i = 0; i < L2C310_COUNTERS_NUM; i++) { + if (l2c310_counters[i].enabled) { + l2c310_buffer[len++] = l2c310_counters[i].key; + l2c310_buffer[len++] = readl(l2c310_base + + l2x0_event_cntx_val[i]); + } + } + + /* l2c310 counters are saturating, not wrapping in case of overflow */ + gator_events_l2c310_reset_counters(); + + if (buffer) + *buffer = l2c310_buffer; + + return len; +} + +static struct gator_interface gator_events_l2c310_interface = { + .create_files = gator_events_l2c310_create_files, + .start = gator_events_l2c310_start, + .stop = gator_events_l2c310_stop, + .read = gator_events_l2c310_read, +}; + +#define L2C310_ADDR_PROBE (~0) + +MODULE_PARM_DESC(l2c310_addr, "L2C310 physical base address (0 to disable)"); +static unsigned long l2c310_addr = L2C310_ADDR_PROBE; +module_param(l2c310_addr, ulong, 0444); + +static void __iomem *gator_events_l2c310_probe(void) +{ + phys_addr_t variants[] = { +#if defined(CONFIG_ARCH_EXYNOS4) || defined(CONFIG_ARCH_S5PV310) + 0x10502000, +#endif +#if defined(CONFIG_ARCH_OMAP4) + 0x48242000, +#endif +#if defined(CONFIG_ARCH_TEGRA) + 0x50043000, +#endif +#if defined(CONFIG_ARCH_U8500) + 0xa0412000, +#endif +#if defined(CONFIG_ARCH_VEXPRESS) + 0x1e00a000, // A9x4 core tile (HBI-0191) + 0x2c0f0000, // New memory map tiles +#endif + }; + int i; + void __iomem *base; +#if defined(CONFIG_OF) + struct device_node *node = of_find_all_nodes(NULL); + + if (node) { + of_node_put(node); + + node = of_find_compatible_node(NULL, NULL, "arm,pl310-cache"); + base = of_iomap(node, 0); + of_node_put(node); + + return base; + } +#endif + + for (i = 0; i < ARRAY_SIZE(variants); i++) { + base = ioremap(variants[i], SZ_4K); + if (base) { + u32 cache_id = readl(base + L2X0_CACHE_ID); + + if ((cache_id & 0xff0003c0) == 0x410000c0) + return base; + + iounmap(base); + } + } + + return NULL; +} + +int gator_events_l2c310_init(void) +{ + int i; + + if (gator_cpuid() != CORTEX_A5 && gator_cpuid() != CORTEX_A9) + return -1; + + if (l2c310_addr == L2C310_ADDR_PROBE) + l2c310_base = gator_events_l2c310_probe(); + else if (l2c310_addr) + l2c310_base = ioremap(l2c310_addr, SZ_4K); + + if (!l2c310_base) + return -1; + + for (i = 0; i < L2C310_COUNTERS_NUM; i++) { + l2c310_counters[i].enabled = 0; + l2c310_counters[i].key = gator_events_get_key(); + } + + return gator_events_install(&gator_events_l2c310_interface); +} diff --git a/drivers/gator/gator_events_mali_4xx.c b/drivers/gator/gator_events_mali_4xx.c new file mode 100644 index 00000000000000..6719c1ec73a2e0 --- /dev/null +++ b/drivers/gator/gator_events_mali_4xx.c @@ -0,0 +1,723 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 "gator.h" + +#include +#include +#include + +#include "linux/mali_linux_trace.h" + +#include "gator_events_mali_common.h" +#include "gator_events_mali_4xx.h" + +/* + * There are (currently) four different variants of the comms between gator and Mali: + * 1 (deprecated): No software counter support + * 2 (deprecated): Tracepoint called for each separate s/w counter value as it appears + * 3 (default): Single tracepoint for all s/w counters in a bundle. + * Interface style 3 is the default if no other is specified. 1 and 2 will be eliminated when + * existing Mali DDKs are upgraded. + * 4. As above, but for the Utgard (Mali-450) driver. + */ + +#if !defined(GATOR_MALI_INTERFACE_STYLE) +#define GATOR_MALI_INTERFACE_STYLE (3) +#endif + +#if GATOR_MALI_INTERFACE_STYLE < 4 +#include "mali/mali_mjollnir_profiling_gator_api.h" +#else +#include "mali/mali_utgard_profiling_gator_api.h" +#endif + +/* + * Check that the MALI_SUPPORT define is set to one of the allowable device codes. + */ +#if (MALI_SUPPORT != MALI_4xx) +#error MALI_SUPPORT set to an invalid device code: expecting MALI_4xx +#endif + +/* gatorfs variables for counter enable state, + * the event the counter should count and the + * 'key' (a unique id set by gatord and returned + * by gator.ko) + */ +static unsigned long counter_enabled[NUMBER_OF_EVENTS]; +static unsigned long counter_event[NUMBER_OF_EVENTS]; +static unsigned long counter_key[NUMBER_OF_EVENTS]; + +/* The data we have recorded */ +static u32 counter_data[NUMBER_OF_EVENTS]; +/* The address to sample (or 0 if samples are sent to us) */ +static u32 *counter_address[NUMBER_OF_EVENTS]; + +/* An array used to return the data we recorded + * as key,value pairs hence the *2 + */ +static unsigned long counter_dump[NUMBER_OF_EVENTS * 2]; +static unsigned long counter_prev[NUMBER_OF_EVENTS]; + +/* Note whether tracepoints have been registered */ +static int trace_registered; + +/* + * These numbers define the actual numbers of each block type that exist in the system. Initially + * these are set to the maxima defined above; if the driver is capable of being queried (newer + * drivers only) then the values may be revised. + */ +static unsigned int n_vp_cores = MAX_NUM_VP_CORES; +static unsigned int n_l2_cores = MAX_NUM_L2_CACHE_CORES; +static unsigned int n_fp_cores = MAX_NUM_FP_CORES; + +/** + * Calculate the difference and handle the overflow. + */ +static u32 get_difference(u32 start, u32 end) +{ + if (start - end >= 0) { + return start - end; + } + + // Mali counters are unsigned 32 bit values that wrap. + return (4294967295u - end) + start; +} + +/** + * Returns non-zero if the given counter ID is an activity counter. + */ +static inline int is_activity_counter(unsigned int event_id) +{ + return (event_id >= FIRST_ACTIVITY_EVENT && + event_id <= LAST_ACTIVITY_EVENT); +} + +/** + * Returns non-zero if the given counter ID is a hardware counter. + */ +static inline int is_hw_counter(unsigned int event_id) +{ + return (event_id >= FIRST_HW_COUNTER && event_id <= LAST_HW_COUNTER); +} + +/* + * These are provided for utgard compatibility. + */ +typedef void _mali_profiling_get_mali_version_type(struct _mali_profiling_mali_version *values); +typedef u32 _mali_profiling_get_l2_counters_type(_mali_profiling_l2_counter_values *values); + +#if GATOR_MALI_INTERFACE_STYLE == 2 +/** + * Returns non-zero if the given counter ID is a software counter. + */ +static inline int is_sw_counter(unsigned int event_id) +{ + return (event_id >= FIRST_SW_COUNTER && event_id <= LAST_SW_COUNTER); +} +#endif + +#if GATOR_MALI_INTERFACE_STYLE == 2 +/* + * The Mali DDK uses s64 types to contain software counter values, but gator + * can only use a maximum of 32 bits. This function scales a software counter + * to an appropriate range. + */ +static u32 scale_sw_counter_value(unsigned int event_id, signed long long value) +{ + u32 scaled_value; + + switch (event_id) { + case COUNTER_GLES_UPLOAD_TEXTURE_TIME: + case COUNTER_GLES_UPLOAD_VBO_TIME: + scaled_value = (u32)div_s64(value, 1000000); + break; + default: + scaled_value = (u32)value; + break; + } + + return scaled_value; +} +#endif + +/* Probe for continuously sampled counter */ +#if 0 //WE_DONT_CURRENTLY_USE_THIS_SO_SUPPRESS_WARNING +GATOR_DEFINE_PROBE(mali_sample_address, TP_PROTO(unsigned int event_id, u32 *addr)) +{ + /* Turning on too many pr_debug statements in frequently called functions + * can cause stability and/or performance problems + */ + //pr_debug("gator: mali_sample_address %d %d\n", event_id, addr); + if (event_id >= ACTIVITY_VP && event_id <= COUNTER_FP3_C1) { + counter_address[event_id] = addr; + } +} +#endif + +/* Probe for hardware counter events */ +GATOR_DEFINE_PROBE(mali_hw_counter, TP_PROTO(unsigned int event_id, unsigned int value)) +{ + /* Turning on too many pr_debug statements in frequently called functions + * can cause stability and/or performance problems + */ + //pr_debug("gator: mali_hw_counter %d %d\n", event_id, value); + if (is_hw_counter(event_id)) { + counter_data[event_id] = value; + } +} + +#if GATOR_MALI_INTERFACE_STYLE == 2 +GATOR_DEFINE_PROBE(mali_sw_counter, TP_PROTO(unsigned int event_id, signed long long value)) +{ + if (is_sw_counter(event_id)) { + counter_data[event_id] = scale_sw_counter_value(event_id, value); + } +} +#endif /* GATOR_MALI_INTERFACE_STYLE == 2 */ + +#if GATOR_MALI_INTERFACE_STYLE >= 3 +GATOR_DEFINE_PROBE(mali_sw_counters, TP_PROTO(pid_t pid, pid_t tid, void *surface_id, unsigned int *counters)) +{ + u32 i; + + /* Copy over the values for those counters which are enabled. */ + for (i = FIRST_SW_COUNTER; i <= LAST_SW_COUNTER; i++) { + if (counter_enabled[i]) { + counter_data[i] = (u32)(counters[i - FIRST_SW_COUNTER]); + } + } +} +#endif /* GATOR_MALI_INTERFACE_STYLE >= 3 */ + +/** + * Create a single filesystem entry for a specified event. + * @param sb the superblock + * @param root Filesystem root + * @param name The name of the entry to create + * @param event The ID of the event + * @param create_event_item boolean indicating whether to create an 'event' filesystem entry. True to create. + * + * @return 0 if ok, non-zero if the create failed. + */ +static int create_fs_entry(struct super_block *sb, struct dentry *root, const char *name, int event, int create_event_item) +{ + struct dentry *dir; + + dir = gatorfs_mkdir(sb, root, name); + + if (!dir) { + return -1; + } + + if (create_event_item) { + gatorfs_create_ulong(sb, dir, "event", &counter_event[event]); + } + + gatorfs_create_ulong(sb, dir, "enabled", &counter_enabled[event]); + gatorfs_create_ro_ulong(sb, dir, "key", &counter_key[event]); + + return 0; +} + +#if GATOR_MALI_INTERFACE_STYLE > 3 +/* + * Read the version info structure if available + */ +static void initialise_version_info(void) +{ + _mali_profiling_get_mali_version_type *mali_profiling_get_mali_version_symbol; + + mali_profiling_get_mali_version_symbol = symbol_get(_mali_profiling_get_mali_version); + + if (mali_profiling_get_mali_version_symbol) { + struct _mali_profiling_mali_version version_info; + + pr_debug("gator: mali online _mali_profiling_get_mali_version symbol @ %p\n", + mali_profiling_get_mali_version_symbol); + + /* + * Revise the number of each different core type using information derived from the DDK. + */ + mali_profiling_get_mali_version_symbol(&version_info); + + n_fp_cores = version_info.num_of_fp_cores; + n_vp_cores = version_info.num_of_vp_cores; + n_l2_cores = version_info.num_of_l2_cores; + + /* Release the function - we're done with it. */ + symbol_put(_mali_profiling_get_mali_version); + } else { + printk("gator: mali online _mali_profiling_get_mali_version symbol not found\n"); + } +} +#endif + +static int create_files(struct super_block *sb, struct dentry *root) +{ + int event; + const char *mali_name = gator_mali_get_mali_name(); + + char buf[40]; + int core_id; + int counter_number; + + pr_debug("gator: Initialising counters with style = %d\n", GATOR_MALI_INTERFACE_STYLE); + +#if GATOR_MALI_INTERFACE_STYLE > 3 + /* + * Initialise first: this sets up the number of cores available (on compatible DDK versions). + * Ideally this would not need guarding but other parts of the code depend on the interface style being set + * correctly; if it is not then the system can enter an inconsistent state. + */ + initialise_version_info(); +#endif + + /* Vertex processor counters */ + for (core_id = 0; core_id < n_vp_cores; core_id++) { + int activity_counter_id = ACTIVITY_VP_0; + snprintf(buf, sizeof buf, "ARM_%s_VP_%d_active", mali_name, core_id); + if (create_fs_entry(sb, root, buf, activity_counter_id, 0) != 0) { + return -1; + } + + for (counter_number = 0; counter_number < 2; counter_number++) { + int counter_id = COUNTER_VP_0_C0 + (2 * core_id) + counter_number; + + snprintf(buf, sizeof buf, "ARM_%s_VP_%d_cnt%d", mali_name, core_id, counter_number); + if (create_fs_entry(sb, root, buf, counter_id, 1) != 0) { + return -1; + } + } + } + + /* Fragment processors' counters */ + for (core_id = 0; core_id < n_fp_cores; core_id++) { + int activity_counter_id = ACTIVITY_FP_0 + core_id; + + snprintf(buf, sizeof buf, "ARM_%s_FP_%d_active", mali_name, core_id); + if (create_fs_entry(sb, root, buf, activity_counter_id, 0) != 0) { + return -1; + } + + for (counter_number = 0; counter_number < 2; counter_number++) { + int counter_id = COUNTER_FP_0_C0 + (2 * core_id) + counter_number; + + snprintf(buf, sizeof buf, "ARM_%s_FP_%d_cnt%d", mali_name, core_id, counter_number); + if (create_fs_entry(sb, root, buf, counter_id, 1) != 0) { + return -1; + } + } + } + + /* L2 Cache counters */ + for (core_id = 0; core_id < n_l2_cores; core_id++) { + for (counter_number = 0; counter_number < 2; counter_number++) { + int counter_id = COUNTER_L2_0_C0 + (2 * core_id) + counter_number; + + snprintf(buf, sizeof buf, "ARM_%s_L2_%d_cnt%d", mali_name, core_id, counter_number); + if (create_fs_entry(sb, root, buf, counter_id, 1) != 0) { + return -1; + } + } + } + + /* Now set up the software counter entries */ + for (event = FIRST_SW_COUNTER; event <= LAST_SW_COUNTER; event++) { + snprintf(buf, sizeof(buf), "ARM_%s_SW_%d", mali_name, event - FIRST_SW_COUNTER); + + if (create_fs_entry(sb, root, buf, event, 0) != 0) { + return -1; + } + } + + /* Now set up the special counter entries */ + snprintf(buf, sizeof(buf), "ARM_%s_Filmstrip_cnt0", mali_name); + if (create_fs_entry(sb, root, buf, COUNTER_FILMSTRIP, 1) != 0) { + return -1; + } + +#ifdef DVFS_REPORTED_BY_DDK + snprintf(buf, sizeof(buf), "ARM_%s_Frequency", mali_name); + if (create_fs_entry(sb, root, buf, COUNTER_FREQUENCY, 1) != 0) { + return -1; + } + + snprintf(buf, sizeof(buf), "ARM_%s_Voltage", mali_name); + if (create_fs_entry(sb, root, buf, COUNTER_VOLTAGE, 1) != 0) { + return -1; + } +#endif + + return 0; +} + +/* + * Local store for the get_counters entry point into the DDK. + * This is stored here since it is used very regularly. + */ +static mali_profiling_get_counters_type *mali_get_counters = NULL; +static _mali_profiling_get_l2_counters_type *mali_get_l2_counters = NULL; + +/* + * Examine list of counters between two index limits and determine if any one is enabled. + * Returns 1 if any counter is enabled, 0 if none is. + */ +static int is_any_counter_enabled(unsigned int first_counter, unsigned int last_counter) +{ + unsigned int i; + + for (i = first_counter; i <= last_counter; i++) { + if (counter_enabled[i]) { + return 1; /* At least one counter is enabled */ + } + } + + return 0; /* No s/w counters enabled */ +} + +static void init_counters(unsigned int from_counter, unsigned int to_counter) +{ + unsigned int counter_id; + + /* If a Mali driver is present and exporting the appropriate symbol + * then we can request the HW counters (of which there are only 2) + * be configured to count the desired events + */ + mali_profiling_set_event_type *mali_set_hw_event; + + mali_set_hw_event = symbol_get(_mali_profiling_set_event); + + if (mali_set_hw_event) { + pr_debug("gator: mali online _mali_profiling_set_event symbol @ %p\n", mali_set_hw_event); + + for (counter_id = from_counter; counter_id <= to_counter; counter_id++) { + if (counter_enabled[counter_id]) { + mali_set_hw_event(counter_id, counter_event[counter_id]); + } else { + mali_set_hw_event(counter_id, 0xFFFFFFFF); + } + } + + symbol_put(_mali_profiling_set_event); + } else { + printk("gator: mali online _mali_profiling_set_event symbol not found\n"); + } +} + +static void mali_counter_initialize(void) +{ + int i; + int core_id; + + mali_profiling_control_type *mali_control; + + init_counters(COUNTER_L2_0_C0, COUNTER_L2_0_C0 + (2 * n_l2_cores) - 1); + init_counters(COUNTER_VP_0_C0, COUNTER_VP_0_C0 + (2 * n_vp_cores) - 1); + init_counters(COUNTER_FP_0_C0, COUNTER_FP_0_C0 + (2 * n_fp_cores) - 1); + + /* Generic control interface for Mali DDK. */ + mali_control = symbol_get(_mali_profiling_control); + if (mali_control) { + /* The event attribute in the XML file keeps the actual frame rate. */ + unsigned int rate = counter_event[COUNTER_FILMSTRIP] & 0xff; + unsigned int resize_factor = (counter_event[COUNTER_FILMSTRIP] >> 8) & 0xff; + + pr_debug("gator: mali online _mali_profiling_control symbol @ %p\n", mali_control); + + mali_control(SW_COUNTER_ENABLE, (is_any_counter_enabled(FIRST_SW_COUNTER, LAST_SW_COUNTER) ? 1 : 0)); + mali_control(FBDUMP_CONTROL_ENABLE, (counter_enabled[COUNTER_FILMSTRIP] ? 1 : 0)); + mali_control(FBDUMP_CONTROL_RATE, rate); + mali_control(FBDUMP_CONTROL_RESIZE_FACTOR, resize_factor); + + pr_debug("gator: sent mali_control enabled=%d, rate=%d\n", (counter_enabled[COUNTER_FILMSTRIP] ? 1 : 0), rate); + + symbol_put(_mali_profiling_control); + } else { + printk("gator: mali online _mali_profiling_control symbol not found\n"); + } + + mali_get_counters = symbol_get(_mali_profiling_get_counters); + if (mali_get_counters) { + pr_debug("gator: mali online _mali_profiling_get_counters symbol @ %p\n", mali_get_counters); + + } else { + pr_debug("gator WARNING: mali _mali_profiling_get_counters symbol not defined"); + } + + mali_get_l2_counters = symbol_get(_mali_profiling_get_l2_counters); + if (mali_get_l2_counters) { + pr_debug("gator: mali online _mali_profiling_get_l2_counters symbol @ %p\n", mali_get_l2_counters); + + } else { + pr_debug("gator WARNING: mali _mali_profiling_get_l2_counters symbol not defined"); + } + + if (!mali_get_counters && !mali_get_l2_counters) { + pr_debug("gator: WARNING: no L2 counters available"); + n_l2_cores = 0; + } + + for (core_id = 0; core_id < n_l2_cores; core_id++) { + int counter_id = COUNTER_L2_0_C0 + (2 * core_id); + counter_prev[counter_id] = 0; + counter_prev[counter_id + 1] = 0; + } + + /* Clear counters in the start */ + for (i = 0; i < NUMBER_OF_EVENTS; i++) { + counter_data[i] = 0; + } +} + +static void mali_counter_deinitialize(void) +{ + mali_profiling_set_event_type *mali_set_hw_event; + mali_profiling_control_type *mali_control; + + mali_set_hw_event = symbol_get(_mali_profiling_set_event); + + if (mali_set_hw_event) { + int i; + + pr_debug("gator: mali offline _mali_profiling_set_event symbol @ %p\n", mali_set_hw_event); + for (i = FIRST_HW_COUNTER; i <= LAST_HW_COUNTER; i++) { + mali_set_hw_event(i, 0xFFFFFFFF); + } + + symbol_put(_mali_profiling_set_event); + } else { + printk("gator: mali offline _mali_profiling_set_event symbol not found\n"); + } + + /* Generic control interface for Mali DDK. */ + mali_control = symbol_get(_mali_profiling_control); + + if (mali_control) { + pr_debug("gator: mali offline _mali_profiling_control symbol @ %p\n", mali_control); + + /* Reset the DDK state - disable counter collection */ + mali_control(SW_COUNTER_ENABLE, 0); + + mali_control(FBDUMP_CONTROL_ENABLE, 0); + + symbol_put(_mali_profiling_control); + } else { + printk("gator: mali offline _mali_profiling_control symbol not found\n"); + } + + if (mali_get_counters) { + symbol_put(_mali_profiling_get_counters); + } + + if (mali_get_l2_counters) { + symbol_put(_mali_profiling_get_l2_counters); + } +} + +static int start(void) +{ + // register tracepoints + if (GATOR_REGISTER_TRACE(mali_hw_counter)) { + printk("gator: mali_hw_counter tracepoint failed to activate\n"); + return -1; + } + +#if GATOR_MALI_INTERFACE_STYLE == 1 + /* None. */ +#elif GATOR_MALI_INTERFACE_STYLE == 2 + /* For patched Mali driver. */ + if (GATOR_REGISTER_TRACE(mali_sw_counter)) { + printk("gator: mali_sw_counter tracepoint failed to activate\n"); + return -1; + } +#elif GATOR_MALI_INTERFACE_STYLE >= 3 + /* For Mali drivers with built-in support. */ + if (GATOR_REGISTER_TRACE(mali_sw_counters)) { + printk("gator: mali_sw_counters tracepoint failed to activate\n"); + return -1; + } +#else +#error Unknown GATOR_MALI_INTERFACE_STYLE option. +#endif + + trace_registered = 1; + + mali_counter_initialize(); + return 0; +} + +static void stop(void) +{ + unsigned int cnt; + + pr_debug("gator: mali stop\n"); + + if (trace_registered) { + GATOR_UNREGISTER_TRACE(mali_hw_counter); + +#if GATOR_MALI_INTERFACE_STYLE == 1 + /* None. */ +#elif GATOR_MALI_INTERFACE_STYLE == 2 + /* For patched Mali driver. */ + GATOR_UNREGISTER_TRACE(mali_sw_counter); +#elif GATOR_MALI_INTERFACE_STYLE >= 3 + /* For Mali drivers with built-in support. */ + GATOR_UNREGISTER_TRACE(mali_sw_counters); +#else +#error Unknown GATOR_MALI_INTERFACE_STYLE option. +#endif + + pr_debug("gator: mali timeline tracepoint deactivated\n"); + + trace_registered = 0; + } + + for (cnt = 0; cnt < NUMBER_OF_EVENTS; cnt++) { + counter_enabled[cnt] = 0; + counter_event[cnt] = 0; + counter_address[cnt] = NULL; + } + + mali_counter_deinitialize(); +} + +static void dump_counters(unsigned int from_counter, unsigned int to_counter, unsigned int *len) +{ + unsigned int counter_id; + + for (counter_id = from_counter; counter_id <= to_counter; counter_id++) { + if (counter_enabled[counter_id]) { + counter_dump[(*len)++] = counter_key[counter_id]; + counter_dump[(*len)++] = counter_data[counter_id]; + + counter_data[counter_id] = 0; + } + } +} + +static int read(int **buffer) +{ + int len = 0; + + if (!on_primary_core()) + return 0; + + // Read the L2 C0 and C1 here. + if (n_l2_cores > 0 && is_any_counter_enabled(COUNTER_L2_0_C0, COUNTER_L2_0_C0 + (2 * n_l2_cores))) { + unsigned int unavailable_l2_caches = 0; + _mali_profiling_l2_counter_values cache_values; + unsigned int cache_id; + struct _mali_profiling_core_counters *per_core; + + /* Poke the driver to get the counter values - older style; only one L2 cache */ + if (mali_get_l2_counters) { + unavailable_l2_caches = mali_get_l2_counters(&cache_values); + } else if (mali_get_counters) { + per_core = &cache_values.cores[0]; + mali_get_counters(&per_core->source0, &per_core->value0, &per_core->source1, &per_core->value1); + } else { + /* This should never happen, as n_l2_caches is only set > 0 if one of the above functions is found. */ + } + + /* Fill in the two cache counter values for each cache block. */ + for (cache_id = 0; cache_id < n_l2_cores; cache_id++) { + unsigned int counter_id_0 = COUNTER_L2_0_C0 + (2 * cache_id); + unsigned int counter_id_1 = counter_id_0 + 1; + + if ((1 << cache_id) & unavailable_l2_caches) { + continue; /* This cache is unavailable (powered-off, possibly). */ + } + + per_core = &cache_values.cores[cache_id]; + + if (counter_enabled[counter_id_0]) { + // Calculate and save src0's counter val0 + counter_dump[len++] = counter_key[counter_id_0]; + counter_dump[len++] = get_difference(per_core->value0, counter_prev[counter_id_0]); + } + + if (counter_enabled[counter_id_1]) { + // Calculate and save src1's counter val1 + counter_dump[len++] = counter_key[counter_id_1]; + counter_dump[len++] = get_difference(per_core->value1, counter_prev[counter_id_1]); + } + + // Save the previous values for the counters. + counter_prev[counter_id_0] = per_core->value0; + counter_prev[counter_id_1] = per_core->value1; + } + } + + /* Process other (non-timeline) counters. */ + dump_counters(COUNTER_VP_0_C0, COUNTER_VP_0_C0 + (2 * n_vp_cores) - 1, &len); + dump_counters(COUNTER_FP_0_C0, COUNTER_FP_0_C0 + (2 * n_fp_cores) - 1, &len); + + dump_counters(FIRST_SW_COUNTER, LAST_SW_COUNTER, &len); + +#ifdef DVFS_REPORTED_BY_DDK + { + int cnt; + /* + * Add in the voltage and frequency counters if enabled. Note that, since these are + * actually passed as events, the counter value should not be cleared. + */ + cnt = COUNTER_FREQUENCY; + if (counter_enabled[cnt]) { + counter_dump[len++] = counter_key[cnt]; + counter_dump[len++] = counter_data[cnt]; + } + + cnt = COUNTER_VOLTAGE; + if (counter_enabled[cnt]) { + counter_dump[len++] = counter_key[cnt]; + counter_dump[len++] = counter_data[cnt]; + } + } +#endif + + if (buffer) { + *buffer = (int *)counter_dump; + } + + return len; +} + +static struct gator_interface gator_events_mali_interface = { + .create_files = create_files, + .start = start, + .stop = stop, + .read = read, +}; + +extern void gator_events_mali_log_dvfs_event(unsigned int frequency_mhz, unsigned int voltage_mv) +{ +#ifdef DVFS_REPORTED_BY_DDK + counter_data[COUNTER_FREQUENCY] = frequency_mhz; + counter_data[COUNTER_VOLTAGE] = voltage_mv; +#endif +} + +int gator_events_mali_init(void) +{ + unsigned int cnt; + + pr_debug("gator: mali init\n"); + + for (cnt = 0; cnt < NUMBER_OF_EVENTS; cnt++) { + counter_enabled[cnt] = 0; + counter_event[cnt] = 0; + counter_key[cnt] = gator_events_get_key(); + counter_address[cnt] = NULL; + counter_data[cnt] = 0; + } + + trace_registered = 0; + + return gator_events_install(&gator_events_mali_interface); +} diff --git a/drivers/gator/gator_events_mali_4xx.h b/drivers/gator/gator_events_mali_4xx.h new file mode 100644 index 00000000000000..413ad0ffe79431 --- /dev/null +++ b/drivers/gator/gator_events_mali_4xx.h @@ -0,0 +1,18 @@ +/** + * Copyright (C) ARM Limited 2011-2013. All rights reserved. + * + * 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. + * + */ + +/* + * Header contains common definitions for the Mali-4xx processors. + */ +#if !defined(GATOR_EVENTS_MALI_4xx_H) +#define GATOR_EVENTS_MALI_4xx_H + +extern void gator_events_mali_log_dvfs_event(unsigned int d0, unsigned int d1); + +#endif /* GATOR_EVENTS_MALI_4xx_H */ diff --git a/drivers/gator/gator_events_mali_common.c b/drivers/gator/gator_events_mali_common.c new file mode 100644 index 00000000000000..466ca1683c7e99 --- /dev/null +++ b/drivers/gator/gator_events_mali_common.c @@ -0,0 +1,81 @@ +/** + * Copyright (C) ARM Limited 2012-2013. All rights reserved. + * + * 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 "gator_events_mali_common.h" + +static u32 gator_mali_get_id(void) +{ + return MALI_SUPPORT; +} + +extern const char *gator_mali_get_mali_name(void) +{ + u32 id = gator_mali_get_id(); + + switch (id) { + case MALI_T6xx: + return "Mali-T6xx"; + case MALI_4xx: + return "Mali-4xx"; + default: + pr_debug("gator: Mali-T6xx: unknown Mali ID (%d)\n", id); + return "Mali-Unknown"; + } +} + +extern int gator_mali_create_file_system(const char *mali_name, const char *event_name, struct super_block *sb, struct dentry *root, mali_counter *counter, unsigned long *event) +{ + int err; + char buf[255]; + struct dentry *dir; + + /* If the counter name is empty ignore it */ + if (strlen(event_name) != 0) { + /* Set up the filesystem entry for this event. */ + snprintf(buf, sizeof(buf), "ARM_%s_%s", mali_name, event_name); + + dir = gatorfs_mkdir(sb, root, buf); + + if (dir == NULL) { + pr_debug("gator: Mali-T6xx: error creating file system for: %s (%s)", event_name, buf); + return -1; + } + + err = gatorfs_create_ulong(sb, dir, "enabled", &counter->enabled); + if (err != 0) { + pr_debug("gator: Mali-T6xx: error calling gatorfs_create_ulong for: %s (%s)", event_name, buf); + return -1; + } + err = gatorfs_create_ro_ulong(sb, dir, "key", &counter->key); + if (err != 0) { + pr_debug("gator: Mali-T6xx: error calling gatorfs_create_ro_ulong for: %s (%s)", event_name, buf); + return -1; + } + if (event != NULL) { + err = gatorfs_create_ulong(sb, dir, "event", event); + if (err != 0) { + pr_debug("gator: Mali-T6xx: error calling gatorfs_create_ro_ulong for: %s (%s)", event_name, buf); + return -1; + } + } + } + + return 0; +} + +extern void gator_mali_initialise_counters(mali_counter counters[], unsigned int n_counters) +{ + unsigned int cnt; + + for (cnt = 0; cnt < n_counters; cnt++) { + mali_counter *counter = &counters[cnt]; + + counter->key = gator_events_get_key(); + counter->enabled = 0; + } +} diff --git a/drivers/gator/gator_events_mali_common.h b/drivers/gator/gator_events_mali_common.h new file mode 100644 index 00000000000000..509f9b61884a3c --- /dev/null +++ b/drivers/gator/gator_events_mali_common.h @@ -0,0 +1,86 @@ +/** + * Copyright (C) ARM Limited 2012-2013. All rights reserved. + * + * 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. + * + */ + +#if !defined(GATOR_EVENTS_MALI_COMMON_H) +#define GATOR_EVENTS_MALI_COMMON_H + +#include "gator.h" + +#include +#include +#include +#include +#include + +/* Device codes for each known GPU */ +#define MALI_4xx (0x0b07) +#define MALI_T6xx (0x0056) + +/* Ensure that MALI_SUPPORT has been defined to something. */ +#ifndef MALI_SUPPORT +#error MALI_SUPPORT not defined! +#endif + +/* Values for the supported activity event types */ +#define ACTIVITY_START (1) +#define ACTIVITY_STOP (2) + +/* + * Runtime state information for a counter. + */ +typedef struct { + unsigned long key; /* 'key' (a unique id set by gatord and returned by gator.ko) */ + unsigned long enabled; /* counter enable state */ +} mali_counter; + +/* + * Mali-4xx + */ +typedef int mali_profiling_set_event_type(unsigned int, int); +typedef void mali_profiling_control_type(unsigned int, unsigned int); +typedef void mali_profiling_get_counters_type(unsigned int *, unsigned int *, unsigned int *, unsigned int *); + +/* + * Driver entry points for functions called directly by gator. + */ +extern int _mali_profiling_set_event(unsigned int, int); +extern void _mali_profiling_control(unsigned int, unsigned int); +extern void _mali_profiling_get_counters(unsigned int *, unsigned int *, unsigned int *, unsigned int *); + +/** + * Returns a name which identifies the GPU type (eg Mali-4xx, Mali-T6xx). + * + * @return The name as a constant string. + */ +extern const char *gator_mali_get_mali_name(void); + +/** + * Creates a filesystem entry under /dev/gator relating to the specified event name and key, and + * associate the key/enable values with this entry point. + * + * @param mali_name A name related to the type of GPU, obtained from a call to gator_mali_get_mali_name() + * @param event_name The name of the event. + * @param sb Linux super block + * @param root Directory under which the entry will be created. + * @param counter_key Ptr to location which will be associated with the counter key. + * @param counter_enabled Ptr to location which will be associated with the counter enable state. + * + * @return 0 if entry point was created, non-zero if not. + */ +extern int gator_mali_create_file_system(const char *mali_name, const char *event_name, struct super_block *sb, struct dentry *root, mali_counter *counter, unsigned long *event); + +/** + * Initializes the counter array. + * + * @param keys The array of counters + * @param n_counters The number of entries in each of the arrays. + */ +extern void gator_mali_initialise_counters(mali_counter counters[], unsigned int n_counters); + +#endif /* GATOR_EVENTS_MALI_COMMON_H */ diff --git a/drivers/gator/gator_events_mali_t6xx.c b/drivers/gator/gator_events_mali_t6xx.c new file mode 100644 index 00000000000000..7bf7d6a6dbf9b0 --- /dev/null +++ b/drivers/gator/gator_events_mali_t6xx.c @@ -0,0 +1,560 @@ +/** + * Copyright (C) ARM Limited 2011-2013. All rights reserved. + * + * 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 "gator.h" + +#include +#include +#include +#include +#include + +#include "linux/mali_linux_trace.h" + +#include "gator_events_mali_common.h" + +/* + * Check that the MALI_SUPPORT define is set to one of the allowable device codes. + */ +#if (MALI_SUPPORT != MALI_T6xx) +#error MALI_SUPPORT set to an invalid device code: expecting MALI_T6xx +#endif + +/* Counters for Mali-T6xx: + * + * - Timeline events + * They are tracepoints, but instead of reporting a number they report a START/STOP event. + * They are reported in Streamline as number of microseconds while that particular counter was active. + * + * - SW counters + * They are tracepoints reporting a particular number. + * They are accumulated in sw_counter_data array until they are passed to Streamline, then they are zeroed. + * + * - Accumulators + * They are the same as software counters but their value is not zeroed. + */ + +/* Timeline (start/stop) activity */ +static const char *timeline_event_names[] = { + "PM_SHADER_0", + "PM_SHADER_1", + "PM_SHADER_2", + "PM_SHADER_3", + "PM_SHADER_4", + "PM_SHADER_5", + "PM_SHADER_6", + "PM_SHADER_7", + "PM_TILER_0", + "PM_L2_0", + "PM_L2_1", + "MMU_AS_0", + "MMU_AS_1", + "MMU_AS_2", + "MMU_AS_3" +}; + +enum { + PM_SHADER_0 = 0, + PM_SHADER_1, + PM_SHADER_2, + PM_SHADER_3, + PM_SHADER_4, + PM_SHADER_5, + PM_SHADER_6, + PM_SHADER_7, + PM_TILER_0, + PM_L2_0, + PM_L2_1, + MMU_AS_0, + MMU_AS_1, + MMU_AS_2, + MMU_AS_3 +}; +/* The number of shader blocks in the enum above */ +#define NUM_PM_SHADER (8) + +/* Software Counters */ +static const char *software_counter_names[] = { + "MMU_PAGE_FAULT_0", + "MMU_PAGE_FAULT_1", + "MMU_PAGE_FAULT_2", + "MMU_PAGE_FAULT_3" +}; + +enum { + MMU_PAGE_FAULT_0 = 0, + MMU_PAGE_FAULT_1, + MMU_PAGE_FAULT_2, + MMU_PAGE_FAULT_3 +}; + +/* Software Counters */ +static const char *accumulators_names[] = { + "TOTAL_ALLOC_PAGES" +}; + +enum { + TOTAL_ALLOC_PAGES = 0 +}; + +#define FIRST_TIMELINE_EVENT (0) +#define NUMBER_OF_TIMELINE_EVENTS (sizeof(timeline_event_names) / sizeof(timeline_event_names[0])) +#define FIRST_SOFTWARE_COUNTER (FIRST_TIMELINE_EVENT + NUMBER_OF_TIMELINE_EVENTS) +#define NUMBER_OF_SOFTWARE_COUNTERS (sizeof(software_counter_names) / sizeof(software_counter_names[0])) +#define FIRST_ACCUMULATOR (FIRST_SOFTWARE_COUNTER + NUMBER_OF_SOFTWARE_COUNTERS) +#define NUMBER_OF_ACCUMULATORS (sizeof(accumulators_names) / sizeof(accumulators_names[0])) +#define FILMSTRIP (NUMBER_OF_TIMELINE_EVENTS + NUMBER_OF_SOFTWARE_COUNTERS + NUMBER_OF_ACCUMULATORS) +#define NUMBER_OF_EVENTS (NUMBER_OF_TIMELINE_EVENTS + NUMBER_OF_SOFTWARE_COUNTERS + NUMBER_OF_ACCUMULATORS + 1) + +/* + * gatorfs variables for counter enable state + */ +static mali_counter counters[NUMBER_OF_EVENTS]; +static unsigned long filmstrip_event; + +/* An array used to return the data we recorded + * as key,value pairs hence the *2 + */ +static unsigned long counter_dump[NUMBER_OF_EVENTS * 2]; + +/* + * Array holding counter start times (in ns) for each counter. A zero here + * indicates that the activity monitored by this counter is not running. + */ +static struct timespec timeline_event_starttime[NUMBER_OF_TIMELINE_EVENTS]; + +/* The data we have recorded */ +static unsigned int timeline_data[NUMBER_OF_TIMELINE_EVENTS]; +static unsigned int sw_counter_data[NUMBER_OF_SOFTWARE_COUNTERS]; +static unsigned int accumulators_data[NUMBER_OF_ACCUMULATORS]; + +/* Hold the previous timestamp, used to calculate the sample interval. */ +static struct timespec prev_timestamp; + +/** + * Returns the timespan (in microseconds) between the two specified timestamps. + * + * @param start Ptr to the start timestamp + * @param end Ptr to the end timestamp + * + * @return Number of microseconds between the two timestamps (can be negative if start follows end). + */ +static inline long get_duration_us(const struct timespec *start, const struct timespec *end) +{ + long event_duration_us = (end->tv_nsec - start->tv_nsec) / 1000; + event_duration_us += (end->tv_sec - start->tv_sec) * 1000000; + + return event_duration_us; +} + +static void record_timeline_event(unsigned int timeline_index, unsigned int type) +{ + struct timespec event_timestamp; + struct timespec *event_start = &timeline_event_starttime[timeline_index]; + + switch (type) { + case ACTIVITY_START: + /* Get the event time... */ + getnstimeofday(&event_timestamp); + + /* Remember the start time if the activity is not already started */ + if (event_start->tv_sec == 0) { + *event_start = event_timestamp; /* Structure copy */ + } + break; + + case ACTIVITY_STOP: + /* if the counter was started... */ + if (event_start->tv_sec != 0) { + /* Get the event time... */ + getnstimeofday(&event_timestamp); + + /* Accumulate the duration in us */ + timeline_data[timeline_index] += get_duration_us(event_start, &event_timestamp); + + /* Reset the start time to indicate the activity is stopped. */ + event_start->tv_sec = 0; + } + break; + + default: + /* Other activity events are ignored. */ + break; + } +} + +/* + * Documentation about the following tracepoints is in mali_linux_trace.h + */ + +GATOR_DEFINE_PROBE(mali_pm_status, TP_PROTO(unsigned int event_id, unsigned long long value)) +{ +#define SHADER_PRESENT_LO 0x100 /* (RO) Shader core present bitmap, low word */ +#define TILER_PRESENT_LO 0x110 /* (RO) Tiler core present bitmap, low word */ +#define L2_PRESENT_LO 0x120 /* (RO) Level 2 cache present bitmap, low word */ +#define BIT_AT(value, pos) ((value >> pos) & 1) + + static unsigned long long previous_shader_bitmask = 0; + static unsigned long long previous_tiler_bitmask = 0; + static unsigned long long previous_l2_bitmask = 0; + + switch (event_id) { + case SHADER_PRESENT_LO: + { + unsigned long long changed_bitmask = previous_shader_bitmask ^ value; + int pos; + + for (pos = 0; pos < NUM_PM_SHADER; ++pos) { + if (BIT_AT(changed_bitmask, pos)) { + record_timeline_event(PM_SHADER_0 + pos, BIT_AT(value, pos) ? ACTIVITY_START : ACTIVITY_STOP); + } + } + + previous_shader_bitmask = value; + break; + } + + case TILER_PRESENT_LO: + { + unsigned long long changed = previous_tiler_bitmask ^ value; + + if (BIT_AT(changed, 0)) { + record_timeline_event(PM_TILER_0, BIT_AT(value, 0) ? ACTIVITY_START : ACTIVITY_STOP); + } + + previous_tiler_bitmask = value; + break; + } + + case L2_PRESENT_LO: + { + unsigned long long changed = previous_l2_bitmask ^ value; + + if (BIT_AT(changed, 0)) { + record_timeline_event(PM_L2_0, BIT_AT(value, 0) ? ACTIVITY_START : ACTIVITY_STOP); + } + if (BIT_AT(changed, 4)) { + record_timeline_event(PM_L2_1, BIT_AT(value, 4) ? ACTIVITY_START : ACTIVITY_STOP); + } + + previous_l2_bitmask = value; + break; + } + + default: + /* No other blocks are supported at present */ + break; + } + +#undef SHADER_PRESENT_LO +#undef TILER_PRESENT_LO +#undef L2_PRESENT_LO +#undef BIT_AT +} + +GATOR_DEFINE_PROBE(mali_page_fault_insert_pages, TP_PROTO(int event_id, unsigned long value)) +{ + /* We add to the previous since we may receive many tracepoints in one sample period */ + sw_counter_data[MMU_PAGE_FAULT_0 + event_id] += value; +} + +GATOR_DEFINE_PROBE(mali_mmu_as_in_use, TP_PROTO(int event_id)) +{ + record_timeline_event(MMU_AS_0 + event_id, ACTIVITY_START); +} + +GATOR_DEFINE_PROBE(mali_mmu_as_released, TP_PROTO(int event_id)) +{ + record_timeline_event(MMU_AS_0 + event_id, ACTIVITY_STOP); +} + +GATOR_DEFINE_PROBE(mali_total_alloc_pages_change, TP_PROTO(long long int event_id)) +{ + accumulators_data[TOTAL_ALLOC_PAGES] = event_id; +} + +static int create_files(struct super_block *sb, struct dentry *root) +{ + int event; + /* + * Create the filesystem for all events + */ + int counter_index = 0; + const char *mali_name = gator_mali_get_mali_name(); + mali_profiling_control_type *mali_control; + + for (event = FIRST_TIMELINE_EVENT; event < FIRST_TIMELINE_EVENT + NUMBER_OF_TIMELINE_EVENTS; event++) { + if (gator_mali_create_file_system(mali_name, timeline_event_names[counter_index], sb, root, &counters[event], NULL) != 0) { + return -1; + } + counter_index++; + } + counter_index = 0; + for (event = FIRST_SOFTWARE_COUNTER; event < FIRST_SOFTWARE_COUNTER + NUMBER_OF_SOFTWARE_COUNTERS; event++) { + if (gator_mali_create_file_system(mali_name, software_counter_names[counter_index], sb, root, &counters[event], NULL) != 0) { + return -1; + } + counter_index++; + } + counter_index = 0; + for (event = FIRST_ACCUMULATOR; event < FIRST_ACCUMULATOR + NUMBER_OF_ACCUMULATORS; event++) { + if (gator_mali_create_file_system(mali_name, accumulators_names[counter_index], sb, root, &counters[event], NULL) != 0) { + return -1; + } + counter_index++; + } + + mali_control = symbol_get(_mali_profiling_control); + if (mali_control) { + if (gator_mali_create_file_system(mali_name, "Filmstrip_cnt0", sb, root, &counters[FILMSTRIP], &filmstrip_event) != 0) { + return -1; + } + symbol_put(_mali_profiling_control); + } + + return 0; +} + +static int register_tracepoints(void) +{ + if (GATOR_REGISTER_TRACE(mali_pm_status)) { + pr_debug("gator: Mali-T6xx: mali_pm_status tracepoint failed to activate\n"); + return 0; + } + + if (GATOR_REGISTER_TRACE(mali_page_fault_insert_pages)) { + pr_debug("gator: Mali-T6xx: mali_page_fault_insert_pages tracepoint failed to activate\n"); + return 0; + } + + if (GATOR_REGISTER_TRACE(mali_mmu_as_in_use)) { + pr_debug("gator: Mali-T6xx: mali_mmu_as_in_use tracepoint failed to activate\n"); + return 0; + } + + if (GATOR_REGISTER_TRACE(mali_mmu_as_released)) { + pr_debug("gator: Mali-T6xx: mali_mmu_as_released tracepoint failed to activate\n"); + return 0; + } + + if (GATOR_REGISTER_TRACE(mali_total_alloc_pages_change)) { + pr_debug("gator: Mali-T6xx: mali_total_alloc_pages_change tracepoint failed to activate\n"); + return 0; + } + + pr_debug("gator: Mali-T6xx: start\n"); + pr_debug("gator: Mali-T6xx: mali_pm_status probe is at %p\n", &probe_mali_pm_status); + pr_debug("gator: Mali-T6xx: mali_page_fault_insert_pages probe is at %p\n", &probe_mali_page_fault_insert_pages); + pr_debug("gator: Mali-T6xx: mali_mmu_as_in_use probe is at %p\n", &probe_mali_mmu_as_in_use); + pr_debug("gator: Mali-T6xx: mali_mmu_as_released probe is at %p\n", &probe_mali_mmu_as_released); + pr_debug("gator: Mali-T6xx: mali_total_alloc_pages_change probe is at %p\n", &probe_mali_total_alloc_pages_change); + + return 1; +} + +static int start(void) +{ + unsigned int cnt; + mali_profiling_control_type *mali_control; + + /* Clean all data for the next capture */ + for (cnt = 0; cnt < NUMBER_OF_TIMELINE_EVENTS; cnt++) { + timeline_event_starttime[cnt].tv_sec = timeline_event_starttime[cnt].tv_nsec = 0; + timeline_data[cnt] = 0; + } + + for (cnt = 0; cnt < NUMBER_OF_SOFTWARE_COUNTERS; cnt++) { + sw_counter_data[cnt] = 0; + } + + for (cnt = 0; cnt < NUMBER_OF_ACCUMULATORS; cnt++) { + accumulators_data[cnt] = 0; + } + + /* Register tracepoints */ + if (register_tracepoints() == 0) { + return -1; + } + + /* Generic control interface for Mali DDK. */ + mali_control = symbol_get(_mali_profiling_control); + if (mali_control) { + /* The event attribute in the XML file keeps the actual frame rate. */ + unsigned int enabled = counters[FILMSTRIP].enabled ? 1 : 0; + unsigned int rate = filmstrip_event & 0xff; + unsigned int resize_factor = (filmstrip_event >> 8) & 0xff; + + pr_debug("gator: mali online _mali_profiling_control symbol @ %p\n", mali_control); + +#define FBDUMP_CONTROL_ENABLE (1) +#define FBDUMP_CONTROL_RATE (2) +#define FBDUMP_CONTROL_RESIZE_FACTOR (4) + mali_control(FBDUMP_CONTROL_ENABLE, enabled); + mali_control(FBDUMP_CONTROL_RATE, rate); + mali_control(FBDUMP_CONTROL_RESIZE_FACTOR, resize_factor); + + pr_debug("gator: sent mali_control enabled=%d, rate=%d, resize_factor=%d\n", enabled, rate, resize_factor); + + symbol_put(_mali_profiling_control); + } else { + printk("gator: mali online _mali_profiling_control symbol not found\n"); + } + + /* + * Set the first timestamp for calculating the sample interval. The first interval could be quite long, + * since it will be the time between 'start' and the first 'read'. + * This means that timeline values will be divided by a big number for the first sample. + */ + getnstimeofday(&prev_timestamp); + + return 0; +} + +static void stop(void) +{ + mali_profiling_control_type *mali_control; + + pr_debug("gator: Mali-T6xx: stop\n"); + + /* + * It is safe to unregister traces even if they were not successfully + * registered, so no need to check. + */ + GATOR_UNREGISTER_TRACE(mali_pm_status); + pr_debug("gator: Mali-T6xx: mali_pm_status tracepoint deactivated\n"); + + GATOR_UNREGISTER_TRACE(mali_page_fault_insert_pages); + pr_debug("gator: Mali-T6xx: mali_page_fault_insert_pages tracepoint deactivated\n"); + + GATOR_UNREGISTER_TRACE(mali_mmu_as_in_use); + pr_debug("gator: Mali-T6xx: mali_mmu_as_in_use tracepoint deactivated\n"); + + GATOR_UNREGISTER_TRACE(mali_mmu_as_released); + pr_debug("gator: Mali-T6xx: mali_mmu_as_released tracepoint deactivated\n"); + + GATOR_UNREGISTER_TRACE(mali_total_alloc_pages_change); + pr_debug("gator: Mali-T6xx: mali_total_alloc_pages_change tracepoint deactivated\n"); + + /* Generic control interface for Mali DDK. */ + mali_control = symbol_get(_mali_profiling_control); + if (mali_control) { + pr_debug("gator: mali offline _mali_profiling_control symbol @ %p\n", mali_control); + + mali_control(FBDUMP_CONTROL_ENABLE, 0); + + symbol_put(_mali_profiling_control); + } else { + printk("gator: mali offline _mali_profiling_control symbol not found\n"); + } +} + +static int read(int **buffer) +{ + int cnt; + int len = 0; + long sample_interval_us = 0; + struct timespec read_timestamp; + + if (!on_primary_core()) { + return 0; + } + + /* Get the start of this sample period. */ + getnstimeofday(&read_timestamp); + + /* + * Calculate the sample interval if the previous sample time is valid. + * We use tv_sec since it will not be 0. + */ + if (prev_timestamp.tv_sec != 0) { + sample_interval_us = get_duration_us(&prev_timestamp, &read_timestamp); + } + + /* Structure copy. Update the previous timestamp. */ + prev_timestamp = read_timestamp; + + /* + * Report the timeline counters (ACTIVITY_START/STOP) + */ + for (cnt = FIRST_TIMELINE_EVENT; cnt < (FIRST_TIMELINE_EVENT + NUMBER_OF_TIMELINE_EVENTS); cnt++) { + mali_counter *counter = &counters[cnt]; + if (counter->enabled) { + const int index = cnt - FIRST_TIMELINE_EVENT; + unsigned int value; + + /* If the activity is still running, reset its start time to the start of this sample period + * to correct the count. Add the time up to the end of the sample onto the count. */ + if (timeline_event_starttime[index].tv_sec != 0) { + const long event_duration = get_duration_us(&timeline_event_starttime[index], &read_timestamp); + timeline_data[index] += event_duration; + timeline_event_starttime[index] = read_timestamp; /* Activity is still running. */ + } + + if (sample_interval_us != 0) { + /* Convert the counter to a percent-of-sample value */ + value = (timeline_data[index] * 100) / sample_interval_us; + } else { + pr_debug("gator: Mali-T6xx: setting value to zero\n"); + value = 0; + } + + /* Clear the counter value ready for the next sample. */ + timeline_data[index] = 0; + + counter_dump[len++] = counter->key; + counter_dump[len++] = value; + } + } + + /* Report the software counters */ + for (cnt = FIRST_SOFTWARE_COUNTER; cnt < (FIRST_SOFTWARE_COUNTER + NUMBER_OF_SOFTWARE_COUNTERS); cnt++) { + const mali_counter *counter = &counters[cnt]; + if (counter->enabled) { + const int index = cnt - FIRST_SOFTWARE_COUNTER; + counter_dump[len++] = counter->key; + counter_dump[len++] = sw_counter_data[index]; + /* Set the value to zero for the next time */ + sw_counter_data[index] = 0; + } + } + + /* Report the accumulators */ + for (cnt = FIRST_ACCUMULATOR; cnt < (FIRST_ACCUMULATOR + NUMBER_OF_ACCUMULATORS); cnt++) { + const mali_counter *counter = &counters[cnt]; + if (counter->enabled) { + const int index = cnt - FIRST_ACCUMULATOR; + counter_dump[len++] = counter->key; + counter_dump[len++] = accumulators_data[index]; + /* Do not zero the accumulator */ + } + } + + /* Update the buffer */ + if (buffer) { + *buffer = (int *)counter_dump; + } + + return len; +} + +static struct gator_interface gator_events_mali_t6xx_interface = { + .create_files = create_files, + .start = start, + .stop = stop, + .read = read +}; + +extern int gator_events_mali_t6xx_init(void) +{ + pr_debug("gator: Mali-T6xx: sw_counters init\n"); + + gator_mali_initialise_counters(counters, NUMBER_OF_EVENTS); + + return gator_events_install(&gator_events_mali_t6xx_interface); +} diff --git a/drivers/gator/gator_events_mali_t6xx_hw.c b/drivers/gator/gator_events_mali_t6xx_hw.c new file mode 100644 index 00000000000000..e406991398d997 --- /dev/null +++ b/drivers/gator/gator_events_mali_t6xx_hw.c @@ -0,0 +1,784 @@ +/** + * Copyright (C) ARM Limited 2012-2013. All rights reserved. + * + * 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 "gator.h" + +#include +#include +#include +#include +#include + +/* Mali T6xx DDK includes */ +#include "linux/mali_linux_trace.h" +#include "kbase/src/common/mali_kbase.h" +#include "kbase/src/linux/mali_kbase_mem_linux.h" + +#include "gator_events_mali_common.h" + +/* If API version is not specified then assume API version 1. */ +#ifndef MALI_DDK_GATOR_API_VERSION +#define MALI_DDK_GATOR_API_VERSION 1 +#endif + +#if (MALI_DDK_GATOR_API_VERSION != 1) && (MALI_DDK_GATOR_API_VERSION != 2) +#error MALI_DDK_GATOR_API_VERSION is invalid (must be 1 for r1/r2 DDK, or 2 for r3 DDK). +#endif + +/* + * Mali-T6xx + */ +typedef struct kbase_device *kbase_find_device_type(int); +typedef kbase_context *kbase_create_context_type(kbase_device *); +typedef void kbase_destroy_context_type(kbase_context *); + +#if MALI_DDK_GATOR_API_VERSION == 1 +typedef void *kbase_va_alloc_type(kbase_context *, u32); +typedef void kbase_va_free_type(kbase_context *, void *); +#elif MALI_DDK_GATOR_API_VERSION == 2 +typedef void *kbase_va_alloc_type(kbase_context *, u32, kbase_hwc_dma_mapping * handle); +typedef void kbase_va_free_type(kbase_context *, kbase_hwc_dma_mapping * handle); +#endif + +typedef mali_error kbase_instr_hwcnt_enable_type(kbase_context *, kbase_uk_hwcnt_setup *); +typedef mali_error kbase_instr_hwcnt_disable_type(kbase_context *); +typedef mali_error kbase_instr_hwcnt_clear_type(kbase_context *); +typedef mali_error kbase_instr_hwcnt_dump_irq_type(kbase_context *); +typedef mali_bool kbase_instr_hwcnt_dump_complete_type(kbase_context *, mali_bool *); + +static kbase_find_device_type *kbase_find_device_symbol; +static kbase_create_context_type *kbase_create_context_symbol; +static kbase_va_alloc_type *kbase_va_alloc_symbol; +static kbase_instr_hwcnt_enable_type *kbase_instr_hwcnt_enable_symbol; +static kbase_instr_hwcnt_clear_type *kbase_instr_hwcnt_clear_symbol; +static kbase_instr_hwcnt_dump_irq_type *kbase_instr_hwcnt_dump_irq_symbol; +static kbase_instr_hwcnt_dump_complete_type *kbase_instr_hwcnt_dump_complete_symbol; +static kbase_instr_hwcnt_disable_type *kbase_instr_hwcnt_disable_symbol; +static kbase_va_free_type *kbase_va_free_symbol; +static kbase_destroy_context_type *kbase_destroy_context_symbol; + +static long shader_present_low = 0; + +/** The interval between reads, in ns. + * + * Earlier we introduced + * a 'hold off for 1ms after last read' to resolve MIDBASE-2178 and MALINE-724. + * However, the 1ms hold off is too long if no context switches occur as there is a race + * between this value and the tick of the read clock in gator which is also 1ms. If we 'miss' the + * current read, the counter values are effectively 'spread' over 2ms and the values seen are half + * what they should be (since Streamline averages over sample time). In the presence of context switches + * this spread can vary and markedly affect the counters. Currently there is no 'proper' solution to + * this, but empirically we have found that reducing the minimum read interval to 950us causes the + * counts to be much more stable. + */ +static const int READ_INTERVAL_NSEC = 950000; + +#if GATOR_TEST +#include "gator_events_mali_t6xx_hw_test.c" +#endif + +/* Blocks for HW counters */ +enum { + JM_BLOCK = 0, + TILER_BLOCK, + SHADER_BLOCK, + MMU_BLOCK +}; + +/* Counters for Mali-T6xx: + * + * - HW counters, 4 blocks + * For HW counters we need strings to create /dev/gator/events files. + * Enums are not needed because the position of the HW name in the array is the same + * of the corresponding value in the received block of memory. + * HW counters are requested by calculating a bitmask, passed then to the driver. + * Every millisecond a HW counters dump is requested, and if the previous has been completed they are read. + */ + +/* Hardware Counters */ +static const char *const hardware_counter_names[] = { + /* Job Manager */ + "", + "", + "", + "", + "MESSAGES_SENT", + "MESSAGES_RECEIVED", + "GPU_ACTIVE", /* 6 */ + "IRQ_ACTIVE", + "JS0_JOBS", + "JS0_TASKS", + "JS0_ACTIVE", + "", + "JS0_WAIT_READ", + "JS0_WAIT_ISSUE", + "JS0_WAIT_DEPEND", + "JS0_WAIT_FINISH", + "JS1_JOBS", + "JS1_TASKS", + "JS1_ACTIVE", + "", + "JS1_WAIT_READ", + "JS1_WAIT_ISSUE", + "JS1_WAIT_DEPEND", + "JS1_WAIT_FINISH", + "JS2_JOBS", + "JS2_TASKS", + "JS2_ACTIVE", + "", + "JS2_WAIT_READ", + "JS2_WAIT_ISSUE", + "JS2_WAIT_DEPEND", + "JS2_WAIT_FINISH", + "JS3_JOBS", + "JS3_TASKS", + "JS3_ACTIVE", + "", + "JS3_WAIT_READ", + "JS3_WAIT_ISSUE", + "JS3_WAIT_DEPEND", + "JS3_WAIT_FINISH", + "JS4_JOBS", + "JS4_TASKS", + "JS4_ACTIVE", + "", + "JS4_WAIT_READ", + "JS4_WAIT_ISSUE", + "JS4_WAIT_DEPEND", + "JS4_WAIT_FINISH", + "JS5_JOBS", + "JS5_TASKS", + "JS5_ACTIVE", + "", + "JS5_WAIT_READ", + "JS5_WAIT_ISSUE", + "JS5_WAIT_DEPEND", + "JS5_WAIT_FINISH", + "JS6_JOBS", + "JS6_TASKS", + "JS6_ACTIVE", + "", + "JS6_WAIT_READ", + "JS6_WAIT_ISSUE", + "JS6_WAIT_DEPEND", + "JS6_WAIT_FINISH", + + /*Tiler */ + "", + "", + "", + "JOBS_PROCESSED", + "TRIANGLES", + "QUADS", + "POLYGONS", + "POINTS", + "LINES", + "VCACHE_HIT", + "VCACHE_MISS", + "FRONT_FACING", + "BACK_FACING", + "PRIM_VISIBLE", + "PRIM_CULLED", + "PRIM_CLIPPED", + "LEVEL0", + "LEVEL1", + "LEVEL2", + "LEVEL3", + "LEVEL4", + "LEVEL5", + "LEVEL6", + "LEVEL7", + "COMMAND_1", + "COMMAND_2", + "COMMAND_3", + "COMMAND_4", + "COMMAND_4_7", + "COMMAND_8_15", + "COMMAND_16_63", + "COMMAND_64", + "COMPRESS_IN", + "COMPRESS_OUT", + "COMPRESS_FLUSH", + "TIMESTAMPS", + "PCACHE_HIT", + "PCACHE_MISS", + "PCACHE_LINE", + "PCACHE_STALL", + "WRBUF_HIT", + "WRBUF_MISS", + "WRBUF_LINE", + "WRBUF_PARTIAL", + "WRBUF_STALL", + "ACTIVE", + "LOADING_DESC", + "INDEX_WAIT", + "INDEX_RANGE_WAIT", + "VERTEX_WAIT", + "PCACHE_WAIT", + "WRBUF_WAIT", + "BUS_READ", + "BUS_WRITE", + "", + "", + "", + "", + "", + "UTLB_STALL", + "UTLB_REPLAY_MISS", + "UTLB_REPLAY_FULL", + "UTLB_NEW_MISS", + "UTLB_HIT", + + /* Shader Core */ + "", + "", + "", + "SHADER_CORE_ACTIVE", + "FRAG_ACTIVE", + "FRAG_PRIMATIVES", + "FRAG_PRIMATIVES_DROPPED", + "FRAG_CYCLE_DESC", + "FRAG_CYCLES_PLR", + "FRAG_CYCLES_VERT", + "FRAG_CYCLES_TRISETUP", + "FRAG_CYCLES_RAST", + "FRAG_THREADS", + "FRAG_DUMMY_THREADS", + "FRAG_QUADS_RAST", + "FRAG_QUADS_EZS_TEST", + "FRAG_QUADS_EZS_KILLED", + "FRAG_QUADS_LZS_TEST", + "FRAG_QUADS_LZS_KILLED", + "FRAG_CYCLE_NO_TILE", + "FRAG_NUM_TILES", + "FRAG_TRANS_ELIM", + "COMPUTE_ACTIVE", + "COMPUTE_TASKS", + "COMPUTE_THREADS", + "COMPUTE_CYCLES_DESC", + "TRIPIPE_ACTIVE", + "ARITH_WORDS", + "ARITH_CYCLES_REG", + "ARITH_CYCLES_L0", + "ARITH_FRAG_DEPEND", + "LS_WORDS", + "LS_ISSUES", + "LS_RESTARTS", + "LS_REISSUES_MISS", + "LS_REISSUES_VD", + "LS_REISSUE_ATTRIB_MISS", + "LS_NO_WB", + "TEX_WORDS", + "TEX_BUBBLES", + "TEX_WORDS_L0", + "TEX_WORDS_DESC", + "TEX_THREADS", + "TEX_RECIRC_FMISS", + "TEX_RECIRC_DESC", + "TEX_RECIRC_MULTI", + "TEX_RECIRC_PMISS", + "TEX_RECIRC_CONF", + "LSC_READ_HITS", + "LSC_READ_MISSES", + "LSC_WRITE_HITS", + "LSC_WRITE_MISSES", + "LSC_ATOMIC_HITS", + "LSC_ATOMIC_MISSES", + "LSC_LINE_FETCHES", + "LSC_DIRTY_LINE", + "LSC_SNOOPS", + "AXI_TLB_STALL", + "AXI_TLB_MIESS", + "AXI_TLB_TRANSACTION", + "LS_TLB_MISS", + "LS_TLB_HIT", + "AXI_BEATS_READ", + "AXI_BEATS_WRITTEN", + + /*L2 and MMU */ + "", + "", + "", + "", + "MMU_HIT", + "MMU_NEW_MISS", + "MMU_REPLAY_FULL", + "MMU_REPLAY_MISS", + "MMU_TABLE_WALK", + "", + "", + "", + "", + "", + "", + "", + "UTLB_HIT", + "UTLB_NEW_MISS", + "UTLB_REPLAY_FULL", + "UTLB_REPLAY_MISS", + "UTLB_STALL", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "L2_WRITE_BEATS", + "L2_READ_BEATS", + "L2_ANY_LOOKUP", + "L2_READ_LOOKUP", + "L2_SREAD_LOOKUP", + "L2_READ_REPLAY", + "L2_READ_SNOOP", + "L2_READ_HIT", + "L2_CLEAN_MISS", + "L2_WRITE_LOOKUP", + "L2_SWRITE_LOOKUP", + "L2_WRITE_REPLAY", + "L2_WRITE_SNOOP", + "L2_WRITE_HIT", + "L2_EXT_READ_FULL", + "L2_EXT_READ_HALF", + "L2_EXT_WRITE_FULL", + "L2_EXT_WRITE_HALF", + "L2_EXT_READ", + "L2_EXT_READ_LINE", + "L2_EXT_WRITE", + "L2_EXT_WRITE_LINE", + "L2_EXT_WRITE_SMALL", + "L2_EXT_BARRIER", + "L2_EXT_AR_STALL", + "L2_EXT_R_BUF_FULL", + "L2_EXT_RD_BUF_FULL", + "L2_EXT_R_RAW", + "L2_EXT_W_STALL", + "L2_EXT_W_BUF_FULL", + "L2_EXT_R_W_HAZARD", + "L2_TAG_HAZARD", + "L2_SNOOP_FULL", + "L2_REPLAY_FULL" +}; + +#define NUMBER_OF_HARDWARE_COUNTERS (sizeof(hardware_counter_names) / sizeof(hardware_counter_names[0])) + +#define GET_HW_BLOCK(c) (((c) >> 6) & 0x3) +#define GET_COUNTER_OFFSET(c) ((c) & 0x3f) + +/* Memory to dump hardware counters into */ +static void *kernel_dump_buffer; + +#if MALI_DDK_GATOR_API_VERSION == 2 +/* DMA state used to manage lifetime of the buffer */ +kbase_hwc_dma_mapping kernel_dump_buffer_handle; +#endif + +/* kbase context and device */ +static kbase_context *kbcontext = NULL; +static struct kbase_device *kbdevice = NULL; + +/* + * The following function has no external prototype in older DDK revisions. When the DDK + * is updated then this should be removed. + */ +struct kbase_device *kbase_find_device(int minor); + +static volatile bool kbase_device_busy = false; +static unsigned int num_hardware_counters_enabled; + +/* + * gatorfs variables for counter enable state + */ +static mali_counter counters[NUMBER_OF_HARDWARE_COUNTERS]; + +/* An array used to return the data we recorded + * as key,value pairs hence the *2 + */ +static unsigned long counter_dump[NUMBER_OF_HARDWARE_COUNTERS * 2]; + +#define SYMBOL_GET(FUNCTION, ERROR_COUNT) \ + if(FUNCTION ## _symbol) \ + { \ + printk("gator: mali " #FUNCTION " symbol was already registered\n"); \ + (ERROR_COUNT)++; \ + } \ + else \ + { \ + FUNCTION ## _symbol = symbol_get(FUNCTION); \ + if(! FUNCTION ## _symbol) \ + { \ + printk("gator: mali online " #FUNCTION " symbol not found\n"); \ + (ERROR_COUNT)++; \ + } \ + } + +#define SYMBOL_CLEANUP(FUNCTION) \ + if(FUNCTION ## _symbol) \ + { \ + symbol_put(FUNCTION); \ + FUNCTION ## _symbol = NULL; \ + } + +/** + * Execute symbol_get for all the Mali symbols and check for success. + * @return the number of symbols not loaded. + */ +static int init_symbols(void) +{ + int error_count = 0; + SYMBOL_GET(kbase_find_device, error_count); + SYMBOL_GET(kbase_create_context, error_count); + SYMBOL_GET(kbase_va_alloc, error_count); + SYMBOL_GET(kbase_instr_hwcnt_enable, error_count); + SYMBOL_GET(kbase_instr_hwcnt_clear, error_count); + SYMBOL_GET(kbase_instr_hwcnt_dump_irq, error_count); + SYMBOL_GET(kbase_instr_hwcnt_dump_complete, error_count); + SYMBOL_GET(kbase_instr_hwcnt_disable, error_count); + SYMBOL_GET(kbase_va_free, error_count); + SYMBOL_GET(kbase_destroy_context, error_count); + + return error_count; +} + +/** + * Execute symbol_put for all the registered Mali symbols. + */ +static void clean_symbols(void) +{ + SYMBOL_CLEANUP(kbase_find_device); + SYMBOL_CLEANUP(kbase_create_context); + SYMBOL_CLEANUP(kbase_va_alloc); + SYMBOL_CLEANUP(kbase_instr_hwcnt_enable); + SYMBOL_CLEANUP(kbase_instr_hwcnt_clear); + SYMBOL_CLEANUP(kbase_instr_hwcnt_dump_irq); + SYMBOL_CLEANUP(kbase_instr_hwcnt_dump_complete); + SYMBOL_CLEANUP(kbase_instr_hwcnt_disable); + SYMBOL_CLEANUP(kbase_va_free); + SYMBOL_CLEANUP(kbase_destroy_context); +} + +/** + * Determines whether a read should take place + * @param current_time The current time, obtained from getnstimeofday() + * @param prev_time_s The number of seconds at the previous read attempt. + * @param next_read_time_ns The time (in ns) when the next read should be allowed. + * + * Note that this function has been separated out here to allow it to be tested. + */ +static int is_read_scheduled(const struct timespec *current_time, u32 *prev_time_s, s32 *next_read_time_ns) +{ + /* If the current ns count rolls over a second, roll the next read time too. */ + if (current_time->tv_sec != *prev_time_s) { + *next_read_time_ns = *next_read_time_ns - NSEC_PER_SEC; + } + + /* Abort the read if the next read time has not arrived. */ + if (current_time->tv_nsec < *next_read_time_ns) { + return 0; + } + + /* Set the next read some fixed time after this one, and update the read timestamp. */ + *next_read_time_ns = current_time->tv_nsec + READ_INTERVAL_NSEC; + + *prev_time_s = current_time->tv_sec; + return 1; +} + +static int start(void) +{ + kbase_uk_hwcnt_setup setup; + mali_error err; + int cnt; + u16 bitmask[] = { 0, 0, 0, 0 }; + unsigned long long shadersPresent = 0; + + /* Setup HW counters */ + num_hardware_counters_enabled = 0; + + if (NUMBER_OF_HARDWARE_COUNTERS != 256) { + pr_debug("Unexpected number of hardware counters defined: expecting 256, got %d\n", NUMBER_OF_HARDWARE_COUNTERS); + } + + /* Calculate enable bitmasks based on counters_enabled array */ + for (cnt = 0; cnt < NUMBER_OF_HARDWARE_COUNTERS; cnt++) { + const mali_counter *counter = &counters[cnt]; + if (counter->enabled) { + int block = GET_HW_BLOCK(cnt); + int enable_bit = GET_COUNTER_OFFSET(cnt) / 4; + bitmask[block] |= (1 << enable_bit); + pr_debug("gator: Mali-T6xx: hardware counter %s selected [%d]\n", hardware_counter_names[cnt], cnt); + num_hardware_counters_enabled++; + } + } + + /* Create a kbase context for HW counters */ + if (num_hardware_counters_enabled > 0) { + if (init_symbols() > 0) { + clean_symbols(); + /* No Mali driver code entrypoints found - not a fault. */ + return 0; + } + + kbdevice = kbase_find_device_symbol(-1); + + /* If we already got a context, fail */ + if (kbcontext) { + pr_debug("gator: Mali-T6xx: error context already present\n"); + goto out; + } + + /* kbcontext will only be valid after all the Mali symbols are loaded successfully */ + kbcontext = kbase_create_context_symbol(kbdevice); + if (!kbcontext) { + pr_debug("gator: Mali-T6xx: error creating kbase context\n"); + goto out; + } + + + /* See if we can get the number of shader cores */ + shadersPresent = kbdevice->shader_present_bitmap; + shader_present_low = (unsigned long)shadersPresent; + + /* + * The amount of memory needed to store the dump (bytes) + * DUMP_SIZE = number of core groups + * * number of blocks (always 8 for midgard) + * * number of counters per block (always 64 for midgard) + * * number of bytes per counter (always 4 in midgard) + * For a Mali-T6xx with a single core group = 1 * 8 * 64 * 4 = 2048 + * For a Mali-T6xx with a dual core group = 2 * 8 * 64 * 4 = 4096 + */ +#if MALI_DDK_GATOR_API_VERSION == 1 + kernel_dump_buffer = kbase_va_alloc_symbol(kbcontext, 4096); +#elif MALI_DDK_GATOR_API_VERSION == 2 + kernel_dump_buffer = kbase_va_alloc_symbol(kbcontext, 4096, &kernel_dump_buffer_handle); +#endif + if (!kernel_dump_buffer) { + pr_debug("gator: Mali-T6xx: error trying to allocate va\n"); + goto destroy_context; + } + + setup.dump_buffer = (uintptr_t)kernel_dump_buffer; + setup.jm_bm = bitmask[JM_BLOCK]; + setup.tiler_bm = bitmask[TILER_BLOCK]; + setup.shader_bm = bitmask[SHADER_BLOCK]; + setup.mmu_l2_bm = bitmask[MMU_BLOCK]; + /* These counters do not exist on Mali-T60x */ + setup.l3_cache_bm = 0; + + /* Use kbase API to enable hardware counters and provide dump buffer */ + err = kbase_instr_hwcnt_enable_symbol(kbcontext, &setup); + if (err != MALI_ERROR_NONE) { + pr_debug("gator: Mali-T6xx: can't setup hardware counters\n"); + goto free_buffer; + } + pr_debug("gator: Mali-T6xx: hardware counters enabled\n"); + kbase_instr_hwcnt_clear_symbol(kbcontext); + pr_debug("gator: Mali-T6xx: hardware counters cleared \n"); + + kbase_device_busy = false; + } + + return 0; + +free_buffer: +#if MALI_DDK_GATOR_API_VERSION == 1 + kbase_va_free_symbol(kbcontext, kernel_dump_buffer); +#elif MALI_DDK_GATOR_API_VERSION == 2 + kbase_va_free_symbol(kbcontext, &kernel_dump_buffer_handle); +#endif + +destroy_context: + kbase_destroy_context_symbol(kbcontext); + +out: + clean_symbols(); + return -1; +} + +static void stop(void) +{ + unsigned int cnt; + kbase_context *temp_kbcontext; + + pr_debug("gator: Mali-T6xx: stop\n"); + + /* Set all counters as disabled */ + for (cnt = 0; cnt < NUMBER_OF_HARDWARE_COUNTERS; cnt++) { + counters[cnt].enabled = 0; + } + + /* Destroy the context for HW counters */ + if (num_hardware_counters_enabled > 0 && kbcontext != NULL) { + /* + * Set the global variable to NULL before destroying it, because + * other function will check this before using it. + */ + temp_kbcontext = kbcontext; + kbcontext = NULL; + + kbase_instr_hwcnt_disable_symbol(temp_kbcontext); + +#if MALI_DDK_GATOR_API_VERSION == 1 + kbase_va_free_symbol(temp_kbcontext, kernel_dump_buffer); +#elif MALI_DDK_GATOR_API_VERSION == 2 + kbase_va_free_symbol(temp_kbcontext, &kernel_dump_buffer_handle); +#endif + + kbase_destroy_context_symbol(temp_kbcontext); + + pr_debug("gator: Mali-T6xx: hardware counters stopped\n"); + + clean_symbols(); + } +} + +static int read(int **buffer) +{ + int cnt; + int len = 0; + u32 value = 0; + mali_bool success; + + struct timespec current_time; + static u32 prev_time_s = 0; + static s32 next_read_time_ns = 0; + + if (!on_primary_core()) { + return 0; + } + + getnstimeofday(¤t_time); + + /* + * Discard reads unless a respectable time has passed. This reduces the load on the GPU without sacrificing + * accuracy on the Streamline display. + */ + if (!is_read_scheduled(¤t_time, &prev_time_s, &next_read_time_ns)) { + return 0; + } + + /* + * Report the HW counters + * Only process hardware counters if at least one of the hardware counters is enabled. + */ + if (num_hardware_counters_enabled > 0) { + const unsigned int vithar_blocks[] = { + 0x700, /* VITHAR_JOB_MANAGER, Block 0 */ + 0x400, /* VITHAR_TILER, Block 1 */ + 0x000, /* VITHAR_SHADER_CORE, Block 2 */ + 0x500 /* VITHAR_MEMORY_SYSTEM, Block 3 */ + }; + + if (!kbcontext) { + return -1; + } + + /* Mali symbols can be called safely since a kbcontext is valid */ + if (kbase_instr_hwcnt_dump_complete_symbol(kbcontext, &success) == MALI_TRUE) { + kbase_device_busy = false; + + if (success == MALI_TRUE) { + /* Cycle through hardware counters and accumulate totals */ + for (cnt = 0; cnt < NUMBER_OF_HARDWARE_COUNTERS; cnt++) { + const mali_counter *counter = &counters[cnt]; + if (counter->enabled) { + const int block = GET_HW_BLOCK(cnt); + const int counter_offset = GET_COUNTER_OFFSET(cnt); + + const char* block_base_address = (char*)kernel_dump_buffer + vithar_blocks[block]; + + /* If counter belongs to shader block need to take into account all cores */ + if (block == SHADER_BLOCK) { + int i = 0; + int shader_core_count = 0; + value = 0; + + for (i = 0; i < 4; i++) { + if ((shader_present_low >> i) & 1) { + value += *((u32*) (block_base_address + (0x100 * i)) + counter_offset); + shader_core_count++; + } + } + + for (i = 0; i < 4; i++) { + if((shader_present_low >> (i+4)) & 1) { + value += *((u32*)(block_base_address + (0x100 * i) + 0x800) + counter_offset); + shader_core_count++; + } + } + + /* Need to total by number of cores to produce an average */ + if (shader_core_count != 0) { + value /= shader_core_count; + } + } else { + value = *((u32*)block_base_address + counter_offset); + } + + counter_dump[len++] = counter->key; + counter_dump[len++] = value; + } + } + } + } + + if (!kbase_device_busy) { + kbase_device_busy = true; + kbase_instr_hwcnt_dump_irq_symbol(kbcontext); + } + } + + /* Update the buffer */ + if (buffer) { + *buffer = (int *)counter_dump; + } + + return len; +} + +static int create_files(struct super_block *sb, struct dentry *root) +{ + unsigned int event; + /* + * Create the filesystem for all events + */ + int counter_index = 0; + const char *mali_name = gator_mali_get_mali_name(); + + for (event = 0; event < NUMBER_OF_HARDWARE_COUNTERS; event++) { + if (gator_mali_create_file_system(mali_name, hardware_counter_names[counter_index], sb, root, &counters[event], NULL) != 0) + return -1; + counter_index++; + } + + return 0; +} + +static struct gator_interface gator_events_mali_t6xx_interface = { + .create_files = create_files, + .start = start, + .stop = stop, + .read = read +}; + +int gator_events_mali_t6xx_hw_init(void) +{ + pr_debug("gator: Mali-T6xx: sw_counters init\n"); + +#if GATOR_TEST + test_all_is_read_scheduled(); +#endif + + gator_mali_initialise_counters(counters, NUMBER_OF_HARDWARE_COUNTERS); + + return gator_events_install(&gator_events_mali_t6xx_interface); +} diff --git a/drivers/gator/gator_events_mali_t6xx_hw_test.c b/drivers/gator/gator_events_mali_t6xx_hw_test.c new file mode 100644 index 00000000000000..efb32ddf548304 --- /dev/null +++ b/drivers/gator/gator_events_mali_t6xx_hw_test.c @@ -0,0 +1,55 @@ +/** + * Copyright (C) ARM Limited 2012-2013. All rights reserved. + * + * 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. + * + */ + +/** + * Test functions for mali_t600_hw code. + */ + +static int is_read_scheduled(const struct timespec *current_time, u32 *prev_time_s, s32 *next_read_time_ns); + +static int test_is_read_scheduled(u32 s, u32 ns, u32 prev_s, s32 next_ns, int expected_result, s32 expected_next_ns) +{ + struct timespec current_time; + u32 prev_time_s = prev_s; + s32 next_read_time_ns = next_ns; + + current_time.tv_sec = s; + current_time.tv_nsec = ns; + + if (is_read_scheduled(¤t_time, &prev_time_s, &next_read_time_ns) != expected_result) { + printk("Failed do_read(%u, %u, %u, %d): expected %d\n", s, ns, prev_s, next_ns, expected_result); + return 0; + } + + if (next_read_time_ns != expected_next_ns) { + printk("Failed: next_read_ns expected=%d, actual=%d\n", expected_next_ns, next_read_time_ns); + return 0; + } + + return 1; +} + +static void test_all_is_read_scheduled(void) +{ + const int HIGHEST_NS = 999999999; + int n_tests_passed = 0; + + printk("gator: running tests on %s\n", __FILE__); + + n_tests_passed += test_is_read_scheduled(0, 0, 0, 0, 1, READ_INTERVAL_NSEC); /* Null time */ + n_tests_passed += test_is_read_scheduled(100, 1000, 0, 0, 1, READ_INTERVAL_NSEC + 1000); /* Initial values */ + + n_tests_passed += test_is_read_scheduled(100, HIGHEST_NS, 100, HIGHEST_NS + 500, 0, HIGHEST_NS + 500); + n_tests_passed += test_is_read_scheduled(101, 0001, 100, HIGHEST_NS + 500, 0, HIGHEST_NS + 500 - NSEC_PER_SEC); + n_tests_passed += test_is_read_scheduled(101, 600, 100, HIGHEST_NS + 500 - NSEC_PER_SEC, 1, 600 + READ_INTERVAL_NSEC); + + n_tests_passed += test_is_read_scheduled(101, 600, 100, HIGHEST_NS + 500, 1, 600 + READ_INTERVAL_NSEC); + + printk("gator: %d tests passed\n", n_tests_passed); +} diff --git a/drivers/gator/gator_events_meminfo.c b/drivers/gator/gator_events_meminfo.c new file mode 100644 index 00000000000000..451290d9af1759 --- /dev/null +++ b/drivers/gator/gator_events_meminfo.c @@ -0,0 +1,387 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 "gator.h" + +#include +#include +#include +#include +#include +#include + +enum { + MEMINFO_MEMFREE, + MEMINFO_MEMUSED, + MEMINFO_BUFFERRAM, + MEMINFO_TOTAL, +}; + +enum { + PROC_SIZE, + PROC_SHARE, + PROC_TEXT, + PROC_DATA, + PROC_COUNT, +}; + +static const char * const meminfo_names[] = { + "Linux_meminfo_memfree", + "Linux_meminfo_memused", + "Linux_meminfo_bufferram", +}; + +static const char * const proc_names[] = { + "Linux_proc_statm_size", + "Linux_proc_statm_share", + "Linux_proc_statm_text", + "Linux_proc_statm_data", +}; + +static bool meminfo_global_enabled; +static ulong meminfo_enabled[MEMINFO_TOTAL]; +static ulong meminfo_keys[MEMINFO_TOTAL]; +static long long meminfo_buffer[2 * (MEMINFO_TOTAL + 2)]; +static int meminfo_length = 0; +static bool new_data_avail; + +static bool proc_global_enabled; +static ulong proc_enabled[PROC_COUNT]; +static ulong proc_keys[PROC_COUNT]; +static DEFINE_PER_CPU(long long, proc_buffer[2 * (PROC_COUNT + 3)]); + +static int gator_meminfo_func(void *data); +static bool gator_meminfo_run; +// Initialize semaphore unlocked to initialize memory values +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) +static DECLARE_MUTEX(gator_meminfo_sem); +#else +static DEFINE_SEMAPHORE(gator_meminfo_sem); +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) +GATOR_DEFINE_PROBE(mm_page_free_direct, TP_PROTO(struct page *page, unsigned int order)) +#else +GATOR_DEFINE_PROBE(mm_page_free, TP_PROTO(struct page *page, unsigned int order)) +#endif +{ + up(&gator_meminfo_sem); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) +GATOR_DEFINE_PROBE(mm_pagevec_free, TP_PROTO(struct page *page, int cold)) +#else +GATOR_DEFINE_PROBE(mm_page_free_batched, TP_PROTO(struct page *page, int cold)) +#endif +{ + up(&gator_meminfo_sem); +} + +GATOR_DEFINE_PROBE(mm_page_alloc, TP_PROTO(struct page *page, unsigned int order, gfp_t gfp_flags, int migratetype)) +{ + up(&gator_meminfo_sem); +} + +static int gator_events_meminfo_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + int i; + + for (i = 0; i < MEMINFO_TOTAL; i++) { + dir = gatorfs_mkdir(sb, root, meminfo_names[i]); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &meminfo_enabled[i]); + gatorfs_create_ro_ulong(sb, dir, "key", &meminfo_keys[i]); + } + + for (i = 0; i < PROC_COUNT; ++i) { + dir = gatorfs_mkdir(sb, root, proc_names[i]); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &proc_enabled[i]); + gatorfs_create_ro_ulong(sb, dir, "key", &proc_keys[i]); + } + + return 0; +} + +static int gator_events_meminfo_start(void) +{ + int i; + + new_data_avail = false; + meminfo_global_enabled = 0; + for (i = 0; i < MEMINFO_TOTAL; i++) { + if (meminfo_enabled[i]) { + meminfo_global_enabled = 1; + break; + } + } + + proc_global_enabled = 0; + for (i = 0; i < PROC_COUNT; ++i) { + if (proc_enabled[i]) { + proc_global_enabled = 1; + break; + } + } + if (meminfo_enabled[MEMINFO_MEMUSED]) { + proc_global_enabled = 1; + } + + if (meminfo_global_enabled == 0) + return 0; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + if (GATOR_REGISTER_TRACE(mm_page_free_direct)) +#else + if (GATOR_REGISTER_TRACE(mm_page_free)) +#endif + goto mm_page_free_exit; +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + if (GATOR_REGISTER_TRACE(mm_pagevec_free)) +#else + if (GATOR_REGISTER_TRACE(mm_page_free_batched)) +#endif + goto mm_page_free_batched_exit; + if (GATOR_REGISTER_TRACE(mm_page_alloc)) + goto mm_page_alloc_exit; + + // Start worker thread + gator_meminfo_run = true; + // Since the mutex starts unlocked, memory values will be initialized + if (IS_ERR(kthread_run(gator_meminfo_func, NULL, "gator_meminfo"))) + goto kthread_run_exit; + + return 0; + +kthread_run_exit: + GATOR_UNREGISTER_TRACE(mm_page_alloc); +mm_page_alloc_exit: +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + GATOR_UNREGISTER_TRACE(mm_pagevec_free); +#else + GATOR_UNREGISTER_TRACE(mm_page_free_batched); +#endif +mm_page_free_batched_exit: +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + GATOR_UNREGISTER_TRACE(mm_page_free_direct); +#else + GATOR_UNREGISTER_TRACE(mm_page_free); +#endif +mm_page_free_exit: + return -1; +} + +static void gator_events_meminfo_stop(void) +{ + if (meminfo_global_enabled) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + GATOR_UNREGISTER_TRACE(mm_page_free_direct); + GATOR_UNREGISTER_TRACE(mm_pagevec_free); +#else + GATOR_UNREGISTER_TRACE(mm_page_free); + GATOR_UNREGISTER_TRACE(mm_page_free_batched); +#endif + GATOR_UNREGISTER_TRACE(mm_page_alloc); + + // Stop worker thread + gator_meminfo_run = false; + up(&gator_meminfo_sem); + } +} + +// Must be run in process context as the kernel function si_meminfo() can sleep +static int gator_meminfo_func(void *data) +{ + struct sysinfo info; + int i, len; + unsigned long long value; + + for (;;) { + if (down_killable(&gator_meminfo_sem)) { + break; + } + + // Eat up any pending events + while (!down_trylock(&gator_meminfo_sem)); + + if (!gator_meminfo_run) { + break; + } + + meminfo_length = len = 0; + + si_meminfo(&info); + for (i = 0; i < MEMINFO_TOTAL; i++) { + if (meminfo_enabled[i]) { + switch (i) { + case MEMINFO_MEMFREE: + value = info.freeram * PAGE_SIZE; + break; + case MEMINFO_MEMUSED: + // pid -1 means system wide + meminfo_buffer[len++] = 1; + meminfo_buffer[len++] = -1; + // Emit value + meminfo_buffer[len++] = meminfo_keys[MEMINFO_MEMUSED]; + meminfo_buffer[len++] = (info.totalram - info.freeram) * PAGE_SIZE; + // Clear pid + meminfo_buffer[len++] = 1; + meminfo_buffer[len++] = 0; + continue; + case MEMINFO_BUFFERRAM: + value = info.bufferram * PAGE_SIZE; + break; + default: + value = 0; + break; + } + meminfo_buffer[len++] = meminfo_keys[i]; + meminfo_buffer[len++] = value; + } + } + + meminfo_length = len; + new_data_avail = true; + } + + return 0; +} + +static int gator_events_meminfo_read(long long **buffer) +{ + if (!on_primary_core() || !meminfo_global_enabled) + return 0; + + if (!new_data_avail) + return 0; + + new_data_avail = false; + + if (buffer) + *buffer = meminfo_buffer; + + return meminfo_length; +} + +static int gator_events_meminfo_read_proc(long long **buffer, struct task_struct *task) +{ + struct mm_struct *mm; + u64 share = 0; + int i; + long long value; + int len = 0; + int cpu = get_physical_cpu(); + long long *buf = per_cpu(proc_buffer, cpu); + + if (!proc_global_enabled) { + return 0; + } + + // Collect the memory stats of the process instead of the thread + if (task->group_leader != NULL) { + task = task->group_leader; + } + + // get_task_mm/mmput is not needed in this context because the task and it's mm are required as part of the sched_switch + mm = task->mm; + if (mm == NULL) { + return 0; + } + + // Derived from task_statm in fs/proc/task_mmu.c + if (meminfo_enabled[MEMINFO_MEMUSED] || proc_enabled[PROC_SHARE]) { + share = get_mm_counter(mm, +#if LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 32) + file_rss +#else + MM_FILEPAGES +#endif + ); + } + + // key of 1 indicates a pid + buf[len++] = 1; + buf[len++] = task->pid; + + for (i = 0; i < PROC_COUNT; ++i) { + if (proc_enabled[i]) { + switch (i) { + case PROC_SIZE: + value = mm->total_vm; + break; + case PROC_SHARE: + value = share; + break; + case PROC_TEXT: + value = (PAGE_ALIGN(mm->end_code) - (mm->start_code & PAGE_MASK)) >> PAGE_SHIFT; + break; + case PROC_DATA: + value = mm->total_vm - mm->shared_vm; + break; + } + + buf[len++] = proc_keys[i]; + buf[len++] = value * PAGE_SIZE; + } + } + + if (meminfo_enabled[MEMINFO_MEMUSED]) { + value = share + get_mm_counter(mm, +#if LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 32) + anon_rss +#else + MM_ANONPAGES +#endif + ); + // Send resident for this pid + buf[len++] = meminfo_keys[MEMINFO_MEMUSED]; + buf[len++] = value * PAGE_SIZE; + } + + // Clear pid + buf[len++] = 1; + buf[len++] = 0; + + if (buffer) + *buffer = buf; + + return len; +} + +static struct gator_interface gator_events_meminfo_interface = { + .create_files = gator_events_meminfo_create_files, + .start = gator_events_meminfo_start, + .stop = gator_events_meminfo_stop, + .read64 = gator_events_meminfo_read, + .read_proc = gator_events_meminfo_read_proc, +}; + +int gator_events_meminfo_init(void) +{ + int i; + + meminfo_global_enabled = 0; + for (i = 0; i < MEMINFO_TOTAL; i++) { + meminfo_enabled[i] = 0; + meminfo_keys[i] = gator_events_get_key(); + } + + proc_global_enabled = 0; + for (i = 0; i < PROC_COUNT; ++i) { + proc_enabled[i] = 0; + proc_keys[i] = gator_events_get_key(); + } + + return gator_events_install(&gator_events_meminfo_interface); +} diff --git a/drivers/gator/gator_events_mmapped.c b/drivers/gator/gator_events_mmapped.c new file mode 100644 index 00000000000000..f055e48d317ada --- /dev/null +++ b/drivers/gator/gator_events_mmapped.c @@ -0,0 +1,209 @@ +/* + * Example events provider + * + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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. + * + * Similar entries to those below must be present in the events.xml file. + * To add them to the events.xml, create an events-mmap.xml with the + * following contents and rebuild gatord: + * + * + * + * + * + * + * + * + * When adding custom events, be sure do the following + * - add any needed .c files to the gator driver Makefile + * - call gator_events_install in the events init function + * - add the init function to GATOR_EVENTS_LIST in gator_main.c + * - add a new events-*.xml file to the gator daemon and rebuild + */ + +#include +#include +#include + +#include "gator.h" + +#define MMAPPED_COUNTERS_NUM 3 + +static int mmapped_global_enabled; + +static struct { + unsigned long enabled; + unsigned long event; + unsigned long key; +} mmapped_counters[MMAPPED_COUNTERS_NUM]; + +static int mmapped_buffer[MMAPPED_COUNTERS_NUM * 2]; + +static s64 prev_time; + +/* Adds mmapped_cntX directories and enabled, event, and key files to /dev/gator/events */ +static int gator_events_mmapped_create_files(struct super_block *sb, + struct dentry *root) +{ + int i; + + for (i = 0; i < MMAPPED_COUNTERS_NUM; i++) { + char buf[16]; + struct dentry *dir; + + snprintf(buf, sizeof(buf), "mmapped_cnt%d", i); + dir = gatorfs_mkdir(sb, root, buf); + if (WARN_ON(!dir)) + return -1; + gatorfs_create_ulong(sb, dir, "enabled", + &mmapped_counters[i].enabled); + gatorfs_create_ulong(sb, dir, "event", + &mmapped_counters[i].event); + gatorfs_create_ro_ulong(sb, dir, "key", + &mmapped_counters[i].key); + } + + return 0; +} + +static int gator_events_mmapped_start(void) +{ + int i; + struct timespec ts; + + getnstimeofday(&ts); + prev_time = timespec_to_ns(&ts); + + mmapped_global_enabled = 0; + for (i = 0; i < MMAPPED_COUNTERS_NUM; i++) { + if (mmapped_counters[i].enabled) { + mmapped_global_enabled = 1; + break; + } + } + + return 0; +} + +static void gator_events_mmapped_stop(void) +{ +} + +/* This function "simulates" counters, generating values of fancy + * functions like sine or triangle... */ +static int mmapped_simulate(int counter, int delta_in_us) +{ + int result = 0; + + switch (counter) { + case 0: /* sort-of-sine */ + { + static int t = 0; + int x; + + t += delta_in_us; + if (t > 2048000) + t = 0; + + if (t % 1024000 < 512000) + x = 512000 - (t % 512000); + else + x = t % 512000; + + result = 32 * x / 512000; + result = result * result; + + if (t < 1024000) + result = 1922 - result; + } + break; + case 1: /* triangle */ + { + static int v, d = 1; + + v = v + d * delta_in_us; + if (v < 0) { + v = 0; + d = 1; + } else if (v > 1000000) { + v = 1000000; + d = -1; + } + + result = v; + } + break; + case 2: /* PWM signal */ + { + static int dc, x, t = 0; + + t += delta_in_us; + if (t > 1000000) + t = 0; + if (x / 1000000 != (x + delta_in_us) / 1000000) + dc = (dc + 100000) % 1000000; + x += delta_in_us; + + result = t < dc ? 0 : 10; + } + break; + } + + return result; +} + +static int gator_events_mmapped_read(int **buffer) +{ + int i; + int len = 0; + int delta_in_us; + struct timespec ts; + s64 time; + + /* System wide counters - read from one core only */ + if (!on_primary_core() || !mmapped_global_enabled) + return 0; + + getnstimeofday(&ts); + time = timespec_to_ns(&ts); + delta_in_us = (int)(time - prev_time) / 1000; + prev_time = time; + + for (i = 0; i < MMAPPED_COUNTERS_NUM; i++) { + if (mmapped_counters[i].enabled) { + mmapped_buffer[len++] = mmapped_counters[i].key; + mmapped_buffer[len++] = + mmapped_simulate(mmapped_counters[i].event, + delta_in_us); + } + } + + if (buffer) + *buffer = mmapped_buffer; + + return len; +} + +static struct gator_interface gator_events_mmapped_interface = { + .create_files = gator_events_mmapped_create_files, + .start = gator_events_mmapped_start, + .stop = gator_events_mmapped_stop, + .read = gator_events_mmapped_read, +}; + +/* Must not be static! */ +int __init gator_events_mmapped_init(void) +{ + int i; + + for (i = 0; i < MMAPPED_COUNTERS_NUM; i++) { + mmapped_counters[i].enabled = 0; + mmapped_counters[i].key = gator_events_get_key(); + } + + return gator_events_install(&gator_events_mmapped_interface); +} diff --git a/drivers/gator/gator_events_net.c b/drivers/gator/gator_events_net.c new file mode 100644 index 00000000000000..9c8d3a43eaebd4 --- /dev/null +++ b/drivers/gator/gator_events_net.c @@ -0,0 +1,172 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 "gator.h" +#include +#include + +#define NETRX 0 +#define NETTX 1 +#define TOTALNET 2 + +static ulong netrx_enabled; +static ulong nettx_enabled; +static ulong netrx_key; +static ulong nettx_key; +static int rx_total, tx_total; +static ulong netPrev[TOTALNET]; +static int netGet[TOTALNET * 4]; + +static struct timer_list net_wake_up_timer; + +// Must be run in process context as the kernel function dev_get_stats() can sleep +static void get_network_stats(struct work_struct *wsptr) +{ + int rx = 0, tx = 0; + struct net_device *dev; + + for_each_netdev(&init_net, dev) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) + const struct net_device_stats *stats = dev_get_stats(dev); +#else + struct rtnl_link_stats64 temp; + const struct rtnl_link_stats64 *stats = dev_get_stats(dev, &temp); +#endif + rx += stats->rx_bytes; + tx += stats->tx_bytes; + } + rx_total = rx; + tx_total = tx; +} + +DECLARE_WORK(wq_get_stats, get_network_stats); + +static void net_wake_up_handler(unsigned long unused_data) +{ + // had to delay scheduling work as attempting to schedule work during the context switch is illegal in kernel versions 3.5 and greater + schedule_work(&wq_get_stats); +} + +static void calculate_delta(int *rx, int *tx) +{ + int rx_calc, tx_calc; + + rx_calc = (int)(rx_total - netPrev[NETRX]); + if (rx_calc < 0) + rx_calc = 0; + netPrev[NETRX] += rx_calc; + + tx_calc = (int)(tx_total - netPrev[NETTX]); + if (tx_calc < 0) + tx_calc = 0; + netPrev[NETTX] += tx_calc; + + *rx = rx_calc; + *tx = tx_calc; +} + +static int gator_events_net_create_files(struct super_block *sb, struct dentry *root) +{ + // Network counters are not currently supported in RT-Preempt full because mod_timer is used +#ifndef CONFIG_PREEMPT_RT_FULL + struct dentry *dir; + + dir = gatorfs_mkdir(sb, root, "Linux_net_rx"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &netrx_enabled); + gatorfs_create_ro_ulong(sb, dir, "key", &netrx_key); + + dir = gatorfs_mkdir(sb, root, "Linux_net_tx"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &nettx_enabled); + gatorfs_create_ro_ulong(sb, dir, "key", &nettx_key); +#endif + + return 0; +} + +static int gator_events_net_start(void) +{ + get_network_stats(0); + netPrev[NETRX] = rx_total; + netPrev[NETTX] = tx_total; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) + setup_timer(&net_wake_up_timer, net_wake_up_handler, 0); +#else + setup_deferrable_timer_on_stack(&net_wake_up_timer, net_wake_up_handler, 0); +#endif + return 0; +} + +static void gator_events_net_stop(void) +{ + del_timer_sync(&net_wake_up_timer); + netrx_enabled = 0; + nettx_enabled = 0; +} + +static int gator_events_net_read(int **buffer) +{ + int len, rx_delta, tx_delta; + static int last_rx_delta = 0, last_tx_delta = 0; + + if (!on_primary_core()) + return 0; + + if (!netrx_enabled && !nettx_enabled) + return 0; + + mod_timer(&net_wake_up_timer, jiffies + 1); + + calculate_delta(&rx_delta, &tx_delta); + + len = 0; + if (netrx_enabled && last_rx_delta != rx_delta) { + last_rx_delta = rx_delta; + netGet[len++] = netrx_key; + netGet[len++] = 0; // indicates to Streamline that rx_delta bytes were transmitted now, not since the last message + netGet[len++] = netrx_key; + netGet[len++] = rx_delta; + } + + if (nettx_enabled && last_tx_delta != tx_delta) { + last_tx_delta = tx_delta; + netGet[len++] = nettx_key; + netGet[len++] = 0; // indicates to Streamline that tx_delta bytes were transmitted now, not since the last message + netGet[len++] = nettx_key; + netGet[len++] = tx_delta; + } + + if (buffer) + *buffer = netGet; + + return len; +} + +static struct gator_interface gator_events_net_interface = { + .create_files = gator_events_net_create_files, + .start = gator_events_net_start, + .stop = gator_events_net_stop, + .read = gator_events_net_read, +}; + +int gator_events_net_init(void) +{ + netrx_key = gator_events_get_key(); + nettx_key = gator_events_get_key(); + + netrx_enabled = 0; + nettx_enabled = 0; + + return gator_events_install(&gator_events_net_interface); +} diff --git a/drivers/gator/gator_events_perf_pmu.c b/drivers/gator/gator_events_perf_pmu.c new file mode 100644 index 00000000000000..d472df918ab0f8 --- /dev/null +++ b/drivers/gator/gator_events_perf_pmu.c @@ -0,0 +1,587 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 "gator.h" + +// gator_events_armvX.c is used for Linux 2.6.x +#if GATOR_PERF_PMU_SUPPORT + +#include +#ifdef CONFIG_OF +#include +#endif +#include +#include + +extern bool event_based_sampling; + +// Maximum number of per-core counters - currently reserves enough space for two full hardware PMUs for big.LITTLE +#define CNTMAX 16 +#define CCI_400 4 +// Maximum number of uncore counters +// + 1 for the cci-400 cycles counter +#define UCCNT (CCI_400 + 1) + +// Default to 0 if unable to probe the revision which was the previous behavior +#define DEFAULT_CCI_REVISION 0 + +// A gator_attr is needed for every counter +struct gator_attr { + // Set once in gator_events_perf_pmu_*_init - the name of the event in the gatorfs + char name[40]; + // Exposed in gatorfs - set by gatord to enable this counter + unsigned long enabled; + // Set once in gator_events_perf_pmu_*_init - the perf type to use, see perf_type_id in the perf_event.h header file. + unsigned long type; + // Exposed in gatorfs - set by gatord to select the event to collect + unsigned long event; + // Exposed in gatorfs - set by gatord with the sample period to use and enable EBS for this counter + unsigned long count; + // Exposed as read only in gatorfs - set once in __attr_init as the key to use in the APC data + unsigned long key; +}; + +// Per-core counter attributes +static struct gator_attr attrs[CNTMAX]; +// Number of initialized per-core counters +static int attr_count; +// Uncore counter attributes +static struct gator_attr uc_attrs[UCCNT]; +// Number of initialized uncore counters +static int uc_attr_count; + +struct gator_event { + int curr; + int prev; + int prev_delta; + bool zero; + struct perf_event *pevent; + struct perf_event_attr *pevent_attr; +}; + +static DEFINE_PER_CPU(struct gator_event[CNTMAX], events); +static struct gator_event uc_events[UCCNT]; +static DEFINE_PER_CPU(int[(CNTMAX + UCCNT)*2], perf_cnt); + +static void gator_events_perf_pmu_stop(void); + +static int __create_files(struct super_block *sb, struct dentry *root, struct gator_attr *const attr) +{ + struct dentry *dir; + + if (attr->name[0] == '\0') { + return 0; + } + dir = gatorfs_mkdir(sb, root, attr->name); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &attr->enabled); + gatorfs_create_ulong(sb, dir, "count", &attr->count); + gatorfs_create_ro_ulong(sb, dir, "key", &attr->key); + gatorfs_create_ulong(sb, dir, "event", &attr->event); + + return 0; +} + +static int gator_events_perf_pmu_create_files(struct super_block *sb, struct dentry *root) +{ + int cnt; + + for (cnt = 0; cnt < attr_count; cnt++) { + if (__create_files(sb, root, &attrs[cnt]) != 0) { + return -1; + } + } + + for (cnt = 0; cnt < uc_attr_count; cnt++) { + if (__create_files(sb, root, &uc_attrs[cnt]) != 0) { + return -1; + } + } + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) +static void ebs_overflow_handler(struct perf_event *event, int unused, struct perf_sample_data *data, struct pt_regs *regs) +#else +static void ebs_overflow_handler(struct perf_event *event, struct perf_sample_data *data, struct pt_regs *regs) +#endif +{ + gator_backtrace_handler(regs); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) +static void dummy_handler(struct perf_event *event, int unused, struct perf_sample_data *data, struct pt_regs *regs) +#else +static void dummy_handler(struct perf_event *event, struct perf_sample_data *data, struct pt_regs *regs) +#endif +{ +// Required as perf_event_create_kernel_counter() requires an overflow handler, even though all we do is poll +} + +static int gator_events_perf_pmu_read(int **buffer); + +static int gator_events_perf_pmu_online(int **buffer, bool migrate) +{ + return gator_events_perf_pmu_read(buffer); +} + +static void __online_dispatch(int cpu, bool migrate, struct gator_attr *const attr, struct gator_event *const event) +{ + perf_overflow_handler_t handler; + + event->zero = true; + + if (event->pevent != NULL || event->pevent_attr == 0 || migrate) { + return; + } + + if (attr->count > 0) { + handler = ebs_overflow_handler; + } else { + handler = dummy_handler; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) + event->pevent = perf_event_create_kernel_counter(event->pevent_attr, cpu, 0, handler); +#else + event->pevent = perf_event_create_kernel_counter(event->pevent_attr, cpu, 0, handler, 0); +#endif + if (IS_ERR(event->pevent)) { + pr_debug("gator: unable to online a counter on cpu %d\n", cpu); + event->pevent = NULL; + return; + } + + if (event->pevent->state != PERF_EVENT_STATE_ACTIVE) { + pr_debug("gator: inactive counter on cpu %d\n", cpu); + perf_event_release_kernel(event->pevent); + event->pevent = NULL; + return; + } +} + +static void gator_events_perf_pmu_online_dispatch(int cpu, bool migrate) +{ + int cnt; + + cpu = pcpu_to_lcpu(cpu); + + for (cnt = 0; cnt < attr_count; cnt++) { + __online_dispatch(cpu, migrate, &attrs[cnt], &per_cpu(events, cpu)[cnt]); + } + + if (cpu == 0) { + for (cnt = 0; cnt < uc_attr_count; cnt++) { + __online_dispatch(cpu, migrate, &uc_attrs[cnt], &uc_events[cnt]); + } + } +} + +static void __offline_dispatch(int cpu, struct gator_event *const event) +{ + struct perf_event *pe = NULL; + + if (event->pevent) { + pe = event->pevent; + event->pevent = NULL; + } + + if (pe) { + perf_event_release_kernel(pe); + } +} + +static void gator_events_perf_pmu_offline_dispatch(int cpu, bool migrate) +{ + int cnt; + + if (migrate) { + return; + } + cpu = pcpu_to_lcpu(cpu); + + for (cnt = 0; cnt < attr_count; cnt++) { + __offline_dispatch(cpu, &per_cpu(events, cpu)[cnt]); + } + + if (cpu == 0) { + for (cnt = 0; cnt < uc_attr_count; cnt++) { + __offline_dispatch(cpu, &uc_events[cnt]); + } + } +} + +static int __check_ebs(struct gator_attr *const attr) +{ + if (attr->count > 0) { + if (!event_based_sampling) { + event_based_sampling = true; + } else { + printk(KERN_WARNING "gator: Only one ebs counter is allowed\n"); + return -1; + } + } + + return 0; +} + +static int __start(struct gator_attr *const attr, struct gator_event *const event) +{ + u32 size = sizeof(struct perf_event_attr); + + event->pevent = NULL; + if (!attr->enabled) { // Skip disabled counters + return 0; + } + + event->prev = 0; + event->curr = 0; + event->prev_delta = 0; + event->pevent_attr = kmalloc(size, GFP_KERNEL); + if (!event->pevent_attr) { + gator_events_perf_pmu_stop(); + return -1; + } + + memset(event->pevent_attr, 0, size); + event->pevent_attr->type = attr->type; + event->pevent_attr->size = size; + event->pevent_attr->config = attr->event; + event->pevent_attr->sample_period = attr->count; + event->pevent_attr->pinned = 1; + + return 0; +} + +static int gator_events_perf_pmu_start(void) +{ + int cnt, cpu; + + event_based_sampling = false; + for (cnt = 0; cnt < attr_count; cnt++) { + if (__check_ebs(&attrs[cnt]) != 0) { + return -1; + } + } + + for (cnt = 0; cnt < uc_attr_count; cnt++) { + if (__check_ebs(&uc_attrs[cnt]) != 0) { + return -1; + } + } + + for_each_present_cpu(cpu) { + for (cnt = 0; cnt < attr_count; cnt++) { + if (__start(&attrs[cnt], &per_cpu(events, cpu)[cnt]) != 0) { + return -1; + } + } + } + + for (cnt = 0; cnt < uc_attr_count; cnt++) { + if (__start(&uc_attrs[cnt], &uc_events[cnt]) != 0) { + return -1; + } + } + + return 0; +} + +static void __event_stop(struct gator_event *const event) +{ + if (event->pevent_attr) { + kfree(event->pevent_attr); + event->pevent_attr = NULL; + } +} + +static void __attr_stop(struct gator_attr *const attr) +{ + attr->enabled = 0; + attr->event = 0; + attr->count = 0; +} + +static void gator_events_perf_pmu_stop(void) +{ + unsigned int cnt, cpu; + + for_each_present_cpu(cpu) { + for (cnt = 0; cnt < attr_count; cnt++) { + __event_stop(&per_cpu(events, cpu)[cnt]); + } + } + + for (cnt = 0; cnt < uc_attr_count; cnt++) { + __event_stop(&uc_events[cnt]); + } + + for (cnt = 0; cnt < attr_count; cnt++) { + __attr_stop(&attrs[cnt]); + } + + for (cnt = 0; cnt < uc_attr_count; cnt++) { + __attr_stop(&uc_attrs[cnt]); + } +} + +static void __read(int *const len, int cpu, struct gator_attr *const attr, struct gator_event *const event) +{ + int delta; + + struct perf_event *const ev = event->pevent; + if (ev != NULL && ev->state == PERF_EVENT_STATE_ACTIVE) { + /* After creating the perf counter in __online_dispatch, there + * is a race condition between gator_events_perf_pmu_online and + * gator_events_perf_pmu_read. So have + * gator_events_perf_pmu_online call gator_events_perf_pmu_read + * and in __read check to see if it's the first call after + * __online_dispatch and if so, run the online code. + */ + if (event->zero) { + ev->pmu->read(ev); + event->prev = event->curr = local64_read(&ev->count); + event->prev_delta = 0; + per_cpu(perf_cnt, cpu)[(*len)++] = attr->key; + per_cpu(perf_cnt, cpu)[(*len)++] = 0; + event->zero = false; + } else { + ev->pmu->read(ev); + event->curr = local64_read(&ev->count); + delta = event->curr - event->prev; + if (delta != 0 || delta != event->prev_delta) { + event->prev_delta = delta; + event->prev = event->curr; + per_cpu(perf_cnt, cpu)[(*len)++] = attr->key; + if (delta < 0) { + delta *= -1; + } + per_cpu(perf_cnt, cpu)[(*len)++] = delta; + } + } + } +} + +static int gator_events_perf_pmu_read(int **buffer) +{ + int cnt, len = 0; + const int cpu = get_logical_cpu(); + + for (cnt = 0; cnt < attr_count; cnt++) { + __read(&len, cpu, &attrs[cnt], &per_cpu(events, cpu)[cnt]); + } + + if (cpu == 0) { + for (cnt = 0; cnt < uc_attr_count; cnt++) { + __read(&len, cpu, &uc_attrs[cnt], &uc_events[cnt]); + } + } + + if (buffer) { + *buffer = per_cpu(perf_cnt, cpu); + } + + return len; +} + +static struct gator_interface gator_events_perf_pmu_interface = { + .create_files = gator_events_perf_pmu_create_files, + .start = gator_events_perf_pmu_start, + .stop = gator_events_perf_pmu_stop, + .online = gator_events_perf_pmu_online, + .online_dispatch = gator_events_perf_pmu_online_dispatch, + .offline_dispatch = gator_events_perf_pmu_offline_dispatch, + .read = gator_events_perf_pmu_read, +}; + +static void __attr_init(struct gator_attr *const attr) +{ + attr->name[0] = '\0'; + attr->enabled = 0; + attr->type = 0; + attr->event = 0; + attr->count = 0; + attr->key = gator_events_get_key(); +} + +#ifdef CONFIG_OF + +static const struct of_device_id arm_cci_matches[] = { + {.compatible = "arm,cci-400" }, + {}, +}; + +static int probe_cci_revision(void) +{ + struct device_node *np; + struct resource res; + void __iomem *cci_ctrl_base; + int rev; + int ret = DEFAULT_CCI_REVISION; + + np = of_find_matching_node(NULL, arm_cci_matches); + if (!np) { + return ret; + } + + if (of_address_to_resource(np, 0, &res)) { + goto node_put; + } + + cci_ctrl_base = ioremap(res.start, resource_size(&res)); + + rev = (readl_relaxed(cci_ctrl_base + 0xfe8) >> 4) & 0xf; + + if (rev <= 4) { + ret = 0; + } else if (rev <= 6) { + ret = 1; + } + + iounmap(cci_ctrl_base); + + node_put: + of_node_put(np); + + return ret; +} + +#else + +static int probe_cci_revision(void) +{ + return DEFAULT_CCI_REVISION; +} + +#endif + +static void gator_events_perf_pmu_cci_init(const int type) +{ + int cnt; + const char *cci_name; + + switch (probe_cci_revision()) { + case 0: + cci_name = "cci-400"; + break; + case 1: + cci_name = "cci-400-r1"; + break; + default: + pr_debug("gator: unrecognized cci-400 revision\n"); + return; + } + + snprintf(uc_attrs[uc_attr_count].name, sizeof(uc_attrs[uc_attr_count].name), "%s_ccnt", cci_name); + uc_attrs[uc_attr_count].type = type; + ++uc_attr_count; + + for (cnt = 0; cnt < CCI_400; ++cnt, ++uc_attr_count) { + struct gator_attr *const attr = &uc_attrs[uc_attr_count]; + snprintf(attr->name, sizeof(attr->name), "%s_cnt%d", cci_name, cnt); + attr->type = type; + } +} + +static void gator_events_perf_pmu_cpu_init(const struct gator_cpu *const gator_cpu, const int type) +{ + int cnt; + + snprintf(attrs[attr_count].name, sizeof(attrs[attr_count].name), "%s_ccnt", gator_cpu->pmnc_name); + attrs[attr_count].type = type; + ++attr_count; + + for (cnt = 0; cnt < gator_cpu->pmnc_counters; ++cnt, ++attr_count) { + struct gator_attr *const attr = &attrs[attr_count]; + snprintf(attr->name, sizeof(attr->name), "%s_cnt%d", gator_cpu->pmnc_name, cnt); + attr->type = type; + } +} + +int gator_events_perf_pmu_init(void) +{ + struct perf_event_attr pea; + struct perf_event *pe; + const struct gator_cpu *gator_cpu; + int type; + int cpu; + int cnt; + bool found_cpu = false; + + for (cnt = 0; cnt < CNTMAX; cnt++) { + __attr_init(&attrs[cnt]); + } + for (cnt = 0; cnt < UCCNT; cnt++) { + __attr_init(&uc_attrs[cnt]); + } + + memset(&pea, 0, sizeof(pea)); + pea.size = sizeof(pea); + pea.config = 0xFF; + attr_count = 0; + uc_attr_count = 0; + for (type = PERF_TYPE_MAX; type < 0x20; ++type) { + pea.type = type; + + // A particular PMU may work on some but not all cores, so try on each core + pe = NULL; + for_each_present_cpu(cpu) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) + pe = perf_event_create_kernel_counter(&pea, cpu, 0, dummy_handler); +#else + pe = perf_event_create_kernel_counter(&pea, cpu, 0, dummy_handler, 0); +#endif + if (!IS_ERR(pe)) { + break; + } + } + // Assume that valid PMUs are contiguous + if (IS_ERR(pe)) { + break; + } + + if (pe->pmu != NULL && type == pe->pmu->type) { + if (strcmp("CCI", pe->pmu->name) == 0 || strcmp("CCI_400", pe->pmu->name) == 0) { + gator_events_perf_pmu_cci_init(type); + } else if ((gator_cpu = gator_find_cpu_by_pmu_name(pe->pmu->name)) != NULL) { + found_cpu = true; + gator_events_perf_pmu_cpu_init(gator_cpu, type); + } + // Initialize gator_attrs for dynamic PMUs here + } + + perf_event_release_kernel(pe); + } + + if (!found_cpu) { + const struct gator_cpu *const gator_cpu = gator_find_cpu_by_cpuid(gator_cpuid()); + if (gator_cpu == NULL) { + return -1; + } + gator_events_perf_pmu_cpu_init(gator_cpu, PERF_TYPE_RAW); + } + + // Initialize gator_attrs for non-dynamic PMUs here + + if (attr_count > CNTMAX) { + printk(KERN_ERR "gator: Too many perf counters\n"); + return -1; + } + + if (uc_attr_count > UCCNT) { + printk(KERN_ERR "gator: Too many perf uncore counters\n"); + return -1; + } + + return gator_events_install(&gator_events_perf_pmu_interface); +} + +#endif diff --git a/drivers/gator/gator_events_sched.c b/drivers/gator/gator_events_sched.c new file mode 100644 index 00000000000000..29f4e39e261c42 --- /dev/null +++ b/drivers/gator/gator_events_sched.c @@ -0,0 +1,113 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 "gator.h" +#include + +#define SCHED_SWITCH 0 +#define SCHED_TOTAL (SCHED_SWITCH+1) + +static ulong sched_switch_enabled; +static ulong sched_switch_key; +static DEFINE_PER_CPU(int[SCHED_TOTAL], schedCnt); +static DEFINE_PER_CPU(int[SCHED_TOTAL * 2], schedGet); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(struct rq *rq, struct task_struct *prev, struct task_struct *next)) +#else +GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(struct task_struct *prev, struct task_struct *next)) +#endif +{ + unsigned long flags; + + // disable interrupts to synchronize with gator_events_sched_read() + // spinlocks not needed since percpu buffers are used + local_irq_save(flags); + per_cpu(schedCnt, get_physical_cpu())[SCHED_SWITCH]++; + local_irq_restore(flags); +} + +static int gator_events_sched_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + + /* switch */ + dir = gatorfs_mkdir(sb, root, "Linux_sched_switch"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &sched_switch_enabled); + gatorfs_create_ro_ulong(sb, dir, "key", &sched_switch_key); + + return 0; +} + +static int gator_events_sched_start(void) +{ + // register tracepoints + if (sched_switch_enabled) + if (GATOR_REGISTER_TRACE(sched_switch)) + goto sched_switch_exit; + pr_debug("gator: registered scheduler event tracepoints\n"); + + return 0; + + // unregister tracepoints on error +sched_switch_exit: + pr_err("gator: scheduler event tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n"); + + return -1; +} + +static void gator_events_sched_stop(void) +{ + if (sched_switch_enabled) + GATOR_UNREGISTER_TRACE(sched_switch); + pr_debug("gator: unregistered scheduler event tracepoints\n"); + + sched_switch_enabled = 0; +} + +static int gator_events_sched_read(int **buffer) +{ + unsigned long flags; + int len, value; + int cpu = get_physical_cpu(); + + len = 0; + if (sched_switch_enabled) { + local_irq_save(flags); + value = per_cpu(schedCnt, cpu)[SCHED_SWITCH]; + per_cpu(schedCnt, cpu)[SCHED_SWITCH] = 0; + local_irq_restore(flags); + per_cpu(schedGet, cpu)[len++] = sched_switch_key; + per_cpu(schedGet, cpu)[len++] = value; + } + + if (buffer) + *buffer = per_cpu(schedGet, cpu); + + return len; +} + +static struct gator_interface gator_events_sched_interface = { + .create_files = gator_events_sched_create_files, + .start = gator_events_sched_start, + .stop = gator_events_sched_stop, + .read = gator_events_sched_read, +}; + +int gator_events_sched_init(void) +{ + sched_switch_enabled = 0; + + sched_switch_key = gator_events_get_key(); + + return gator_events_install(&gator_events_sched_interface); +} diff --git a/drivers/gator/gator_events_scorpion.c b/drivers/gator/gator_events_scorpion.c new file mode 100644 index 00000000000000..c91db1219d080b --- /dev/null +++ b/drivers/gator/gator_events_scorpion.c @@ -0,0 +1,669 @@ +/** + * Copyright (C) ARM Limited 2011-2013. All rights reserved. + * + * 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 "gator.h" + +// gator_events_perf_pmu.c is used if perf is supported +#if GATOR_NO_PERF_SUPPORT + +static const char *pmnc_name; +static int pmnc_counters; + +// Per-CPU PMNC: config reg +#define PMNC_E (1 << 0) /* Enable all counters */ +#define PMNC_P (1 << 1) /* Reset all counters */ +#define PMNC_C (1 << 2) /* Cycle counter reset */ +#define PMNC_D (1 << 3) /* CCNT counts every 64th cpu cycle */ +#define PMNC_X (1 << 4) /* Export to ETM */ +#define PMNC_DP (1 << 5) /* Disable CCNT if non-invasive debug */ +#define PMNC_MASK 0x3f /* Mask for writable bits */ + +// ccnt reg +#define CCNT_REG (1 << 31) + +#define CCNT 0 +#define CNT0 1 +#define CNTMAX (4+1) + +static unsigned long pmnc_enabled[CNTMAX]; +static unsigned long pmnc_event[CNTMAX]; +static unsigned long pmnc_key[CNTMAX]; + +static DEFINE_PER_CPU(int[CNTMAX * 2], perfCnt); + +enum scorpion_perf_types { + SCORPION_ICACHE_EXPL_INV = 0x4c, + SCORPION_ICACHE_MISS = 0x4d, + SCORPION_ICACHE_ACCESS = 0x4e, + SCORPION_ICACHE_CACHEREQ_L2 = 0x4f, + SCORPION_ICACHE_NOCACHE_L2 = 0x50, + SCORPION_HIQUP_NOPED = 0x51, + SCORPION_DATA_ABORT = 0x52, + SCORPION_IRQ = 0x53, + SCORPION_FIQ = 0x54, + SCORPION_ALL_EXCPT = 0x55, + SCORPION_UNDEF = 0x56, + SCORPION_SVC = 0x57, + SCORPION_SMC = 0x58, + SCORPION_PREFETCH_ABORT = 0x59, + SCORPION_INDEX_CHECK = 0x5a, + SCORPION_NULL_CHECK = 0x5b, + SCORPION_EXPL_ICIALLU = 0x5c, + SCORPION_IMPL_ICIALLU = 0x5d, + SCORPION_NONICIALLU_BTAC_INV = 0x5e, + SCORPION_ICIMVAU_IMPL_ICIALLU = 0x5f, + SCORPION_SPIPE_ONLY_CYCLES = 0x60, + SCORPION_XPIPE_ONLY_CYCLES = 0x61, + SCORPION_DUAL_CYCLES = 0x62, + SCORPION_DISPATCH_ANY_CYCLES = 0x63, + SCORPION_FIFO_FULLBLK_CMT = 0x64, + SCORPION_FAIL_COND_INST = 0x65, + SCORPION_PASS_COND_INST = 0x66, + SCORPION_ALLOW_VU_CLK = 0x67, + SCORPION_VU_IDLE = 0x68, + SCORPION_ALLOW_L2_CLK = 0x69, + SCORPION_L2_IDLE = 0x6a, + SCORPION_DTLB_IMPL_INV_SCTLR_DACR = 0x6b, + SCORPION_DTLB_EXPL_INV = 0x6c, + SCORPION_DTLB_MISS = 0x6d, + SCORPION_DTLB_ACCESS = 0x6e, + SCORPION_ITLB_MISS = 0x6f, + SCORPION_ITLB_IMPL_INV = 0x70, + SCORPION_ITLB_EXPL_INV = 0x71, + SCORPION_UTLB_D_MISS = 0x72, + SCORPION_UTLB_D_ACCESS = 0x73, + SCORPION_UTLB_I_MISS = 0x74, + SCORPION_UTLB_I_ACCESS = 0x75, + SCORPION_UTLB_INV_ASID = 0x76, + SCORPION_UTLB_INV_MVA = 0x77, + SCORPION_UTLB_INV_ALL = 0x78, + SCORPION_S2_HOLD_RDQ_UNAVAIL = 0x79, + SCORPION_S2_HOLD = 0x7a, + SCORPION_S2_HOLD_DEV_OP = 0x7b, + SCORPION_S2_HOLD_ORDER = 0x7c, + SCORPION_S2_HOLD_BARRIER = 0x7d, + SCORPION_VIU_DUAL_CYCLE = 0x7e, + SCORPION_VIU_SINGLE_CYCLE = 0x7f, + SCORPION_VX_PIPE_WAR_STALL_CYCLES = 0x80, + SCORPION_VX_PIPE_WAW_STALL_CYCLES = 0x81, + SCORPION_VX_PIPE_RAW_STALL_CYCLES = 0x82, + SCORPION_VX_PIPE_LOAD_USE_STALL = 0x83, + SCORPION_VS_PIPE_WAR_STALL_CYCLES = 0x84, + SCORPION_VS_PIPE_WAW_STALL_CYCLES = 0x85, + SCORPION_VS_PIPE_RAW_STALL_CYCLES = 0x86, + SCORPION_EXCEPTIONS_INV_OPERATION = 0x87, + SCORPION_EXCEPTIONS_DIV_BY_ZERO = 0x88, + SCORPION_COND_INST_FAIL_VX_PIPE = 0x89, + SCORPION_COND_INST_FAIL_VS_PIPE = 0x8a, + SCORPION_EXCEPTIONS_OVERFLOW = 0x8b, + SCORPION_EXCEPTIONS_UNDERFLOW = 0x8c, + SCORPION_EXCEPTIONS_DENORM = 0x8d, +#ifdef CONFIG_ARCH_MSM_SCORPIONMP + SCORPIONMP_NUM_BARRIERS = 0x8e, + SCORPIONMP_BARRIER_CYCLES = 0x8f, +#else + SCORPION_BANK_AB_HIT = 0x8e, + SCORPION_BANK_AB_ACCESS = 0x8f, + SCORPION_BANK_CD_HIT = 0x90, + SCORPION_BANK_CD_ACCESS = 0x91, + SCORPION_BANK_AB_DSIDE_HIT = 0x92, + SCORPION_BANK_AB_DSIDE_ACCESS = 0x93, + SCORPION_BANK_CD_DSIDE_HIT = 0x94, + SCORPION_BANK_CD_DSIDE_ACCESS = 0x95, + SCORPION_BANK_AB_ISIDE_HIT = 0x96, + SCORPION_BANK_AB_ISIDE_ACCESS = 0x97, + SCORPION_BANK_CD_ISIDE_HIT = 0x98, + SCORPION_BANK_CD_ISIDE_ACCESS = 0x99, + SCORPION_ISIDE_RD_WAIT = 0x9a, + SCORPION_DSIDE_RD_WAIT = 0x9b, + SCORPION_BANK_BYPASS_WRITE = 0x9c, + SCORPION_BANK_AB_NON_CASTOUT = 0x9d, + SCORPION_BANK_AB_L2_CASTOUT = 0x9e, + SCORPION_BANK_CD_NON_CASTOUT = 0x9f, + SCORPION_BANK_CD_L2_CASTOUT = 0xa0, +#endif + MSM_MAX_EVT +}; + +struct scorp_evt { + u32 evt_type; + u32 val; + u8 grp; + u32 evt_type_act; +}; + +static const struct scorp_evt sc_evt[] = { + {SCORPION_ICACHE_EXPL_INV, 0x80000500, 0, 0x4d}, + {SCORPION_ICACHE_MISS, 0x80050000, 0, 0x4e}, + {SCORPION_ICACHE_ACCESS, 0x85000000, 0, 0x4f}, + {SCORPION_ICACHE_CACHEREQ_L2, 0x86000000, 0, 0x4f}, + {SCORPION_ICACHE_NOCACHE_L2, 0x87000000, 0, 0x4f}, + {SCORPION_HIQUP_NOPED, 0x80080000, 0, 0x4e}, + {SCORPION_DATA_ABORT, 0x8000000a, 0, 0x4c}, + {SCORPION_IRQ, 0x80000a00, 0, 0x4d}, + {SCORPION_FIQ, 0x800a0000, 0, 0x4e}, + {SCORPION_ALL_EXCPT, 0x8a000000, 0, 0x4f}, + {SCORPION_UNDEF, 0x8000000b, 0, 0x4c}, + {SCORPION_SVC, 0x80000b00, 0, 0x4d}, + {SCORPION_SMC, 0x800b0000, 0, 0x4e}, + {SCORPION_PREFETCH_ABORT, 0x8b000000, 0, 0x4f}, + {SCORPION_INDEX_CHECK, 0x8000000c, 0, 0x4c}, + {SCORPION_NULL_CHECK, 0x80000c00, 0, 0x4d}, + {SCORPION_EXPL_ICIALLU, 0x8000000d, 0, 0x4c}, + {SCORPION_IMPL_ICIALLU, 0x80000d00, 0, 0x4d}, + {SCORPION_NONICIALLU_BTAC_INV, 0x800d0000, 0, 0x4e}, + {SCORPION_ICIMVAU_IMPL_ICIALLU, 0x8d000000, 0, 0x4f}, + + {SCORPION_SPIPE_ONLY_CYCLES, 0x80000600, 1, 0x51}, + {SCORPION_XPIPE_ONLY_CYCLES, 0x80060000, 1, 0x52}, + {SCORPION_DUAL_CYCLES, 0x86000000, 1, 0x53}, + {SCORPION_DISPATCH_ANY_CYCLES, 0x89000000, 1, 0x53}, + {SCORPION_FIFO_FULLBLK_CMT, 0x8000000d, 1, 0x50}, + {SCORPION_FAIL_COND_INST, 0x800d0000, 1, 0x52}, + {SCORPION_PASS_COND_INST, 0x8d000000, 1, 0x53}, + {SCORPION_ALLOW_VU_CLK, 0x8000000e, 1, 0x50}, + {SCORPION_VU_IDLE, 0x80000e00, 1, 0x51}, + {SCORPION_ALLOW_L2_CLK, 0x800e0000, 1, 0x52}, + {SCORPION_L2_IDLE, 0x8e000000, 1, 0x53}, + + {SCORPION_DTLB_IMPL_INV_SCTLR_DACR, 0x80000001, 2, 0x54}, + {SCORPION_DTLB_EXPL_INV, 0x80000100, 2, 0x55}, + {SCORPION_DTLB_MISS, 0x80010000, 2, 0x56}, + {SCORPION_DTLB_ACCESS, 0x81000000, 2, 0x57}, + {SCORPION_ITLB_MISS, 0x80000200, 2, 0x55}, + {SCORPION_ITLB_IMPL_INV, 0x80020000, 2, 0x56}, + {SCORPION_ITLB_EXPL_INV, 0x82000000, 2, 0x57}, + {SCORPION_UTLB_D_MISS, 0x80000003, 2, 0x54}, + {SCORPION_UTLB_D_ACCESS, 0x80000300, 2, 0x55}, + {SCORPION_UTLB_I_MISS, 0x80030000, 2, 0x56}, + {SCORPION_UTLB_I_ACCESS, 0x83000000, 2, 0x57}, + {SCORPION_UTLB_INV_ASID, 0x80000400, 2, 0x55}, + {SCORPION_UTLB_INV_MVA, 0x80040000, 2, 0x56}, + {SCORPION_UTLB_INV_ALL, 0x84000000, 2, 0x57}, + {SCORPION_S2_HOLD_RDQ_UNAVAIL, 0x80000800, 2, 0x55}, + {SCORPION_S2_HOLD, 0x88000000, 2, 0x57}, + {SCORPION_S2_HOLD_DEV_OP, 0x80000900, 2, 0x55}, + {SCORPION_S2_HOLD_ORDER, 0x80090000, 2, 0x56}, + {SCORPION_S2_HOLD_BARRIER, 0x89000000, 2, 0x57}, + + {SCORPION_VIU_DUAL_CYCLE, 0x80000001, 4, 0x5c}, + {SCORPION_VIU_SINGLE_CYCLE, 0x80000100, 4, 0x5d}, + {SCORPION_VX_PIPE_WAR_STALL_CYCLES, 0x80000005, 4, 0x5c}, + {SCORPION_VX_PIPE_WAW_STALL_CYCLES, 0x80000500, 4, 0x5d}, + {SCORPION_VX_PIPE_RAW_STALL_CYCLES, 0x80050000, 4, 0x5e}, + {SCORPION_VX_PIPE_LOAD_USE_STALL, 0x80000007, 4, 0x5c}, + {SCORPION_VS_PIPE_WAR_STALL_CYCLES, 0x80000008, 4, 0x5c}, + {SCORPION_VS_PIPE_WAW_STALL_CYCLES, 0x80000800, 4, 0x5d}, + {SCORPION_VS_PIPE_RAW_STALL_CYCLES, 0x80080000, 4, 0x5e}, + {SCORPION_EXCEPTIONS_INV_OPERATION, 0x8000000b, 4, 0x5c}, + {SCORPION_EXCEPTIONS_DIV_BY_ZERO, 0x80000b00, 4, 0x5d}, + {SCORPION_COND_INST_FAIL_VX_PIPE, 0x800b0000, 4, 0x5e}, + {SCORPION_COND_INST_FAIL_VS_PIPE, 0x8b000000, 4, 0x5f}, + {SCORPION_EXCEPTIONS_OVERFLOW, 0x8000000c, 4, 0x5c}, + {SCORPION_EXCEPTIONS_UNDERFLOW, 0x80000c00, 4, 0x5d}, + {SCORPION_EXCEPTIONS_DENORM, 0x8c000000, 4, 0x5f}, + +#ifdef CONFIG_ARCH_MSM_SCORPIONMP + {SCORPIONMP_NUM_BARRIERS, 0x80000e00, 3, 0x59}, + {SCORPIONMP_BARRIER_CYCLES, 0x800e0000, 3, 0x5a}, +#else + {SCORPION_BANK_AB_HIT, 0x80000001, 3, 0x58}, + {SCORPION_BANK_AB_ACCESS, 0x80000100, 3, 0x59}, + {SCORPION_BANK_CD_HIT, 0x80010000, 3, 0x5a}, + {SCORPION_BANK_CD_ACCESS, 0x81000000, 3, 0x5b}, + {SCORPION_BANK_AB_DSIDE_HIT, 0x80000002, 3, 0x58}, + {SCORPION_BANK_AB_DSIDE_ACCESS, 0x80000200, 3, 0x59}, + {SCORPION_BANK_CD_DSIDE_HIT, 0x80020000, 3, 0x5a}, + {SCORPION_BANK_CD_DSIDE_ACCESS, 0x82000000, 3, 0x5b}, + {SCORPION_BANK_AB_ISIDE_HIT, 0x80000003, 3, 0x58}, + {SCORPION_BANK_AB_ISIDE_ACCESS, 0x80000300, 3, 0x59}, + {SCORPION_BANK_CD_ISIDE_HIT, 0x80030000, 3, 0x5a}, + {SCORPION_BANK_CD_ISIDE_ACCESS, 0x83000000, 3, 0x5b}, + {SCORPION_ISIDE_RD_WAIT, 0x80000009, 3, 0x58}, + {SCORPION_DSIDE_RD_WAIT, 0x80090000, 3, 0x5a}, + {SCORPION_BANK_BYPASS_WRITE, 0x8000000a, 3, 0x58}, + {SCORPION_BANK_AB_NON_CASTOUT, 0x8000000c, 3, 0x58}, + {SCORPION_BANK_AB_L2_CASTOUT, 0x80000c00, 3, 0x59}, + {SCORPION_BANK_CD_NON_CASTOUT, 0x800c0000, 3, 0x5a}, + {SCORPION_BANK_CD_L2_CASTOUT, 0x8c000000, 3, 0x5b}, +#endif +}; + +static inline void scorpion_pmnc_write(u32 val) +{ + val &= PMNC_MASK; + asm volatile("mcr p15, 0, %0, c9, c12, 0" : : "r" (val)); +} + +static inline u32 scorpion_pmnc_read(void) +{ + u32 val; + asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val)); + return val; +} + +static inline u32 scorpion_ccnt_read(void) +{ + u32 val; + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r" (val)); + return val; +} + +static inline u32 scorpion_cntn_read(void) +{ + u32 val; + asm volatile("mrc p15, 0, %0, c9, c13, 2" : "=r" (val)); + return val; +} + +static inline u32 scorpion_pmnc_enable_counter(unsigned int cnt) +{ + u32 val; + + if (cnt >= CNTMAX) { + pr_err("gator: CPU%u enabling wrong PMNC counter %d\n", smp_processor_id(), cnt); + return -1; + } + + if (cnt == CCNT) + val = CCNT_REG; + else + val = (1 << (cnt - CNT0)); + + asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (val)); + + return cnt; +} + +static inline u32 scorpion_pmnc_disable_counter(unsigned int cnt) +{ + u32 val; + + if (cnt >= CNTMAX) { + pr_err("gator: CPU%u disabling wrong PMNC counter %d\n", smp_processor_id(), cnt); + return -1; + } + + if (cnt == CCNT) + val = CCNT_REG; + else + val = (1 << (cnt - CNT0)); + + asm volatile("mcr p15, 0, %0, c9, c12, 2" : : "r" (val)); + + return cnt; +} + +static inline int scorpion_pmnc_select_counter(unsigned int cnt) +{ + u32 val; + + if ((cnt == CCNT) || (cnt >= CNTMAX)) { + pr_err("gator: CPU%u selecting wrong PMNC counter %d\n", smp_processor_id(), cnt); + return -1; + } + + val = (cnt - CNT0); + asm volatile("mcr p15, 0, %0, c9, c12, 5" : : "r" (val)); + + return cnt; +} + +static u32 scorpion_read_lpm0(void) +{ + u32 val; + asm volatile("mrc p15, 0, %0, c15, c0, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_lpm0(u32 val) +{ + asm volatile("mcr p15, 0, %0, c15, c0, 0" : : "r" (val)); +} + +static u32 scorpion_read_lpm1(void) +{ + u32 val; + asm volatile("mrc p15, 1, %0, c15, c0, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_lpm1(u32 val) +{ + asm volatile("mcr p15, 1, %0, c15, c0, 0" : : "r" (val)); +} + +static u32 scorpion_read_lpm2(void) +{ + u32 val; + asm volatile("mrc p15, 2, %0, c15, c0, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_lpm2(u32 val) +{ + asm volatile("mcr p15, 2, %0, c15, c0, 0" : : "r" (val)); +} + +static u32 scorpion_read_l2lpm(void) +{ + u32 val; + asm volatile("mrc p15, 3, %0, c15, c2, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_l2lpm(u32 val) +{ + asm volatile("mcr p15, 3, %0, c15, c2, 0" : : "r" (val)); +} + +static u32 scorpion_read_vlpm(void) +{ + u32 val; + asm volatile("mrc p10, 7, %0, c11, c0, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_vlpm(u32 val) +{ + asm volatile("mcr p10, 7, %0, c11, c0, 0" : : "r" (val)); +} + +struct scorpion_access_funcs { + u32(*read)(void); + void (*write)(u32); +}; + +struct scorpion_access_funcs scor_func[] = { + {scorpion_read_lpm0, scorpion_write_lpm0}, + {scorpion_read_lpm1, scorpion_write_lpm1}, + {scorpion_read_lpm2, scorpion_write_lpm2}, + {scorpion_read_l2lpm, scorpion_write_l2lpm}, + {scorpion_read_vlpm, scorpion_write_vlpm}, +}; + +u32 venum_orig_val; +u32 fp_orig_val; + +static void scorpion_pre_vlpm(void) +{ + u32 venum_new_val; + u32 fp_new_val; + + /* CPACR Enable CP10 access */ + asm volatile("mrc p15, 0, %0, c1, c0, 2" : "=r" (venum_orig_val)); + venum_new_val = venum_orig_val | 0x00300000; + asm volatile("mcr p15, 0, %0, c1, c0, 2" : : "r" (venum_new_val)); + /* Enable FPEXC */ + asm volatile("mrc p10, 7, %0, c8, c0, 0" : "=r" (fp_orig_val)); + fp_new_val = fp_orig_val | 0x40000000; + asm volatile("mcr p10, 7, %0, c8, c0, 0" : : "r" (fp_new_val)); +} + +static void scorpion_post_vlpm(void) +{ + /* Restore FPEXC */ + asm volatile("mcr p10, 7, %0, c8, c0, 0" : : "r" (fp_orig_val)); + /* Restore CPACR */ + asm volatile("mcr p15, 0, %0, c1, c0, 2" : : "r" (venum_orig_val)); +} + +#define COLMN0MASK 0x000000ff +#define COLMN1MASK 0x0000ff00 +#define COLMN2MASK 0x00ff0000 +static u32 scorpion_get_columnmask(u32 setval) +{ + if (setval & COLMN0MASK) + return 0xffffff00; + else if (setval & COLMN1MASK) + return 0xffff00ff; + else if (setval & COLMN2MASK) + return 0xff00ffff; + else + return 0x80ffffff; +} + +static void scorpion_evt_setup(u32 gr, u32 setval) +{ + u32 val; + if (gr == 4) + scorpion_pre_vlpm(); + val = scorpion_get_columnmask(setval) & scor_func[gr].read(); + val = val | setval; + scor_func[gr].write(val); + if (gr == 4) + scorpion_post_vlpm(); +} + +static int get_scorpion_evtinfo(unsigned int evt_type, struct scorp_evt *evtinfo) +{ + u32 idx; + if ((evt_type < 0x4c) || (evt_type >= MSM_MAX_EVT)) + return 0; + idx = evt_type - 0x4c; + if (sc_evt[idx].evt_type == evt_type) { + evtinfo->val = sc_evt[idx].val; + evtinfo->grp = sc_evt[idx].grp; + evtinfo->evt_type_act = sc_evt[idx].evt_type_act; + return 1; + } + return 0; +} + +static inline void scorpion_pmnc_write_evtsel(unsigned int cnt, u32 val) +{ + if (scorpion_pmnc_select_counter(cnt) == cnt) { + if (val < 0x40) { + asm volatile("mcr p15, 0, %0, c9, c13, 1" : : "r" (val)); + } else { + u32 zero = 0; + struct scorp_evt evtinfo; + // extract evtinfo.grp and evtinfo.tevt_type_act from val + if (get_scorpion_evtinfo(val, &evtinfo) == 0) + return; + asm volatile("mcr p15, 0, %0, c9, c13, 1" : : "r" (evtinfo.evt_type_act)); + asm volatile("mcr p15, 0, %0, c9, c15, 0" : : "r" (zero)); + scorpion_evt_setup(evtinfo.grp, val); + } + } +} + +static void scorpion_pmnc_reset_counter(unsigned int cnt) +{ + u32 val = 0; + + if (cnt == CCNT) { + scorpion_pmnc_disable_counter(cnt); + + asm volatile("mcr p15, 0, %0, c9, c13, 0" : : "r" (val)); + + if (pmnc_enabled[cnt] != 0) + scorpion_pmnc_enable_counter(cnt); + + } else if (cnt >= CNTMAX) { + pr_err("gator: CPU%u resetting wrong PMNC counter %d\n", smp_processor_id(), cnt); + } else { + scorpion_pmnc_disable_counter(cnt); + + if (scorpion_pmnc_select_counter(cnt) == cnt) + asm volatile("mcr p15, 0, %0, c9, c13, 2" : : "r" (val)); + + if (pmnc_enabled[cnt] != 0) + scorpion_pmnc_enable_counter(cnt); + } +} + +static int gator_events_scorpion_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + int i; + + for (i = 0; i < pmnc_counters; i++) { + char buf[40]; + if (i == 0) { + snprintf(buf, sizeof buf, "%s_ccnt", pmnc_name); + } else { + snprintf(buf, sizeof buf, "%s_cnt%d", pmnc_name, i - 1); + } + dir = gatorfs_mkdir(sb, root, buf); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &pmnc_enabled[i]); + gatorfs_create_ro_ulong(sb, dir, "key", &pmnc_key[i]); + if (i > 0) { + gatorfs_create_ulong(sb, dir, "event", &pmnc_event[i]); + } + } + + return 0; +} + +static int gator_events_scorpion_online(int **buffer, bool migrate) +{ + unsigned int cnt, len = 0, cpu = smp_processor_id(); + + if (scorpion_pmnc_read() & PMNC_E) { + scorpion_pmnc_write(scorpion_pmnc_read() & ~PMNC_E); + } + + /* Initialize & Reset PMNC: C bit and P bit */ + scorpion_pmnc_write(PMNC_P | PMNC_C); + + for (cnt = CCNT; cnt < CNTMAX; cnt++) { + unsigned long event; + + if (!pmnc_enabled[cnt]) + continue; + + // disable counter + scorpion_pmnc_disable_counter(cnt); + + event = pmnc_event[cnt] & 255; + + // Set event (if destined for PMNx counters), We don't need to set the event if it's a cycle count + if (cnt != CCNT) + scorpion_pmnc_write_evtsel(cnt, event); + + // reset counter + scorpion_pmnc_reset_counter(cnt); + + // Enable counter, do not enable interrupt for this counter + scorpion_pmnc_enable_counter(cnt); + } + + // enable + scorpion_pmnc_write(scorpion_pmnc_read() | PMNC_E); + + // read the counters and toss the invalid data, return zero instead + for (cnt = 0; cnt < pmnc_counters; cnt++) { + if (pmnc_enabled[cnt]) { + if (cnt == CCNT) { + scorpion_ccnt_read(); + } else if (scorpion_pmnc_select_counter(cnt) == cnt) { + scorpion_cntn_read(); + } + scorpion_pmnc_reset_counter(cnt); + + per_cpu(perfCnt, cpu)[len++] = pmnc_key[cnt]; + per_cpu(perfCnt, cpu)[len++] = 0; + } + } + + if (buffer) + *buffer = per_cpu(perfCnt, cpu); + + return len; +} + +static int gator_events_scorpion_offline(int **buffer, bool migrate) +{ + scorpion_pmnc_write(scorpion_pmnc_read() & ~PMNC_E); + return 0; +} + +static void gator_events_scorpion_stop(void) +{ + unsigned int cnt; + + for (cnt = CCNT; cnt < CNTMAX; cnt++) { + pmnc_enabled[cnt] = 0; + pmnc_event[cnt] = 0; + } +} + +static int gator_events_scorpion_read(int **buffer) +{ + int cnt, len = 0; + int cpu = smp_processor_id(); + + // a context switch may occur before the online hotplug event, thus need to check that the pmu is enabled + if (!(scorpion_pmnc_read() & PMNC_E)) { + return 0; + } + + for (cnt = 0; cnt < pmnc_counters; cnt++) { + if (pmnc_enabled[cnt]) { + int value; + if (cnt == CCNT) { + value = scorpion_ccnt_read(); + } else if (scorpion_pmnc_select_counter(cnt) == cnt) { + value = scorpion_cntn_read(); + } else { + value = 0; + } + scorpion_pmnc_reset_counter(cnt); + + per_cpu(perfCnt, cpu)[len++] = pmnc_key[cnt]; + per_cpu(perfCnt, cpu)[len++] = value; + } + } + + if (buffer) + *buffer = per_cpu(perfCnt, cpu); + + return len; +} + +static struct gator_interface gator_events_scorpion_interface = { + .create_files = gator_events_scorpion_create_files, + .stop = gator_events_scorpion_stop, + .online = gator_events_scorpion_online, + .offline = gator_events_scorpion_offline, + .read = gator_events_scorpion_read, +}; + +int gator_events_scorpion_init(void) +{ + unsigned int cnt; + + switch (gator_cpuid()) { + case SCORPION: + pmnc_name = "Scorpion"; + pmnc_counters = 4; + break; + case SCORPIONMP: + pmnc_name = "ScorpionMP"; + pmnc_counters = 4; + break; + default: + return -1; + } + + pmnc_counters++; // CNT[n] + CCNT + + for (cnt = CCNT; cnt < CNTMAX; cnt++) { + pmnc_enabled[cnt] = 0; + pmnc_event[cnt] = 0; + pmnc_key[cnt] = gator_events_get_key(); + } + + return gator_events_install(&gator_events_scorpion_interface); +} + +#endif diff --git a/drivers/gator/gator_fs.c b/drivers/gator/gator_fs.c new file mode 100644 index 00000000000000..fe6f83d547e907 --- /dev/null +++ b/drivers/gator/gator_fs.c @@ -0,0 +1,382 @@ +/** + * @file gatorfs.c + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * + * A simple filesystem for configuration and + * access of oprofile. + */ + +#include +#include +#include +#include +#include + +#define gatorfs_MAGIC 0x24051020 +#define TMPBUFSIZE 50 +DEFINE_SPINLOCK(gatorfs_lock); + +static struct inode *gatorfs_get_inode(struct super_block *sb, int mode) +{ + struct inode *inode = new_inode(sb); + + if (inode) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) + inode->i_ino = get_next_ino(); +#endif + inode->i_mode = mode; + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; + } + return inode; +} + +static const struct super_operations s_ops = { + .statfs = simple_statfs, + .drop_inode = generic_delete_inode, +}; + +ssize_t gatorfs_str_to_user(char const *str, char __user *buf, size_t count, loff_t *offset) +{ + return simple_read_from_buffer(buf, count, offset, str, strlen(str)); +} + +ssize_t gatorfs_ulong_to_user(unsigned long val, char __user *buf, size_t count, loff_t *offset) +{ + char tmpbuf[TMPBUFSIZE]; + size_t maxlen = snprintf(tmpbuf, TMPBUFSIZE, "%lu\n", val); + if (maxlen > TMPBUFSIZE) + maxlen = TMPBUFSIZE; + return simple_read_from_buffer(buf, count, offset, tmpbuf, maxlen); +} + +ssize_t gatorfs_u64_to_user(u64 val, char __user *buf, size_t count, loff_t *offset) +{ + char tmpbuf[TMPBUFSIZE]; + size_t maxlen = snprintf(tmpbuf, TMPBUFSIZE, "%llu\n", val); + if (maxlen > TMPBUFSIZE) + maxlen = TMPBUFSIZE; + return simple_read_from_buffer(buf, count, offset, tmpbuf, maxlen); +} + +int gatorfs_ulong_from_user(unsigned long *val, char const __user *buf, size_t count) +{ + char tmpbuf[TMPBUFSIZE]; + unsigned long flags; + + if (!count) + return 0; + + if (count > TMPBUFSIZE - 1) + return -EINVAL; + + memset(tmpbuf, 0x0, TMPBUFSIZE); + + if (copy_from_user(tmpbuf, buf, count)) + return -EFAULT; + + spin_lock_irqsave(&gatorfs_lock, flags); + *val = simple_strtoul(tmpbuf, NULL, 0); + spin_unlock_irqrestore(&gatorfs_lock, flags); + return 0; +} + +int gatorfs_u64_from_user(u64 *val, char const __user *buf, size_t count) +{ + char tmpbuf[TMPBUFSIZE]; + unsigned long flags; + + if (!count) + return 0; + + if (count > TMPBUFSIZE - 1) + return -EINVAL; + + memset(tmpbuf, 0x0, TMPBUFSIZE); + + if (copy_from_user(tmpbuf, buf, count)) + return -EFAULT; + + spin_lock_irqsave(&gatorfs_lock, flags); + *val = simple_strtoull(tmpbuf, NULL, 0); + spin_unlock_irqrestore(&gatorfs_lock, flags); + return 0; +} + +static ssize_t ulong_read_file(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + unsigned long *val = file->private_data; + return gatorfs_ulong_to_user(*val, buf, count, offset); +} + +static ssize_t u64_read_file(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + u64 *val = file->private_data; + return gatorfs_u64_to_user(*val, buf, count, offset); +} + +static ssize_t ulong_write_file(struct file *file, char const __user *buf, size_t count, loff_t *offset) +{ + unsigned long *value = file->private_data; + int retval; + + if (*offset) + return -EINVAL; + + retval = gatorfs_ulong_from_user(value, buf, count); + + if (retval) + return retval; + return count; +} + +static ssize_t u64_write_file(struct file *file, char const __user *buf, size_t count, loff_t *offset) +{ + u64 *value = file->private_data; + int retval; + + if (*offset) + return -EINVAL; + + retval = gatorfs_u64_from_user(value, buf, count); + + if (retval) + return retval; + return count; +} + +static int default_open(struct inode *inode, struct file *filp) +{ + if (inode->i_private) + filp->private_data = inode->i_private; + return 0; +} + +static const struct file_operations ulong_fops = { + .read = ulong_read_file, + .write = ulong_write_file, + .open = default_open, +}; + +static const struct file_operations u64_fops = { + .read = u64_read_file, + .write = u64_write_file, + .open = default_open, +}; + +static const struct file_operations ulong_ro_fops = { + .read = ulong_read_file, + .open = default_open, +}; + +static const struct file_operations u64_ro_fops = { + .read = u64_read_file, + .open = default_open, +}; + +static struct dentry *__gatorfs_create_file(struct super_block *sb, + struct dentry *root, + char const *name, + const struct file_operations *fops, + int perm) +{ + struct dentry *dentry; + struct inode *inode; + + dentry = d_alloc_name(root, name); + if (!dentry) + return NULL; + inode = gatorfs_get_inode(sb, S_IFREG | perm); + if (!inode) { + dput(dentry); + return NULL; + } + inode->i_fop = fops; + d_add(dentry, inode); + return dentry; +} + +int gatorfs_create_ulong(struct super_block *sb, struct dentry *root, + char const *name, unsigned long *val) +{ + struct dentry *d = __gatorfs_create_file(sb, root, name, + &ulong_fops, 0644); + if (!d) + return -EFAULT; + + d->d_inode->i_private = val; + return 0; +} + +int gatorfs_create_u64(struct super_block *sb, struct dentry *root, + char const *name, u64 *val) +{ + struct dentry *d = __gatorfs_create_file(sb, root, name, + &u64_fops, 0644); + if (!d) + return -EFAULT; + + d->d_inode->i_private = val; + return 0; +} + +int gatorfs_create_ro_ulong(struct super_block *sb, struct dentry *root, + char const *name, unsigned long *val) +{ + struct dentry *d = __gatorfs_create_file(sb, root, name, + &ulong_ro_fops, 0444); + if (!d) + return -EFAULT; + + d->d_inode->i_private = val; + return 0; +} + +int gatorfs_create_ro_u64(struct super_block *sb, struct dentry *root, + char const *name, u64 * val) +{ + struct dentry *d = + __gatorfs_create_file(sb, root, name, &u64_ro_fops, 0444); + if (!d) + return -EFAULT; + + d->d_inode->i_private = val; + return 0; +} + +static ssize_t atomic_read_file(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + atomic_t *val = file->private_data; + return gatorfs_ulong_to_user(atomic_read(val), buf, count, offset); +} + +static const struct file_operations atomic_ro_fops = { + .read = atomic_read_file, + .open = default_open, +}; + +int gatorfs_create_ro_atomic(struct super_block *sb, struct dentry *root, + char const *name, atomic_t *val) +{ + struct dentry *d = __gatorfs_create_file(sb, root, name, + &atomic_ro_fops, 0444); + if (!d) + return -EFAULT; + + d->d_inode->i_private = val; + return 0; +} + +int gatorfs_create_file(struct super_block *sb, struct dentry *root, + char const *name, const struct file_operations *fops) +{ + if (!__gatorfs_create_file(sb, root, name, fops, 0644)) + return -EFAULT; + return 0; +} + +int gatorfs_create_file_perm(struct super_block *sb, struct dentry *root, + char const *name, + const struct file_operations *fops, int perm) +{ + if (!__gatorfs_create_file(sb, root, name, fops, perm)) + return -EFAULT; + return 0; +} + +struct dentry *gatorfs_mkdir(struct super_block *sb, + struct dentry *root, char const *name) +{ + struct dentry *dentry; + struct inode *inode; + + dentry = d_alloc_name(root, name); + if (!dentry) + return NULL; + inode = gatorfs_get_inode(sb, S_IFDIR | 0755); + if (!inode) { + dput(dentry); + return NULL; + } + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + d_add(dentry, inode); + return dentry; +} + +static int gatorfs_fill_super(struct super_block *sb, void *data, int silent) +{ + struct inode *root_inode; + struct dentry *root_dentry; + + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = gatorfs_MAGIC; + sb->s_op = &s_ops; + sb->s_time_gran = 1; + + root_inode = gatorfs_get_inode(sb, S_IFDIR | 0755); + if (!root_inode) + return -ENOMEM; + root_inode->i_op = &simple_dir_inode_operations; + root_inode->i_fop = &simple_dir_operations; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + root_dentry = d_alloc_root(root_inode); +#else + root_dentry = d_make_root(root_inode); +#endif + + if (!root_dentry) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + iput(root_inode); +#endif + return -ENOMEM; + } + + sb->s_root = root_dentry; + + gator_op_create_files(sb, root_dentry); + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) +static int gatorfs_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data, + struct vfsmount *mnt) +{ + return get_sb_single(fs_type, flags, data, gatorfs_fill_super, mnt); +} +#else +static struct dentry *gatorfs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_nodev(fs_type, flags, data, gatorfs_fill_super); +} +#endif + +static struct file_system_type gatorfs_type = { + .owner = THIS_MODULE, + .name = "gatorfs", +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) + .get_sb = gatorfs_get_sb, +#else + .mount = gatorfs_mount, +#endif + + .kill_sb = kill_litter_super, +}; + +int __init gatorfs_register(void) +{ + return register_filesystem(&gatorfs_type); +} + +void gatorfs_unregister(void) +{ + unregister_filesystem(&gatorfs_type); +} diff --git a/drivers/gator/gator_hrtimer_gator.c b/drivers/gator/gator_hrtimer_gator.c new file mode 100644 index 00000000000000..b0c947afe1e163 --- /dev/null +++ b/drivers/gator/gator_hrtimer_gator.c @@ -0,0 +1,86 @@ +/** + * Copyright (C) ARM Limited 2011-2013. All rights reserved. + * + * 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. + * + */ + +// gator_hrtimer_perf.c is used if perf is supported +// update, gator_hrtimer_gator.c always used until issues resolved with perf hrtimers +#if 1 + +void (*callback)(void); +DEFINE_PER_CPU(struct hrtimer, percpu_hrtimer); +DEFINE_PER_CPU(ktime_t, hrtimer_expire); +DEFINE_PER_CPU(int, hrtimer_is_active); +static ktime_t profiling_interval; +static void gator_hrtimer_online(void); +static void gator_hrtimer_offline(void); + +static enum hrtimer_restart gator_hrtimer_notify(struct hrtimer *hrtimer) +{ + int cpu = get_logical_cpu(); + hrtimer_forward(hrtimer, per_cpu(hrtimer_expire, cpu), profiling_interval); + per_cpu(hrtimer_expire, cpu) = ktime_add(per_cpu(hrtimer_expire, cpu), profiling_interval); + (*callback)(); + return HRTIMER_RESTART; +} + +static void gator_hrtimer_online(void) +{ + int cpu = get_logical_cpu(); + struct hrtimer *hrtimer = &per_cpu(percpu_hrtimer, cpu); + + if (per_cpu(hrtimer_is_active, cpu) || profiling_interval.tv64 == 0) + return; + + per_cpu(hrtimer_is_active, cpu) = 1; + hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + hrtimer->function = gator_hrtimer_notify; +#ifdef CONFIG_PREEMPT_RT_BASE + hrtimer->irqsafe = 1; +#endif + per_cpu(hrtimer_expire, cpu) = ktime_add(hrtimer->base->get_time(), profiling_interval); + hrtimer_start(hrtimer, per_cpu(hrtimer_expire, cpu), HRTIMER_MODE_ABS_PINNED); +} + +static void gator_hrtimer_offline(void) +{ + int cpu = get_logical_cpu(); + struct hrtimer *hrtimer = &per_cpu(percpu_hrtimer, cpu); + + if (!per_cpu(hrtimer_is_active, cpu)) + return; + + per_cpu(hrtimer_is_active, cpu) = 0; + hrtimer_cancel(hrtimer); +} + +static int gator_hrtimer_init(int interval, void (*func)(void)) +{ + int cpu; + + (callback) = (func); + + for_each_present_cpu(cpu) { + per_cpu(hrtimer_is_active, cpu) = 0; + } + + // calculate profiling interval + if (interval > 0) { + profiling_interval = ns_to_ktime(1000000000UL / interval); + } else { + profiling_interval.tv64 = 0; + } + + return 0; +} + +static void gator_hrtimer_shutdown(void) +{ + /* empty */ +} + +#endif diff --git a/drivers/gator/gator_hrtimer_perf.c b/drivers/gator/gator_hrtimer_perf.c new file mode 100644 index 00000000000000..7b95399478e42a --- /dev/null +++ b/drivers/gator/gator_hrtimer_perf.c @@ -0,0 +1,113 @@ +/** + * Copyright (C) ARM Limited 2011-2013. All rights reserved. + * + * 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. + * + */ + +// gator_hrtimer_gator.c is used if perf is not supported +// update, gator_hrtimer_gator.c always used until issues resolved with perf hrtimers +#if 0 + +// Note: perf Cortex support added in 2.6.35 and PERF_COUNT_SW_CPU_CLOCK/hrtimer broken on 2.6.35 and 2.6.36 +// not relevant as this code is not active until 3.0.0, but wanted to document the issue + +void (*callback)(void); +static int profiling_interval; +static DEFINE_PER_CPU(struct perf_event *, perf_hrtimer); +static DEFINE_PER_CPU(struct perf_event_attr *, perf_hrtimer_attr); + +static void gator_hrtimer_shutdown(void); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) +static void hrtimer_overflow_handler(struct perf_event *event, int unused, struct perf_sample_data *data, struct pt_regs *regs) +#else +static void hrtimer_overflow_handler(struct perf_event *event, struct perf_sample_data *data, struct pt_regs *regs) +#endif +{ + (*callback)(); +} + +static int gator_online_single_hrtimer(int cpu) +{ + if (per_cpu(perf_hrtimer, cpu) != 0 || per_cpu(perf_hrtimer_attr, cpu) == 0) + return 0; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) + per_cpu(perf_hrtimer, cpu) = perf_event_create_kernel_counter(per_cpu(perf_hrtimer_attr, cpu), cpu, 0, hrtimer_overflow_handler); +#else + per_cpu(perf_hrtimer, cpu) = perf_event_create_kernel_counter(per_cpu(perf_hrtimer_attr, cpu), cpu, 0, hrtimer_overflow_handler, 0); +#endif + if (IS_ERR(per_cpu(perf_hrtimer, cpu))) { + per_cpu(perf_hrtimer, cpu) = NULL; + return -1; + } + + if (per_cpu(perf_hrtimer, cpu)->state != PERF_EVENT_STATE_ACTIVE) { + perf_event_release_kernel(per_cpu(perf_hrtimer, cpu)); + per_cpu(perf_hrtimer, cpu) = NULL; + return -1; + } + + return 0; +} + +static void gator_hrtimer_online(int cpu) +{ + if (gator_online_single_hrtimer(cpu) < 0) { + pr_debug("gator: unable to online the hrtimer on cpu%d\n", cpu); + } +} + +static void gator_hrtimer_offline(int cpu) +{ + if (per_cpu(perf_hrtimer, cpu)) { + perf_event_release_kernel(per_cpu(perf_hrtimer, cpu)); + per_cpu(perf_hrtimer, cpu) = NULL; + } +} + +static int gator_hrtimer_init(int interval, void (*func)(void)) +{ + u32 size = sizeof(struct perf_event_attr); + int cpu; + + callback = func; + + // calculate profiling interval + profiling_interval = 1000000000 / interval; + + for_each_present_cpu(cpu) { + per_cpu(perf_hrtimer, cpu) = 0; + per_cpu(perf_hrtimer_attr, cpu) = kmalloc(size, GFP_KERNEL); + if (per_cpu(perf_hrtimer_attr, cpu) == 0) { + gator_hrtimer_shutdown(); + return -1; + } + + memset(per_cpu(perf_hrtimer_attr, cpu), 0, size); + per_cpu(perf_hrtimer_attr, cpu)->type = PERF_TYPE_SOFTWARE; + per_cpu(perf_hrtimer_attr, cpu)->size = size; + per_cpu(perf_hrtimer_attr, cpu)->config = PERF_COUNT_SW_CPU_CLOCK; + per_cpu(perf_hrtimer_attr, cpu)->sample_period = profiling_interval; + per_cpu(perf_hrtimer_attr, cpu)->pinned = 1; + } + + return 0; +} + +static void gator_hrtimer_shutdown(void) +{ + int cpu; + + for_each_present_cpu(cpu) { + if (per_cpu(perf_hrtimer_attr, cpu)) { + kfree(per_cpu(perf_hrtimer_attr, cpu)); + per_cpu(perf_hrtimer_attr, cpu) = NULL; + } + } +} + +#endif diff --git a/drivers/gator/gator_iks.c b/drivers/gator/gator_iks.c new file mode 100644 index 00000000000000..0a90bdd1904e95 --- /dev/null +++ b/drivers/gator/gator_iks.c @@ -0,0 +1,197 @@ +/** + * Copyright (C) ARM Limited 2013. All rights reserved. + * + * 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. + * + */ + +#if GATOR_IKS_SUPPORT + +#include +#include +#include +#include + +static bool map_cpuids; +static int mpidr_cpuids[NR_CPUS]; +static const struct gator_cpu * mpidr_cpus[NR_CPUS]; +static int __lcpu_to_pcpu[NR_CPUS]; + +static const struct gator_cpu *gator_find_cpu_by_dt_name(const char *const name) +{ + int i; + + for (i = 0; gator_cpus[i].cpuid != 0; ++i) { + const struct gator_cpu *const gator_cpu = &gator_cpus[i]; + if (gator_cpu->dt_name != NULL && strcmp(gator_cpu->dt_name, name) == 0) { + return gator_cpu; + } + } + + return NULL; +} + +static void calc_first_cluster_size(void) +{ + int len; + const u32 *val; + const char *compatible; + struct device_node *cn = NULL; + int mpidr_cpuids_count = 0; + + // Zero is a valid cpuid, so initialize the array to 0xff's + memset(&mpidr_cpuids, 0xff, sizeof(mpidr_cpuids)); + memset(&mpidr_cpus, 0, sizeof(mpidr_cpus)); + + while ((cn = of_find_node_by_type(cn, "cpu"))) { + BUG_ON(mpidr_cpuids_count >= NR_CPUS); + + val = of_get_property(cn, "reg", &len); + if (!val || len != 4) { + pr_err("%s missing reg property\n", cn->full_name); + continue; + } + compatible = of_get_property(cn, "compatible", NULL); + if (compatible == NULL) { + pr_err("%s missing compatible property\n", cn->full_name); + continue; + } + + mpidr_cpuids[mpidr_cpuids_count] = be32_to_cpup(val); + mpidr_cpus[mpidr_cpuids_count] = gator_find_cpu_by_dt_name(compatible); + ++mpidr_cpuids_count; + } + + map_cpuids = (mpidr_cpuids_count == nr_cpu_ids); +} + +static int linearize_mpidr(int mpidr) +{ + int i; + for (i = 0; i < nr_cpu_ids; ++i) { + if (mpidr_cpuids[i] == mpidr) { + return i; + } + } + + BUG(); +} + +int lcpu_to_pcpu(const int lcpu) +{ + int pcpu; + + if (!map_cpuids) + return lcpu; + + BUG_ON(lcpu >= nr_cpu_ids || lcpu < 0); + pcpu = __lcpu_to_pcpu[lcpu]; + BUG_ON(pcpu >= nr_cpu_ids || pcpu < 0); + return pcpu; +} + +int pcpu_to_lcpu(const int pcpu) +{ + int lcpu; + + if (!map_cpuids) + return pcpu; + + BUG_ON(pcpu >= nr_cpu_ids || pcpu < 0); + for (lcpu = 0; lcpu < nr_cpu_ids; ++lcpu) { + if (__lcpu_to_pcpu[lcpu] == pcpu) { + BUG_ON(lcpu >= nr_cpu_ids || lcpu < 0); + return lcpu; + } + } + BUG(); +} + +static void gator_update_cpu_mapping(u32 cpu_hwid) +{ + int lcpu = smp_processor_id(); + int pcpu = linearize_mpidr(cpu_hwid & MPIDR_HWID_BITMASK); + BUG_ON(lcpu >= nr_cpu_ids || lcpu < 0); + BUG_ON(pcpu >= nr_cpu_ids || pcpu < 0); + __lcpu_to_pcpu[lcpu] = pcpu; +} + +GATOR_DEFINE_PROBE(cpu_migrate_begin, TP_PROTO(u64 timestamp, u32 cpu_hwid)) +{ + const int cpu = get_physical_cpu(); + + gator_timer_offline((void *)1); + gator_timer_offline_dispatch(cpu, true); +} + +GATOR_DEFINE_PROBE(cpu_migrate_finish, TP_PROTO(u64 timestamp, u32 cpu_hwid)) +{ + int cpu; + + gator_update_cpu_mapping(cpu_hwid); + + // get_physical_cpu must be called after gator_update_cpu_mapping + cpu = get_physical_cpu(); + gator_timer_online_dispatch(cpu, true); + gator_timer_online((void *)1); +} + +GATOR_DEFINE_PROBE(cpu_migrate_current, TP_PROTO(u64 timestamp, u32 cpu_hwid)) +{ + gator_update_cpu_mapping(cpu_hwid); +} + +static void gator_send_iks_core_names(void) +{ + int cpu; + // Send the cpu names + preempt_disable(); + for (cpu = 0; cpu < nr_cpu_ids; ++cpu) { + if (mpidr_cpus[cpu] != NULL) { + gator_send_core_name(cpu, mpidr_cpus[cpu]->cpuid, mpidr_cpus[cpu]); + } + } + preempt_enable(); +} + +static int gator_migrate_start(void) +{ + int retval = 0; + + if (!map_cpuids) + return retval; + + if (retval == 0) + retval = GATOR_REGISTER_TRACE(cpu_migrate_begin); + if (retval == 0) + retval = GATOR_REGISTER_TRACE(cpu_migrate_finish); + if (retval == 0) + retval = GATOR_REGISTER_TRACE(cpu_migrate_current); + if (retval == 0) { + // Initialize the logical to physical cpu mapping + memset(&__lcpu_to_pcpu, 0xff, sizeof(__lcpu_to_pcpu)); + bL_switcher_trace_trigger(); + } + return retval; +} + +static void gator_migrate_stop(void) +{ + if (!map_cpuids) + return; + + GATOR_UNREGISTER_TRACE(cpu_migrate_current); + GATOR_UNREGISTER_TRACE(cpu_migrate_finish); + GATOR_UNREGISTER_TRACE(cpu_migrate_begin); +} + +#else + +#define calc_first_cluster_size() +#define gator_send_iks_core_names() +#define gator_migrate_start() 0 +#define gator_migrate_stop() + +#endif diff --git a/drivers/gator/gator_main.c b/drivers/gator/gator_main.c new file mode 100644 index 00000000000000..9773ae24d6f2d5 --- /dev/null +++ b/drivers/gator/gator_main.c @@ -0,0 +1,1532 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 version must match the gator daemon version +#define PROTOCOL_VERSION 17 +static unsigned long gator_protocol_version = PROTOCOL_VERSION; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gator.h" + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) +#error kernels prior to 2.6.32 are not supported +#endif + +#if defined(MODULE) && !defined(CONFIG_MODULES) +#error Cannot build a module against a kernel that does not support modules. To resolve, either rebuild the kernel to support modules or build gator as part of the kernel. +#endif + +#if !defined(CONFIG_GENERIC_TRACER) && !defined(CONFIG_TRACING) +#error gator requires the kernel to have CONFIG_GENERIC_TRACER or CONFIG_TRACING defined +#endif + +#ifndef CONFIG_PROFILING +#error gator requires the kernel to have CONFIG_PROFILING defined +#endif + +#ifndef CONFIG_HIGH_RES_TIMERS +#error gator requires the kernel to have CONFIG_HIGH_RES_TIMERS defined to support PC sampling +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0) && defined(__arm__) && defined(CONFIG_SMP) && !defined(CONFIG_LOCAL_TIMERS) +#error gator requires the kernel to have CONFIG_LOCAL_TIMERS defined on SMP systems +#endif + +#if (GATOR_PERF_SUPPORT) && (!(GATOR_PERF_PMU_SUPPORT)) +#ifndef CONFIG_PERF_EVENTS +#error gator requires the kernel to have CONFIG_PERF_EVENTS defined to support pmu hardware counters +#elif !defined CONFIG_HW_PERF_EVENTS +#error gator requires the kernel to have CONFIG_HW_PERF_EVENTS defined to support pmu hardware counters +#endif +#endif + +/****************************************************************************** + * DEFINES + ******************************************************************************/ +#define SUMMARY_BUFFER_SIZE (1*1024) +#define BACKTRACE_BUFFER_SIZE (128*1024) +#define NAME_BUFFER_SIZE (64*1024) +#define COUNTER_BUFFER_SIZE (64*1024) // counters have the core as part of the data and the core value in the frame header may be discarded +#define BLOCK_COUNTER_BUFFER_SIZE (128*1024) +#define ANNOTATE_BUFFER_SIZE (128*1024) // annotate counters have the core as part of the data and the core value in the frame header may be discarded +#define SCHED_TRACE_BUFFER_SIZE (128*1024) +#define GPU_TRACE_BUFFER_SIZE (64*1024) // gpu trace counters have the core as part of the data and the core value in the frame header may be discarded +#define IDLE_BUFFER_SIZE (32*1024) // idle counters have the core as part of the data and the core value in the frame header may be discarded + +#define NO_COOKIE 0U +#define UNRESOLVED_COOKIE ~0U + +#define FRAME_SUMMARY 1 +#define FRAME_BACKTRACE 2 +#define FRAME_NAME 3 +#define FRAME_COUNTER 4 +#define FRAME_BLOCK_COUNTER 5 +#define FRAME_ANNOTATE 6 +#define FRAME_SCHED_TRACE 7 +#define FRAME_GPU_TRACE 8 +#define FRAME_IDLE 9 + +#define MESSAGE_END_BACKTRACE 1 + +#define MESSAGE_COOKIE 1 +#define MESSAGE_THREAD_NAME 2 +#define HRTIMER_CORE_NAME 3 +#define MESSAGE_LINK 4 + +#define MESSAGE_GPU_START 1 +#define MESSAGE_GPU_STOP 2 + +#define MESSAGE_SCHED_SWITCH 1 +#define MESSAGE_SCHED_EXIT 2 +#define MESSAGE_SCHED_START 3 + +#define MESSAGE_IDLE_ENTER 1 +#define MESSAGE_IDLE_EXIT 2 + +#define MAXSIZE_PACK32 5 +#define MAXSIZE_PACK64 10 + +#define FRAME_HEADER_SIZE 3 + +#if defined(__arm__) +#define PC_REG regs->ARM_pc +#elif defined(__aarch64__) +#define PC_REG regs->pc +#else +#define PC_REG regs->ip +#endif + +enum { + SUMMARY_BUF, + BACKTRACE_BUF, + NAME_BUF, + COUNTER_BUF, + BLOCK_COUNTER_BUF, + ANNOTATE_BUF, + SCHED_TRACE_BUF, + GPU_TRACE_BUF, + IDLE_BUF, + NUM_GATOR_BUFS +}; + +/****************************************************************************** + * Globals + ******************************************************************************/ +static unsigned long gator_cpu_cores; +// Size of the largest buffer. Effectively constant, set in gator_op_create_files +static unsigned long userspace_buffer_size; +static unsigned long gator_backtrace_depth; +// How often to commit the buffers for live in nanoseconds +static u64 gator_live_rate; + +static unsigned long gator_started; +static u64 gator_monotonic_started; +static u64 gator_hibernate_time; +static unsigned long gator_buffer_opened; +static unsigned long gator_timer_count; +static unsigned long gator_response_type; +static DEFINE_MUTEX(start_mutex); +static DEFINE_MUTEX(gator_buffer_mutex); + +bool event_based_sampling; + +static DECLARE_WAIT_QUEUE_HEAD(gator_buffer_wait); +static DECLARE_WAIT_QUEUE_HEAD(gator_annotate_wait); +static struct timer_list gator_buffer_wake_up_timer; +static bool gator_buffer_wake_stop; +static struct task_struct *gator_buffer_wake_thread; +static LIST_HEAD(gator_events); + +static DEFINE_PER_CPU(u64, last_timestamp); + +static bool printed_monotonic_warning; + +static bool sent_core_name[NR_CPUS]; + +/****************************************************************************** + * Prototypes + ******************************************************************************/ +static void buffer_check(int cpu, int buftype, u64 time); +static void gator_commit_buffer(int cpu, int buftype, u64 time); +static int buffer_bytes_available(int cpu, int buftype); +static bool buffer_check_space(int cpu, int buftype, int bytes); +static int contiguous_space_available(int cpu, int bufytpe); +static void gator_buffer_write_packed_int(int cpu, int buftype, int x); +static void gator_buffer_write_packed_int64(int cpu, int buftype, long long x); +static void gator_buffer_write_bytes(int cpu, int buftype, const char *x, int len); +static void gator_buffer_write_string(int cpu, int buftype, const char *x); +static void gator_add_trace(int cpu, unsigned long address); +static void gator_add_sample(int cpu, struct pt_regs *const regs, u64 time); +static u64 gator_get_time(void); + +// Size of the buffer, must be a power of 2. Effectively constant, set in gator_op_setup. +static uint32_t gator_buffer_size[NUM_GATOR_BUFS]; +// gator_buffer_size - 1, bitwise and with pos to get offset into the array. Effectively constant, set in gator_op_setup. +static uint32_t gator_buffer_mask[NUM_GATOR_BUFS]; +// Read position in the buffer. Initialized to zero in gator_op_setup and incremented after bytes are read by userspace in userspace_buffer_read +static DEFINE_PER_CPU(int[NUM_GATOR_BUFS], gator_buffer_read); +// Write position in the buffer. Initialized to zero in gator_op_setup and incremented after bytes are written to the buffer +static DEFINE_PER_CPU(int[NUM_GATOR_BUFS], gator_buffer_write); +// Commit position in the buffer. Initialized to zero in gator_op_setup and incremented after a frame is ready to be read by userspace +static DEFINE_PER_CPU(int[NUM_GATOR_BUFS], gator_buffer_commit); +// If set to false, decreases the number of bytes returned by buffer_bytes_available. Set in buffer_check_space if no space is remaining. Initialized to true in gator_op_setup +// This means that if we run out of space, continue to report that no space is available until bytes are read by userspace +static DEFINE_PER_CPU(int[NUM_GATOR_BUFS], buffer_space_available); +// The buffer. Allocated in gator_op_setup +static DEFINE_PER_CPU(char *[NUM_GATOR_BUFS], gator_buffer); +// The time after which the buffer should be committed for live display +static DEFINE_PER_CPU(u64, gator_buffer_commit_time); + +// List of all gator events - new events must be added to this list +#define GATOR_EVENTS_LIST \ + GATOR_EVENT(gator_events_armv6_init) \ + GATOR_EVENT(gator_events_armv7_init) \ + GATOR_EVENT(gator_events_block_init) \ + GATOR_EVENT(gator_events_ccn504_init) \ + GATOR_EVENT(gator_events_irq_init) \ + GATOR_EVENT(gator_events_l2c310_init) \ + GATOR_EVENT(gator_events_mali_init) \ + GATOR_EVENT(gator_events_mali_t6xx_hw_init) \ + GATOR_EVENT(gator_events_mali_t6xx_init) \ + GATOR_EVENT(gator_events_meminfo_init) \ + GATOR_EVENT(gator_events_mmapped_init) \ + GATOR_EVENT(gator_events_net_init) \ + GATOR_EVENT(gator_events_perf_pmu_init) \ + GATOR_EVENT(gator_events_sched_init) \ + GATOR_EVENT(gator_events_scorpion_init) \ + +#define GATOR_EVENT(EVENT_INIT) __weak int EVENT_INIT(void); +GATOR_EVENTS_LIST +#undef GATOR_EVENT + +static int (*gator_events_list[])(void) = { +#define GATOR_EVENT(EVENT_INIT) EVENT_INIT, +GATOR_EVENTS_LIST +#undef GATOR_EVENT +}; + +/****************************************************************************** + * Application Includes + ******************************************************************************/ +#include "gator_marshaling.c" +#include "gator_hrtimer_perf.c" +#include "gator_hrtimer_gator.c" +#include "gator_cookies.c" +#include "gator_annotate.c" +#include "gator_trace_sched.c" +#include "gator_trace_power.c" +#include "gator_trace_gpu.c" +#include "gator_backtrace.c" +#include "gator_fs.c" +#include "gator_pack.c" + +/****************************************************************************** + * Misc + ******************************************************************************/ + +const struct gator_cpu gator_cpus[] = { + { + .cpuid = ARM1136, + .core_name = "ARM1136", + .pmnc_name = "ARM_ARM11", + .dt_name = "arm,arm1136", + .pmnc_counters = 3, + }, + { + .cpuid = ARM1156, + .core_name = "ARM1156", + .pmnc_name = "ARM_ARM11", + .dt_name = "arm,arm1156", + .pmnc_counters = 3, + }, + { + .cpuid = ARM1176, + .core_name = "ARM1176", + .pmnc_name = "ARM_ARM11", + .dt_name = "arm,arm1176", + .pmnc_counters = 3, + }, + { + .cpuid = ARM11MPCORE, + .core_name = "ARM11MPCore", + .pmnc_name = "ARM_ARM11MPCore", + .dt_name = "arm,arm11mpcore", + .pmnc_counters = 3, + }, + { + .cpuid = CORTEX_A5, + .core_name = "Cortex-A5", + .pmu_name = "ARMv7_Cortex_A5", + .pmnc_name = "ARM_Cortex-A5", + .dt_name = "arm,cortex-a5", + .pmnc_counters = 2, + }, + { + .cpuid = CORTEX_A7, + .core_name = "Cortex-A7", + .pmu_name = "ARMv7_Cortex_A7", + .pmnc_name = "ARM_Cortex-A7", + .dt_name = "arm,cortex-a7", + .pmnc_counters = 4, + }, + { + .cpuid = CORTEX_A8, + .core_name = "Cortex-A8", + .pmu_name = "ARMv7_Cortex_A8", + .pmnc_name = "ARM_Cortex-A8", + .dt_name = "arm,cortex-a8", + .pmnc_counters = 4, + }, + { + .cpuid = CORTEX_A9, + .core_name = "Cortex-A9", + .pmu_name = "ARMv7_Cortex_A9", + .pmnc_name = "ARM_Cortex-A9", + .dt_name = "arm,cortex-a9", + .pmnc_counters = 6, + }, + { + .cpuid = CORTEX_A12, + .core_name = "Cortex-A12", + .pmu_name = "ARMv7_Cortex_A12", + .pmnc_name = "ARM_Cortex-A12", + .dt_name = "arm,cortex-a12", + .pmnc_counters = 6, + }, + { + .cpuid = CORTEX_A15, + .core_name = "Cortex-A15", + .pmu_name = "ARMv7_Cortex_A15", + .pmnc_name = "ARM_Cortex-A15", + .dt_name = "arm,cortex-a15", + .pmnc_counters = 6, + }, + { + .cpuid = SCORPION, + .core_name = "Scorpion", + .pmnc_name = "Scorpion", + .pmnc_counters = 4, + }, + { + .cpuid = SCORPIONMP, + .core_name = "ScorpionMP", + .pmnc_name = "ScorpionMP", + .pmnc_counters = 4, + }, + { + .cpuid = KRAITSIM, + .core_name = "KraitSIM", + .pmnc_name = "Krait", + .pmnc_counters = 4, + }, + { + .cpuid = KRAIT, + .core_name = "Krait", + .pmnc_name = "Krait", + .pmnc_counters = 4, + }, + { + .cpuid = KRAIT_S4_PRO, + .core_name = "Krait S4 Pro", + .pmnc_name = "Krait", + .pmnc_counters = 4, + }, + { + .cpuid = CORTEX_A53, + .core_name = "Cortex-A53", + .pmnc_name = "ARM_Cortex-A53", + .dt_name = "arm,cortex-a53", + .pmnc_counters = 6, + }, + { + .cpuid = CORTEX_A57, + .core_name = "Cortex-A57", + .pmnc_name = "ARM_Cortex-A57", + .dt_name = "arm,cortex-a57", + .pmnc_counters = 6, + }, + { + .cpuid = AARCH64, + .core_name = "AArch64", + .pmnc_name = "ARM_AArch64", + .pmnc_counters = 6, + }, + { + .cpuid = OTHER, + .core_name = "Other", + .pmnc_name = "Other", + .pmnc_counters = 6, + }, + {} +}; + +const struct gator_cpu *gator_find_cpu_by_cpuid(const u32 cpuid) +{ + int i; + + for (i = 0; gator_cpus[i].cpuid != 0; ++i) { + const struct gator_cpu *const gator_cpu = &gator_cpus[i]; + if (gator_cpu->cpuid == cpuid) { + return gator_cpu; + } + } + + return NULL; +} + +const struct gator_cpu *gator_find_cpu_by_pmu_name(const char *const name) +{ + int i; + + for (i = 0; gator_cpus[i].cpuid != 0; ++i) { + const struct gator_cpu *const gator_cpu = &gator_cpus[i]; + if (gator_cpu->pmu_name != NULL && strcmp(gator_cpu->pmu_name, name) == 0) { + return gator_cpu; + } + } + + return NULL; +} + +u32 gator_cpuid(void) +{ +#if defined(__arm__) || defined(__aarch64__) + u32 val; +#if !defined(__aarch64__) + asm volatile("mrc p15, 0, %0, c0, c0, 0" : "=r" (val)); +#else + asm volatile("mrs %0, midr_el1" : "=r" (val)); +#endif + return (val >> 4) & 0xfff; +#else + return OTHER; +#endif +} + +static void gator_buffer_wake_up(unsigned long data) +{ + wake_up(&gator_buffer_wait); +} + +static int gator_buffer_wake_func(void *data) +{ + while (!gator_buffer_wake_stop) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + if (gator_buffer_wake_stop) { + break; + } + + gator_buffer_wake_up(0); + } + + return 0; +} + +/****************************************************************************** + * Commit interface + ******************************************************************************/ +static bool buffer_commit_ready(int *cpu, int *buftype) +{ + int cpu_x, x; + for_each_present_cpu(cpu_x) { + for (x = 0; x < NUM_GATOR_BUFS; x++) + if (per_cpu(gator_buffer_commit, cpu_x)[x] != per_cpu(gator_buffer_read, cpu_x)[x]) { + *cpu = cpu_x; + *buftype = x; + return true; + } + } + *cpu = -1; + *buftype = -1; + return false; +} + +/****************************************************************************** + * Buffer management + ******************************************************************************/ +static int buffer_bytes_available(int cpu, int buftype) +{ + int remaining, filled; + + filled = per_cpu(gator_buffer_write, cpu)[buftype] - per_cpu(gator_buffer_read, cpu)[buftype]; + if (filled < 0) { + filled += gator_buffer_size[buftype]; + } + + remaining = gator_buffer_size[buftype] - filled; + + if (per_cpu(buffer_space_available, cpu)[buftype]) { + // Give some extra room; also allows space to insert the overflow error packet + remaining -= 200; + } else { + // Hysteresis, prevents multiple overflow messages + remaining -= 2000; + } + + return remaining; +} + +static int contiguous_space_available(int cpu, int buftype) +{ + int remaining = buffer_bytes_available(cpu, buftype); + int contiguous = gator_buffer_size[buftype] - per_cpu(gator_buffer_write, cpu)[buftype]; + if (remaining < contiguous) + return remaining; + else + return contiguous; +} + +static bool buffer_check_space(int cpu, int buftype, int bytes) +{ + int remaining = buffer_bytes_available(cpu, buftype); + + if (remaining < bytes) { + per_cpu(buffer_space_available, cpu)[buftype] = false; + } else { + per_cpu(buffer_space_available, cpu)[buftype] = true; + } + + return per_cpu(buffer_space_available, cpu)[buftype]; +} + +static void gator_buffer_write_bytes(int cpu, int buftype, const char *x, int len) +{ + int i; + u32 write = per_cpu(gator_buffer_write, cpu)[buftype]; + u32 mask = gator_buffer_mask[buftype]; + char *buffer = per_cpu(gator_buffer, cpu)[buftype]; + + for (i = 0; i < len; i++) { + buffer[write] = x[i]; + write = (write + 1) & mask; + } + + per_cpu(gator_buffer_write, cpu)[buftype] = write; +} + +static void gator_buffer_write_string(int cpu, int buftype, const char *x) +{ + int len = strlen(x); + gator_buffer_write_packed_int(cpu, buftype, len); + gator_buffer_write_bytes(cpu, buftype, x, len); +} + +static void gator_commit_buffer(int cpu, int buftype, u64 time) +{ + int type_length, commit, length, byte; + + if (!per_cpu(gator_buffer, cpu)[buftype]) + return; + + // post-populate the length, which does not include the response type length nor the length itself, i.e. only the length of the payload + type_length = gator_response_type ? 1 : 0; + commit = per_cpu(gator_buffer_commit, cpu)[buftype]; + length = per_cpu(gator_buffer_write, cpu)[buftype] - commit; + if (length < 0) { + length += gator_buffer_size[buftype]; + } + length = length - type_length - sizeof(s32); + + if (length <= FRAME_HEADER_SIZE) { + // Nothing to write, only the frame header is present + return; + } + + for (byte = 0; byte < sizeof(s32); byte++) { + per_cpu(gator_buffer, cpu)[buftype][(commit + type_length + byte) & gator_buffer_mask[buftype]] = (length >> byte * 8) & 0xFF; + } + + per_cpu(gator_buffer_commit, cpu)[buftype] = per_cpu(gator_buffer_write, cpu)[buftype]; + + if (gator_live_rate > 0) { + while (time > per_cpu(gator_buffer_commit_time, cpu)) { + per_cpu(gator_buffer_commit_time, cpu) += gator_live_rate; + } + } + + marshal_frame(cpu, buftype); + + // had to delay scheduling work as attempting to schedule work during the context switch is illegal in kernel versions 3.5 and greater + if (per_cpu(in_scheduler_context, cpu)) { +#ifndef CONFIG_PREEMPT_RT_FULL + // mod_timer can not be used in interrupt context in RT-Preempt full + mod_timer(&gator_buffer_wake_up_timer, jiffies + 1); +#endif + } else { + wake_up_process(gator_buffer_wake_thread); + } +} + +static void buffer_check(int cpu, int buftype, u64 time) +{ + int filled = per_cpu(gator_buffer_write, cpu)[buftype] - per_cpu(gator_buffer_commit, cpu)[buftype]; + if (filled < 0) { + filled += gator_buffer_size[buftype]; + } + if (filled >= ((gator_buffer_size[buftype] * 3) / 4)) { + gator_commit_buffer(cpu, buftype, time); + } +} + +static void gator_add_trace(int cpu, unsigned long address) +{ + off_t offset = 0; + unsigned long cookie = get_address_cookie(cpu, current, address & ~1, &offset); + + if (cookie == NO_COOKIE || cookie == UNRESOLVED_COOKIE) { + offset = address; + } + + marshal_backtrace(offset & ~1, cookie); +} + +static void gator_add_sample(int cpu, struct pt_regs *const regs, u64 time) +{ + bool inKernel; + unsigned long exec_cookie; + + if (!regs) + return; + + inKernel = !user_mode(regs); + exec_cookie = get_exec_cookie(cpu, current); + + if (!marshal_backtrace_header(exec_cookie, current->tgid, current->pid, inKernel, time)) + return; + + if (inKernel) { + kernel_backtrace(cpu, regs); + } else { + // Cookie+PC + gator_add_trace(cpu, PC_REG); + + // Backtrace + if (gator_backtrace_depth) + arm_backtrace_eabi(cpu, regs, gator_backtrace_depth); + } + + marshal_backtrace_footer(time); +} + +/****************************************************************************** + * hrtimer interrupt processing + ******************************************************************************/ +static void gator_timer_interrupt(void) +{ + struct pt_regs *const regs = get_irq_regs(); + gator_backtrace_handler(regs); +} + +void gator_backtrace_handler(struct pt_regs *const regs) +{ + u64 time = gator_get_time(); + int cpu = get_physical_cpu(); + + // Output backtrace + gator_add_sample(cpu, regs, time); + + // Collect counters + if (!per_cpu(collecting, cpu)) { + collect_counters(time, NULL); + } + + // No buffer flushing occurs during sched switch for RT-Preempt full. The block counter frame will be flushed by collect_counters, but the sched buffer needs to be explicitly flushed +#ifdef CONFIG_PREEMPT_RT_FULL + buffer_check(cpu, SCHED_TRACE_BUF, time); +#endif +} + +static int gator_running; + +// This function runs in interrupt context and on the appropriate core +static void gator_timer_offline(void *migrate) +{ + struct gator_interface *gi; + int i, len, cpu = get_physical_cpu(); + int *buffer; + u64 time; + + gator_trace_sched_offline(); + gator_trace_power_offline(); + + if (!migrate) { + gator_hrtimer_offline(); + } + + // Offline any events and output counters + time = gator_get_time(); + if (marshal_event_header(time)) { + list_for_each_entry(gi, &gator_events, list) { + if (gi->offline) { + len = gi->offline(&buffer, migrate); + marshal_event(len, buffer); + } + } + // Only check after writing all counters so that time and corresponding counters appear in the same frame + buffer_check(cpu, BLOCK_COUNTER_BUF, time); + } + + // Flush all buffers on this core + for (i = 0; i < NUM_GATOR_BUFS; i++) + gator_commit_buffer(cpu, i, time); +} + +// This function runs in interrupt context and may be running on a core other than core 'cpu' +static void gator_timer_offline_dispatch(int cpu, bool migrate) +{ + struct gator_interface *gi; + + list_for_each_entry(gi, &gator_events, list) { + if (gi->offline_dispatch) { + gi->offline_dispatch(cpu, migrate); + } + } +} + +static void gator_timer_stop(void) +{ + int cpu; + + if (gator_running) { + on_each_cpu(gator_timer_offline, NULL, 1); + for_each_online_cpu(cpu) { + gator_timer_offline_dispatch(lcpu_to_pcpu(cpu), false); + } + + gator_running = 0; + gator_hrtimer_shutdown(); + } +} + +#if defined(__arm__) || defined(__aarch64__) +static void gator_send_core_name(int cpu, const u32 cpuid, const struct gator_cpu *const gator_cpu) { + const char *core_name = NULL; + char core_name_buf[32]; + + if (!sent_core_name[cpu]) { + if (gator_cpu != NULL) { + core_name = gator_cpu->core_name; + } else { + snprintf(core_name_buf, sizeof(core_name_buf), "Unknown (0x%.3x)", cpuid); + core_name = core_name_buf; + } + + marshal_core_name(cpu, cpuid, core_name); + sent_core_name[cpu] = true; + } +} +#endif + +// This function runs in interrupt context and on the appropriate core +static void gator_timer_online(void *migrate) +{ + struct gator_interface *gi; + int len, cpu = get_physical_cpu(); + int *buffer; + u64 time; + + gator_trace_power_online(); + + // online any events and output counters + time = gator_get_time(); + if (marshal_event_header(time)) { + list_for_each_entry(gi, &gator_events, list) { + if (gi->online) { + len = gi->online(&buffer, migrate); + marshal_event(len, buffer); + } + } + // Only check after writing all counters so that time and corresponding counters appear in the same frame + buffer_check(cpu, BLOCK_COUNTER_BUF, time); + } + + if (!migrate) { + gator_hrtimer_online(); + } + +#if defined(__arm__) || defined(__aarch64__) + if (!sent_core_name[cpu]) { + const u32 cpuid = gator_cpuid(); + gator_send_core_name(cpu, cpuid, gator_find_cpu_by_cpuid(cpuid)); + } +#endif +} + +// This function runs in interrupt context and may be running on a core other than core 'cpu' +static void gator_timer_online_dispatch(int cpu, bool migrate) +{ + struct gator_interface *gi; + + list_for_each_entry(gi, &gator_events, list) { + if (gi->online_dispatch) { + gi->online_dispatch(cpu, migrate); + } + } +} + +#include "gator_iks.c" + +int gator_timer_start(unsigned long sample_rate) +{ + int cpu; + + if (gator_running) { + pr_notice("gator: already running\n"); + return 0; + } + + gator_running = 1; + + // event based sampling trumps hr timer based sampling + if (event_based_sampling) { + sample_rate = 0; + } + + if (gator_hrtimer_init(sample_rate, gator_timer_interrupt) == -1) + return -1; + + gator_send_iks_core_names(); + for_each_online_cpu(cpu) { + gator_timer_online_dispatch(lcpu_to_pcpu(cpu), false); + } + on_each_cpu(gator_timer_online, NULL, 1); + + return 0; +} + +static u64 gator_get_time(void) +{ + struct timespec ts; + u64 timestamp; + u64 prev_timestamp; + u64 delta; + int cpu = smp_processor_id(); + + // Match clock_gettime(CLOCK_MONOTONIC_RAW, &ts) from userspace + getrawmonotonic(&ts); + timestamp = timespec_to_ns(&ts); + + // getrawmonotonic is not monotonic on all systems. Detect and attempt to correct these cases. + // up to 0.5ms delta has been seen on some systems, which can skew Streamline data when viewing at high resolution. + // This doesn't work well with interrupts, but that it's OK - the real concern is to catch big jumps in time + prev_timestamp = per_cpu(last_timestamp, cpu); + if (prev_timestamp <= timestamp) { + per_cpu(last_timestamp, cpu) = timestamp; + } else { + delta = prev_timestamp - timestamp; + // Log the error once + if (!printed_monotonic_warning && delta > 500000) { + printk(KERN_ERR "%s: getrawmonotonic is not monotonic cpu: %i delta: %lli\nSkew in Streamline data may be present at the fine zoom levels\n", __FUNCTION__, cpu, delta); + printed_monotonic_warning = true; + } + timestamp = prev_timestamp; + } + + return timestamp - gator_monotonic_started; +} + +/****************************************************************************** + * cpu hotplug and pm notifiers + ******************************************************************************/ +static int __cpuinit gator_hotcpu_notify(struct notifier_block *self, unsigned long action, void *hcpu) +{ + int cpu = lcpu_to_pcpu((long)hcpu); + + switch (action) { + case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: + smp_call_function_single(cpu, gator_timer_offline, NULL, 1); + gator_timer_offline_dispatch(cpu, false); + break; + case CPU_ONLINE: + case CPU_ONLINE_FROZEN: + gator_timer_online_dispatch(cpu, false); + smp_call_function_single(cpu, gator_timer_online, NULL, 1); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block __refdata gator_hotcpu_notifier = { + .notifier_call = gator_hotcpu_notify, +}; + +// n.b. calling "on_each_cpu" only runs on those that are online +// Registered linux events are not disabled, so their counters will continue to collect +static int gator_pm_notify(struct notifier_block *nb, unsigned long event, void *dummy) +{ + int cpu; + struct timespec ts; + + switch (event) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + unregister_hotcpu_notifier(&gator_hotcpu_notifier); + unregister_scheduler_tracepoints(); + on_each_cpu(gator_timer_offline, NULL, 1); + for_each_online_cpu(cpu) { + gator_timer_offline_dispatch(lcpu_to_pcpu(cpu), false); + } + + // Record the wallclock hibernate time + getnstimeofday(&ts); + gator_hibernate_time = timespec_to_ns(&ts) - gator_get_time(); + break; + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + // Adjust gator_monotonic_started for the time spent sleeping, as gator_get_time does not account for it + if (gator_hibernate_time > 0) { + getnstimeofday(&ts); + gator_monotonic_started += gator_hibernate_time + gator_get_time() - timespec_to_ns(&ts); + gator_hibernate_time = 0; + } + + for_each_online_cpu(cpu) { + gator_timer_online_dispatch(lcpu_to_pcpu(cpu), false); + } + on_each_cpu(gator_timer_online, NULL, 1); + register_scheduler_tracepoints(); + register_hotcpu_notifier(&gator_hotcpu_notifier); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block gator_pm_notifier = { + .notifier_call = gator_pm_notify, +}; + +static int gator_notifier_start(void) +{ + int retval; + retval = register_hotcpu_notifier(&gator_hotcpu_notifier); + if (retval == 0) + retval = register_pm_notifier(&gator_pm_notifier); + return retval; +} + +static void gator_notifier_stop(void) +{ + unregister_pm_notifier(&gator_pm_notifier); + unregister_hotcpu_notifier(&gator_hotcpu_notifier); +} + +/****************************************************************************** + * Main + ******************************************************************************/ +static void gator_summary(void) +{ + u64 timestamp, uptime; + struct timespec ts; + char uname_buf[512]; + void (*m2b)(struct timespec *ts); + unsigned long flags; + + snprintf(uname_buf, sizeof(uname_buf), "%s %s %s %s %s GNU/Linux", utsname()->sysname, utsname()->nodename, utsname()->release, utsname()->version, utsname()->machine); + + getnstimeofday(&ts); + timestamp = timespec_to_ns(&ts); + + do_posix_clock_monotonic_gettime(&ts); + // monotonic_to_bootbased is not defined for some versions of Android + m2b = symbol_get(monotonic_to_bootbased); + if (m2b) { + m2b(&ts); + } + uptime = timespec_to_ns(&ts); + + // Disable interrupts as gator_get_time calls smp_processor_id to verify time is monotonic + local_irq_save(flags); + // Set monotonic_started to zero as gator_get_time is uptime minus monotonic_started + gator_monotonic_started = 0; + gator_monotonic_started = gator_get_time(); + local_irq_restore(flags); + + marshal_summary(timestamp, uptime, gator_monotonic_started, uname_buf); +} + +int gator_events_install(struct gator_interface *interface) +{ + list_add_tail(&interface->list, &gator_events); + + return 0; +} + +int gator_events_get_key(void) +{ + // key 0 is reserved as a timestamp + // key 1 is reserved as the marker for thread specific counters + // Odd keys are assigned by the driver, even keys by the daemon + static int key = 3; + + const int ret = key; + key += 2; + return ret; +} + +static int gator_init(void) +{ + int i; + + calc_first_cluster_size(); + + // events sources + for (i = 0; i < ARRAY_SIZE(gator_events_list); i++) + if (gator_events_list[i]) + gator_events_list[i](); + + gator_trace_sched_init(); + gator_trace_power_init(); + + return 0; +} + +static void gator_exit(void) +{ + struct gator_interface *gi; + + list_for_each_entry(gi, &gator_events, list) + if (gi->shutdown) + gi->shutdown(); +} + +static int gator_start(void) +{ + unsigned long cpu, i; + struct gator_interface *gi; + + gator_buffer_wake_stop = false; + if (IS_ERR(gator_buffer_wake_thread = kthread_run(gator_buffer_wake_func, NULL, "gator_bwake"))) { + goto bwake_failure; + } + + if (gator_migrate_start()) + goto migrate_failure; + + // Initialize the buffer with the frame type and core + for_each_present_cpu(cpu) { + for (i = 0; i < NUM_GATOR_BUFS; i++) { + marshal_frame(cpu, i); + } + per_cpu(last_timestamp, cpu) = 0; + } + printed_monotonic_warning = false; + + // Capture the start time + gator_summary(); + + // start all events + list_for_each_entry(gi, &gator_events, list) { + if (gi->start && gi->start() != 0) { + struct list_head *ptr = gi->list.prev; + + while (ptr != &gator_events) { + gi = list_entry(ptr, struct gator_interface, list); + + if (gi->stop) + gi->stop(); + + ptr = ptr->prev; + } + goto events_failure; + } + } + + // cookies shall be initialized before trace_sched_start() and gator_timer_start() + if (cookies_initialize()) + goto cookies_failure; + if (gator_annotate_start()) + goto annotate_failure; + if (gator_trace_sched_start()) + goto sched_failure; + if (gator_trace_power_start()) + goto power_failure; + if (gator_trace_gpu_start()) + goto gpu_failure; + if (gator_timer_start(gator_timer_count)) + goto timer_failure; + if (gator_notifier_start()) + goto notifier_failure; + + return 0; + +notifier_failure: + gator_timer_stop(); +timer_failure: + gator_trace_gpu_stop(); +gpu_failure: + gator_trace_power_stop(); +power_failure: + gator_trace_sched_stop(); +sched_failure: + gator_annotate_stop(); +annotate_failure: + cookies_release(); +cookies_failure: + // stop all events + list_for_each_entry(gi, &gator_events, list) + if (gi->stop) + gi->stop(); +events_failure: + gator_migrate_stop(); +migrate_failure: + gator_buffer_wake_stop = true; + wake_up_process(gator_buffer_wake_thread); +bwake_failure: + + return -1; +} + +static void gator_stop(void) +{ + struct gator_interface *gi; + + gator_annotate_stop(); + gator_trace_sched_stop(); + gator_trace_power_stop(); + gator_trace_gpu_stop(); + + // stop all interrupt callback reads before tearing down other interfaces + gator_notifier_stop(); // should be called before gator_timer_stop to avoid re-enabling the hrtimer after it has been offlined + gator_timer_stop(); + + // stop all events + list_for_each_entry(gi, &gator_events, list) + if (gi->stop) + gi->stop(); + + gator_migrate_stop(); + + gator_buffer_wake_stop = true; + wake_up_process(gator_buffer_wake_thread); +} + +/****************************************************************************** + * Filesystem + ******************************************************************************/ +/* fopen("buffer") */ +static int gator_op_setup(void) +{ + int err = 0; + int cpu, i; + + mutex_lock(&start_mutex); + + gator_buffer_size[SUMMARY_BUF] = SUMMARY_BUFFER_SIZE; + gator_buffer_mask[SUMMARY_BUF] = SUMMARY_BUFFER_SIZE - 1; + + gator_buffer_size[BACKTRACE_BUF] = BACKTRACE_BUFFER_SIZE; + gator_buffer_mask[BACKTRACE_BUF] = BACKTRACE_BUFFER_SIZE - 1; + + gator_buffer_size[NAME_BUF] = NAME_BUFFER_SIZE; + gator_buffer_mask[NAME_BUF] = NAME_BUFFER_SIZE - 1; + + gator_buffer_size[COUNTER_BUF] = COUNTER_BUFFER_SIZE; + gator_buffer_mask[COUNTER_BUF] = COUNTER_BUFFER_SIZE - 1; + + gator_buffer_size[BLOCK_COUNTER_BUF] = BLOCK_COUNTER_BUFFER_SIZE; + gator_buffer_mask[BLOCK_COUNTER_BUF] = BLOCK_COUNTER_BUFFER_SIZE - 1; + + gator_buffer_size[ANNOTATE_BUF] = ANNOTATE_BUFFER_SIZE; + gator_buffer_mask[ANNOTATE_BUF] = ANNOTATE_BUFFER_SIZE - 1; + + gator_buffer_size[SCHED_TRACE_BUF] = SCHED_TRACE_BUFFER_SIZE; + gator_buffer_mask[SCHED_TRACE_BUF] = SCHED_TRACE_BUFFER_SIZE - 1; + + gator_buffer_size[GPU_TRACE_BUF] = GPU_TRACE_BUFFER_SIZE; + gator_buffer_mask[GPU_TRACE_BUF] = GPU_TRACE_BUFFER_SIZE - 1; + + gator_buffer_size[IDLE_BUF] = IDLE_BUFFER_SIZE; + gator_buffer_mask[IDLE_BUF] = IDLE_BUFFER_SIZE - 1; + + // Initialize percpu per buffer variables + for (i = 0; i < NUM_GATOR_BUFS; i++) { + // Verify buffers are a power of 2 + if (gator_buffer_size[i] & (gator_buffer_size[i] - 1)) { + err = -ENOEXEC; + goto setup_error; + } + + for_each_present_cpu(cpu) { + per_cpu(gator_buffer_read, cpu)[i] = 0; + per_cpu(gator_buffer_write, cpu)[i] = 0; + per_cpu(gator_buffer_commit, cpu)[i] = 0; + per_cpu(buffer_space_available, cpu)[i] = true; + per_cpu(gator_buffer_commit_time, cpu) = gator_live_rate; + + // Annotation is a special case that only uses a single buffer + if (cpu > 0 && i == ANNOTATE_BUF) { + per_cpu(gator_buffer, cpu)[i] = NULL; + continue; + } + + per_cpu(gator_buffer, cpu)[i] = vmalloc(gator_buffer_size[i]); + if (!per_cpu(gator_buffer, cpu)[i]) { + err = -ENOMEM; + goto setup_error; + } + } + } + +setup_error: + mutex_unlock(&start_mutex); + return err; +} + +/* Actually start profiling (echo 1>/dev/gator/enable) */ +static int gator_op_start(void) +{ + int err = 0; + + mutex_lock(&start_mutex); + + if (gator_started || gator_start()) + err = -EINVAL; + else + gator_started = 1; + + mutex_unlock(&start_mutex); + + return err; +} + +/* echo 0>/dev/gator/enable */ +static void gator_op_stop(void) +{ + mutex_lock(&start_mutex); + + if (gator_started) { + gator_stop(); + + mutex_lock(&gator_buffer_mutex); + + gator_started = 0; + gator_monotonic_started = 0; + cookies_release(); + wake_up(&gator_buffer_wait); + + mutex_unlock(&gator_buffer_mutex); + } + + mutex_unlock(&start_mutex); +} + +static void gator_shutdown(void) +{ + int cpu, i; + + mutex_lock(&start_mutex); + + for_each_present_cpu(cpu) { + mutex_lock(&gator_buffer_mutex); + for (i = 0; i < NUM_GATOR_BUFS; i++) { + vfree(per_cpu(gator_buffer, cpu)[i]); + per_cpu(gator_buffer, cpu)[i] = NULL; + per_cpu(gator_buffer_read, cpu)[i] = 0; + per_cpu(gator_buffer_write, cpu)[i] = 0; + per_cpu(gator_buffer_commit, cpu)[i] = 0; + per_cpu(buffer_space_available, cpu)[i] = true; + per_cpu(gator_buffer_commit_time, cpu) = 0; + } + mutex_unlock(&gator_buffer_mutex); + } + + memset(&sent_core_name, 0, sizeof(sent_core_name)); + + mutex_unlock(&start_mutex); +} + +static int gator_set_backtrace(unsigned long val) +{ + int err = 0; + + mutex_lock(&start_mutex); + + if (gator_started) + err = -EBUSY; + else + gator_backtrace_depth = val; + + mutex_unlock(&start_mutex); + + return err; +} + +static ssize_t enable_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + return gatorfs_ulong_to_user(gator_started, buf, count, offset); +} + +static ssize_t enable_write(struct file *file, char const __user *buf, size_t count, loff_t *offset) +{ + unsigned long val; + int retval; + + if (*offset) + return -EINVAL; + + retval = gatorfs_ulong_from_user(&val, buf, count); + if (retval) + return retval; + + if (val) + retval = gator_op_start(); + else + gator_op_stop(); + + if (retval) + return retval; + return count; +} + +static const struct file_operations enable_fops = { + .read = enable_read, + .write = enable_write, +}; + +static int userspace_buffer_open(struct inode *inode, struct file *file) +{ + int err = -EPERM; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (test_and_set_bit_lock(0, &gator_buffer_opened)) + return -EBUSY; + + if ((err = gator_op_setup())) + goto fail; + + /* NB: the actual start happens from userspace + * echo 1 >/dev/gator/enable + */ + + return 0; + +fail: + __clear_bit_unlock(0, &gator_buffer_opened); + return err; +} + +static int userspace_buffer_release(struct inode *inode, struct file *file) +{ + gator_op_stop(); + gator_shutdown(); + __clear_bit_unlock(0, &gator_buffer_opened); + return 0; +} + +static ssize_t userspace_buffer_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + int commit, length1, length2, read; + char *buffer1; + char *buffer2; + int cpu, buftype; + int written = 0; + + // ensure there is enough space for a whole frame + if (count < userspace_buffer_size || *offset) { + return -EINVAL; + } + + // sleep until the condition is true or a signal is received + // the condition is checked each time gator_buffer_wait is woken up + wait_event_interruptible(gator_buffer_wait, buffer_commit_ready(&cpu, &buftype) || !gator_started); + + if (signal_pending(current)) { + return -EINTR; + } + + if (buftype == -1 || cpu == -1) { + return 0; + } + + mutex_lock(&gator_buffer_mutex); + + do { + read = per_cpu(gator_buffer_read, cpu)[buftype]; + commit = per_cpu(gator_buffer_commit, cpu)[buftype]; + + // May happen if the buffer is freed during pending reads. + if (!per_cpu(gator_buffer, cpu)[buftype]) { + break; + } + + // determine the size of two halves + length1 = commit - read; + length2 = 0; + buffer1 = &(per_cpu(gator_buffer, cpu)[buftype][read]); + buffer2 = &(per_cpu(gator_buffer, cpu)[buftype][0]); + if (length1 < 0) { + length1 = gator_buffer_size[buftype] - read; + length2 = commit; + } + + if (length1 + length2 > count - written) { + break; + } + + // start, middle or end + if (length1 > 0 && copy_to_user(&buf[written], buffer1, length1)) { + break; + } + + // possible wrap around + if (length2 > 0 && copy_to_user(&buf[written + length1], buffer2, length2)) { + break; + } + + per_cpu(gator_buffer_read, cpu)[buftype] = commit; + written += length1 + length2; + + // Wake up annotate_write if more space is available + if (buftype == ANNOTATE_BUF) { + wake_up(&gator_annotate_wait); + } + } while (buffer_commit_ready(&cpu, &buftype)); + + mutex_unlock(&gator_buffer_mutex); + + // kick just in case we've lost an SMP event + wake_up(&gator_buffer_wait); + + return written > 0 ? written : -EFAULT; +} + +const struct file_operations gator_event_buffer_fops = { + .open = userspace_buffer_open, + .release = userspace_buffer_release, + .read = userspace_buffer_read, +}; + +static ssize_t depth_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + return gatorfs_ulong_to_user(gator_backtrace_depth, buf, count, offset); +} + +static ssize_t depth_write(struct file *file, char const __user *buf, size_t count, loff_t *offset) +{ + unsigned long val; + int retval; + + if (*offset) + return -EINVAL; + + retval = gatorfs_ulong_from_user(&val, buf, count); + if (retval) + return retval; + + retval = gator_set_backtrace(val); + + if (retval) + return retval; + return count; +} + +static const struct file_operations depth_fops = { + .read = depth_read, + .write = depth_write +}; + +void gator_op_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + struct gator_interface *gi; + int cpu; + + /* reinitialize default values */ + gator_cpu_cores = 0; + for_each_present_cpu(cpu) { + gator_cpu_cores++; + } + userspace_buffer_size = BACKTRACE_BUFFER_SIZE; + gator_response_type = 1; + gator_live_rate = 0; + + gatorfs_create_file(sb, root, "enable", &enable_fops); + gatorfs_create_file(sb, root, "buffer", &gator_event_buffer_fops); + gatorfs_create_file(sb, root, "backtrace_depth", &depth_fops); + gatorfs_create_ro_ulong(sb, root, "cpu_cores", &gator_cpu_cores); + gatorfs_create_ro_ulong(sb, root, "buffer_size", &userspace_buffer_size); + gatorfs_create_ulong(sb, root, "tick", &gator_timer_count); + gatorfs_create_ulong(sb, root, "response_type", &gator_response_type); + gatorfs_create_ro_ulong(sb, root, "version", &gator_protocol_version); + gatorfs_create_ro_u64(sb, root, "started", &gator_monotonic_started); + gatorfs_create_u64(sb, root, "live_rate", &gator_live_rate); + + // Annotate interface + gator_annotate_create_files(sb, root); + + // Linux Events + dir = gatorfs_mkdir(sb, root, "events"); + list_for_each_entry(gi, &gator_events, list) + if (gi->create_files) + gi->create_files(sb, dir); + + // Sched Events + sched_trace_create_files(sb, dir); + + // Power interface + gator_trace_power_create_files(sb, dir); +} + +/****************************************************************************** + * Module + ******************************************************************************/ +static int __init gator_module_init(void) +{ + if (gatorfs_register()) { + return -1; + } + + if (gator_init()) { + gatorfs_unregister(); + return -1; + } + + setup_timer(&gator_buffer_wake_up_timer, gator_buffer_wake_up, 0); + + return 0; +} + +static void __exit gator_module_exit(void) +{ + del_timer_sync(&gator_buffer_wake_up_timer); + tracepoint_synchronize_unregister(); + gator_exit(); + gatorfs_unregister(); +} + +module_init(gator_module_init); +module_exit(gator_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("ARM Ltd"); +MODULE_DESCRIPTION("Gator system profiler"); +#define STRIFY2(ARG) #ARG +#define STRIFY(ARG) STRIFY2(ARG) +MODULE_VERSION(STRIFY(PROTOCOL_VERSION)); diff --git a/drivers/gator/gator_marshaling.c b/drivers/gator/gator_marshaling.c new file mode 100644 index 00000000000000..af80ff62e712c4 --- /dev/null +++ b/drivers/gator/gator_marshaling.c @@ -0,0 +1,432 @@ +/** + * Copyright (C) ARM Limited 2012-2013. All rights reserved. + * + * 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. + * + */ + +#define NEWLINE_CANARY \ + /* Unix */ \ + "1\n" \ + /* Windows */ \ + "2\r\n" \ + /* Mac OS */ \ + "3\r" \ + /* RISC OS */ \ + "4\n\r" \ + /* Add another character so the length isn't 0x0a bytes */ \ + "5" + +#ifdef MALI_SUPPORT +#include "gator_events_mali_common.h" +#endif + +static void marshal_summary(long long timestamp, long long uptime, long long monotonic_delta, const char * uname) +{ + unsigned long flags; + int cpu = 0; + + local_irq_save(flags); + gator_buffer_write_string(cpu, SUMMARY_BUF, NEWLINE_CANARY); + gator_buffer_write_packed_int64(cpu, SUMMARY_BUF, timestamp); + gator_buffer_write_packed_int64(cpu, SUMMARY_BUF, uptime); + gator_buffer_write_packed_int64(cpu, SUMMARY_BUF, monotonic_delta); + gator_buffer_write_string(cpu, SUMMARY_BUF, "uname"); + gator_buffer_write_string(cpu, SUMMARY_BUF, uname); +#if GATOR_IKS_SUPPORT + gator_buffer_write_string(cpu, SUMMARY_BUF, "iks"); + gator_buffer_write_string(cpu, SUMMARY_BUF, ""); +#endif + // Let Streamline know which GPU is used so that it can label the GPU Activity appropriately. This is a temporary fix, to be improved in a future release. +#ifdef MALI_SUPPORT + gator_buffer_write_string(cpu, SUMMARY_BUF, "mali_type"); +#if (MALI_SUPPORT == MALI_4xx) + gator_buffer_write_string(cpu, SUMMARY_BUF, "4xx"); +#elif (MALI_SUPPORT == MALI_T6xx) + gator_buffer_write_string(cpu, SUMMARY_BUF, "6xx"); +#else + gator_buffer_write_string(cpu, SUMMARY_BUF, "unknown"); +#endif +#endif + gator_buffer_write_string(cpu, SUMMARY_BUF, ""); + // Commit the buffer now so it can be one of the first frames read by Streamline + gator_commit_buffer(cpu, SUMMARY_BUF, gator_get_time()); + local_irq_restore(flags); +} + +static bool marshal_cookie_header(const char *text) +{ + int cpu = get_physical_cpu(); + return buffer_check_space(cpu, NAME_BUF, strlen(text) + 3 * MAXSIZE_PACK32); +} + +static void marshal_cookie(int cookie, const char *text) +{ + int cpu = get_physical_cpu(); + // buffer_check_space already called by marshal_cookie_header + gator_buffer_write_packed_int(cpu, NAME_BUF, MESSAGE_COOKIE); + gator_buffer_write_packed_int(cpu, NAME_BUF, cookie); + gator_buffer_write_string(cpu, NAME_BUF, text); + buffer_check(cpu, NAME_BUF, gator_get_time()); +} + +static void marshal_thread_name(int pid, char *name) +{ + unsigned long flags, cpu; + u64 time; + local_irq_save(flags); + cpu = get_physical_cpu(); + time = gator_get_time(); + if (buffer_check_space(cpu, NAME_BUF, TASK_COMM_LEN + 3 * MAXSIZE_PACK32 + MAXSIZE_PACK64)) { + gator_buffer_write_packed_int(cpu, NAME_BUF, MESSAGE_THREAD_NAME); + gator_buffer_write_packed_int64(cpu, NAME_BUF, time); + gator_buffer_write_packed_int(cpu, NAME_BUF, pid); + gator_buffer_write_string(cpu, NAME_BUF, name); + } + buffer_check(cpu, NAME_BUF, time); + local_irq_restore(flags); +} + +static void marshal_link(int cookie, int tgid, int pid) +{ + unsigned long cpu = get_physical_cpu(), flags; + u64 time; + + local_irq_save(flags); + time = gator_get_time(); + if (buffer_check_space(cpu, NAME_BUF, MAXSIZE_PACK64 + 5 * MAXSIZE_PACK32)) { + gator_buffer_write_packed_int(cpu, NAME_BUF, MESSAGE_LINK); + gator_buffer_write_packed_int64(cpu, NAME_BUF, time); + gator_buffer_write_packed_int(cpu, NAME_BUF, cookie); + gator_buffer_write_packed_int(cpu, NAME_BUF, tgid); + gator_buffer_write_packed_int(cpu, NAME_BUF, pid); + } + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, NAME_BUF, time); + local_irq_restore(flags); +} + +static bool marshal_backtrace_header(int exec_cookie, int tgid, int pid, int inKernel, u64 time) +{ + int cpu = get_physical_cpu(); + if (!buffer_check_space(cpu, BACKTRACE_BUF, MAXSIZE_PACK64 + 5 * MAXSIZE_PACK32 + gator_backtrace_depth * 2 * MAXSIZE_PACK32)) { + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, BACKTRACE_BUF, time); + + return false; + } + + gator_buffer_write_packed_int64(cpu, BACKTRACE_BUF, time); + gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, exec_cookie); + gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, tgid); + gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, pid); + gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, inKernel); + + return true; +} + +static void marshal_backtrace(unsigned long address, int cookie) +{ + int cpu = get_physical_cpu(); + gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, cookie); + gator_buffer_write_packed_int64(cpu, BACKTRACE_BUF, address); +} + +static void marshal_backtrace_footer(u64 time) +{ + int cpu = get_physical_cpu(); + gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, MESSAGE_END_BACKTRACE); + + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, BACKTRACE_BUF, time); +} + +static bool marshal_event_header(u64 time) +{ + unsigned long flags, cpu = get_physical_cpu(); + bool retval = false; + + local_irq_save(flags); + if (buffer_check_space(cpu, BLOCK_COUNTER_BUF, MAXSIZE_PACK32 + MAXSIZE_PACK64)) { + gator_buffer_write_packed_int(cpu, BLOCK_COUNTER_BUF, 0); // key of zero indicates a timestamp + gator_buffer_write_packed_int64(cpu, BLOCK_COUNTER_BUF, time); + retval = true; + } + local_irq_restore(flags); + + return retval; +} + +static void marshal_event(int len, int *buffer) +{ + unsigned long i, flags, cpu = get_physical_cpu(); + + if (len <= 0) + return; + + // length must be even since all data is a (key, value) pair + if (len & 0x1) { + pr_err("gator: invalid counter data detected and discarded"); + return; + } + + // events must be written in key,value pairs + local_irq_save(flags); + for (i = 0; i < len; i += 2) { + if (!buffer_check_space(cpu, BLOCK_COUNTER_BUF, 2 * MAXSIZE_PACK32)) { + break; + } + gator_buffer_write_packed_int(cpu, BLOCK_COUNTER_BUF, buffer[i]); + gator_buffer_write_packed_int(cpu, BLOCK_COUNTER_BUF, buffer[i + 1]); + } + local_irq_restore(flags); +} + +static void marshal_event64(int len, long long *buffer64) +{ + unsigned long i, flags, cpu = get_physical_cpu(); + + if (len <= 0) + return; + + // length must be even since all data is a (key, value) pair + if (len & 0x1) { + pr_err("gator: invalid counter data detected and discarded"); + return; + } + + // events must be written in key,value pairs + local_irq_save(flags); + for (i = 0; i < len; i += 2) { + if (!buffer_check_space(cpu, BLOCK_COUNTER_BUF, 2 * MAXSIZE_PACK64)) { + break; + } + gator_buffer_write_packed_int64(cpu, BLOCK_COUNTER_BUF, buffer64[i]); + gator_buffer_write_packed_int64(cpu, BLOCK_COUNTER_BUF, buffer64[i + 1]); + } + local_irq_restore(flags); +} + +#if GATOR_CPU_FREQ_SUPPORT +static void marshal_event_single(int core, int key, int value) +{ + unsigned long flags, cpu; + u64 time; + + local_irq_save(flags); + cpu = get_physical_cpu(); + time = gator_get_time(); + if (buffer_check_space(cpu, COUNTER_BUF, MAXSIZE_PACK64 + 3 * MAXSIZE_PACK32)) { + gator_buffer_write_packed_int64(cpu, COUNTER_BUF, time); + gator_buffer_write_packed_int(cpu, COUNTER_BUF, core); + gator_buffer_write_packed_int(cpu, COUNTER_BUF, key); + gator_buffer_write_packed_int(cpu, COUNTER_BUF, value); + } + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, COUNTER_BUF, time); + local_irq_restore(flags); +} +#endif + +static void marshal_sched_gpu_start(int unit, int core, int tgid, int pid) +{ + unsigned long cpu = get_physical_cpu(), flags; + u64 time; + + if (!per_cpu(gator_buffer, cpu)[GPU_TRACE_BUF]) + return; + + local_irq_save(flags); + time = gator_get_time(); + if (buffer_check_space(cpu, GPU_TRACE_BUF, MAXSIZE_PACK64 + 5 * MAXSIZE_PACK32)) { + gator_buffer_write_packed_int(cpu, GPU_TRACE_BUF, MESSAGE_GPU_START); + gator_buffer_write_packed_int64(cpu, GPU_TRACE_BUF, time); + gator_buffer_write_packed_int(cpu, GPU_TRACE_BUF, unit); + gator_buffer_write_packed_int(cpu, GPU_TRACE_BUF, core); + gator_buffer_write_packed_int(cpu, GPU_TRACE_BUF, tgid); + gator_buffer_write_packed_int(cpu, GPU_TRACE_BUF, pid); + } + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, GPU_TRACE_BUF, time); + local_irq_restore(flags); +} + +static void marshal_sched_gpu_stop(int unit, int core) +{ + unsigned long cpu = get_physical_cpu(), flags; + u64 time; + + if (!per_cpu(gator_buffer, cpu)[GPU_TRACE_BUF]) + return; + + local_irq_save(flags); + time = gator_get_time(); + if (buffer_check_space(cpu, GPU_TRACE_BUF, MAXSIZE_PACK64 + 3 * MAXSIZE_PACK32)) { + gator_buffer_write_packed_int(cpu, GPU_TRACE_BUF, MESSAGE_GPU_STOP); + gator_buffer_write_packed_int64(cpu, GPU_TRACE_BUF, time); + gator_buffer_write_packed_int(cpu, GPU_TRACE_BUF, unit); + gator_buffer_write_packed_int(cpu, GPU_TRACE_BUF, core); + } + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, GPU_TRACE_BUF, time); + local_irq_restore(flags); +} + +static void marshal_sched_trace_start(int tgid, int pid, int cookie) +{ + unsigned long cpu = get_physical_cpu(), flags; + u64 time; + + if (!per_cpu(gator_buffer, cpu)[SCHED_TRACE_BUF]) + return; + + local_irq_save(flags); + time = gator_get_time(); + if (buffer_check_space(cpu, SCHED_TRACE_BUF, MAXSIZE_PACK64 + 5 * MAXSIZE_PACK32)) { + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, MESSAGE_SCHED_START); + gator_buffer_write_packed_int64(cpu, SCHED_TRACE_BUF, time); + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, tgid); + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, pid); + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, cookie); + } + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, SCHED_TRACE_BUF, time); + local_irq_restore(flags); +} + +static void marshal_sched_trace_switch(int tgid, int pid, int cookie, int state) +{ + unsigned long cpu = get_physical_cpu(), flags; + u64 time; + + if (!per_cpu(gator_buffer, cpu)[SCHED_TRACE_BUF]) + return; + + local_irq_save(flags); + time = gator_get_time(); + if (buffer_check_space(cpu, SCHED_TRACE_BUF, MAXSIZE_PACK64 + 5 * MAXSIZE_PACK32)) { + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, MESSAGE_SCHED_SWITCH); + gator_buffer_write_packed_int64(cpu, SCHED_TRACE_BUF, time); + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, tgid); + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, pid); + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, cookie); + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, state); + } + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, SCHED_TRACE_BUF, time); + local_irq_restore(flags); +} + +static void marshal_sched_trace_exit(int tgid, int pid) +{ + unsigned long cpu = get_physical_cpu(), flags; + u64 time; + + if (!per_cpu(gator_buffer, cpu)[SCHED_TRACE_BUF]) + return; + + local_irq_save(flags); + time = gator_get_time(); + if (buffer_check_space(cpu, SCHED_TRACE_BUF, MAXSIZE_PACK64 + 2 * MAXSIZE_PACK32)) { + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, MESSAGE_SCHED_EXIT); + gator_buffer_write_packed_int64(cpu, SCHED_TRACE_BUF, time); + gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, pid); + } + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, SCHED_TRACE_BUF, time); + local_irq_restore(flags); +} + +#if GATOR_CPU_FREQ_SUPPORT +static void marshal_idle(int core, int state) +{ + unsigned long flags, cpu; + u64 time; + + local_irq_save(flags); + cpu = get_physical_cpu(); + time = gator_get_time(); + if (buffer_check_space(cpu, IDLE_BUF, MAXSIZE_PACK64 + 2 * MAXSIZE_PACK32)) { + gator_buffer_write_packed_int(cpu, IDLE_BUF, state); + gator_buffer_write_packed_int64(cpu, IDLE_BUF, time); + gator_buffer_write_packed_int(cpu, IDLE_BUF, core); + } + // Check and commit; commit is set to occur once buffer is 3/4 full + buffer_check(cpu, IDLE_BUF, time); + local_irq_restore(flags); +} +#endif + +static void marshal_frame(int cpu, int buftype) +{ + int frame; + + if (!per_cpu(gator_buffer, cpu)[buftype]) { + return; + } + + switch (buftype) { + case SUMMARY_BUF: + frame = FRAME_SUMMARY; + break; + case BACKTRACE_BUF: + frame = FRAME_BACKTRACE; + break; + case NAME_BUF: + frame = FRAME_NAME; + break; + case COUNTER_BUF: + frame = FRAME_COUNTER; + break; + case BLOCK_COUNTER_BUF: + frame = FRAME_BLOCK_COUNTER; + break; + case ANNOTATE_BUF: + frame = FRAME_ANNOTATE; + break; + case SCHED_TRACE_BUF: + frame = FRAME_SCHED_TRACE; + break; + case GPU_TRACE_BUF: + frame = FRAME_GPU_TRACE; + break; + case IDLE_BUF: + frame = FRAME_IDLE; + break; + default: + frame = -1; + break; + } + + // add response type + if (gator_response_type > 0) { + gator_buffer_write_packed_int(cpu, buftype, gator_response_type); + } + + // leave space for 4-byte unpacked length + per_cpu(gator_buffer_write, cpu)[buftype] = (per_cpu(gator_buffer_write, cpu)[buftype] + sizeof(s32)) & gator_buffer_mask[buftype]; + + // add frame type and core number + gator_buffer_write_packed_int(cpu, buftype, frame); + gator_buffer_write_packed_int(cpu, buftype, cpu); +} + +#if defined(__arm__) || defined(__aarch64__) +static void marshal_core_name(const int core, const int cpuid, const char *name) +{ + int cpu = get_physical_cpu(); + unsigned long flags; + local_irq_save(flags); + if (buffer_check_space(cpu, NAME_BUF, MAXSIZE_PACK32 + MAXSIZE_CORE_NAME)) { + gator_buffer_write_packed_int(cpu, NAME_BUF, HRTIMER_CORE_NAME); + gator_buffer_write_packed_int(cpu, NAME_BUF, core); + gator_buffer_write_packed_int(cpu, NAME_BUF, cpuid); + gator_buffer_write_string(cpu, NAME_BUF, name); + } + // Commit core names now so that they can show up in live + gator_commit_buffer(cpu, NAME_BUF, gator_get_time()); + local_irq_restore(flags); +} +#endif diff --git a/drivers/gator/gator_pack.c b/drivers/gator/gator_pack.c new file mode 100644 index 00000000000000..2c082f283adc18 --- /dev/null +++ b/drivers/gator/gator_pack.c @@ -0,0 +1,58 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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. + * + */ + +static void gator_buffer_write_packed_int(int cpu, int buftype, int x) +{ + uint32_t write = per_cpu(gator_buffer_write, cpu)[buftype]; + uint32_t mask = gator_buffer_mask[buftype]; + char *buffer = per_cpu(gator_buffer, cpu)[buftype]; + int packedBytes = 0; + int more = true; + while (more) { + // low order 7 bits of x + char b = x & 0x7f; + x >>= 7; + + if ((x == 0 && (b & 0x40) == 0) || (x == -1 && (b & 0x40) != 0)) { + more = false; + } else { + b |= 0x80; + } + + buffer[(write + packedBytes) & mask] = b; + packedBytes++; + } + + per_cpu(gator_buffer_write, cpu)[buftype] = (write + packedBytes) & mask; +} + +static void gator_buffer_write_packed_int64(int cpu, int buftype, long long x) +{ + uint32_t write = per_cpu(gator_buffer_write, cpu)[buftype]; + uint32_t mask = gator_buffer_mask[buftype]; + char *buffer = per_cpu(gator_buffer, cpu)[buftype]; + int packedBytes = 0; + int more = true; + while (more) { + // low order 7 bits of x + char b = x & 0x7f; + x >>= 7; + + if ((x == 0 && (b & 0x40) == 0) || (x == -1 && (b & 0x40) != 0)) { + more = false; + } else { + b |= 0x80; + } + + buffer[(write + packedBytes) & mask] = b; + packedBytes++; + } + + per_cpu(gator_buffer_write, cpu)[buftype] = (write + packedBytes) & mask; +} diff --git a/drivers/gator/gator_trace_gpu.c b/drivers/gator/gator_trace_gpu.c new file mode 100644 index 00000000000000..be135b4aac563b --- /dev/null +++ b/drivers/gator/gator_trace_gpu.c @@ -0,0 +1,294 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 "gator.h" + +#include +#include +#include +#include + +#ifdef MALI_SUPPORT +#include "linux/mali_linux_trace.h" +#endif +#include "gator_trace_gpu.h" + +/* + * Taken from MALI_PROFILING_EVENT_TYPE_* items in Mali DDK. + */ +#define EVENT_TYPE_SINGLE 0 +#define EVENT_TYPE_START 1 +#define EVENT_TYPE_STOP 2 +#define EVENT_TYPE_SUSPEND 3 +#define EVENT_TYPE_RESUME 4 + +/* Note whether tracepoints have been registered */ +static int mali_timeline_trace_registered; +static int mali_job_slots_trace_registered; +static int gpu_trace_registered; + +enum { + GPU_UNIT_NONE = 0, + GPU_UNIT_VP, + GPU_UNIT_FP, + GPU_UNIT_CL, + NUMBER_OF_GPU_UNITS +}; + +#define MALI_4xx (0x0b07) +#define MALI_T6xx (0x0056) + +struct mali_gpu_job { + int count; + int last_tgid; + int last_pid; + int last_job_id; +}; + +#define NUMBER_OF_GPU_CORES 16 +static struct mali_gpu_job mali_gpu_jobs[NUMBER_OF_GPU_UNITS][NUMBER_OF_GPU_CORES]; +static DEFINE_SPINLOCK(mali_gpu_jobs_lock); + +/* Only one event should be running on a unit and core at a time (ie, a start + * event can only be followed by a stop and vice versa), but because the kernel + * only knows when a job is enqueued and not started, it is possible for a + * start1, start2, stop1, stop2. Change it back into start1, stop1, start2, + * stop2 by queueing up start2 and releasing it when stop1 is received. + */ +static void mali_gpu_enqueue(int unit, int core, int tgid, int pid, int job_id) +{ + int count; + + spin_lock(&mali_gpu_jobs_lock); + count = mali_gpu_jobs[unit][core].count; + BUG_ON(count < 0); + ++mali_gpu_jobs[unit][core].count; + if (count) { + mali_gpu_jobs[unit][core].last_tgid = tgid; + mali_gpu_jobs[unit][core].last_pid = pid; + mali_gpu_jobs[unit][core].last_job_id = job_id; + } + spin_unlock(&mali_gpu_jobs_lock); + + if (!count) { + marshal_sched_gpu_start(unit, core, tgid, pid/*, job_id*/); + } +} + +static void mali_gpu_stop(int unit, int core) +{ + int count; + int last_tgid = 0; + int last_pid = 0; + //int last_job_id = 0; + + spin_lock(&mali_gpu_jobs_lock); + if (mali_gpu_jobs[unit][core].count == 0) { + spin_unlock(&mali_gpu_jobs_lock); + return; + } + --mali_gpu_jobs[unit][core].count; + count = mali_gpu_jobs[unit][core].count; + if (count) { + last_tgid = mali_gpu_jobs[unit][core].last_tgid; + last_pid = mali_gpu_jobs[unit][core].last_pid; + //last_job_id = mali_gpu_jobs[unit][core].last_job_id; + } + spin_unlock(&mali_gpu_jobs_lock); + + marshal_sched_gpu_stop(unit, core); + if (count) { + marshal_sched_gpu_start(unit, core, last_tgid, last_pid/*, last_job_id*/); + } +} + +#if defined(MALI_SUPPORT) && (MALI_SUPPORT != MALI_T6xx) +#include "gator_events_mali_4xx.h" + +/* + * Taken from MALI_PROFILING_EVENT_CHANNEL_* in Mali DDK. + */ +enum { + EVENT_CHANNEL_SOFTWARE = 0, + EVENT_CHANNEL_VP0 = 1, + EVENT_CHANNEL_FP0 = 5, + EVENT_CHANNEL_FP1, + EVENT_CHANNEL_FP2, + EVENT_CHANNEL_FP3, + EVENT_CHANNEL_FP4, + EVENT_CHANNEL_FP5, + EVENT_CHANNEL_FP6, + EVENT_CHANNEL_FP7, + EVENT_CHANNEL_GPU = 21 +}; + +/** + * These events are applicable when the type MALI_PROFILING_EVENT_TYPE_SINGLE is used from the GPU channel + */ +enum { + EVENT_REASON_SINGLE_GPU_NONE = 0, + EVENT_REASON_SINGLE_GPU_FREQ_VOLT_CHANGE = 1, +}; + +GATOR_DEFINE_PROBE(mali_timeline_event, TP_PROTO(unsigned int event_id, unsigned int d0, unsigned int d1, unsigned int d2, unsigned int d3, unsigned int d4)) +{ + unsigned int component, state; + + // do as much work as possible before disabling interrupts + component = (event_id >> 16) & 0xFF; // component is an 8-bit field + state = (event_id >> 24) & 0xF; // state is a 4-bit field + + switch (state) { + case EVENT_TYPE_START: + if (component == EVENT_CHANNEL_VP0) { + /* tgid = d0; pid = d1; */ + mali_gpu_enqueue(GPU_UNIT_VP, 0, d0, d1, 0); + } else if (component >= EVENT_CHANNEL_FP0 && component <= EVENT_CHANNEL_FP7) { + /* tgid = d0; pid = d1; */ + mali_gpu_enqueue(GPU_UNIT_FP, component - EVENT_CHANNEL_FP0, d0, d1, 0); + } + break; + + case EVENT_TYPE_STOP: + if (component == EVENT_CHANNEL_VP0) { + mali_gpu_stop(GPU_UNIT_VP, 0); + } else if (component >= EVENT_CHANNEL_FP0 && component <= EVENT_CHANNEL_FP7) { + mali_gpu_stop(GPU_UNIT_FP, component - EVENT_CHANNEL_FP0); + } + break; + + case EVENT_TYPE_SINGLE: + if (component == EVENT_CHANNEL_GPU) { + unsigned int reason = (event_id & 0xffff); + + if (reason == EVENT_REASON_SINGLE_GPU_FREQ_VOLT_CHANGE) { + gator_events_mali_log_dvfs_event(d0, d1); + } + } + break; + + default: + break; + } +} +#endif + +#if defined(MALI_SUPPORT) && (MALI_SUPPORT == MALI_T6xx) +#if defined(MALI_JOB_SLOTS_EVENT_CHANGED) +GATOR_DEFINE_PROBE(mali_job_slots_event, TP_PROTO(unsigned int event_id, unsigned int tgid, unsigned int pid, unsigned char job_id)) +#else +GATOR_DEFINE_PROBE(mali_job_slots_event, TP_PROTO(unsigned int event_id, unsigned int tgid, unsigned int pid)) +#endif +{ + unsigned int component, state, unit; +#if !defined(MALI_JOB_SLOTS_EVENT_CHANGED) + unsigned char job_id = 0; +#endif + + component = (event_id >> 16) & 0xFF; // component is an 8-bit field + state = (event_id >> 24) & 0xF; // state is a 4-bit field + + switch (component) { + case 0: + unit = GPU_UNIT_FP; + break; + case 1: + unit = GPU_UNIT_VP; + break; + case 2: + unit = GPU_UNIT_CL; + break; + default: + unit = GPU_UNIT_NONE; + } + + if (unit != GPU_UNIT_NONE) { + switch (state) { + case EVENT_TYPE_START: + mali_gpu_enqueue(unit, 0, tgid, (pid != 0 ? pid : tgid), job_id); + break; + case EVENT_TYPE_STOP: + mali_gpu_stop(unit, 0); + break; + default: + /* + * Some jobs can be soft-stopped, so ensure that this terminates the activity trace. + */ + mali_gpu_stop(unit, 0); + } + } +} +#endif + +GATOR_DEFINE_PROBE(gpu_activity_start, TP_PROTO(int gpu_unit, int gpu_core, struct task_struct *p)) +{ + mali_gpu_enqueue(gpu_unit, gpu_core, (int)p->tgid, (int)p->pid, 0); +} + +GATOR_DEFINE_PROBE(gpu_activity_stop, TP_PROTO(int gpu_unit, int gpu_core)) +{ + mali_gpu_stop(gpu_unit, gpu_core); +} + +int gator_trace_gpu_start(void) +{ + /* + * Returns nonzero for installation failed + * Absence of gpu trace points is not an error + */ + + memset(&mali_gpu_jobs, 0, sizeof(mali_gpu_jobs)); + gpu_trace_registered = mali_timeline_trace_registered = mali_job_slots_trace_registered = 0; + +#if defined(MALI_SUPPORT) && (MALI_SUPPORT != MALI_T6xx) + if (!GATOR_REGISTER_TRACE(mali_timeline_event)) { + mali_timeline_trace_registered = 1; + } +#endif + +#if defined(MALI_SUPPORT) && (MALI_SUPPORT == MALI_T6xx) + if (!GATOR_REGISTER_TRACE(mali_job_slots_event)) { + mali_job_slots_trace_registered = 1; + } +#endif + + if (!mali_timeline_trace_registered) { + if (GATOR_REGISTER_TRACE(gpu_activity_start)) { + return 0; + } + if (GATOR_REGISTER_TRACE(gpu_activity_stop)) { + GATOR_UNREGISTER_TRACE(gpu_activity_start); + return 0; + } + gpu_trace_registered = 1; + } + + return 0; +} + +void gator_trace_gpu_stop(void) +{ +#if defined(MALI_SUPPORT) && (MALI_SUPPORT != MALI_T6xx) + if (mali_timeline_trace_registered) { + GATOR_UNREGISTER_TRACE(mali_timeline_event); + } +#endif + +#if defined(MALI_SUPPORT) && (MALI_SUPPORT == MALI_T6xx) + if (mali_job_slots_trace_registered) { + GATOR_UNREGISTER_TRACE(mali_job_slots_event); + } +#endif + + if (gpu_trace_registered) { + GATOR_UNREGISTER_TRACE(gpu_activity_stop); + GATOR_UNREGISTER_TRACE(gpu_activity_start); + } + + gpu_trace_registered = mali_timeline_trace_registered = mali_job_slots_trace_registered = 0; +} diff --git a/drivers/gator/gator_trace_gpu.h b/drivers/gator/gator_trace_gpu.h new file mode 100644 index 00000000000000..bb0f42d290dabf --- /dev/null +++ b/drivers/gator/gator_trace_gpu.h @@ -0,0 +1,79 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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. + */ + +#undef TRACE_GPU +#define TRACE_GPU gpu + +#if !defined(_TRACE_GPU_H) +#define _TRACE_GPU_H + +#include + +/* + * UNIT - the GPU processor type + * 1 = Vertex Processor + * 2 = Fragment Processor + * + * CORE - the GPU processor core number + * this is not the CPU core number + */ + +/* + * Tracepoint for calling GPU unit start activity on core + */ +TRACE_EVENT(gpu_activity_start, + + TP_PROTO(int gpu_unit, int gpu_core, struct task_struct *p), + + TP_ARGS(gpu_unit, gpu_core, p), + + TP_STRUCT__entry( + __field(int, gpu_unit) + __field(int, gpu_core) + __array(char, comm, TASK_COMM_LEN) + __field(pid_t, pid) + ), + + TP_fast_assign( + __entry->gpu_unit = gpu_unit; + __entry->gpu_core = gpu_core; + memcpy(__entry->comm, p->comm, TASK_COMM_LEN); + __entry->pid = p->pid; + ), + + TP_printk("unit=%d core=%d comm=%s pid=%d", + __entry->gpu_unit, __entry->gpu_core, __entry->comm, + __entry->pid) + ); + +/* + * Tracepoint for calling GPU unit stop activity on core + */ +TRACE_EVENT(gpu_activity_stop, + + TP_PROTO(int gpu_unit, int gpu_core), + + TP_ARGS(gpu_unit, gpu_core), + + TP_STRUCT__entry( + __field(int, gpu_unit) + __field(int, gpu_core) + ), + + TP_fast_assign( + __entry->gpu_unit = gpu_unit; + __entry->gpu_core = gpu_core; + ), + + TP_printk("unit=%d core=%d", __entry->gpu_unit, __entry->gpu_core) + ); + +#endif /* _TRACE_GPU_H */ + +/* This part must be outside protection */ +#include diff --git a/drivers/gator/gator_trace_power.c b/drivers/gator/gator_trace_power.c new file mode 100644 index 00000000000000..272e05684ee8e7 --- /dev/null +++ b/drivers/gator/gator_trace_power.c @@ -0,0 +1,203 @@ +/** + * Copyright (C) ARM Limited 2011-2013. All rights reserved. + * + * 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 +#include + +#if defined(__arm__) + +#include + +#define implements_wfi() (!machine_is_omap3_beagle()) + +#else + +#define implements_wfi() false + +#endif + +// cpu_frequency and cpu_idle trace points were introduced in Linux kernel v2.6.38 +// the now deprecated power_frequency trace point was available prior to 2.6.38, but only for x86 +#if GATOR_CPU_FREQ_SUPPORT +enum { + POWER_CPU_FREQ, + POWER_CPU_IDLE, + POWER_TOTAL +}; + +static DEFINE_PER_CPU(ulong, idle_prev_state); +static ulong power_cpu_enabled[POWER_TOTAL]; +static ulong power_cpu_key[POWER_TOTAL]; + +static int gator_trace_power_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + int cpu; + bool found_nonzero_freq = false; + + // Even if CONFIG_CPU_FREQ is defined, it still may not be used. Check + // for non-zero values from cpufreq_quick_get + for_each_online_cpu(cpu) { + if (cpufreq_quick_get(cpu) > 0) { + found_nonzero_freq = true; + break; + } + } + + if (found_nonzero_freq) { + // cpu_frequency + dir = gatorfs_mkdir(sb, root, "Linux_power_cpu_freq"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &power_cpu_enabled[POWER_CPU_FREQ]); + gatorfs_create_ro_ulong(sb, dir, "key", &power_cpu_key[POWER_CPU_FREQ]); + } + + // cpu_idle + dir = gatorfs_mkdir(sb, root, "Linux_power_cpu_idle"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &power_cpu_enabled[POWER_CPU_IDLE]); + gatorfs_create_ro_ulong(sb, dir, "key", &power_cpu_key[POWER_CPU_IDLE]); + + return 0; +} + +// 'cpu' may not equal smp_processor_id(), i.e. may not be running on the core that is having the freq/idle state change +GATOR_DEFINE_PROBE(cpu_frequency, TP_PROTO(unsigned int frequency, unsigned int cpu)) +{ + cpu = lcpu_to_pcpu(cpu); + marshal_event_single(cpu, power_cpu_key[POWER_CPU_FREQ], frequency * 1000); +} + +GATOR_DEFINE_PROBE(cpu_idle, TP_PROTO(unsigned int state, unsigned int cpu)) +{ + cpu = lcpu_to_pcpu(cpu); + + if (state == per_cpu(idle_prev_state, cpu)) { + return; + } + + if (implements_wfi()) { + if (state == PWR_EVENT_EXIT) { + // transition from wfi to non-wfi + marshal_idle(cpu, MESSAGE_IDLE_EXIT); + } else { + // transition from non-wfi to wfi + marshal_idle(cpu, MESSAGE_IDLE_ENTER); + } + } + + per_cpu(idle_prev_state, cpu) = state; + + if (power_cpu_enabled[POWER_CPU_IDLE]) { + // Increment state so that no negative numbers are sent + marshal_event_single(cpu, power_cpu_key[POWER_CPU_IDLE], state + 1); + } +} + +static void gator_trace_power_online(void) +{ + int pcpu = get_physical_cpu(); + int lcpu = get_logical_cpu(); + if (power_cpu_enabled[POWER_CPU_FREQ]) { + marshal_event_single(pcpu, power_cpu_key[POWER_CPU_FREQ], cpufreq_quick_get(lcpu) * 1000); + } +} + +static void gator_trace_power_offline(void) +{ + // Set frequency to zero on an offline + int cpu = get_physical_cpu(); + if (power_cpu_enabled[POWER_CPU_FREQ]) { + marshal_event_single(cpu, power_cpu_key[POWER_CPU_FREQ], 0); + } +} + +static int gator_trace_power_start(void) +{ + int cpu; + + // register tracepoints + if (power_cpu_enabled[POWER_CPU_FREQ]) + if (GATOR_REGISTER_TRACE(cpu_frequency)) + goto fail_cpu_frequency_exit; + + // Always register for cpu:idle for detecting WFI, independent of power_cpu_enabled[POWER_CPU_IDLE] + if (GATOR_REGISTER_TRACE(cpu_idle)) + goto fail_cpu_idle_exit; + pr_debug("gator: registered power event tracepoints\n"); + + for_each_present_cpu(cpu) { + per_cpu(idle_prev_state, cpu) = 0; + } + + return 0; + + // unregister tracepoints on error +fail_cpu_idle_exit: + if (power_cpu_enabled[POWER_CPU_FREQ]) + GATOR_UNREGISTER_TRACE(cpu_frequency); +fail_cpu_frequency_exit: + pr_err("gator: power event tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n"); + + return -1; +} + +static void gator_trace_power_stop(void) +{ + int i; + + if (power_cpu_enabled[POWER_CPU_FREQ]) + GATOR_UNREGISTER_TRACE(cpu_frequency); + GATOR_UNREGISTER_TRACE(cpu_idle); + pr_debug("gator: unregistered power event tracepoints\n"); + + for (i = 0; i < POWER_TOTAL; i++) { + power_cpu_enabled[i] = 0; + } +} + +void gator_trace_power_init(void) +{ + int i; + for (i = 0; i < POWER_TOTAL; i++) { + power_cpu_enabled[i] = 0; + power_cpu_key[i] = gator_events_get_key(); + } +} +#else +static int gator_trace_power_create_files(struct super_block *sb, struct dentry *root) +{ + return 0; +} + +static void gator_trace_power_online(void) +{ +} + +static void gator_trace_power_offline(void) +{ +} + +static int gator_trace_power_start(void) +{ + return 0; +} + +static void gator_trace_power_stop(void) +{ +} + +void gator_trace_power_init(void) +{ +} +#endif diff --git a/drivers/gator/gator_trace_sched.c b/drivers/gator/gator_trace_sched.c new file mode 100644 index 00000000000000..332b3f6ba965d9 --- /dev/null +++ b/drivers/gator/gator_trace_sched.c @@ -0,0 +1,270 @@ +/** + * Copyright (C) ARM Limited 2010-2013. All rights reserved. + * + * 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 +#include "gator.h" + +#define TASK_MAP_ENTRIES 1024 /* must be power of 2 */ +#define TASK_MAX_COLLISIONS 2 + +enum { + STATE_WAIT_ON_OTHER = 0, + STATE_CONTENTION, + STATE_WAIT_ON_IO, + CPU_WAIT_TOTAL +}; + +static DEFINE_PER_CPU(uint64_t *, taskname_keys); +static DEFINE_PER_CPU(int, collecting); +static DEFINE_PER_CPU(bool, in_scheduler_context); + +// this array is never read as the cpu wait charts are derived counters +// the files are needed, nonetheless, to show that these counters are available +static ulong cpu_wait_enabled[CPU_WAIT_TOTAL]; +static ulong sched_cpu_key[CPU_WAIT_TOTAL]; + +static int sched_trace_create_files(struct super_block *sb, struct dentry *root) +{ + struct dentry *dir; + + // CPU Wait - Contention + dir = gatorfs_mkdir(sb, root, "Linux_cpu_wait_contention"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &cpu_wait_enabled[STATE_CONTENTION]); + gatorfs_create_ro_ulong(sb, dir, "key", &sched_cpu_key[STATE_CONTENTION]); + + // CPU Wait - I/O + dir = gatorfs_mkdir(sb, root, "Linux_cpu_wait_io"); + if (!dir) { + return -1; + } + gatorfs_create_ulong(sb, dir, "enabled", &cpu_wait_enabled[STATE_WAIT_ON_IO]); + gatorfs_create_ro_ulong(sb, dir, "key", &sched_cpu_key[STATE_WAIT_ON_IO]); + + return 0; +} + +void emit_pid_name(struct task_struct *task) +{ + bool found = false; + char taskcomm[TASK_COMM_LEN + 3]; + unsigned long x, cpu = get_physical_cpu(); + uint64_t *keys = &(per_cpu(taskname_keys, cpu)[(task->pid & 0xFF) * TASK_MAX_COLLISIONS]); + uint64_t value; + + value = gator_chksum_crc32(task->comm); + value = (value << 32) | (uint32_t)task->pid; + + // determine if the thread name was emitted already + for (x = 0; x < TASK_MAX_COLLISIONS; x++) { + if (keys[x] == value) { + found = true; + break; + } + } + + if (!found) { + // shift values, new value always in front + uint64_t oldv, newv = value; + for (x = 0; x < TASK_MAX_COLLISIONS; x++) { + oldv = keys[x]; + keys[x] = newv; + newv = oldv; + } + + // emit pid names, cannot use get_task_comm, as it's not exported on all kernel versions + if (strlcpy(taskcomm, task->comm, TASK_COMM_LEN) == TASK_COMM_LEN - 1) { + // append ellipses if task->comm has length of TASK_COMM_LEN - 1 + strcat(taskcomm, "..."); + } + + marshal_thread_name(task->pid, taskcomm); + } +} + +static void collect_counters(u64 time, struct task_struct *task) +{ + int *buffer, len, cpu = get_physical_cpu(); + long long *buffer64; + struct gator_interface *gi; + + if (marshal_event_header(time)) { + list_for_each_entry(gi, &gator_events, list) { + if (gi->read) { + len = gi->read(&buffer); + marshal_event(len, buffer); + } else if (gi->read64) { + len = gi->read64(&buffer64); + marshal_event64(len, buffer64); + } + if (gi->read_proc && task != NULL) { + len = gi->read_proc(&buffer64, task); + marshal_event64(len, buffer64); + } + } + // Only check after writing all counters so that time and corresponding counters appear in the same frame + buffer_check(cpu, BLOCK_COUNTER_BUF, time); + + // Commit buffers on timeout + if (gator_live_rate > 0 && time >= per_cpu(gator_buffer_commit_time, cpu)) { + static const int buftypes[] = { NAME_BUF, COUNTER_BUF, BLOCK_COUNTER_BUF, SCHED_TRACE_BUF }; + unsigned long flags; + int i; + + local_irq_save(flags); + for (i = 0; i < ARRAY_SIZE(buftypes); ++i) { + gator_commit_buffer(cpu, buftypes[i], time); + } + local_irq_restore(flags); + + // Try to preemptively flush the annotate buffer to reduce the chance of the buffer being full + if (on_primary_core() && spin_trylock(&annotate_lock)) { + gator_commit_buffer(0, ANNOTATE_BUF, time); + spin_unlock(&annotate_lock); + } + } + } +} + +// special case used during a suspend of the system +static void trace_sched_insert_idle(void) +{ + marshal_sched_trace_switch(0, 0, 0, 0); +} + +GATOR_DEFINE_PROBE(sched_process_fork, TP_PROTO(struct task_struct *parent, struct task_struct *child)) +{ + int cookie; + int cpu = get_physical_cpu(); + + cookie = get_exec_cookie(cpu, child); + emit_pid_name(child); + + marshal_sched_trace_start(child->tgid, child->pid, cookie); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(struct rq *rq, struct task_struct *prev, struct task_struct *next)) +#else +GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(struct task_struct *prev, struct task_struct *next)) +#endif +{ + int cookie; + int state; + int cpu = get_physical_cpu(); + + per_cpu(in_scheduler_context, cpu) = true; + + // do as much work as possible before disabling interrupts + cookie = get_exec_cookie(cpu, next); + emit_pid_name(next); + if (prev->state == TASK_RUNNING) { + state = STATE_CONTENTION; + } else if (prev->in_iowait) { + state = STATE_WAIT_ON_IO; + } else { + state = STATE_WAIT_ON_OTHER; + } + + per_cpu(collecting, cpu) = 1; + collect_counters(gator_get_time(), prev); + per_cpu(collecting, cpu) = 0; + + marshal_sched_trace_switch(next->tgid, next->pid, cookie, state); + + per_cpu(in_scheduler_context, cpu) = false; +} + +GATOR_DEFINE_PROBE(sched_process_free, TP_PROTO(struct task_struct *p)) +{ + marshal_sched_trace_exit(p->tgid, p->pid); +} + +static void do_nothing(void *info) +{ + // Intentionally do nothing + (void)info; +} + +static int register_scheduler_tracepoints(void) +{ + // register tracepoints + if (GATOR_REGISTER_TRACE(sched_process_fork)) + goto fail_sched_process_fork; + if (GATOR_REGISTER_TRACE(sched_switch)) + goto fail_sched_switch; + if (GATOR_REGISTER_TRACE(sched_process_free)) + goto fail_sched_process_free; + pr_debug("gator: registered tracepoints\n"); + + // Now that the scheduler tracepoint is registered, force a context switch + // on all cpus to capture what is currently running. + on_each_cpu(do_nothing, NULL, 0); + + return 0; + + // unregister tracepoints on error +fail_sched_process_free: + GATOR_UNREGISTER_TRACE(sched_switch); +fail_sched_switch: + GATOR_UNREGISTER_TRACE(sched_process_fork); +fail_sched_process_fork: + pr_err("gator: tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n"); + + return -1; +} + +int gator_trace_sched_start(void) +{ + int cpu, size; + + for_each_present_cpu(cpu) { + size = TASK_MAP_ENTRIES * TASK_MAX_COLLISIONS * sizeof(uint64_t); + per_cpu(taskname_keys, cpu) = (uint64_t *)kmalloc(size, GFP_KERNEL); + if (!per_cpu(taskname_keys, cpu)) + return -1; + memset(per_cpu(taskname_keys, cpu), 0, size); + } + + return register_scheduler_tracepoints(); +} + +void gator_trace_sched_offline(void) +{ + trace_sched_insert_idle(); +} + +static void unregister_scheduler_tracepoints(void) +{ + GATOR_UNREGISTER_TRACE(sched_process_fork); + GATOR_UNREGISTER_TRACE(sched_switch); + GATOR_UNREGISTER_TRACE(sched_process_free); + pr_debug("gator: unregistered tracepoints\n"); +} + +void gator_trace_sched_stop(void) +{ + int cpu; + unregister_scheduler_tracepoints(); + + for_each_present_cpu(cpu) { + kfree(per_cpu(taskname_keys, cpu)); + } +} + +void gator_trace_sched_init(void) +{ + int i; + for (i = 0; i < CPU_WAIT_TOTAL; i++) { + cpu_wait_enabled[i] = 0; + sched_cpu_key[i] = gator_events_get_key(); + } +} diff --git a/drivers/gator/mali/mali_mjollnir_profiling_gator_api.h b/drivers/gator/mali/mali_mjollnir_profiling_gator_api.h new file mode 100644 index 00000000000000..347a4fe404bc8b --- /dev/null +++ b/drivers/gator/mali/mali_mjollnir_profiling_gator_api.h @@ -0,0 +1,163 @@ +/** + * Copyright (C) ARM Limited 2013. All rights reserved. + * + * 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. + * + */ + +#ifndef __MALI_MJOLLNIR_PROFILING_GATOR_API_H__ +#define __MALI_MJOLLNIR_PROFILING_GATOR_API_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/* + * The number of processor cores. Update to suit your hardware implementation. + */ +#define MAX_NUM_FP_CORES (4) +#define MAX_NUM_VP_CORES (1) +#define MAX_NUM_L2_CACHE_CORES (1) + +enum counters +{ + /* Timeline activity */ + ACTIVITY_VP_0 = 0, + ACTIVITY_FP_0, + ACTIVITY_FP_1, + ACTIVITY_FP_2, + ACTIVITY_FP_3, + + /* L2 cache counters */ + COUNTER_L2_0_C0, + COUNTER_L2_0_C1, + + /* Vertex processor counters */ + COUNTER_VP_0_C0, + COUNTER_VP_0_C1, + + /* Fragment processor counters */ + COUNTER_FP_0_C0, + COUNTER_FP_0_C1, + COUNTER_FP_1_C0, + COUNTER_FP_1_C1, + COUNTER_FP_2_C0, + COUNTER_FP_2_C1, + COUNTER_FP_3_C0, + COUNTER_FP_3_C1, + + /* EGL Software Counters */ + COUNTER_EGL_BLIT_TIME, + + /* GLES Software Counters */ + COUNTER_GLES_DRAW_ELEMENTS_CALLS, + COUNTER_GLES_DRAW_ELEMENTS_NUM_INDICES, + COUNTER_GLES_DRAW_ELEMENTS_NUM_TRANSFORMED, + COUNTER_GLES_DRAW_ARRAYS_CALLS, + COUNTER_GLES_DRAW_ARRAYS_NUM_TRANSFORMED, + COUNTER_GLES_DRAW_POINTS, + COUNTER_GLES_DRAW_LINES, + COUNTER_GLES_DRAW_LINE_LOOP, + COUNTER_GLES_DRAW_LINE_STRIP, + COUNTER_GLES_DRAW_TRIANGLES, + COUNTER_GLES_DRAW_TRIANGLE_STRIP, + COUNTER_GLES_DRAW_TRIANGLE_FAN, + COUNTER_GLES_NON_VBO_DATA_COPY_TIME, + COUNTER_GLES_UNIFORM_BYTES_COPIED_TO_MALI, + COUNTER_GLES_UPLOAD_TEXTURE_TIME, + COUNTER_GLES_UPLOAD_VBO_TIME, + COUNTER_GLES_NUM_FLUSHES, + COUNTER_GLES_NUM_VSHADERS_GENERATED, + COUNTER_GLES_NUM_FSHADERS_GENERATED, + COUNTER_GLES_VSHADER_GEN_TIME, + COUNTER_GLES_FSHADER_GEN_TIME, + COUNTER_GLES_INPUT_TRIANGLES, + COUNTER_GLES_VXCACHE_HIT, + COUNTER_GLES_VXCACHE_MISS, + COUNTER_GLES_VXCACHE_COLLISION, + COUNTER_GLES_CULLED_TRIANGLES, + COUNTER_GLES_CULLED_LINES, + COUNTER_GLES_BACKFACE_TRIANGLES, + COUNTER_GLES_GBCLIP_TRIANGLES, + COUNTER_GLES_GBCLIP_LINES, + COUNTER_GLES_TRIANGLES_DRAWN, + COUNTER_GLES_DRAWCALL_TIME, + COUNTER_GLES_TRIANGLES_COUNT, + COUNTER_GLES_INDEPENDENT_TRIANGLES_COUNT, + COUNTER_GLES_STRIP_TRIANGLES_COUNT, + COUNTER_GLES_FAN_TRIANGLES_COUNT, + COUNTER_GLES_LINES_COUNT, + COUNTER_GLES_INDEPENDENT_LINES_COUNT, + COUNTER_GLES_STRIP_LINES_COUNT, + COUNTER_GLES_LOOP_LINES_COUNT, + + COUNTER_FILMSTRIP, + COUNTER_FREQUENCY, + COUNTER_VOLTAGE, + + NUMBER_OF_EVENTS +}; + +#define FIRST_ACTIVITY_EVENT ACTIVITY_VP_0 +#define LAST_ACTIVITY_EVENT ACTIVITY_FP_3 + +#define FIRST_HW_COUNTER COUNTER_L2_0_C0 +#define LAST_HW_COUNTER COUNTER_FP_3_C1 + +#define FIRST_SW_COUNTER COUNTER_EGL_BLIT_TIME +#define LAST_SW_COUNTER COUNTER_GLES_LOOP_LINES_COUNT + +/* Signifies that the system is able to report voltage and frequency numbers. */ +#define DVFS_REPORTED_BY_DDK 1 + +/** + * Structure to pass performance counter data of a Mali core + */ +typedef struct _mali_profiling_core_counters +{ + u32 source0; + u32 value0; + u32 source1; + u32 value1; +} _mali_profiling_core_counters; + +/* + * For compatibility with utgard. + */ +typedef struct _mali_profiling_l2_counter_values +{ + struct _mali_profiling_core_counters cores[MAX_NUM_L2_CACHE_CORES]; +} _mali_profiling_l2_counter_values; + +typedef struct _mali_profiling_mali_version +{ + u32 mali_product_id; + u32 mali_version_major; + u32 mali_version_minor; + u32 num_of_l2_cores; + u32 num_of_fp_cores; + u32 num_of_vp_cores; +} _mali_profiling_mali_version; + +extern void _mali_profiling_get_mali_version(struct _mali_profiling_mali_version *values); +extern u32 _mali_profiling_get_l2_counters(_mali_profiling_l2_counter_values *values); + +/* + * List of possible actions allowing DDK to be controlled by Streamline. + * The following numbers are used by DDK to control the frame buffer dumping. + */ +#define FBDUMP_CONTROL_ENABLE (1) +#define FBDUMP_CONTROL_RATE (2) +#define SW_COUNTER_ENABLE (3) +#define FBDUMP_CONTROL_RESIZE_FACTOR (4) + + +#ifdef __cplusplus +} +#endif + +#endif /* __MALI_MJOLLNIR_PROFILING_GATOR_API_H__ */ diff --git a/drivers/gator/mali/mali_utgard_profiling_gator_api.h b/drivers/gator/mali/mali_utgard_profiling_gator_api.h new file mode 100644 index 00000000000000..559647a76d2909 --- /dev/null +++ b/drivers/gator/mali/mali_utgard_profiling_gator_api.h @@ -0,0 +1,201 @@ +/** + * Copyright (C) ARM Limited 2013. All rights reserved. + * + * 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. + * + */ + +#ifndef __MALI_UTGARD_PROFILING_GATOR_API_H__ +#define __MALI_UTGARD_PROFILING_GATOR_API_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define MALI_PROFILING_API_VERSION 4 + +#define MAX_NUM_L2_CACHE_CORES 3 +#define MAX_NUM_FP_CORES 8 +#define MAX_NUM_VP_CORES 1 + +/** The list of events supported by the Mali DDK. */ +typedef enum +{ + /* Vertex processor activity */ + ACTIVITY_VP_0 = 0, + + /* Fragment processor activity */ + ACTIVITY_FP_0, /* 1 */ + ACTIVITY_FP_1, + ACTIVITY_FP_2, + ACTIVITY_FP_3, + ACTIVITY_FP_4, + ACTIVITY_FP_5, + ACTIVITY_FP_6, + ACTIVITY_FP_7, + + /* L2 cache counters */ + COUNTER_L2_0_C0, + COUNTER_L2_0_C1, + COUNTER_L2_1_C0, + COUNTER_L2_1_C1, + COUNTER_L2_2_C0, + COUNTER_L2_2_C1, + + /* Vertex processor counters */ + COUNTER_VP_0_C0, /*15*/ + COUNTER_VP_0_C1, + + /* Fragment processor counters */ + COUNTER_FP_0_C0, + COUNTER_FP_0_C1, + COUNTER_FP_1_C0, + COUNTER_FP_1_C1, + COUNTER_FP_2_C0, + COUNTER_FP_2_C1, + COUNTER_FP_3_C0, + COUNTER_FP_3_C1, + COUNTER_FP_4_C0, + COUNTER_FP_4_C1, + COUNTER_FP_5_C0, + COUNTER_FP_5_C1, + COUNTER_FP_6_C0, + COUNTER_FP_6_C1, + COUNTER_FP_7_C0, + COUNTER_FP_7_C1, /* 32 */ + + /* + * If more hardware counters are added, the _mali_osk_hw_counter_table + * below should also be updated. + */ + + /* EGL software counters */ + COUNTER_EGL_BLIT_TIME, + + /* GLES software counters */ + COUNTER_GLES_DRAW_ELEMENTS_CALLS, + COUNTER_GLES_DRAW_ELEMENTS_NUM_INDICES, + COUNTER_GLES_DRAW_ELEMENTS_NUM_TRANSFORMED, + COUNTER_GLES_DRAW_ARRAYS_CALLS, + COUNTER_GLES_DRAW_ARRAYS_NUM_TRANSFORMED, + COUNTER_GLES_DRAW_POINTS, + COUNTER_GLES_DRAW_LINES, + COUNTER_GLES_DRAW_LINE_LOOP, + COUNTER_GLES_DRAW_LINE_STRIP, + COUNTER_GLES_DRAW_TRIANGLES, + COUNTER_GLES_DRAW_TRIANGLE_STRIP, + COUNTER_GLES_DRAW_TRIANGLE_FAN, + COUNTER_GLES_NON_VBO_DATA_COPY_TIME, + COUNTER_GLES_UNIFORM_BYTES_COPIED_TO_MALI, + COUNTER_GLES_UPLOAD_TEXTURE_TIME, + COUNTER_GLES_UPLOAD_VBO_TIME, + COUNTER_GLES_NUM_FLUSHES, + COUNTER_GLES_NUM_VSHADERS_GENERATED, + COUNTER_GLES_NUM_FSHADERS_GENERATED, + COUNTER_GLES_VSHADER_GEN_TIME, + COUNTER_GLES_FSHADER_GEN_TIME, + COUNTER_GLES_INPUT_TRIANGLES, + COUNTER_GLES_VXCACHE_HIT, + COUNTER_GLES_VXCACHE_MISS, + COUNTER_GLES_VXCACHE_COLLISION, + COUNTER_GLES_CULLED_TRIANGLES, + COUNTER_GLES_CULLED_LINES, + COUNTER_GLES_BACKFACE_TRIANGLES, + COUNTER_GLES_GBCLIP_TRIANGLES, + COUNTER_GLES_GBCLIP_LINES, + COUNTER_GLES_TRIANGLES_DRAWN, + COUNTER_GLES_DRAWCALL_TIME, + COUNTER_GLES_TRIANGLES_COUNT, + COUNTER_GLES_INDEPENDENT_TRIANGLES_COUNT, + COUNTER_GLES_STRIP_TRIANGLES_COUNT, + COUNTER_GLES_FAN_TRIANGLES_COUNT, + COUNTER_GLES_LINES_COUNT, + COUNTER_GLES_INDEPENDENT_LINES_COUNT, + COUNTER_GLES_STRIP_LINES_COUNT, + COUNTER_GLES_LOOP_LINES_COUNT, + + /* Framebuffer capture pseudo-counter */ + COUNTER_FILMSTRIP, + + NUMBER_OF_EVENTS +} _mali_osk_counter_id; + +#define FIRST_ACTIVITY_EVENT ACTIVITY_VP_0 +#define LAST_ACTIVITY_EVENT ACTIVITY_FP_7 + +#define FIRST_HW_COUNTER COUNTER_L2_0_C0 +#define LAST_HW_COUNTER COUNTER_FP_7_C1 + +#define FIRST_SW_COUNTER COUNTER_EGL_BLIT_TIME +#define LAST_SW_COUNTER COUNTER_GLES_LOOP_LINES_COUNT + +#define FIRST_SPECIAL_COUNTER COUNTER_FILMSTRIP +#define LAST_SPECIAL_COUNTER COUNTER_FILMSTRIP + +/** + * Structure to pass performance counter data of a Mali core + */ +typedef struct _mali_profiling_core_counters +{ + u32 source0; + u32 value0; + u32 source1; + u32 value1; +} _mali_profiling_core_counters; + +/** + * Structure to pass performance counter data of Mali L2 cache cores + */ +typedef struct _mali_profiling_l2_counter_values +{ + struct _mali_profiling_core_counters cores[MAX_NUM_L2_CACHE_CORES]; +} _mali_profiling_l2_counter_values; + +/** + * Structure to pass data defining Mali instance in use: + * + * mali_product_id - Mali product id + * mali_version_major - Mali version major number + * mali_version_minor - Mali version minor number + * num_of_l2_cores - number of L2 cache cores + * num_of_fp_cores - number of fragment processor cores + * num_of_vp_cores - number of vertex processor cores + */ +typedef struct _mali_profiling_mali_version +{ + u32 mali_product_id; + u32 mali_version_major; + u32 mali_version_minor; + u32 num_of_l2_cores; + u32 num_of_fp_cores; + u32 num_of_vp_cores; +} _mali_profiling_mali_version; + +/* + * List of possible actions to be controlled by Streamline. + * The following numbers are used by gator to control the frame buffer dumping and s/w counter reporting. + * We cannot use the enums in mali_uk_types.h because they are unknown inside gator. + */ +#define FBDUMP_CONTROL_ENABLE (1) +#define FBDUMP_CONTROL_RATE (2) +#define SW_COUNTER_ENABLE (3) +#define FBDUMP_CONTROL_RESIZE_FACTOR (4) + +void _mali_profiling_control(u32 action, u32 value); + +u32 _mali_profiling_get_l2_counters(_mali_profiling_l2_counter_values *values); + +int _mali_profiling_set_event(u32 counter_id, s32 event_id); + +u32 _mali_profiling_get_api_version(void); + +void _mali_profiling_get_mali_version(struct _mali_profiling_mali_version *values); + +#ifdef __cplusplus +} +#endif + +#endif /* __MALI_UTGARD_PROFILING_GATOR_API_H__ */ diff --git a/drivers/gator/mali_t6xx.mk b/drivers/gator/mali_t6xx.mk new file mode 100644 index 00000000000000..1a98c1c6a73fe3 --- /dev/null +++ b/drivers/gator/mali_t6xx.mk @@ -0,0 +1,26 @@ +# Defines for Mali-T6xx driver +EXTRA_CFLAGS += -DMALI_USE_UMP=1 \ + -DMALI_LICENSE_IS_GPL=1 \ + -DMALI_BASE_TRACK_MEMLEAK=0 \ + -DMALI_DEBUG=0 \ + -DMALI_ERROR_INJECT_ON=0 \ + -DMALI_CUSTOMER_RELEASE=1 \ + -DMALI_UNIT_TEST=0 \ + -DMALI_BACKEND_KERNEL=1 \ + -DMALI_NO_MALI=0 + +DDK_DIR ?= . +KBASE_DIR = $(DDK_DIR)/drivers/gpu/arm/t6xx/kbase +OSK_DIR = $(DDK_DIR)/drivers/gpu/arm/t6xx/kbase/osk +UMP_DIR = $(DDK_DIR)/include/linux + +# Include directories in the DDK +EXTRA_CFLAGS += -I$(KBASE_DIR)/ \ + -I$(KBASE_DIR)/.. \ + -I$(OSK_DIR)/.. \ + -I$(UMP_DIR)/.. \ + -I$(DDK_DIR)/include \ + -I$(KBASE_DIR)/osk/src/linux/include \ + -I$(KBASE_DIR)/platform_dummy \ + -I$(KBASE_DIR)/src + diff --git a/drivers/gpio/gpio-bcm-kona.c b/drivers/gpio/gpio-bcm-kona.c index f32357e2d78d89..5622cfb8325ae4 100644 --- a/drivers/gpio/gpio-bcm-kona.c +++ b/drivers/gpio/gpio-bcm-kona.c @@ -28,6 +28,10 @@ #define GPIO_BANK(gpio) ((gpio) >> 5) #define GPIO_BIT(gpio) ((gpio) & (GPIO_PER_BANK - 1)) +/* There is a GPIO control register for each GPIO */ +#define GPIO_CONTROL(gpio) (0x00000100 + ((gpio) << 2)) + +/* The remaining registers are per GPIO bank */ #define GPIO_OUT_STATUS(bank) (0x00000000 + ((bank) << 2)) #define GPIO_IN_STATUS(bank) (0x00000020 + ((bank) << 2)) #define GPIO_OUT_SET(bank) (0x00000040 + ((bank) << 2)) @@ -35,7 +39,6 @@ #define GPIO_INT_STATUS(bank) (0x00000080 + ((bank) << 2)) #define GPIO_INT_MASK(bank) (0x000000a0 + ((bank) << 2)) #define GPIO_INT_MSKCLR(bank) (0x000000c0 + ((bank) << 2)) -#define GPIO_CONTROL(bank) (0x00000100 + ((bank) << 2)) #define GPIO_PWD_STATUS(bank) (0x00000500 + ((bank) << 2)) #define GPIO_GPPWR_OFFSET 0x00000520 @@ -80,22 +83,43 @@ static inline struct bcm_kona_gpio *to_kona_gpio(struct gpio_chip *chip) return container_of(chip, struct bcm_kona_gpio, gpio_chip); } -static void bcm_kona_gpio_set_lockcode_bank(void __iomem *reg_base, - int bank_id, int lockcode) +static inline void bcm_kona_gpio_write_lock_regs(void __iomem *reg_base, + int bank_id, u32 lockcode) { writel(BCM_GPIO_PASSWD, reg_base + GPIO_GPPWR_OFFSET); writel(lockcode, reg_base + GPIO_PWD_STATUS(bank_id)); } -static inline void bcm_kona_gpio_lock_bank(void __iomem *reg_base, int bank_id) +static void bcm_kona_gpio_lock_gpio(struct bcm_kona_gpio *kona_gpio, + unsigned gpio) { - bcm_kona_gpio_set_lockcode_bank(reg_base, bank_id, LOCK_CODE); + u32 val; + unsigned long flags; + int bank_id = GPIO_BANK(gpio); + + spin_lock_irqsave(&kona_gpio->lock, flags); + + val = readl(kona_gpio->reg_base + GPIO_PWD_STATUS(bank_id)); + val |= BIT(gpio); + bcm_kona_gpio_write_lock_regs(kona_gpio->reg_base, bank_id, val); + + spin_unlock_irqrestore(&kona_gpio->lock, flags); } -static inline void bcm_kona_gpio_unlock_bank(void __iomem *reg_base, - int bank_id) +static void bcm_kona_gpio_unlock_gpio(struct bcm_kona_gpio *kona_gpio, + unsigned gpio) { - bcm_kona_gpio_set_lockcode_bank(reg_base, bank_id, UNLOCK_CODE); + u32 val; + unsigned long flags; + int bank_id = GPIO_BANK(gpio); + + spin_lock_irqsave(&kona_gpio->lock, flags); + + val = readl(kona_gpio->reg_base + GPIO_PWD_STATUS(bank_id)); + val &= ~BIT(gpio); + bcm_kona_gpio_write_lock_regs(kona_gpio->reg_base, bank_id, val); + + spin_unlock_irqrestore(&kona_gpio->lock, flags); } static void bcm_kona_gpio_set(struct gpio_chip *chip, unsigned gpio, int value) @@ -110,7 +134,6 @@ static void bcm_kona_gpio_set(struct gpio_chip *chip, unsigned gpio, int value) kona_gpio = to_kona_gpio(chip); reg_base = kona_gpio->reg_base; spin_lock_irqsave(&kona_gpio->lock, flags); - bcm_kona_gpio_unlock_bank(reg_base, bank_id); /* determine the GPIO pin direction */ val = readl(reg_base + GPIO_CONTROL(gpio)); @@ -127,7 +150,6 @@ static void bcm_kona_gpio_set(struct gpio_chip *chip, unsigned gpio, int value) writel(val, reg_base + reg_offset); out: - bcm_kona_gpio_lock_bank(reg_base, bank_id); spin_unlock_irqrestore(&kona_gpio->lock, flags); } @@ -143,7 +165,6 @@ static int bcm_kona_gpio_get(struct gpio_chip *chip, unsigned gpio) kona_gpio = to_kona_gpio(chip); reg_base = kona_gpio->reg_base; spin_lock_irqsave(&kona_gpio->lock, flags); - bcm_kona_gpio_unlock_bank(reg_base, bank_id); /* determine the GPIO pin direction */ val = readl(reg_base + GPIO_CONTROL(gpio)); @@ -154,32 +175,43 @@ static int bcm_kona_gpio_get(struct gpio_chip *chip, unsigned gpio) GPIO_IN_STATUS(bank_id) : GPIO_OUT_STATUS(bank_id); val = readl(reg_base + reg_offset); - bcm_kona_gpio_lock_bank(reg_base, bank_id); spin_unlock_irqrestore(&kona_gpio->lock, flags); /* return the specified bit status */ return !!(val & BIT(bit)); } +static int bcm_kona_gpio_request(struct gpio_chip *chip, unsigned gpio) +{ + struct bcm_kona_gpio *kona_gpio = to_kona_gpio(chip); + + bcm_kona_gpio_unlock_gpio(kona_gpio, gpio); + return 0; +} + +static void bcm_kona_gpio_free(struct gpio_chip *chip, unsigned gpio) +{ + struct bcm_kona_gpio *kona_gpio = to_kona_gpio(chip); + + bcm_kona_gpio_lock_gpio(kona_gpio, gpio); +} + static int bcm_kona_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) { struct bcm_kona_gpio *kona_gpio; void __iomem *reg_base; u32 val; unsigned long flags; - int bank_id = GPIO_BANK(gpio); kona_gpio = to_kona_gpio(chip); reg_base = kona_gpio->reg_base; spin_lock_irqsave(&kona_gpio->lock, flags); - bcm_kona_gpio_unlock_bank(reg_base, bank_id); val = readl(reg_base + GPIO_CONTROL(gpio)); val &= ~GPIO_GPCTR0_IOTR_MASK; val |= GPIO_GPCTR0_IOTR_CMD_INPUT; writel(val, reg_base + GPIO_CONTROL(gpio)); - bcm_kona_gpio_lock_bank(reg_base, bank_id); spin_unlock_irqrestore(&kona_gpio->lock, flags); return 0; @@ -198,7 +230,6 @@ static int bcm_kona_gpio_direction_output(struct gpio_chip *chip, kona_gpio = to_kona_gpio(chip); reg_base = kona_gpio->reg_base; spin_lock_irqsave(&kona_gpio->lock, flags); - bcm_kona_gpio_unlock_bank(reg_base, bank_id); val = readl(reg_base + GPIO_CONTROL(gpio)); val &= ~GPIO_GPCTR0_IOTR_MASK; @@ -210,7 +241,6 @@ static int bcm_kona_gpio_direction_output(struct gpio_chip *chip, val |= BIT(bit); writel(val, reg_base + reg_offset); - bcm_kona_gpio_lock_bank(reg_base, bank_id); spin_unlock_irqrestore(&kona_gpio->lock, flags); return 0; @@ -233,7 +263,6 @@ static int bcm_kona_gpio_set_debounce(struct gpio_chip *chip, unsigned gpio, void __iomem *reg_base; u32 val, res; unsigned long flags; - int bank_id = GPIO_BANK(gpio); kona_gpio = to_kona_gpio(chip); reg_base = kona_gpio->reg_base; @@ -257,7 +286,6 @@ static int bcm_kona_gpio_set_debounce(struct gpio_chip *chip, unsigned gpio, /* spin lock for read-modify-write of the GPIO register */ spin_lock_irqsave(&kona_gpio->lock, flags); - bcm_kona_gpio_unlock_bank(reg_base, bank_id); val = readl(reg_base + GPIO_CONTROL(gpio)); val &= ~GPIO_GPCTR0_DBR_MASK; @@ -272,7 +300,6 @@ static int bcm_kona_gpio_set_debounce(struct gpio_chip *chip, unsigned gpio, writel(val, reg_base + GPIO_CONTROL(gpio)); - bcm_kona_gpio_lock_bank(reg_base, bank_id); spin_unlock_irqrestore(&kona_gpio->lock, flags); return 0; @@ -281,6 +308,8 @@ static int bcm_kona_gpio_set_debounce(struct gpio_chip *chip, unsigned gpio, static struct gpio_chip template_chip = { .label = "bcm-kona-gpio", .owner = THIS_MODULE, + .request = bcm_kona_gpio_request, + .free = bcm_kona_gpio_free, .direction_input = bcm_kona_gpio_direction_input, .get = bcm_kona_gpio_get, .direction_output = bcm_kona_gpio_direction_output, @@ -294,7 +323,7 @@ static void bcm_kona_gpio_irq_ack(struct irq_data *d) { struct bcm_kona_gpio *kona_gpio; void __iomem *reg_base; - int gpio = d->hwirq; + unsigned gpio = d->hwirq; int bank_id = GPIO_BANK(gpio); int bit = GPIO_BIT(gpio); u32 val; @@ -303,13 +332,11 @@ static void bcm_kona_gpio_irq_ack(struct irq_data *d) kona_gpio = irq_data_get_irq_chip_data(d); reg_base = kona_gpio->reg_base; spin_lock_irqsave(&kona_gpio->lock, flags); - bcm_kona_gpio_unlock_bank(reg_base, bank_id); val = readl(reg_base + GPIO_INT_STATUS(bank_id)); val |= BIT(bit); writel(val, reg_base + GPIO_INT_STATUS(bank_id)); - bcm_kona_gpio_lock_bank(reg_base, bank_id); spin_unlock_irqrestore(&kona_gpio->lock, flags); } @@ -317,7 +344,7 @@ static void bcm_kona_gpio_irq_mask(struct irq_data *d) { struct bcm_kona_gpio *kona_gpio; void __iomem *reg_base; - int gpio = d->hwirq; + unsigned gpio = d->hwirq; int bank_id = GPIO_BANK(gpio); int bit = GPIO_BIT(gpio); u32 val; @@ -326,13 +353,11 @@ static void bcm_kona_gpio_irq_mask(struct irq_data *d) kona_gpio = irq_data_get_irq_chip_data(d); reg_base = kona_gpio->reg_base; spin_lock_irqsave(&kona_gpio->lock, flags); - bcm_kona_gpio_unlock_bank(reg_base, bank_id); val = readl(reg_base + GPIO_INT_MASK(bank_id)); val |= BIT(bit); writel(val, reg_base + GPIO_INT_MASK(bank_id)); - bcm_kona_gpio_lock_bank(reg_base, bank_id); spin_unlock_irqrestore(&kona_gpio->lock, flags); } @@ -340,7 +365,7 @@ static void bcm_kona_gpio_irq_unmask(struct irq_data *d) { struct bcm_kona_gpio *kona_gpio; void __iomem *reg_base; - int gpio = d->hwirq; + unsigned gpio = d->hwirq; int bank_id = GPIO_BANK(gpio); int bit = GPIO_BIT(gpio); u32 val; @@ -349,13 +374,11 @@ static void bcm_kona_gpio_irq_unmask(struct irq_data *d) kona_gpio = irq_data_get_irq_chip_data(d); reg_base = kona_gpio->reg_base; spin_lock_irqsave(&kona_gpio->lock, flags); - bcm_kona_gpio_unlock_bank(reg_base, bank_id); val = readl(reg_base + GPIO_INT_MSKCLR(bank_id)); val |= BIT(bit); writel(val, reg_base + GPIO_INT_MSKCLR(bank_id)); - bcm_kona_gpio_lock_bank(reg_base, bank_id); spin_unlock_irqrestore(&kona_gpio->lock, flags); } @@ -363,11 +386,10 @@ static int bcm_kona_gpio_irq_set_type(struct irq_data *d, unsigned int type) { struct bcm_kona_gpio *kona_gpio; void __iomem *reg_base; - int gpio = d->hwirq; + unsigned gpio = d->hwirq; u32 lvl_type; u32 val; unsigned long flags; - int bank_id = GPIO_BANK(gpio); kona_gpio = irq_data_get_irq_chip_data(d); reg_base = kona_gpio->reg_base; @@ -394,14 +416,12 @@ static int bcm_kona_gpio_irq_set_type(struct irq_data *d, unsigned int type) } spin_lock_irqsave(&kona_gpio->lock, flags); - bcm_kona_gpio_unlock_bank(reg_base, bank_id); val = readl(reg_base + GPIO_CONTROL(gpio)); val &= ~GPIO_GPCTR0_ITR_MASK; val |= lvl_type << GPIO_GPCTR0_ITR_SHIFT; writel(val, reg_base + GPIO_CONTROL(gpio)); - bcm_kona_gpio_lock_bank(reg_base, bank_id); spin_unlock_irqrestore(&kona_gpio->lock, flags); return 0; @@ -424,7 +444,6 @@ static void bcm_kona_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) */ reg_base = bank->kona_gpio->reg_base; bank_id = bank->id; - bcm_kona_gpio_unlock_bank(reg_base, bank_id); while ((sta = readl(reg_base + GPIO_INT_STATUS(bank_id)) & (~(readl(reg_base + GPIO_INT_MASK(bank_id)))))) { @@ -444,8 +463,6 @@ static void bcm_kona_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) } } - bcm_kona_gpio_lock_bank(reg_base, bank_id); - chained_irq_exit(chip, desc); } @@ -531,10 +548,12 @@ static void bcm_kona_gpio_reset(struct bcm_kona_gpio *kona_gpio) reg_base = kona_gpio->reg_base; /* disable interrupts and clear status */ for (i = 0; i < kona_gpio->num_bank; i++) { - bcm_kona_gpio_unlock_bank(reg_base, i); + /* Unlock the entire bank first */ + bcm_kona_gpio_write_lock_regs(kona_gpio, i, UNLOCK_CODE); writel(0xffffffff, reg_base + GPIO_INT_MASK(i)); writel(0xffffffff, reg_base + GPIO_INT_STATUS(i)); - bcm_kona_gpio_lock_bank(reg_base, i); + /* Now re-lock the bank */ + bcm_kona_gpio_write_lock_regs(kona_gpio, i, LOCK_CODE); } } diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c index b4d42112d02d5c..14febe97738e62 100644 --- a/drivers/gpio/gpio-pl061.c +++ b/drivers/gpio/gpio-pl061.c @@ -256,6 +256,19 @@ static const struct irq_domain_ops pl061_domain_ops = { .xlate = irq_domain_xlate_twocell, }; +/* Parse gpio base from DT */ +static int pl061_parse_gpio_base(struct device *dev) +{ + struct device_node *np = dev->of_node; + int ret; + + if (of_property_read_u32(np, "linux,gpio-base", &ret)) + return -ENOENT; + if (ret >= 0) + return ret; + return -EINVAL; +} + static int pl061_probe(struct amba_device *adev, const struct amba_id *id) { struct device *dev = &adev->dev; @@ -273,7 +286,7 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id) if (irq_base <= 0) return -ENODEV; } else { - chip->gc.base = -1; + chip->gc.base = pl061_parse_gpio_base(dev); irq_base = 0; } @@ -288,8 +301,11 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id) spin_lock_init(&chip->lock); - chip->gc.request = pl061_gpio_request; - chip->gc.free = pl061_gpio_free; + /* Hook the request()/free() for pinctrl operation */ + if (of_get_property(dev->of_node, "gpio-ranges", NULL)) { + chip->gc.request = pl061_gpio_request; + chip->gc.free = pl061_gpio_free; + } chip->gc.direction_input = pl061_direction_input; chip->gc.direction_output = pl061_direction_output; chip->gc.get = pl061_get_value; diff --git a/drivers/gpio/gpio-samsung.c b/drivers/gpio/gpio-samsung.c index a85e00bf9834c0..81e6c83d5de7b1 100644 --- a/drivers/gpio/gpio-samsung.c +++ b/drivers/gpio/gpio-samsung.c @@ -50,10 +50,10 @@ int samsung_gpio_setpull_updown(struct samsung_gpio_chip *chip, int shift = off * 2; u32 pup; - pup = __raw_readl(reg); + pup = readl_relaxed(reg); pup &= ~(3 << shift); pup |= pull << shift; - __raw_writel(pup, reg); + writel_relaxed(pup, reg); return 0; } @@ -63,7 +63,7 @@ samsung_gpio_pull_t samsung_gpio_getpull_updown(struct samsung_gpio_chip *chip, { void __iomem *reg = chip->base + 0x08; int shift = off * 2; - u32 pup = __raw_readl(reg); + u32 pup = readl_relaxed(reg); pup >>= shift; pup &= 0x3; @@ -116,7 +116,7 @@ static int s3c24xx_gpio_setpull_1(struct samsung_gpio_chip *chip, samsung_gpio_pull_t updown) { void __iomem *reg = chip->base + 0x08; - u32 pup = __raw_readl(reg); + u32 pup = readl_relaxed(reg); if (pull == updown) pup &= ~(1 << off); @@ -125,7 +125,7 @@ static int s3c24xx_gpio_setpull_1(struct samsung_gpio_chip *chip, else return -EINVAL; - __raw_writel(pup, reg); + writel_relaxed(pup, reg); return 0; } @@ -134,7 +134,7 @@ static samsung_gpio_pull_t s3c24xx_gpio_getpull_1(struct samsung_gpio_chip *chip samsung_gpio_pull_t updown) { void __iomem *reg = chip->base + 0x08; - u32 pup = __raw_readl(reg); + u32 pup = readl_relaxed(reg); pup &= (1 << off); return pup ? S3C_GPIO_PULL_NONE : updown; @@ -193,10 +193,10 @@ static int samsung_gpio_setcfg_2bit(struct samsung_gpio_chip *chip, cfg <<= shift; } - con = __raw_readl(reg); + con = readl_relaxed(reg); con &= ~(0x3 << shift); con |= cfg; - __raw_writel(con, reg); + writel_relaxed(con, reg); return 0; } @@ -216,7 +216,7 @@ static unsigned int samsung_gpio_getcfg_2bit(struct samsung_gpio_chip *chip, { u32 con; - con = __raw_readl(chip->base); + con = readl_relaxed(chip->base); con >>= off * 2; con &= 3; @@ -256,10 +256,10 @@ static int samsung_gpio_setcfg_4bit(struct samsung_gpio_chip *chip, cfg <<= shift; } - con = __raw_readl(reg); + con = readl_relaxed(reg); con &= ~(0xf << shift); con |= cfg; - __raw_writel(con, reg); + writel_relaxed(con, reg); return 0; } @@ -286,7 +286,7 @@ static unsigned samsung_gpio_getcfg_4bit(struct samsung_gpio_chip *chip, if (off < 8 && chip->chip.ngpio > 8) reg -= 4; - con = __raw_readl(reg); + con = readl_relaxed(reg); con >>= shift; con &= 0xf; @@ -324,10 +324,10 @@ static int s3c24xx_gpio_setcfg_abank(struct samsung_gpio_chip *chip, cfg <<= shift; } - con = __raw_readl(reg); + con = readl_relaxed(reg); con &= ~(0x1 << shift); con |= cfg; - __raw_writel(con, reg); + writel_relaxed(con, reg); return 0; } @@ -349,7 +349,7 @@ static unsigned s3c24xx_gpio_getcfg_abank(struct samsung_gpio_chip *chip, { u32 con; - con = __raw_readl(chip->base); + con = readl_relaxed(chip->base); con >>= off; con &= 1; con++; @@ -389,10 +389,10 @@ static int s5p64x0_gpio_setcfg_rbank(struct samsung_gpio_chip *chip, cfg <<= shift; } - con = __raw_readl(reg); + con = readl_relaxed(reg); con &= ~(0xf << shift); con |= cfg; - __raw_writel(con, reg); + writel_relaxed(con, reg); return 0; } @@ -490,10 +490,10 @@ static int samsung_gpiolib_2bit_input(struct gpio_chip *chip, unsigned offset) samsung_gpio_lock(ourchip, flags); - con = __raw_readl(base + 0x00); + con = readl_relaxed(base + 0x00); con &= ~(3 << (offset * 2)); - __raw_writel(con, base + 0x00); + writel_relaxed(con, base + 0x00); samsung_gpio_unlock(ourchip, flags); return 0; @@ -510,18 +510,18 @@ static int samsung_gpiolib_2bit_output(struct gpio_chip *chip, samsung_gpio_lock(ourchip, flags); - dat = __raw_readl(base + 0x04); + dat = readl_relaxed(base + 0x04); dat &= ~(1 << offset); if (value) dat |= 1 << offset; - __raw_writel(dat, base + 0x04); + writel_relaxed(dat, base + 0x04); - con = __raw_readl(base + 0x00); + con = readl_relaxed(base + 0x00); con &= ~(3 << (offset * 2)); con |= 1 << (offset * 2); - __raw_writel(con, base + 0x00); - __raw_writel(dat, base + 0x04); + writel_relaxed(con, base + 0x00); + writel_relaxed(dat, base + 0x04); samsung_gpio_unlock(ourchip, flags); return 0; @@ -550,12 +550,12 @@ static int samsung_gpiolib_4bit_input(struct gpio_chip *chip, void __iomem *base = ourchip->base; unsigned long con; - con = __raw_readl(base + GPIOCON_OFF); + con = readl_relaxed(base + GPIOCON_OFF); if (ourchip->bitmap_gpio_int & BIT(offset)) con |= 0xf << con_4bit_shift(offset); else con &= ~(0xf << con_4bit_shift(offset)); - __raw_writel(con, base + GPIOCON_OFF); + writel_relaxed(con, base + GPIOCON_OFF); pr_debug("%s: %p: CON now %08lx\n", __func__, base, con); @@ -570,20 +570,20 @@ static int samsung_gpiolib_4bit_output(struct gpio_chip *chip, unsigned long con; unsigned long dat; - con = __raw_readl(base + GPIOCON_OFF); + con = readl_relaxed(base + GPIOCON_OFF); con &= ~(0xf << con_4bit_shift(offset)); con |= 0x1 << con_4bit_shift(offset); - dat = __raw_readl(base + GPIODAT_OFF); + dat = readl_relaxed(base + GPIODAT_OFF); if (value) dat |= 1 << offset; else dat &= ~(1 << offset); - __raw_writel(dat, base + GPIODAT_OFF); - __raw_writel(con, base + GPIOCON_OFF); - __raw_writel(dat, base + GPIODAT_OFF); + writel_relaxed(dat, base + GPIODAT_OFF); + writel_relaxed(con, base + GPIOCON_OFF); + writel_relaxed(dat, base + GPIODAT_OFF); pr_debug("%s: %p: CON %08lx, DAT %08lx\n", __func__, base, con, dat); @@ -625,9 +625,9 @@ static int samsung_gpiolib_4bit2_input(struct gpio_chip *chip, else regcon -= 4; - con = __raw_readl(regcon); + con = readl_relaxed(regcon); con &= ~(0xf << con_4bit_shift(offset)); - __raw_writel(con, regcon); + writel_relaxed(con, regcon); pr_debug("%s: %p: CON %08lx\n", __func__, base, con); @@ -649,20 +649,20 @@ static int samsung_gpiolib_4bit2_output(struct gpio_chip *chip, else regcon -= 4; - con = __raw_readl(regcon); + con = readl_relaxed(regcon); con &= ~(0xf << con_4bit_shift(con_offset)); con |= 0x1 << con_4bit_shift(con_offset); - dat = __raw_readl(base + GPIODAT_OFF); + dat = readl_relaxed(base + GPIODAT_OFF); if (value) dat |= 1 << offset; else dat &= ~(1 << offset); - __raw_writel(dat, base + GPIODAT_OFF); - __raw_writel(con, regcon); - __raw_writel(dat, base + GPIODAT_OFF); + writel_relaxed(dat, base + GPIODAT_OFF); + writel_relaxed(con, regcon); + writel_relaxed(dat, base + GPIODAT_OFF); pr_debug("%s: %p: CON %08lx, DAT %08lx\n", __func__, base, con, dat); @@ -688,19 +688,19 @@ static int s3c24xx_gpiolib_banka_output(struct gpio_chip *chip, local_irq_save(flags); - con = __raw_readl(base + 0x00); - dat = __raw_readl(base + 0x04); + con = readl_relaxed(base + 0x00); + dat = readl_relaxed(base + 0x04); dat &= ~(1 << offset); if (value) dat |= 1 << offset; - __raw_writel(dat, base + 0x04); + writel_relaxed(dat, base + 0x04); con &= ~(1 << offset); - __raw_writel(con, base + 0x00); - __raw_writel(dat, base + 0x04); + writel_relaxed(con, base + 0x00); + writel_relaxed(dat, base + 0x04); local_irq_restore(flags); return 0; @@ -736,9 +736,9 @@ static int s5p64x0_gpiolib_rbank_input(struct gpio_chip *chip, samsung_gpio_lock(ourchip, flags); - con = __raw_readl(regcon); + con = readl_relaxed(regcon); con &= ~(0xf << con_4bit_shift(offset)); - __raw_writel(con, regcon); + writel_relaxed(con, regcon); samsung_gpio_unlock(ourchip, flags); @@ -774,18 +774,18 @@ static int s5p64x0_gpiolib_rbank_output(struct gpio_chip *chip, samsung_gpio_lock(ourchip, flags); - con = __raw_readl(regcon); + con = readl_relaxed(regcon); con &= ~(0xf << con_4bit_shift(con_offset)); con |= 0x1 << con_4bit_shift(con_offset); - dat = __raw_readl(base + GPIODAT_OFF); + dat = readl_relaxed(base + GPIODAT_OFF); if (value) dat |= 1 << offset; else dat &= ~(1 << offset); - __raw_writel(con, regcon); - __raw_writel(dat, base + GPIODAT_OFF); + writel_relaxed(con, regcon); + writel_relaxed(dat, base + GPIODAT_OFF); samsung_gpio_unlock(ourchip, flags); @@ -802,11 +802,11 @@ static void samsung_gpiolib_set(struct gpio_chip *chip, samsung_gpio_lock(ourchip, flags); - dat = __raw_readl(base + 0x04); + dat = readl_relaxed(base + 0x04); dat &= ~(1 << offset); if (value) dat |= 1 << offset; - __raw_writel(dat, base + 0x04); + writel_relaxed(dat, base + 0x04); samsung_gpio_unlock(ourchip, flags); } @@ -816,7 +816,7 @@ static int samsung_gpiolib_get(struct gpio_chip *chip, unsigned offset) struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); unsigned long val; - val = __raw_readl(ourchip->base + 0x04); + val = readl_relaxed(ourchip->base + 0x04); val >>= offset; val &= 1; @@ -2289,7 +2289,7 @@ s5p_gpio_drvstr_t s5p_gpio_get_drvstr(unsigned int pin) shift = off * 2; reg = chip->base + 0x0C; - drvstr = __raw_readl(reg); + drvstr = readl_relaxed(reg); drvstr = drvstr >> shift; drvstr &= 0x3; @@ -2312,11 +2312,11 @@ int s5p_gpio_set_drvstr(unsigned int pin, s5p_gpio_drvstr_t drvstr) shift = off * 2; reg = chip->base + 0x0C; - tmp = __raw_readl(reg); + tmp = readl_relaxed(reg); tmp &= ~(0x3 << shift); tmp |= drvstr << shift; - __raw_writel(tmp, reg); + writel_relaxed(tmp, reg); return 0; } @@ -2330,10 +2330,10 @@ unsigned int s3c2410_modify_misccr(unsigned int clear, unsigned int change) unsigned long misccr; local_irq_save(flags); - misccr = __raw_readl(S3C24XX_MISCCR); + misccr = readl_relaxed(S3C24XX_MISCCR); misccr &= ~clear; misccr ^= change; - __raw_writel(misccr, S3C24XX_MISCCR); + writel_relaxed(misccr, S3C24XX_MISCCR); local_irq_restore(flags); return misccr; diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index 639b49e1ec0586..eedd1455259c70 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -12,7 +12,8 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_IOMMU) += exynos_drm_iommu.o exynosdrm-$(CONFIG_DRM_EXYNOS_DMABUF) += exynos_drm_dmabuf.o exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o \ - exynos_ddc.o exynos_hdmiphy.o \ + exynos_ddc.o exynos_hdmiphy_i2c.o \ + exynos_hdmiphy_platform.o \ exynos_drm_hdmi.o exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI) += exynos_drm_vidi.o exynosdrm-$(CONFIG_DRM_EXYNOS_G2D) += exynos_drm_g2d.o diff --git a/drivers/gpu/drm/exynos/exynos_ddc.c b/drivers/gpu/drm/exynos/exynos_ddc.c index 6a8c84e7c83916..54ead551317afc 100644 --- a/drivers/gpu/drm/exynos/exynos_ddc.c +++ b/drivers/gpu/drm/exynos/exynos_ddc.c @@ -51,12 +51,17 @@ static struct of_device_id hdmiddc_match_types[] = { } }; +static struct i2c_device_id ddc_idtable[] = { + { }, +}; + struct i2c_driver ddc_driver = { .driver = { .name = "exynos-hdmiddc", .owner = THIS_MODULE, .of_match_table = hdmiddc_match_types, }, + .id_table = ddc_idtable, .probe = s5p_ddc_probe, .remove = s5p_ddc_remove, .command = NULL, diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index c204b4e3356e80..537a1dc75148e0 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -325,7 +325,10 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) { int ret; - ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (config_enabled(CONFIG_ARM_LPAE)) + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)); + else + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); if (ret) return ret; diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index c021ddc1ffb4b4..96a71725c44fe5 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #include @@ -42,6 +44,7 @@ #include "exynos_drm_hdmi.h" #include "exynos_hdmi.h" +#include "exynos_hdmiphy.h" #include #include @@ -70,10 +73,11 @@ struct hdmi_resources { struct clk *sclk_hdmi; struct clk *sclk_pixel; struct clk *sclk_hdmiphy; - struct clk *hdmiphy; struct clk *mout_hdmi; +#if 0 struct regulator_bulk_data *regul_bulk; int regul_count; +#endif }; struct hdmi_tg_regs { @@ -181,9 +185,10 @@ struct hdmi_context { int irq; struct i2c_client *ddc_port; - struct i2c_client *hdmiphy_port; + struct device *phy_dev; + struct exynos_hdmiphy_ops *phy_ops; - /* current hdmiphy conf regs */ + /* current hdmi ip configuration registers. */ struct hdmi_conf_regs mode_conf; struct hdmi_resources res; @@ -193,178 +198,9 @@ struct hdmi_context { enum hdmi_type type; }; -struct hdmiphy_config { - int pixel_clock; - u8 conf[32]; -}; - -/* list of phy config settings */ -static const struct hdmiphy_config hdmiphy_v13_configs[] = { - { - .pixel_clock = 27000000, - .conf = { - 0x01, 0x05, 0x00, 0xD8, 0x10, 0x1C, 0x30, 0x40, - 0x6B, 0x10, 0x02, 0x51, 0xDF, 0xF2, 0x54, 0x87, - 0x84, 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0, - 0x22, 0x40, 0xE3, 0x26, 0x00, 0x00, 0x00, 0x00, - }, - }, - { - .pixel_clock = 27027000, - .conf = { - 0x01, 0x05, 0x00, 0xD4, 0x10, 0x9C, 0x09, 0x64, - 0x6B, 0x10, 0x02, 0x51, 0xDF, 0xF2, 0x54, 0x87, - 0x84, 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0, - 0x22, 0x40, 0xE3, 0x26, 0x00, 0x00, 0x00, 0x00, - }, - }, - { - .pixel_clock = 74176000, - .conf = { - 0x01, 0x05, 0x00, 0xD8, 0x10, 0x9C, 0xef, 0x5B, - 0x6D, 0x10, 0x01, 0x51, 0xef, 0xF3, 0x54, 0xb9, - 0x84, 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0, - 0x22, 0x40, 0xa5, 0x26, 0x01, 0x00, 0x00, 0x00, - }, - }, - { - .pixel_clock = 74250000, - .conf = { - 0x01, 0x05, 0x00, 0xd8, 0x10, 0x9c, 0xf8, 0x40, - 0x6a, 0x10, 0x01, 0x51, 0xff, 0xf1, 0x54, 0xba, - 0x84, 0x00, 0x10, 0x38, 0x00, 0x08, 0x10, 0xe0, - 0x22, 0x40, 0xa4, 0x26, 0x01, 0x00, 0x00, 0x00, - }, - }, - { - .pixel_clock = 148500000, - .conf = { - 0x01, 0x05, 0x00, 0xD8, 0x10, 0x9C, 0xf8, 0x40, - 0x6A, 0x18, 0x00, 0x51, 0xff, 0xF1, 0x54, 0xba, - 0x84, 0x00, 0x10, 0x38, 0x00, 0x08, 0x10, 0xE0, - 0x22, 0x40, 0xa4, 0x26, 0x02, 0x00, 0x00, 0x00, - }, - }, -}; - -static const struct hdmiphy_config hdmiphy_v14_configs[] = { - { - .pixel_clock = 25200000, - .conf = { - 0x01, 0x51, 0x2A, 0x75, 0x40, 0x01, 0x00, 0x08, - 0x82, 0x80, 0xfc, 0xd8, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xf4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 27000000, - .conf = { - 0x01, 0xd1, 0x22, 0x51, 0x40, 0x08, 0xfc, 0x20, - 0x98, 0xa0, 0xcb, 0xd8, 0x45, 0xa0, 0xac, 0x80, - 0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xe4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 27027000, - .conf = { - 0x01, 0xd1, 0x2d, 0x72, 0x40, 0x64, 0x12, 0x08, - 0x43, 0xa0, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xe3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, - }, - }, - { - .pixel_clock = 36000000, - .conf = { - 0x01, 0x51, 0x2d, 0x55, 0x40, 0x01, 0x00, 0x08, - 0x82, 0x80, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xab, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 40000000, - .conf = { - 0x01, 0x51, 0x32, 0x55, 0x40, 0x01, 0x00, 0x08, - 0x82, 0x80, 0x2c, 0xd9, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0x9a, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 65000000, - .conf = { - 0x01, 0xd1, 0x36, 0x34, 0x40, 0x1e, 0x0a, 0x08, - 0x82, 0xa0, 0x45, 0xd9, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xbd, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 74176000, - .conf = { - 0x01, 0xd1, 0x3e, 0x35, 0x40, 0x5b, 0xde, 0x08, - 0x82, 0xa0, 0x73, 0xd9, 0x45, 0xa0, 0xac, 0x80, - 0x56, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xa6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 74250000, - .conf = { - 0x01, 0xd1, 0x1f, 0x10, 0x40, 0x40, 0xf8, 0x08, - 0x81, 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, - 0x3c, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xa5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x00, - }, - }, - { - .pixel_clock = 83500000, - .conf = { - 0x01, 0xd1, 0x23, 0x11, 0x40, 0x0c, 0xfb, 0x08, - 0x85, 0xa0, 0xd1, 0xd8, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0x93, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 106500000, - .conf = { - 0x01, 0xd1, 0x2c, 0x12, 0x40, 0x0c, 0x09, 0x08, - 0x84, 0xa0, 0x0a, 0xd9, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0x73, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 108000000, - .conf = { - 0x01, 0x51, 0x2d, 0x15, 0x40, 0x01, 0x00, 0x08, - 0x82, 0x80, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xc7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 146250000, - .conf = { - 0x01, 0xd1, 0x3d, 0x15, 0x40, 0x18, 0xfd, 0x08, - 0x83, 0xa0, 0x6e, 0xd9, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0x50, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, - }, - }, - { - .pixel_clock = 148500000, - .conf = { - 0x01, 0xd1, 0x1f, 0x00, 0x40, 0x40, 0xf8, 0x08, - 0x81, 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, - 0x3c, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0x4b, 0x25, 0x03, 0x00, 0x00, 0x01, 0x00, - }, - }, +struct hdmi_drv_data { + enum hdmi_type type; + bool i2c_hdmiphy; }; static inline u32 hdmi_reg_read(struct hdmi_context *hdata, u32 reg_id) @@ -755,28 +591,6 @@ static struct edid *hdmi_get_edid(void *ctx, struct drm_connector *connector) return raw_edid; } -static int hdmi_find_phy_conf(struct hdmi_context *hdata, u32 pixel_clock) -{ - const struct hdmiphy_config *confs; - int count, i; - - if (hdata->type == HDMI_TYPE13) { - confs = hdmiphy_v13_configs; - count = ARRAY_SIZE(hdmiphy_v13_configs); - } else if (hdata->type == HDMI_TYPE14) { - confs = hdmiphy_v14_configs; - count = ARRAY_SIZE(hdmiphy_v14_configs); - } else - return -EINVAL; - - for (i = 0; i < count; i++) - if (confs[i].pixel_clock == pixel_clock) - return i; - - DRM_DEBUG_KMS("Could not find phy config for %d\n", pixel_clock); - return -EINVAL; -} - static int hdmi_check_mode(void *ctx, struct drm_display_mode *mode) { struct hdmi_context *hdata = ctx; @@ -787,7 +601,7 @@ static int hdmi_check_mode(void *ctx, struct drm_display_mode *mode) (mode->flags & DRM_MODE_FLAG_INTERLACE) ? true : false, mode->clock * 1000); - ret = hdmi_find_phy_conf(hdata, mode->clock * 1000); + ret = hdata->phy_ops->check_mode(hdata->phy_dev, mode); if (ret < 0) return ret; return 0; @@ -1288,19 +1102,13 @@ static void hdmi_mode_apply(struct hdmi_context *hdata) static void hdmiphy_conf_reset(struct hdmi_context *hdata) { - u8 buffer[2]; u32 reg; clk_disable_unprepare(hdata->res.sclk_hdmi); clk_set_parent(hdata->res.mout_hdmi, hdata->res.sclk_pixel); clk_prepare_enable(hdata->res.sclk_hdmi); - /* operation mode */ - buffer[0] = 0x1f; - buffer[1] = 0x00; - - if (hdata->hdmiphy_port) - i2c_master_send(hdata->hdmiphy_port, buffer, 2); + hdata->phy_ops->enable(hdata->phy_dev, 0); if (hdata->type == HDMI_TYPE13) reg = HDMI_V13_PHY_RSTOUT; @@ -1319,76 +1127,24 @@ static void hdmiphy_poweron(struct hdmi_context *hdata) if (hdata->type == HDMI_TYPE14) hdmi_reg_writemask(hdata, HDMI_PHY_CON_0, 0, HDMI_PHY_POWER_OFF_EN); + + hdata->phy_ops->poweron(hdata->phy_dev, 1); } static void hdmiphy_poweroff(struct hdmi_context *hdata) { + hdata->phy_ops->poweron(hdata->phy_dev, 0); + if (hdata->type == HDMI_TYPE14) hdmi_reg_writemask(hdata, HDMI_PHY_CON_0, ~0, HDMI_PHY_POWER_OFF_EN); } -static void hdmiphy_conf_apply(struct hdmi_context *hdata) -{ - const u8 *hdmiphy_data; - u8 buffer[32]; - u8 operation[2]; - u8 read_buffer[32] = {0, }; - int ret; - int i; - - if (!hdata->hdmiphy_port) { - DRM_ERROR("hdmiphy is not attached\n"); - return; - } - - /* pixel clock */ - i = hdmi_find_phy_conf(hdata, hdata->mode_conf.pixel_clock); - if (i < 0) { - DRM_ERROR("failed to find hdmiphy conf\n"); - return; - } - - if (hdata->type == HDMI_TYPE13) - hdmiphy_data = hdmiphy_v13_configs[i].conf; - else - hdmiphy_data = hdmiphy_v14_configs[i].conf; - - memcpy(buffer, hdmiphy_data, 32); - ret = i2c_master_send(hdata->hdmiphy_port, buffer, 32); - if (ret != 32) { - DRM_ERROR("failed to configure HDMIPHY via I2C\n"); - return; - } - - usleep_range(10000, 12000); - - /* operation mode */ - operation[0] = 0x1f; - operation[1] = 0x80; - - ret = i2c_master_send(hdata->hdmiphy_port, operation, 2); - if (ret != 2) { - DRM_ERROR("failed to enable hdmiphy\n"); - return; - } - - ret = i2c_master_recv(hdata->hdmiphy_port, read_buffer, 32); - if (ret < 0) { - DRM_ERROR("failed to read hdmiphy config\n"); - return; - } - - for (i = 0; i < ret; i++) - DRM_DEBUG_KMS("hdmiphy[0x%02x] write[0x%02x] - " - "recv [0x%02x]\n", i, buffer[i], read_buffer[i]); -} - static void hdmi_conf_apply(struct hdmi_context *hdata) { hdmiphy_conf_reset(hdata); - hdmiphy_conf_apply(hdata); - + hdata->phy_ops->commit(hdata->phy_dev); + hdata->phy_ops->enable(hdata->phy_dev, 1); mutex_lock(&hdata->hdmi_mutex); hdmi_conf_reset(hdata); hdmi_conf_init(hdata); @@ -1632,6 +1388,8 @@ static void hdmi_mode_set(void *ctx, struct drm_display_mode *mode) hdmi_v13_mode_set(hdata, mode); else hdmi_v14_mode_set(hdata, mode); + + hdata->phy_ops->mode_set(hdata->phy_dev, mode); } static void hdmi_get_max_resol(void *ctx, unsigned int *width, @@ -1669,10 +1427,11 @@ static void hdmi_poweron(struct hdmi_context *hdata) mutex_unlock(&hdata->hdmi_mutex); +#if 0 if (regulator_bulk_enable(res->regul_count, res->regul_bulk)) DRM_DEBUG_KMS("failed to enable regulator bulk\n"); +#endif - clk_prepare_enable(res->hdmiphy); clk_prepare_enable(res->hdmi); clk_prepare_enable(res->sclk_hdmi); @@ -1697,8 +1456,9 @@ static void hdmi_poweroff(struct hdmi_context *hdata) clk_disable_unprepare(res->sclk_hdmi); clk_disable_unprepare(res->hdmi); - clk_disable_unprepare(res->hdmiphy); +#if 0 regulator_bulk_disable(res->regul_count, res->regul_bulk); +#endif mutex_lock(&hdata->hdmi_mutex); @@ -1750,7 +1510,7 @@ static irqreturn_t hdmi_irq_thread(int irq, void *arg) struct hdmi_context *hdata = ctx->ctx; mutex_lock(&hdata->hdmi_mutex); - hdata->hpd = gpio_get_value(hdata->hpd_gpio); + hdata->hpd = true; /* gpio_get_value(hdata->hpd_gpio); */ mutex_unlock(&hdata->hdmi_mutex); if (ctx->drm_dev) @@ -1763,12 +1523,14 @@ static int hdmi_resources_init(struct hdmi_context *hdata) { struct device *dev = hdata->dev; struct hdmi_resources *res = &hdata->res; +#if 0 static char *supply[] = { "hdmi-en", "vdd", "vdd_osc", "vdd_pll", }; +#endif int i, ret; DRM_DEBUG_KMS("HDMI resource init\n"); @@ -1796,11 +1558,6 @@ static int hdmi_resources_init(struct hdmi_context *hdata) DRM_ERROR("failed to get clock 'sclk_hdmiphy'\n"); goto fail; } - res->hdmiphy = devm_clk_get(dev, "hdmiphy"); - if (IS_ERR(res->hdmiphy)) { - DRM_ERROR("failed to get clock 'hdmiphy'\n"); - goto fail; - } res->mout_hdmi = devm_clk_get(dev, "mout_hdmi"); if (IS_ERR(res->mout_hdmi)) { DRM_ERROR("failed to get clock 'mout_hdmi'\n"); @@ -1809,6 +1566,7 @@ static int hdmi_resources_init(struct hdmi_context *hdata) clk_set_parent(res->mout_hdmi, res->sclk_pixel); +#if 0 res->regul_bulk = devm_kzalloc(dev, ARRAY_SIZE(supply) * sizeof(res->regul_bulk[0]), GFP_KERNEL); if (!res->regul_bulk) @@ -1823,6 +1581,7 @@ static int hdmi_resources_init(struct hdmi_context *hdata) goto fail; } res->regul_count = ARRAY_SIZE(supply); +#endif return 0; fail: @@ -1830,7 +1589,7 @@ static int hdmi_resources_init(struct hdmi_context *hdata) return -ENODEV; } -static struct i2c_client *hdmi_ddc, *hdmi_hdmiphy; +static struct i2c_client *hdmi_ddc; void hdmi_attach_ddc_client(struct i2c_client *ddc) { @@ -1838,12 +1597,6 @@ void hdmi_attach_ddc_client(struct i2c_client *ddc) hdmi_ddc = ddc; } -void hdmi_attach_hdmiphy_client(struct i2c_client *hdmiphy) -{ - if (hdmiphy) - hdmi_hdmiphy = hdmiphy; -} - static struct s5p_hdmi_platform_data *drm_hdmi_dt_parse_pdata (struct device *dev) { @@ -1868,13 +1621,94 @@ static struct s5p_hdmi_platform_data *drm_hdmi_dt_parse_pdata return NULL; } +static int hdmi_register_phy_device(struct hdmi_context *hdata, bool i2c_dev) +{ + struct device_node *np; + struct i2c_client *client; + struct platform_device *pdev; + int ret; + + /* register hdmiphy driver */ + if (i2c_dev) + ret = exynos_hdmiphy_i2c_driver_register(); + else + ret = exynos_hdmiphy_platform_driver_register(); + if (ret) { + DRM_ERROR("failed to register phy driver. ret %d.\n", ret); + goto err; + } + + np = of_parse_phandle(hdata->dev->of_node, "phy", 0); + if (!np) { + DRM_ERROR("Could not find 'phy' property\n"); + ret = -ENOENT; + goto err; + } + + if (i2c_dev) { + /* find hdmi phy on i2c bus */ + client = of_find_i2c_device_by_node(np); + if (!client) { + DRM_ERROR("Could not find i2c 'phy' device\n"); + ret = -ENODEV; + goto err; + } + hdata->phy_dev = &client->dev; + hdata->phy_ops = exynos_hdmiphy_i2c_device_get_ops( + hdata->phy_dev); + } else { + /* find hdmi phy on platform bus */ + pdev = of_find_device_by_node(np); + if (!pdev) { + DRM_ERROR("Could not find platform 'phy' device\n"); + ret = -ENODEV; + goto err; + } + hdata->phy_dev = &pdev->dev; + hdata->phy_ops = exynos_hdmiphy_platform_device_get_ops( + hdata->phy_dev); + } + + if (!hdata->phy_ops) { + ret = -EINVAL; + goto err; + } + + of_node_put(np); + return 0; +err: + of_node_put(np); + return ret; +} + +static struct hdmi_drv_data exynos5250_hdmi_drv_data = { + .type = HDMI_TYPE14, + .i2c_hdmiphy = 1, +}; + +static struct hdmi_drv_data exynos5420_hdmi_drv_data = { + .type = HDMI_TYPE14, + .i2c_hdmiphy = 0, +}; + +static struct hdmi_drv_data exynos5410_hdmi_drv_data = { + .type = HDMI_TYPE14, + .i2c_hdmiphy = 0, +}; + static struct of_device_id hdmi_match_types[] = { { .compatible = "samsung,exynos5-hdmi", - .data = (void *)HDMI_TYPE14, + .data = &exynos5250_hdmi_drv_data, }, { .compatible = "samsung,exynos4212-hdmi", - .data = (void *)HDMI_TYPE14, + .data = &exynos5250_hdmi_drv_data, + }, { + .compatible = "samsung,exynos5420-hdmi", + .data = &exynos5420_hdmi_drv_data, + }, { + .compatible = "samsung,exynos5410-hdmi", + .data = &exynos5410_hdmi_drv_data, }, { /* end node */ } @@ -1887,6 +1721,7 @@ static int hdmi_probe(struct platform_device *pdev) struct hdmi_context *hdata; struct s5p_hdmi_platform_data *pdata; struct resource *res; + struct hdmi_drv_data *drv; const struct of_device_id *match; int ret; @@ -1915,8 +1750,9 @@ static int hdmi_probe(struct platform_device *pdev) match = of_match_node(hdmi_match_types, dev->of_node); if (!match) return -ENODEV; - hdata->type = (enum hdmi_type)match->data; + drv = (struct hdmi_drv_data *)match->data; + hdata->type = drv->type; hdata->hpd_gpio = pdata->hpd_gpio; hdata->dev = dev; @@ -1945,15 +1781,13 @@ static int hdmi_probe(struct platform_device *pdev) hdata->ddc_port = hdmi_ddc; - /* hdmiphy i2c driver */ - if (i2c_add_driver(&hdmiphy_driver)) { - DRM_ERROR("failed to register hdmiphy i2c driver\n"); - ret = -ENOENT; + /* hdmiphy driver */ + ret = hdmi_register_phy_device(hdata, drv->i2c_hdmiphy); + if (ret) { + DRM_ERROR("failed to get hdmiphy device. ret %d.\n", ret); goto err_ddc; } - hdata->hdmiphy_port = hdmi_hdmiphy; - hdata->irq = gpio_to_irq(hdata->hpd_gpio); if (hdata->irq < 0) { DRM_ERROR("failed to get GPIO irq\n"); @@ -1961,7 +1795,7 @@ static int hdmi_probe(struct platform_device *pdev) goto err_hdmiphy; } - hdata->hpd = gpio_get_value(hdata->hpd_gpio); + hdata->hpd = true; /* gpio_get_value(hdata->hpd_gpio); */ ret = devm_request_threaded_irq(dev, hdata->irq, NULL, hdmi_irq_thread, IRQF_TRIGGER_RISING | @@ -1983,7 +1817,10 @@ static int hdmi_probe(struct platform_device *pdev) return 0; err_hdmiphy: - i2c_del_driver(&hdmiphy_driver); + if (drv->i2c_hdmiphy) + exynos_hdmiphy_i2c_driver_unregister(); + else + exynos_hdmiphy_platform_driver_unregister(); err_ddc: i2c_del_driver(&ddc_driver); return ret; @@ -1992,17 +1829,32 @@ static int hdmi_probe(struct platform_device *pdev) static int hdmi_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); + struct hdmi_context *hdata = ctx->ctx; pm_runtime_disable(dev); - /* hdmiphy i2c driver */ - i2c_del_driver(&hdmiphy_driver); + /* hdmiphy driver */ + if (i2c_verify_client(hdata->phy_dev)) + exynos_hdmiphy_i2c_driver_unregister(); + else + exynos_hdmiphy_platform_driver_unregister(); + /* DDC i2c driver */ i2c_del_driver(&ddc_driver); return 0; } +static void hdmi_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); + struct hdmi_context *hdata = ctx->ctx; + + hdmi_poweroff(hdata); +} + #ifdef CONFIG_PM_SLEEP static int hdmi_suspend(struct device *dev) { @@ -2030,7 +1882,7 @@ static int hdmi_resume(struct device *dev) struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); struct hdmi_context *hdata = ctx->ctx; - hdata->hpd = gpio_get_value(hdata->hpd_gpio); + hdata->hpd = true; /* gpio_get_value(hdata->hpd_gpio); */ enable_irq(hdata->irq); @@ -2075,6 +1927,7 @@ static const struct dev_pm_ops hdmi_pm_ops = { struct platform_driver hdmi_driver = { .probe = hdmi_probe, .remove = hdmi_remove, + .shutdown = hdmi_shutdown, .driver = { .name = "exynos-hdmi", .owner = THIS_MODULE, diff --git a/drivers/gpu/drm/exynos/exynos_hdmiphy.c b/drivers/gpu/drm/exynos/exynos_hdmiphy.c index 59abb1494ceb45..ac1f2da53c5fcf 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmiphy.c +++ b/drivers/gpu/drm/exynos/exynos_hdmiphy.c @@ -52,14 +52,20 @@ static struct of_device_id hdmiphy_match_types[] = { } }; +static const struct i2c_device_id hdmiphy_id[] = { + { }, +}; + struct i2c_driver hdmiphy_driver = { .driver = { .name = "exynos-hdmiphy", .owner = THIS_MODULE, .of_match_table = hdmiphy_match_types, }, + .id_table = hdmiphy_id, .probe = hdmiphy_probe, .remove = hdmiphy_remove, .command = NULL, }; + EXPORT_SYMBOL(hdmiphy_driver); diff --git a/drivers/gpu/drm/exynos/exynos_hdmiphy.h b/drivers/gpu/drm/exynos/exynos_hdmiphy.h new file mode 100644 index 00000000000000..26f279d7a4c0fb --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_hdmiphy.h @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * Authors: + * Rahul Sharma + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _EXYNOS_HDMIPHY_H_ +#define _EXYNOS_HDMIPHY_H_ + +/* + * Exynos DRM Hdmiphy Structure. + * + * @check_mode: check if mode is supported. + * @mode_set: set the mode if supported. + * @commit: apply the mode. + * @enable: enable phy operation. + * @poweron: phy power on or off. + */ +struct exynos_hdmiphy_ops { + int (*check_mode)(struct device *dev, + struct drm_display_mode *mode); + int (*mode_set)(struct device *dev, + struct drm_display_mode *mode); + int (*commit)(struct device *dev); + void (*enable)(struct device *dev, int enable); + void (*poweron)(struct device *dev, int mode); +}; + +int exynos_hdmiphy_i2c_driver_register(void); +void exynos_hdmiphy_i2c_driver_unregister(void); + +int exynos_hdmiphy_platform_driver_register(void); +void exynos_hdmiphy_platform_driver_unregister(void); + +struct exynos_hdmiphy_ops *exynos_hdmiphy_i2c_device_get_ops + (struct device *dev); +struct exynos_hdmiphy_ops *exynos_hdmiphy_platform_device_get_ops + (struct device *dev); + +#endif diff --git a/drivers/gpu/drm/exynos/exynos_hdmiphy_i2c.c b/drivers/gpu/drm/exynos/exynos_hdmiphy_i2c.c new file mode 100644 index 00000000000000..6b411bd27edc86 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_hdmiphy_i2c.c @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2013 Samsung Electronics Co.Ltd + * Authors: + * Rahul Sharma + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include + +#include +#include +#include + +#include "regs-hdmiphy.h" +#include "exynos_hdmiphy.h" +#include "exynos_hdmiphy_priv.h" + +/* list of default phy config settings */ +static struct hdmiphy_config hdmiphy_4212_configs[] = { + { + .pixel_clock = 25200000, + .conf = { + 0x51, 0x2A, 0x75, 0x40, 0x01, 0x00, 0x08, 0x82, + 0x80, 0xfc, 0xd8, 0x45, 0xa0, 0xac, 0x80, 0x08, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0xf4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 27000000, + .conf = { + 0xd1, 0x22, 0x51, 0x40, 0x08, 0xfc, 0x20, 0x98, + 0xa0, 0xcb, 0xd8, 0x45, 0xa0, 0xac, 0x80, 0x06, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0xe4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 27027000, + .conf = { + 0xd1, 0x2d, 0x72, 0x40, 0x64, 0x12, 0x08, 0x43, + 0xa0, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, 0x08, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0xe3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, + }, + }, + { + .pixel_clock = 36000000, + .conf = { + 0x51, 0x2d, 0x55, 0x40, 0x01, 0x00, 0x08, 0x82, + 0x80, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, 0x08, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0xab, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 40000000, + .conf = { + 0x51, 0x32, 0x55, 0x40, 0x01, 0x00, 0x08, 0x82, + 0x80, 0x2c, 0xd9, 0x45, 0xa0, 0xac, 0x80, 0x08, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0x9a, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 65000000, + .conf = { + 0xd1, 0x36, 0x34, 0x40, 0x1e, 0x0a, 0x08, 0x82, + 0xa0, 0x45, 0xd9, 0x45, 0xa0, 0xac, 0x80, 0x08, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0xbd, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 74176000, + .conf = { + 0xd1, 0x3e, 0x35, 0x40, 0x5b, 0xde, 0x08, 0x82, + 0xa0, 0x73, 0xd9, 0x45, 0xa0, 0xac, 0x80, 0x56, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0xa6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 74250000, + .conf = { + 0xd1, 0x1f, 0x10, 0x40, 0x40, 0xf8, 0x08, 0x81, + 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, 0x3c, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0xa5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x00, + }, + }, + { + .pixel_clock = 83500000, + .conf = { + 0xd1, 0x23, 0x11, 0x40, 0x0c, 0xfb, 0x08, 0x85, + 0xa0, 0xd1, 0xd8, 0x45, 0xa0, 0xac, 0x80, 0x08, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0x93, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 106500000, + .conf = { + 0xd1, 0x2c, 0x12, 0x40, 0x0c, 0x09, 0x08, 0x84, + 0xa0, 0x0a, 0xd9, 0x45, 0xa0, 0xac, 0x80, 0x08, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0x73, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 108000000, + .conf = { + 0x51, 0x2d, 0x15, 0x40, 0x01, 0x00, 0x08, 0x82, + 0x80, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, 0x08, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0xc7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 146250000, + .conf = { + 0xd1, 0x3d, 0x15, 0x40, 0x18, 0xfd, 0x08, 0x83, + 0xa0, 0x6e, 0xd9, 0x45, 0xa0, 0xac, 0x80, 0x08, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0x50, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 148500000, + .conf = { + 0xd1, 0x1f, 0x00, 0x40, 0x40, 0xf8, 0x08, 0x81, + 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, 0x3c, + 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, 0x54, + 0x4b, 0x25, 0x03, 0x00, 0x00, 0x01, 0x00, + }, + }, +}; + +static struct hdmiphy_config hdmiphy_4210_configs[] = { + { + .pixel_clock = 27000000, + .conf = { + 0x05, 0x00, 0xD8, 0x10, 0x1C, 0x30, 0x40, 0x6B, + 0x10, 0x02, 0x51, 0xDF, 0xF2, 0x54, 0x87, 0x84, + 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0, 0x22, + 0x40, 0xE3, 0x26, 0x00, 0x00, 0x00, 0x00, + }, + }, + { + .pixel_clock = 27027000, + .conf = { + 0x05, 0x00, 0xD4, 0x10, 0x9C, 0x09, 0x64, 0x6B, + 0x10, 0x02, 0x51, 0xDF, 0xF2, 0x54, 0x87, 0x84, + 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0, 0x22, + 0x40, 0xE3, 0x26, 0x00, 0x00, 0x00, 0x00, + }, + }, + { + .pixel_clock = 74176000, + .conf = { + 0x05, 0x00, 0xD8, 0x10, 0x9C, 0xef, 0x5B, 0x6D, + 0x10, 0x01, 0x51, 0xef, 0xF3, 0x54, 0xb9, 0x84, + 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0, 0x22, + 0x40, 0xa5, 0x26, 0x01, 0x00, 0x00, 0x00, + }, + }, + { + .pixel_clock = 74250000, + .conf = { + 0x05, 0x00, 0xd8, 0x10, 0x9c, 0xf8, 0x40, 0x6a, + 0x10, 0x01, 0x51, 0xff, 0xf1, 0x54, 0xba, 0x84, + 0x00, 0x10, 0x38, 0x00, 0x08, 0x10, 0xe0, 0x22, + 0x40, 0xa4, 0x26, 0x01, 0x00, 0x00, 0x00, + }, + }, + { + .pixel_clock = 148500000, + .conf = { + 0x05, 0x00, 0xD8, 0x10, 0x9C, 0xf8, 0x40, 0x6A, + 0x18, 0x00, 0x51, 0xff, 0xF1, 0x54, 0xba, 0x84, + 0x00, 0x10, 0x38, 0x00, 0x08, 0x10, 0xE0, 0x22, + 0x40, 0xa4, 0x26, 0x02, 0x00, 0x00, 0x00, + }, + }, +}; + +static struct hdmiphy_config *hdmiphy_find_conf(struct hdmiphy_context *hdata, + unsigned int pixel_clk) +{ + int i; + + for (i = 0; i < hdata->nr_confs; i++) + if (hdata->confs[i].pixel_clock == pixel_clk) + return &hdata->confs[i]; + + return NULL; +} + +static int hdmiphy_dt_parse_power_control(struct i2c_client *client) +{ + struct device_node *phy_pow_ctrl_node; + struct hdmiphy_context *hdata = i2c_get_clientdata(client); + u32 buf[2]; + int ret = 0; + + phy_pow_ctrl_node = of_get_child_by_name(client->dev.of_node, + "phy-power-control"); + if (!phy_pow_ctrl_node) { + DRM_ERROR("Failed to find phy power control node\n"); + return -ENODEV; + } + + /* reg property holds two informations: addr of pmu register, size */ + if (of_property_read_u32_array(phy_pow_ctrl_node, "reg", buf, 2)) { + DRM_ERROR("faild to get phy power control reg\n"); + ret = -EINVAL; + goto out; + } + + hdata->phy_pow_ctrl_reg = devm_ioremap(&client->dev, buf[0], buf[1]); + if (!hdata->phy_pow_ctrl_reg) { + DRM_ERROR("failed to ioremap phy pmu reg\n"); + ret = -ENOMEM; + } + +out: + of_node_put(phy_pow_ctrl_node); + return ret; +} + +static inline void hdmiphy_pow_ctrl_reg_writemask( + struct hdmiphy_context *hdata, + u32 value, u32 mask) +{ + u32 old = readl(hdata->phy_pow_ctrl_reg); + value = (value & mask) | (old & ~mask); + writel(value, hdata->phy_pow_ctrl_reg); +} + +static int hdmiphy_reg_writeb(struct device *dev, + u32 reg_offset, u8 value) +{ + u8 buffer[2]; + int ret; + + if (reg_offset >= HDMIPHY_REG_COUNT) + return -EINVAL; + + buffer[0] = reg_offset; + buffer[1] = value; + + ret = i2c_master_send(to_i2c_client(dev), + buffer, 2); + if (ret == 2) + return 0; + return ret; +} + +static int hdmiphy_reg_write_buf(struct device *dev, + u32 reg_offset, const u8 *buf, u32 len) +{ + int ret; + u8 buffer[HDMIPHY_REG_COUNT]; + + if ((reg_offset + len) > HDMIPHY_REG_COUNT) + return -EINVAL; + + buffer[0] = reg_offset; + memcpy(&buffer[1], buf, len); + + ret = i2c_master_send(to_i2c_client(dev), + buffer, len); + if (ret == len) + return 0; + return ret; +} + +static int hdmiphy_check_mode(struct device *dev, + struct drm_display_mode *mode) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + const struct hdmiphy_config *conf; + + DRM_DEBUG("xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n", + mode->hdisplay, mode->vdisplay, + mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) + ? true : false, mode->clock * 1000); + + conf = hdmiphy_find_conf(hdata, (mode->clock * 1000)); + if (!conf) { + DRM_DEBUG("Display Mode is not supported.\n"); + return -EINVAL; + } + return 0; +} + +static int hdmiphy_mode_set(struct device *dev, + struct drm_display_mode *mode) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + hdata->current_conf = hdmiphy_find_conf(hdata, mode->clock * 1000); + if (!hdata->current_conf) { + DRM_ERROR("Display Mode is not supported.\n"); + return -EINVAL; + } + return 0; +} + +static int hdmiphy_commit(struct device *dev) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + int ret; + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + ret = hdmiphy_reg_write_buf(dev, 1, hdata->current_conf->conf, + HDMIPHY_REG_COUNT - 1); + if (ret) { + DRM_ERROR("failed to configure hdmiphy.\n"); + return ret; + } + + /* need this delay before phy can be set to operation. */ + usleep_range(10000, 12000); + return 0; +} + +static void hdmiphy_enable(struct device *dev, int enable) +{ + int ret; + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + if (enable) + ret = hdmiphy_reg_writeb(dev, HDMIPHY_MODE_SET_DONE, + HDMIPHY_MODE_EN); + else + ret = hdmiphy_reg_writeb(dev, HDMIPHY_MODE_SET_DONE, 0); + + if (ret < 0) { + DRM_ERROR("failed to %s hdmiphy. ret %d.\n", + enable ? "enable" : "disable", ret); + return; + } +} + +static void hdmiphy_poweron(struct device *dev, int mode) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + if (mode) + hdmiphy_pow_ctrl_reg_writemask(hdata, PMU_HDMI_PHY_ENABLE, + PMU_HDMI_PHY_CONTROL_MASK); + else + hdmiphy_pow_ctrl_reg_writemask(hdata, PMU_HDMI_PHY_DISABLE, + PMU_HDMI_PHY_CONTROL_MASK); +} + +struct exynos_hdmiphy_ops *exynos_hdmiphy_i2c_device_get_ops + (struct device *dev) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + if (hdata) + return hdata->ops; + + return NULL; +} + +static struct exynos_hdmiphy_ops phy_ops = { + .check_mode = hdmiphy_check_mode, + .mode_set = hdmiphy_mode_set, + .commit = hdmiphy_commit, + .enable = hdmiphy_enable, + .poweron = hdmiphy_poweron, +}; + +static struct hdmiphy_drv_data exynos4212_hdmiphy_drv_data = { + .confs = hdmiphy_4212_configs, + .count = ARRAY_SIZE(hdmiphy_4212_configs) +}; + +static struct hdmiphy_drv_data exynos4210_hdmiphy_drv_data = { + .confs = hdmiphy_4210_configs, + .count = ARRAY_SIZE(hdmiphy_4210_configs) +}; + +static struct of_device_id hdmiphy_i2c_device_match_types[] = { + { + .compatible = "samsung,exynos5-hdmiphy", + .data = &exynos4212_hdmiphy_drv_data, + }, { + .compatible = "samsung,exynos4210-hdmiphy", + .data = &exynos4210_hdmiphy_drv_data, + }, { + .compatible = "samsung,exynos4212-hdmiphy", + .data = &exynos4212_hdmiphy_drv_data, + }, { + /* end node */ + } +}; + +static int hdmiphy_i2c_device_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct hdmiphy_context *hdata; + struct hdmiphy_drv_data *drv; + const struct of_device_id *match; + int ret; + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + hdata = devm_kzalloc(dev, sizeof(*hdata), GFP_KERNEL); + if (!hdata) { + DRM_ERROR("failed to allocate hdmiphy context.\n"); + return -ENOMEM; + } + + match = of_match_node(of_match_ptr( + hdmiphy_i2c_device_match_types), + dev->of_node); + + if (!match) + return -ENODEV; + + drv = (struct hdmiphy_drv_data *)match->data; + + hdata->confs = drv->confs; + hdata->nr_confs = drv->count; + hdata->ops = &phy_ops; + + i2c_set_clientdata(client, hdata); + ret = hdmiphy_dt_parse_power_control(client); + if (ret) { + DRM_ERROR("failed to map hdmiphy pow control reg.\n"); + return ret; + } + + return 0; +} + +static const struct i2c_device_id hdmiphy_id[] = { + { }, +}; + +struct i2c_driver hdmiphy_i2c_driver = { + .driver = { + .name = "exynos-hdmiphy", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr( + hdmiphy_i2c_device_match_types), + }, + .id_table = hdmiphy_id, + .probe = hdmiphy_i2c_device_probe, + .command = NULL, +}; + +int exynos_hdmiphy_i2c_driver_register(void) +{ + int ret; + + ret = i2c_add_driver(&hdmiphy_i2c_driver); + if (ret) + return ret; + + return 0; +} + +void exynos_hdmiphy_i2c_driver_unregister(void) +{ + i2c_del_driver(&hdmiphy_i2c_driver); +} diff --git a/drivers/gpu/drm/exynos/exynos_hdmiphy_platform.c b/drivers/gpu/drm/exynos/exynos_hdmiphy_platform.c new file mode 100644 index 00000000000000..7ba1ee6007d388 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_hdmiphy_platform.c @@ -0,0 +1,556 @@ +/* + * Copyright (C) 2013 Samsung Electronics Co.Ltd + * Authors: + * Rahul Sharma + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ +#include + +#include +#include + +#include "regs-hdmiphy.h" +#include "exynos_hdmiphy.h" +#include "exynos_hdmiphy_priv.h" + +/* default phy config settings for exynos5420 */ +static struct hdmiphy_config hdmiphy_5420_configs[] = { + { + .pixel_clock = 25200000, + .conf = { + 0x52, 0x3F, 0x55, 0x40, 0x01, 0x00, 0xC8, 0x82, + 0xC8, 0xBD, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x06, + 0x80, 0x01, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xF4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 27000000, + .conf = { + 0xD1, 0x22, 0x51, 0x40, 0x08, 0xFC, 0xE0, 0x98, + 0xE8, 0xCB, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x06, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xE4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 27027000, + .conf = { + 0xD1, 0x2D, 0x72, 0x40, 0x64, 0x12, 0xC8, 0x43, + 0xE8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x06, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xE3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 36000000, + .conf = { + 0x51, 0x2D, 0x55, 0x40, 0x40, 0x00, 0xC8, 0x02, + 0xC8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xAB, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 40000000, + .conf = { + 0xD1, 0x21, 0x31, 0x40, 0x3C, 0x28, 0xC8, 0x87, + 0xE8, 0xC8, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x9A, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 65000000, + .conf = { + 0xD1, 0x36, 0x34, 0x40, 0x0C, 0x04, 0xC8, 0x82, + 0xE8, 0x45, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xBD, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 71000000, + .conf = { + 0xD1, 0x3B, 0x35, 0x40, 0x0C, 0x04, 0xC8, 0x85, + 0xE8, 0x63, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x57, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 74176000, + .conf = { + 0xD1, 0x1F, 0x10, 0x40, 0x5B, 0xEF, 0xC8, 0x81, + 0xE8, 0xB9, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x56, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xA6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 74250000, + .conf = { + 0xD1, 0x1F, 0x10, 0x40, 0x40, 0xF8, 0xC8, 0x81, + 0xE8, 0xBA, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x56, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xA5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 83500000, + .conf = { + 0xD1, 0x23, 0x11, 0x40, 0x0C, 0xFB, 0xC8, 0x85, + 0xE8, 0xD1, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x4A, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 106500000, + .conf = { + 0xD1, 0x2C, 0x12, 0x40, 0x0C, 0x09, 0xC8, 0x84, + 0xE8, 0x0A, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x73, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 108000000, + .conf = { + 0x51, 0x2D, 0x15, 0x40, 0x01, 0x00, 0xC8, 0x82, + 0xC8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xC7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 146250000, + .conf = { + 0xD1, 0x3D, 0x15, 0x40, 0x18, 0xFD, 0xC8, 0x83, + 0xE8, 0x6E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x54, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 148500000, + .conf = { + 0xD1, 0x1F, 0x00, 0x40, 0x40, 0xF8, 0xC8, 0x81, + 0xE8, 0xBA, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x66, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x4B, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, +}; + +/* default phy config settings for exynos5410 */ +static struct hdmiphy_config hdmiphy_5410_configs[] = { + { + .pixel_clock = 25200000, + .conf = { + 0x52, 0x3F, 0x55, 0x40, 0x01, 0x00, 0xC8, 0x82, + 0xC8, 0xBD, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x06, + 0x80, 0x01, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xF4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 27000000, + .conf = { + 0xD1, 0x22, 0x51, 0x40, 0x08, 0xFC, 0x20, 0x98, + 0xE8, 0xCB, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x06, + 0x80, 0x01, 0x84, 0x05, 0x22, 0x24, 0x86, 0x54, + 0xE4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 27027000, + .conf = { + 0xD1, 0x2D, 0x72, 0x40, 0x64, 0x12, 0x08, 0x43, + 0xE8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x06, + 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x86, 0x54, + 0xE3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 36000000, + .conf = { + 0x51, 0x2D, 0x55, 0x40, 0x40, 0x00, 0xC8, 0x02, + 0xC8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xAB, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 40000000, + .conf = { + 0xD1, 0x21, 0x31, 0x40, 0x3C, 0x28, 0xC8, 0x87, + 0xE8, 0xC8, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x9A, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 65000000, + .conf = { + 0xD1, 0x36, 0x34, 0x40, 0x0C, 0x04, 0xC8, 0x82, + 0xE8, 0x45, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xBD, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 71000000, + .conf = { + 0xD1, 0x3B, 0x35, 0x40, 0x0C, 0x04, 0xC8, 0x85, + 0xE8, 0x63, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x57, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 74175000, + .conf = { + 0xD1, 0x1F, 0x10, 0x40, 0x5B, 0xEF, 0x08, 0x81, + 0xE8, 0xB9, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x56, + 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x86, 0x54, + 0xA6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 74250000, + .conf = { + 0xD1, 0x1F, 0x10, 0x40, 0x40, 0xF8, 0x08, 0x81, + 0xE8, 0xBA, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x56, + 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x86, 0x54, + 0xA5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 83500000, + .conf = { + 0xD1, 0x23, 0x11, 0x40, 0x0C, 0xFB, 0xC8, 0x85, + 0xE8, 0xD1, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x4A, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 106500000, + .conf = { + 0xD1, 0x2C, 0x12, 0x40, 0x0C, 0x09, 0xC8, 0x84, + 0xE8, 0x0A, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0x73, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 108000000, + .conf = { + 0x51, 0x2D, 0x15, 0x40, 0x01, 0x00, 0xC8, 0x82, + 0xC8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, 0x08, + 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, 0x54, + 0xC7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 148352000, + .conf = { + 0xD1, 0x1F, 0x00, 0x40, 0x5B, 0xEF, 0x08, 0x81, + 0xE8, 0xB9, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x66, + 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x86, 0x54, + 0x4B, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 148500000, + .conf = { + 0xD1, 0x1F, 0x00, 0x40, 0x40, 0xF0, 0x08, 0x81, + 0xe8, 0xBA, 0xD8, 0x45, 0xA0, 0xAC, 0x80, 0x66, + 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x86, 0x54, + 0x4B, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, +}; + +static struct hdmiphy_config *hdmiphy_find_conf(struct hdmiphy_context *hdata, + unsigned int pixel_clk) +{ + int i; + + for (i = 0; i < hdata->nr_confs; i++) + if (hdata->confs[i].pixel_clock == pixel_clk) + return &hdata->confs[i]; + + return NULL; +} + +static int hdmiphy_dt_parse_power_control(struct platform_device *pdev) +{ + struct device_node *phy_pow_ctrl_node; + struct hdmiphy_context *hdata = platform_get_drvdata(pdev); + u32 buf[2]; + int ret = 0; + + phy_pow_ctrl_node = of_get_child_by_name(pdev->dev.of_node, + "phy-power-control"); + if (!phy_pow_ctrl_node) { + DRM_ERROR("Failed to find phy power control node\n"); + return -ENODEV; + } + + /* reg property holds two informations: addr of pmu register, size */ + if (of_property_read_u32_array(phy_pow_ctrl_node, "reg", buf, 2)) { + DRM_ERROR("faild to get phy power control reg\n"); + ret = -EINVAL; + goto out; + } + + hdata->phy_pow_ctrl_reg = devm_ioremap(&pdev->dev, buf[0], buf[1]); + if (!hdata->phy_pow_ctrl_reg) { + DRM_ERROR("failed to ioremap phy pmu reg\n"); + ret = -ENOMEM; + } + +out: + of_node_put(phy_pow_ctrl_node); + return ret; +} + +static void hdmiphy_pow_ctrl_reg_writemask( + struct hdmiphy_context *hdata, + u32 value, u32 mask) +{ + u32 old = readl(hdata->phy_pow_ctrl_reg); + value = (value & mask) | (old & ~mask); + writel(value, hdata->phy_pow_ctrl_reg); +} + +static int hdmiphy_reg_writeb(struct hdmiphy_context *hdata, + u32 reg_offset, u8 value) +{ + if (reg_offset >= HDMIPHY_REG_COUNT) + return -EINVAL; + + writeb(value, hdata->regs + (reg_offset<<2)); + return 0; +} + +static int hdmiphy_reg_write_buf(struct hdmiphy_context *hdata, + u32 reg_offset, const u8 *buf, u32 len) +{ + int i; + + if ((reg_offset + len) > HDMIPHY_REG_COUNT) + return -EINVAL; + + for (i = 0; i < len; i++) + writeb(buf[i], hdata->regs + + ((reg_offset + i)<<2)); + return 0; +} + +static int hdmiphy_check_mode(struct device *dev, + struct drm_display_mode *mode) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + const struct hdmiphy_config *conf; + + DRM_DEBUG("%s xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n", + __func__, mode->hdisplay, mode->vdisplay, + mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) + ? true : false, mode->clock * 1000); + + conf = hdmiphy_find_conf(hdata, mode->clock * 1000); + if (!conf) { + DRM_DEBUG("Display Mode is not supported.\n"); + return -EINVAL; + } + return 0; +} + +static int hdmiphy_mode_set(struct device *dev, + struct drm_display_mode *mode) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + hdata->current_conf = hdmiphy_find_conf(hdata, mode->clock * 1000); + if (!hdata->current_conf) { + DRM_ERROR("Display Mode is not supported.\n"); + return -EINVAL; + } + return 0; +} + +static int hdmiphy_commit(struct device *dev) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + int ret; + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + ret = hdmiphy_reg_write_buf(hdata, 1, hdata->current_conf->conf, + HDMIPHY_REG_COUNT - 1); + if (ret) { + DRM_ERROR("failed to configure hdmiphy. ret %d.\n", ret); + return ret; + } + + /* need this delay before phy can be set to operation. */ + usleep_range(10000, 12000); + return 0; +} + +static void hdmiphy_enable(struct device *dev, int enable) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + if (enable) + hdmiphy_reg_writeb(hdata, HDMIPHY_MODE_SET_DONE, + HDMIPHY_MODE_EN); + else + hdmiphy_reg_writeb(hdata, HDMIPHY_MODE_SET_DONE, 0); +} + +static void hdmiphy_poweron(struct device *dev, int mode) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + if (mode) + hdmiphy_pow_ctrl_reg_writemask(hdata, PMU_HDMI_PHY_ENABLE, + PMU_HDMI_PHY_CONTROL_MASK); + else + hdmiphy_pow_ctrl_reg_writemask(hdata, PMU_HDMI_PHY_DISABLE, + PMU_HDMI_PHY_CONTROL_MASK); +} + +struct exynos_hdmiphy_ops *exynos_hdmiphy_platform_device_get_ops + (struct device *dev) +{ + struct hdmiphy_context *hdata = dev_get_drvdata(dev); + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + if (hdata) + return hdata->ops; + + return NULL; +} + +static struct exynos_hdmiphy_ops phy_ops = { + .check_mode = hdmiphy_check_mode, + .mode_set = hdmiphy_mode_set, + .commit = hdmiphy_commit, + .enable = hdmiphy_enable, + .poweron = hdmiphy_poweron, +}; + +static struct hdmiphy_drv_data exynos5420_hdmiphy_drv_data = { + .confs = hdmiphy_5420_configs, + .count = ARRAY_SIZE(hdmiphy_5420_configs) +}; + +static struct hdmiphy_drv_data exynos5410_hdmiphy_drv_data = { + .confs = hdmiphy_5410_configs, + .count = ARRAY_SIZE(hdmiphy_5410_configs) +}; + +static struct of_device_id hdmiphy_platform_device_match_types[] = { + { + .compatible = "samsung,exynos5420-hdmiphy", + .data = &exynos5420_hdmiphy_drv_data, + }, { + .compatible = "samsung,exynos5410-hdmiphy", + .data = &exynos5410_hdmiphy_drv_data, + }, { + /* end node */ + } +}; + +static int hdmiphy_platform_device_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hdmiphy_context *hdata; + struct hdmiphy_drv_data *drv; + struct resource *res; + const struct of_device_id *match; + int ret; + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + hdata = devm_kzalloc(dev, sizeof(*hdata), GFP_KERNEL); + if (!hdata) { + DRM_ERROR("failed to allocate hdmiphy context.\n"); + return -ENOMEM; + } + + match = of_match_node(of_match_ptr( + hdmiphy_platform_device_match_types), + dev->of_node); + + if (!match) + return -ENODEV; + + drv = (struct hdmiphy_drv_data *)match->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DRM_ERROR("failed to find phy registers\n"); + return -ENOENT; + } + + hdata->regs = devm_request_and_ioremap(&pdev->dev, res); + if (!hdata->regs) { + DRM_ERROR("failed to map registers\n"); + return -ENXIO; + } + + hdata->confs = drv->confs; + hdata->nr_confs = drv->count; + hdata->ops = &phy_ops; + + platform_set_drvdata(pdev, hdata); + ret = hdmiphy_dt_parse_power_control(pdev); + if (ret) { + DRM_ERROR("failed to map hdmiphy pow control reg.\n"); + return ret; + } + + return 0; +} + +struct platform_driver hdmiphy_platform_driver = { + .driver = { + .name = "exynos-hdmiphy", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr( + hdmiphy_platform_device_match_types), + }, + .probe = hdmiphy_platform_device_probe, +}; + +int exynos_hdmiphy_platform_driver_register(void) +{ + int ret; + + ret = platform_driver_register(&hdmiphy_platform_driver); + if (ret) + return ret; + + return 0; +} + +void exynos_hdmiphy_platform_driver_unregister(void) +{ + platform_driver_unregister(&hdmiphy_platform_driver); +} diff --git a/drivers/gpu/drm/exynos/exynos_hdmiphy_priv.h b/drivers/gpu/drm/exynos/exynos_hdmiphy_priv.h new file mode 100644 index 00000000000000..5987baf269983c --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_hdmiphy_priv.h @@ -0,0 +1,36 @@ +/* + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * Authors: + * Rahul Sharma + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _EXYNOS_HDMI_PRIV_H_ +#define _EXYNOS_HDMI_PRIV_H_ + +struct hdmiphy_context { + /* hdmiphy resources */ + void __iomem *phy_pow_ctrl_reg; + void __iomem *regs; + struct exynos_hdmiphy_ops *ops; + struct hdmiphy_config *confs; + unsigned int nr_confs; + struct hdmiphy_config *current_conf; +}; + +struct hdmiphy_config { + int pixel_clock; + u8 conf[HDMIPHY_REG_COUNT]; +}; + +struct hdmiphy_drv_data { + struct hdmiphy_config *confs; + unsigned int count; +}; + +#endif diff --git a/drivers/gpu/drm/exynos/regs-hdmiphy.h b/drivers/gpu/drm/exynos/regs-hdmiphy.h new file mode 100644 index 00000000000000..97e87abeac6ace --- /dev/null +++ b/drivers/gpu/drm/exynos/regs-hdmiphy.h @@ -0,0 +1,37 @@ +/* + * + * regs-hdmiphy.h + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * HDMI-PHY register header file for Samsung HDMI driver + * + * 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. +*/ + +#ifndef SAMSUNG_REGS_HDMIPHY_H +#define SAMSUNG_REGS_HDMIPHY_H + +#define HDMIPHY_REG_COUNT 32 + +/* + * Register part +*/ +#define HDMIPHY_MODE_SET_DONE 0x1f + +/* + * Bit definition part + */ + +/* HDMIPHY_MODE_SET_DONE */ +#define HDMIPHY_MODE_EN (1 << 7) + +/* hdmiphy pmu control bits */ +#define PMU_HDMI_PHY_CONTROL_MASK 1 +#define PMU_HDMI_PHY_ENABLE 1 +#define PMU_HDMI_PHY_DISABLE 0 + +#endif /* SAMSUNG_REGS_HDMIPHY_H */ diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index a713e6211419c8..811947d7370388 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1410,8 +1410,9 @@ int hidinput_connect(struct hid_device *hid, unsigned int force) * UGCI) cram a lot of unrelated inputs into the * same interface. */ hidinput->report = report; - if (drv->input_configured) - drv->input_configured(hid, hidinput); + if (drv->input_configured && + drv->input_configured(hid, hidinput)) + goto out_cleanup; if (input_register_device(hidinput->input)) goto out_cleanup; hidinput = NULL; @@ -1432,8 +1433,9 @@ int hidinput_connect(struct hid_device *hid, unsigned int force) } if (hidinput) { - if (drv->input_configured) - drv->input_configured(hid, hidinput); + if (drv->input_configured && + drv->input_configured(hid, hidinput)) + goto out_cleanup; if (input_register_device(hidinput->input)) goto out_cleanup; } diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 221d503f1c24fa..37aa2f9d711b78 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -418,6 +418,16 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi, (usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) td->mt_flags |= INPUT_MT_POINTER; + /* Only map fields from TouchScreen or TouchPad collections. + * We need to ignore fields that belong to other collections + * such as Mouse that might have the same GenericDesktop usages. */ + if (field->application == HID_DG_TOUCHSCREEN) + set_bit(INPUT_PROP_DIRECT, hi->input->propbit); + else if (field->application == HID_DG_TOUCHPAD) + set_bit(INPUT_PROP_POINTER, hi->input->propbit); + else + return 0; + if (usage->usage_index) prev_usage = &field->usage[usage->usage_index - 1]; @@ -747,12 +757,13 @@ static void mt_touch_report(struct hid_device *hid, struct hid_report *report) mt_sync_frame(td, report->field[0]->hidinput->input); } -static void mt_touch_input_configured(struct hid_device *hdev, +static int mt_touch_input_configured(struct hid_device *hdev, struct hid_input *hi) { struct mt_device *td = hid_get_drvdata(hdev); struct mt_class *cls = &td->mtclass; struct input_dev *input = hi->input; + int ret; if (!td->maxcontacts) td->maxcontacts = MT_DEFAULT_MAXCONTACT; @@ -767,9 +778,12 @@ static void mt_touch_input_configured(struct hid_device *hdev, if (cls->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP) td->mt_flags |= INPUT_MT_DROP_UNUSED; - input_mt_init_slots(input, td->maxcontacts, td->mt_flags); + ret = input_mt_init_slots(input, td->maxcontacts, td->mt_flags); + if (ret) + return ret; td->mt_flags = 0; + return 0; } static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, @@ -902,14 +916,15 @@ static void mt_post_parse(struct mt_device *td) cls->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; } -static void mt_input_configured(struct hid_device *hdev, struct hid_input *hi) +static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) { struct mt_device *td = hid_get_drvdata(hdev); char *name; const char *suffix = NULL; + int ret = 0; if (hi->report->id == td->mt_report_id) - mt_touch_input_configured(hdev, hi); + ret = mt_touch_input_configured(hdev, hi); if (hi->report->field[0]->physical == HID_DG_STYLUS) { suffix = "Pen"; @@ -925,6 +940,7 @@ static void mt_input_configured(struct hid_device *hdev, struct hid_input *hi) hi->input->name = name; } } + return ret; } static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) diff --git a/drivers/iio/industrialio-event.c b/drivers/iio/industrialio-event.c index c9c1419fe6e0ff..c31db87986d65f 100644 --- a/drivers/iio/industrialio-event.c +++ b/drivers/iio/industrialio-event.c @@ -35,6 +35,7 @@ */ struct iio_event_interface { wait_queue_head_t wait; + struct mutex read_lock; DECLARE_KFIFO(det_events, struct iio_event_data, 16); struct list_head dev_attr_list; @@ -111,14 +112,16 @@ static ssize_t iio_event_chrdev_read(struct file *filep, if (count < sizeof(struct iio_event_data)) return -EINVAL; - spin_lock_irq(&ev_int->wait.lock); + if (mutex_lock_interruptible(&ev_int->read_lock)) + return -ERESTARTSYS; + if (kfifo_is_empty(&ev_int->det_events)) { if (filep->f_flags & O_NONBLOCK) { ret = -EAGAIN; goto error_unlock; } /* Blocking on device; waiting for something to be there */ - ret = wait_event_interruptible_locked_irq(ev_int->wait, + ret = wait_event_interruptible(ev_int->wait, !kfifo_is_empty(&ev_int->det_events) || indio_dev->info == NULL); if (ret) @@ -133,7 +136,7 @@ static ssize_t iio_event_chrdev_read(struct file *filep, ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied); error_unlock: - spin_unlock_irq(&ev_int->wait.lock); + mutex_unlock(&ev_int->read_lock); return ret ? ret : copied; } @@ -425,6 +428,7 @@ static void iio_setup_ev_int(struct iio_event_interface *ev_int) { INIT_KFIFO(ev_int->det_events); init_waitqueue_head(&ev_int->wait); + mutex_init(&ev_int->read_lock); } static const char *iio_event_group_name = "events"; @@ -488,6 +492,7 @@ int iio_device_register_eventset(struct iio_dev *indio_dev) error_free_setup_event_lines: iio_free_chan_devattr_list(&indio_dev->event_interface->dev_attr_list); + mutex_destroy(&indio_dev->event_interface->read_lock); kfree(indio_dev->event_interface); error_ret: @@ -514,5 +519,6 @@ void iio_device_unregister_eventset(struct iio_dev *indio_dev) return; iio_free_chan_devattr_list(&indio_dev->event_interface->dev_attr_list); kfree(indio_dev->event_interface->group.attrs); + mutex_destroy(&indio_dev->event_interface->read_lock); kfree(indio_dev->event_interface); } diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index a11ff74a512701..9c13b63ef915fb 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -174,6 +174,15 @@ config INPUT_APMPOWER To compile this driver as a module, choose M here: the module will be called apm-power. +config INPUT_KEYRESET + tristate "Reset key" + depends on INPUT + ---help--- + Say Y here if you want to reboot when some keys are pressed; + + To compile this driver as a module, choose M here: the + module will be called keyreset. + comment "Input Device Drivers" source "drivers/input/keyboard/Kconfig" diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 5ca3f631497f4d..191ea43d0800f8 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -25,3 +25,4 @@ obj-$(CONFIG_INPUT_TOUCHSCREEN) += touchscreen/ obj-$(CONFIG_INPUT_MISC) += misc/ obj-$(CONFIG_INPUT_APMPOWER) += apm-power.o +obj-$(CONFIG_INPUT_KEYRESET) += keyreset.o diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index ce953d895f5b2b..fbf93cb2a4c237 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "input-compat.h" struct evdev { @@ -46,6 +47,9 @@ struct evdev_client { unsigned int tail; unsigned int packet_head; /* [future] position of the first element of next packet */ spinlock_t buffer_lock; /* protects access to buffer, head and tail */ + struct wake_lock wake_lock; + bool use_wake_lock; + char name[28]; struct fasync_struct *fasync; struct evdev *evdev; struct list_head node; @@ -150,10 +154,14 @@ static void __pass_event(struct evdev_client *client, client->buffer[client->tail].value = 0; client->packet_head = client->tail; + if (client->use_wake_lock) + wake_unlock(&client->wake_lock); } if (event->type == EV_SYN && event->code == SYN_REPORT) { client->packet_head = client->head; + if (client->use_wake_lock) + wake_lock(&client->wake_lock); kill_fasync(&client->fasync, SIGIO, POLL_IN); } } @@ -372,10 +380,14 @@ static int evdev_release(struct inode *inode, struct file *file) evdev_detach_client(evdev, client); + if (client->use_wake_lock) + wake_lock_destroy(&client->wake_lock); + if (is_vmalloc_addr(client)) vfree(client); else kfree(client); + kfree(client); evdev_close_device(evdev); @@ -408,6 +420,8 @@ static int evdev_open(struct inode *inode, struct file *file) client->bufsize = bufsize; spin_lock_init(&client->buffer_lock); + snprintf(client->name, sizeof(client->name), "%s-%d", + dev_name(&evdev->dev), task_tgid_vnr(current)); client->evdev = evdev; evdev_attach_client(evdev, client); @@ -474,6 +488,9 @@ static int evdev_fetch_next_event(struct evdev_client *client, if (have_event) { *event = client->buffer[client->tail++]; client->tail &= client->bufsize - 1; + if (client->use_wake_lock && + client->packet_head == client->tail) + wake_unlock(&client->wake_lock); } spin_unlock_irq(&client->buffer_lock); @@ -811,6 +828,11 @@ static int evdev_handle_mt_request(struct input_dev *dev, return 0; } +/* + * HACK: disable conflicting EVIOCREVOKE until Android userspace stops using + * EVIOCSSUSPENDBLOCK + */ +/* static int evdev_revoke(struct evdev *evdev, struct evdev_client *client, struct file *file) { @@ -821,6 +843,36 @@ static int evdev_revoke(struct evdev *evdev, struct evdev_client *client, return 0; } +*/ + +static int evdev_enable_suspend_block(struct evdev *evdev, + struct evdev_client *client) +{ + if (client->use_wake_lock) + return 0; + + spin_lock_irq(&client->buffer_lock); + wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name); + client->use_wake_lock = true; + if (client->packet_head != client->tail) + wake_lock(&client->wake_lock); + spin_unlock_irq(&client->buffer_lock); + return 0; +} + +static int evdev_disable_suspend_block(struct evdev *evdev, + struct evdev_client *client) +{ + if (!client->use_wake_lock) + return 0; + + spin_lock_irq(&client->buffer_lock); + client->use_wake_lock = false; + wake_lock_destroy(&client->wake_lock); + spin_unlock_irq(&client->buffer_lock); + + return 0; +} static long evdev_do_ioctl(struct file *file, unsigned int cmd, void __user *p, int compat_mode) @@ -884,12 +936,17 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, else return evdev_ungrab(evdev, client); + /* + * HACK: disable conflicting EVIOCREVOKE until Android userspace stops + * using EVIOCSSUSPENDBLOCK + */ + /* case EVIOCREVOKE: if (p) return -EINVAL; else return evdev_revoke(evdev, client, file); - + */ case EVIOCSCLOCKID: if (copy_from_user(&i, p, sizeof(unsigned int))) return -EFAULT; @@ -909,6 +966,15 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, case EVIOCSKEYCODE_V2: return evdev_handle_set_keycode_v2(dev, p); + + case EVIOCGSUSPENDBLOCK: + return put_user(client->use_wake_lock, ip); + + case EVIOCSSUSPENDBLOCK: + if (p) + return evdev_enable_suspend_block(evdev, client); + else + return evdev_disable_suspend_block(evdev, client); } size = _IOC_SIZE(cmd); diff --git a/drivers/input/keyboard/omap4-keypad.c b/drivers/input/keyboard/omap4-keypad.c index 0400b3f2b4b950..deb66ed7020da9 100644 --- a/drivers/input/keyboard/omap4-keypad.c +++ b/drivers/input/keyboard/omap4-keypad.c @@ -88,26 +88,26 @@ struct omap4_keypad { static int kbd_readl(struct omap4_keypad *keypad_data, u32 offset) { - return __raw_readl(keypad_data->base + + return readl_relaxed(keypad_data->base + keypad_data->reg_offset + offset); } static void kbd_writel(struct omap4_keypad *keypad_data, u32 offset, u32 value) { - __raw_writel(value, + writel_relaxed(value, keypad_data->base + keypad_data->reg_offset + offset); } static int kbd_read_irqreg(struct omap4_keypad *keypad_data, u32 offset) { - return __raw_readl(keypad_data->base + + return readl_relaxed(keypad_data->base + keypad_data->irqreg_offset + offset); } static void kbd_write_irqreg(struct omap4_keypad *keypad_data, u32 offset, u32 value) { - __raw_writel(value, + writel_relaxed(value, keypad_data->base + keypad_data->irqreg_offset + offset); } @@ -311,7 +311,7 @@ static int omap4_keypad_probe(struct platform_device *pdev) dev_err(&pdev->dev, "pm_runtime_get_sync() failed\n"); goto err_unmap; } - rev = __raw_readl(keypad_data->base + OMAP4_KBD_REVISION); + rev = readl_relaxed(keypad_data->base + OMAP4_KBD_REVISION); rev &= 0x03 << 30; rev >>= 30; switch (rev) { diff --git a/drivers/input/keyreset.c b/drivers/input/keyreset.c new file mode 100644 index 00000000000000..36208fe0baae66 --- /dev/null +++ b/drivers/input/keyreset.c @@ -0,0 +1,239 @@ +/* drivers/input/keyreset.c + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +struct keyreset_state { + struct input_handler input_handler; + unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; + unsigned long upbit[BITS_TO_LONGS(KEY_CNT)]; + unsigned long key[BITS_TO_LONGS(KEY_CNT)]; + spinlock_t lock; + int key_down_target; + int key_down; + int key_up; + int restart_disabled; + int (*reset_fn)(void); +}; + +int restart_requested; +static void deferred_restart(struct work_struct *dummy) +{ + restart_requested = 2; + sys_sync(); + restart_requested = 3; + kernel_restart(NULL); +} +static DECLARE_WORK(restart_work, deferred_restart); + +static void keyreset_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + unsigned long flags; + struct keyreset_state *state = handle->private; + + if (type != EV_KEY) + return; + + if (code >= KEY_MAX) + return; + + if (!test_bit(code, state->keybit)) + return; + + spin_lock_irqsave(&state->lock, flags); + if (!test_bit(code, state->key) == !value) + goto done; + __change_bit(code, state->key); + if (test_bit(code, state->upbit)) { + if (value) { + state->restart_disabled = 1; + state->key_up++; + } else + state->key_up--; + } else { + if (value) + state->key_down++; + else + state->key_down--; + } + if (state->key_down == 0 && state->key_up == 0) + state->restart_disabled = 0; + + pr_debug("reset key changed %d %d new state %d-%d-%d\n", code, value, + state->key_down, state->key_up, state->restart_disabled); + + if (value && !state->restart_disabled && + state->key_down == state->key_down_target) { + state->restart_disabled = 1; + if (restart_requested) + panic("keyboard reset failed, %d", restart_requested); + if (state->reset_fn) { + restart_requested = state->reset_fn(); + } else { + pr_info("keyboard reset\n"); + schedule_work(&restart_work); + restart_requested = 1; + } + } +done: + spin_unlock_irqrestore(&state->lock, flags); +} + +static int keyreset_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + int i; + int ret; + struct input_handle *handle; + struct keyreset_state *state = + container_of(handler, struct keyreset_state, input_handler); + + for (i = 0; i < KEY_MAX; i++) { + if (test_bit(i, state->keybit) && test_bit(i, dev->keybit)) + break; + } + if (i == KEY_MAX) + return -ENODEV; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "keyreset"; + handle->private = state; + + ret = input_register_handle(handle); + if (ret) + goto err_input_register_handle; + + ret = input_open_device(handle); + if (ret) + goto err_input_open_device; + + pr_info("using input dev %s for key reset\n", dev->name); + + return 0; + +err_input_open_device: + input_unregister_handle(handle); +err_input_register_handle: + kfree(handle); + return ret; +} + +static void keyreset_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id keyreset_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + { }, +}; +MODULE_DEVICE_TABLE(input, keyreset_ids); + +static int keyreset_probe(struct platform_device *pdev) +{ + int ret; + int key, *keyp; + struct keyreset_state *state; + struct keyreset_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return -EINVAL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + spin_lock_init(&state->lock); + keyp = pdata->keys_down; + while ((key = *keyp++)) { + if (key >= KEY_MAX) + continue; + state->key_down_target++; + __set_bit(key, state->keybit); + } + if (pdata->keys_up) { + keyp = pdata->keys_up; + while ((key = *keyp++)) { + if (key >= KEY_MAX) + continue; + __set_bit(key, state->keybit); + __set_bit(key, state->upbit); + } + } + + if (pdata->reset_fn) + state->reset_fn = pdata->reset_fn; + + state->input_handler.event = keyreset_event; + state->input_handler.connect = keyreset_connect; + state->input_handler.disconnect = keyreset_disconnect; + state->input_handler.name = KEYRESET_NAME; + state->input_handler.id_table = keyreset_ids; + ret = input_register_handler(&state->input_handler); + if (ret) { + kfree(state); + return ret; + } + platform_set_drvdata(pdev, state); + return 0; +} + +int keyreset_remove(struct platform_device *pdev) +{ + struct keyreset_state *state = platform_get_drvdata(pdev); + input_unregister_handler(&state->input_handler); + kfree(state); + return 0; +} + + +struct platform_driver keyreset_driver = { + .driver.name = KEYRESET_NAME, + .probe = keyreset_probe, + .remove = keyreset_remove, +}; + +static int __init keyreset_init(void) +{ + return platform_driver_register(&keyreset_driver); +} + +static void __exit keyreset_exit(void) +{ + return platform_driver_unregister(&keyreset_driver); +} + +module_init(keyreset_init); +module_exit(keyreset_exit); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 7904ab05527a4f..5282181bd172dd 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -93,6 +93,15 @@ config INPUT_BMA150 To compile this driver as a module, choose M here: the module will be called bma150. +config INPUT_HI6421_ONKEY + tristate "Hisilicon Hi6421 PMIC ONKEY support" + depends on MFD_HI6421_PMIC + help + Say Y to enable support for Hi6421 PMIC ONKEY. + + To compile this driver as a module, choose M here: the + module will be called hi6421_pwrkey. + config INPUT_PCSPKR tristate "PC Speaker support" depends on PCSPKR_PLATFORM @@ -308,6 +317,17 @@ config INPUT_ATI_REMOTE2 To compile this driver as a module, choose M here: the module will be called ati_remote2. +config INPUT_KEYCHORD + tristate "Key chord input driver support" + help + Say Y here if you want to enable the key chord driver + accessible at /dev/keychord. This driver can be used + for receiving notifications when client specified key + combinations are pressed. + + To compile this driver as a module, choose M here: the + module will be called keychord. + config INPUT_KEYSPAN_REMOTE tristate "Keyspan DMR USB remote control" depends on USB_ARCH_HAS_HCD @@ -443,6 +463,11 @@ config INPUT_SGI_BTNS To compile this driver as a module, choose M here: the module will be called sgi_btns. +config INPUT_GPIO + tristate "GPIO driver support" + help + Say Y here if you want to support gpio based keys, wheels etc... + config HP_SDC_RTC tristate "HP SDC Real Time Clock" depends on (GSC || HP300) && SERIO diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index cda71fc52fb3a6..6e5d2766625778 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -29,9 +29,12 @@ obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o obj-$(CONFIG_INPUT_GPIO_BEEPER) += gpio-beeper.o obj-$(CONFIG_INPUT_GPIO_TILT_POLLED) += gpio_tilt_polled.o +obj-$(CONFIG_INPUT_GPIO) += gpio_event.o gpio_matrix.o gpio_input.o gpio_output.o gpio_axis.o +obj-$(CONFIG_INPUT_HI6421_ONKEY) += hi6421_pwrkey.o obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o +obj-$(CONFIG_INPUT_KEYCHORD) += keychord.o obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o diff --git a/drivers/input/misc/gpio_axis.c b/drivers/input/misc/gpio_axis.c new file mode 100644 index 00000000000000..0acf4a576f53a2 --- /dev/null +++ b/drivers/input/misc/gpio_axis.c @@ -0,0 +1,192 @@ +/* drivers/input/misc/gpio_axis.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include + +struct gpio_axis_state { + struct gpio_event_input_devs *input_devs; + struct gpio_event_axis_info *info; + uint32_t pos; +}; + +uint16_t gpio_axis_4bit_gray_map_table[] = { + [0x0] = 0x0, [0x1] = 0x1, /* 0000 0001 */ + [0x3] = 0x2, [0x2] = 0x3, /* 0011 0010 */ + [0x6] = 0x4, [0x7] = 0x5, /* 0110 0111 */ + [0x5] = 0x6, [0x4] = 0x7, /* 0101 0100 */ + [0xc] = 0x8, [0xd] = 0x9, /* 1100 1101 */ + [0xf] = 0xa, [0xe] = 0xb, /* 1111 1110 */ + [0xa] = 0xc, [0xb] = 0xd, /* 1010 1011 */ + [0x9] = 0xe, [0x8] = 0xf, /* 1001 1000 */ +}; +uint16_t gpio_axis_4bit_gray_map(struct gpio_event_axis_info *info, uint16_t in) +{ + return gpio_axis_4bit_gray_map_table[in]; +} + +uint16_t gpio_axis_5bit_singletrack_map_table[] = { + [0x10] = 0x00, [0x14] = 0x01, [0x1c] = 0x02, /* 10000 10100 11100 */ + [0x1e] = 0x03, [0x1a] = 0x04, [0x18] = 0x05, /* 11110 11010 11000 */ + [0x08] = 0x06, [0x0a] = 0x07, [0x0e] = 0x08, /* 01000 01010 01110 */ + [0x0f] = 0x09, [0x0d] = 0x0a, [0x0c] = 0x0b, /* 01111 01101 01100 */ + [0x04] = 0x0c, [0x05] = 0x0d, [0x07] = 0x0e, /* 00100 00101 00111 */ + [0x17] = 0x0f, [0x16] = 0x10, [0x06] = 0x11, /* 10111 10110 00110 */ + [0x02] = 0x12, [0x12] = 0x13, [0x13] = 0x14, /* 00010 10010 10011 */ + [0x1b] = 0x15, [0x0b] = 0x16, [0x03] = 0x17, /* 11011 01011 00011 */ + [0x01] = 0x18, [0x09] = 0x19, [0x19] = 0x1a, /* 00001 01001 11001 */ + [0x1d] = 0x1b, [0x15] = 0x1c, [0x11] = 0x1d, /* 11101 10101 10001 */ +}; +uint16_t gpio_axis_5bit_singletrack_map( + struct gpio_event_axis_info *info, uint16_t in) +{ + return gpio_axis_5bit_singletrack_map_table[in]; +} + +static void gpio_event_update_axis(struct gpio_axis_state *as, int report) +{ + struct gpio_event_axis_info *ai = as->info; + int i; + int change; + uint16_t state = 0; + uint16_t pos; + uint16_t old_pos = as->pos; + for (i = ai->count - 1; i >= 0; i--) + state = (state << 1) | gpio_get_value(ai->gpio[i]); + pos = ai->map(ai, state); + if (ai->flags & GPIOEAF_PRINT_RAW) + pr_info("axis %d-%d raw %x, pos %d -> %d\n", + ai->type, ai->code, state, old_pos, pos); + if (report && pos != old_pos) { + if (ai->type == EV_REL) { + change = (ai->decoded_size + pos - old_pos) % + ai->decoded_size; + if (change > ai->decoded_size / 2) + change -= ai->decoded_size; + if (change == ai->decoded_size / 2) { + if (ai->flags & GPIOEAF_PRINT_EVENT) + pr_info("axis %d-%d unknown direction, " + "pos %d -> %d\n", ai->type, + ai->code, old_pos, pos); + change = 0; /* no closest direction */ + } + if (ai->flags & GPIOEAF_PRINT_EVENT) + pr_info("axis %d-%d change %d\n", + ai->type, ai->code, change); + input_report_rel(as->input_devs->dev[ai->dev], + ai->code, change); + } else { + if (ai->flags & GPIOEAF_PRINT_EVENT) + pr_info("axis %d-%d now %d\n", + ai->type, ai->code, pos); + input_event(as->input_devs->dev[ai->dev], + ai->type, ai->code, pos); + } + input_sync(as->input_devs->dev[ai->dev]); + } + as->pos = pos; +} + +static irqreturn_t gpio_axis_irq_handler(int irq, void *dev_id) +{ + struct gpio_axis_state *as = dev_id; + gpio_event_update_axis(as, 1); + return IRQ_HANDLED; +} + +int gpio_event_axis_func(struct gpio_event_input_devs *input_devs, + struct gpio_event_info *info, void **data, int func) +{ + int ret; + int i; + int irq; + struct gpio_event_axis_info *ai; + struct gpio_axis_state *as; + + ai = container_of(info, struct gpio_event_axis_info, info); + if (func == GPIO_EVENT_FUNC_SUSPEND) { + for (i = 0; i < ai->count; i++) + disable_irq(gpio_to_irq(ai->gpio[i])); + return 0; + } + if (func == GPIO_EVENT_FUNC_RESUME) { + for (i = 0; i < ai->count; i++) + enable_irq(gpio_to_irq(ai->gpio[i])); + return 0; + } + + if (func == GPIO_EVENT_FUNC_INIT) { + *data = as = kmalloc(sizeof(*as), GFP_KERNEL); + if (as == NULL) { + ret = -ENOMEM; + goto err_alloc_axis_state_failed; + } + as->input_devs = input_devs; + as->info = ai; + if (ai->dev >= input_devs->count) { + pr_err("gpio_event_axis: bad device index %d >= %d " + "for %d:%d\n", ai->dev, input_devs->count, + ai->type, ai->code); + ret = -EINVAL; + goto err_bad_device_index; + } + + input_set_capability(input_devs->dev[ai->dev], + ai->type, ai->code); + if (ai->type == EV_ABS) { + input_set_abs_params(input_devs->dev[ai->dev], ai->code, + 0, ai->decoded_size - 1, 0, 0); + } + for (i = 0; i < ai->count; i++) { + ret = gpio_request(ai->gpio[i], "gpio_event_axis"); + if (ret < 0) + goto err_request_gpio_failed; + ret = gpio_direction_input(ai->gpio[i]); + if (ret < 0) + goto err_gpio_direction_input_failed; + ret = irq = gpio_to_irq(ai->gpio[i]); + if (ret < 0) + goto err_get_irq_num_failed; + ret = request_irq(irq, gpio_axis_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "gpio_event_axis", as); + if (ret < 0) + goto err_request_irq_failed; + } + gpio_event_update_axis(as, 0); + return 0; + } + + ret = 0; + as = *data; + for (i = ai->count - 1; i >= 0; i--) { + free_irq(gpio_to_irq(ai->gpio[i]), as); +err_request_irq_failed: +err_get_irq_num_failed: +err_gpio_direction_input_failed: + gpio_free(ai->gpio[i]); +err_request_gpio_failed: + ; + } +err_bad_device_index: + kfree(as); + *data = NULL; +err_alloc_axis_state_failed: + return ret; +} diff --git a/drivers/input/misc/gpio_event.c b/drivers/input/misc/gpio_event.c new file mode 100644 index 00000000000000..90f07eba3ce9e9 --- /dev/null +++ b/drivers/input/misc/gpio_event.c @@ -0,0 +1,228 @@ +/* drivers/input/misc/gpio_event.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include + +struct gpio_event { + struct gpio_event_input_devs *input_devs; + const struct gpio_event_platform_data *info; + void *state[0]; +}; + +static int gpio_input_event( + struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + int i; + int devnr; + int ret = 0; + int tmp_ret; + struct gpio_event_info **ii; + struct gpio_event *ip = input_get_drvdata(dev); + + for (devnr = 0; devnr < ip->input_devs->count; devnr++) + if (ip->input_devs->dev[devnr] == dev) + break; + if (devnr == ip->input_devs->count) { + pr_err("gpio_input_event: unknown device %p\n", dev); + return -EIO; + } + + for (i = 0, ii = ip->info->info; i < ip->info->info_count; i++, ii++) { + if ((*ii)->event) { + tmp_ret = (*ii)->event(ip->input_devs, *ii, + &ip->state[i], + devnr, type, code, value); + if (tmp_ret) + ret = tmp_ret; + } + } + return ret; +} + +static int gpio_event_call_all_func(struct gpio_event *ip, int func) +{ + int i; + int ret; + struct gpio_event_info **ii; + + if (func == GPIO_EVENT_FUNC_INIT || func == GPIO_EVENT_FUNC_RESUME) { + ii = ip->info->info; + for (i = 0; i < ip->info->info_count; i++, ii++) { + if ((*ii)->func == NULL) { + ret = -ENODEV; + pr_err("gpio_event_probe: Incomplete pdata, " + "no function\n"); + goto err_no_func; + } + if (func == GPIO_EVENT_FUNC_RESUME && (*ii)->no_suspend) + continue; + ret = (*ii)->func(ip->input_devs, *ii, &ip->state[i], + func); + if (ret) { + pr_err("gpio_event_probe: function failed\n"); + goto err_func_failed; + } + } + return 0; + } + + ret = 0; + i = ip->info->info_count; + ii = ip->info->info + i; + while (i > 0) { + i--; + ii--; + if ((func & ~1) == GPIO_EVENT_FUNC_SUSPEND && (*ii)->no_suspend) + continue; + (*ii)->func(ip->input_devs, *ii, &ip->state[i], func & ~1); +err_func_failed: +err_no_func: + ; + } + return ret; +} + +static void __maybe_unused gpio_event_suspend(struct gpio_event *ip) +{ + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_SUSPEND); + if (ip->info->power) + ip->info->power(ip->info, 0); +} + +static void __maybe_unused gpio_event_resume(struct gpio_event *ip) +{ + if (ip->info->power) + ip->info->power(ip->info, 1); + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_RESUME); +} + +static int gpio_event_probe(struct platform_device *pdev) +{ + int err; + struct gpio_event *ip; + struct gpio_event_platform_data *event_info; + int dev_count = 1; + int i; + int registered = 0; + + event_info = pdev->dev.platform_data; + if (event_info == NULL) { + pr_err("gpio_event_probe: No pdata\n"); + return -ENODEV; + } + if ((!event_info->name && !event_info->names[0]) || + !event_info->info || !event_info->info_count) { + pr_err("gpio_event_probe: Incomplete pdata\n"); + return -ENODEV; + } + if (!event_info->name) + while (event_info->names[dev_count]) + dev_count++; + ip = kzalloc(sizeof(*ip) + + sizeof(ip->state[0]) * event_info->info_count + + sizeof(*ip->input_devs) + + sizeof(ip->input_devs->dev[0]) * dev_count, GFP_KERNEL); + if (ip == NULL) { + err = -ENOMEM; + pr_err("gpio_event_probe: Failed to allocate private data\n"); + goto err_kp_alloc_failed; + } + ip->input_devs = (void*)&ip->state[event_info->info_count]; + platform_set_drvdata(pdev, ip); + + for (i = 0; i < dev_count; i++) { + struct input_dev *input_dev = input_allocate_device(); + if (input_dev == NULL) { + err = -ENOMEM; + pr_err("gpio_event_probe: " + "Failed to allocate input device\n"); + goto err_input_dev_alloc_failed; + } + input_set_drvdata(input_dev, ip); + input_dev->name = event_info->name ? + event_info->name : event_info->names[i]; + input_dev->event = gpio_input_event; + ip->input_devs->dev[i] = input_dev; + } + ip->input_devs->count = dev_count; + ip->info = event_info; + if (event_info->power) + ip->info->power(ip->info, 1); + + err = gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_INIT); + if (err) + goto err_call_all_func_failed; + + for (i = 0; i < dev_count; i++) { + err = input_register_device(ip->input_devs->dev[i]); + if (err) { + pr_err("gpio_event_probe: Unable to register %s " + "input device\n", ip->input_devs->dev[i]->name); + goto err_input_register_device_failed; + } + registered++; + } + + return 0; + +err_input_register_device_failed: + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT); +err_call_all_func_failed: + if (event_info->power) + ip->info->power(ip->info, 0); + for (i = 0; i < registered; i++) + input_unregister_device(ip->input_devs->dev[i]); + for (i = dev_count - 1; i >= registered; i--) { + input_free_device(ip->input_devs->dev[i]); +err_input_dev_alloc_failed: + ; + } + kfree(ip); +err_kp_alloc_failed: + return err; +} + +static int gpio_event_remove(struct platform_device *pdev) +{ + struct gpio_event *ip = platform_get_drvdata(pdev); + int i; + + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT); + if (ip->info->power) + ip->info->power(ip->info, 0); + for (i = 0; i < ip->input_devs->count; i++) + input_unregister_device(ip->input_devs->dev[i]); + kfree(ip); + return 0; +} + +static struct platform_driver gpio_event_driver = { + .probe = gpio_event_probe, + .remove = gpio_event_remove, + .driver = { + .name = GPIO_EVENT_DEV_NAME, + }, +}; + +module_platform_driver(gpio_event_driver); + +MODULE_DESCRIPTION("GPIO Event Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/misc/gpio_input.c b/drivers/input/misc/gpio_input.c new file mode 100644 index 00000000000000..eefd02725aff40 --- /dev/null +++ b/drivers/input/misc/gpio_input.c @@ -0,0 +1,390 @@ +/* drivers/input/misc/gpio_input.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + DEBOUNCE_UNSTABLE = BIT(0), /* Got irq, while debouncing */ + DEBOUNCE_PRESSED = BIT(1), + DEBOUNCE_NOTPRESSED = BIT(2), + DEBOUNCE_WAIT_IRQ = BIT(3), /* Stable irq state */ + DEBOUNCE_POLL = BIT(4), /* Stable polling state */ + + DEBOUNCE_UNKNOWN = + DEBOUNCE_PRESSED | DEBOUNCE_NOTPRESSED, +}; + +struct gpio_key_state { + struct gpio_input_state *ds; + uint8_t debounce; +}; + +struct gpio_input_state { + struct gpio_event_input_devs *input_devs; + const struct gpio_event_input_info *info; + struct hrtimer timer; + int use_irq; + int debounce_count; + spinlock_t irq_lock; + struct wakeup_source *ws; + struct gpio_key_state key_state[0]; +}; + +static enum hrtimer_restart gpio_event_input_timer_func(struct hrtimer *timer) +{ + int i; + int pressed; + struct gpio_input_state *ds = + container_of(timer, struct gpio_input_state, timer); + unsigned gpio_flags = ds->info->flags; + unsigned npolarity; + int nkeys = ds->info->keymap_size; + const struct gpio_event_direct_entry *key_entry; + struct gpio_key_state *key_state; + unsigned long irqflags; + uint8_t debounce; + bool sync_needed; + +#if 0 + key_entry = kp->keys_info->keymap; + key_state = kp->key_state; + for (i = 0; i < nkeys; i++, key_entry++, key_state++) + pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio, + gpio_read_detect_status(key_entry->gpio)); +#endif + key_entry = ds->info->keymap; + key_state = ds->key_state; + sync_needed = false; + spin_lock_irqsave(&ds->irq_lock, irqflags); + for (i = 0; i < nkeys; i++, key_entry++, key_state++) { + debounce = key_state->debounce; + if (debounce & DEBOUNCE_WAIT_IRQ) + continue; + if (key_state->debounce & DEBOUNCE_UNSTABLE) { + debounce = key_state->debounce = DEBOUNCE_UNKNOWN; + enable_irq(gpio_to_irq(key_entry->gpio)); + if (gpio_flags & GPIOEDF_PRINT_KEY_UNSTABLE) + pr_info("gpio_keys_scan_keys: key %x-%x, %d " + "(%d) continue debounce\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + } + npolarity = !(gpio_flags & GPIOEDF_ACTIVE_HIGH); + pressed = gpio_get_value(key_entry->gpio) ^ npolarity; + if (debounce & DEBOUNCE_POLL) { + if (pressed == !(debounce & DEBOUNCE_PRESSED)) { + ds->debounce_count++; + key_state->debounce = DEBOUNCE_UNKNOWN; + if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_keys_scan_keys: key %x-" + "%x, %d (%d) start debounce\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + } + continue; + } + if (pressed && (debounce & DEBOUNCE_NOTPRESSED)) { + if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_keys_scan_keys: key %x-%x, %d " + "(%d) debounce pressed 1\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + key_state->debounce = DEBOUNCE_PRESSED; + continue; + } + if (!pressed && (debounce & DEBOUNCE_PRESSED)) { + if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_keys_scan_keys: key %x-%x, %d " + "(%d) debounce pressed 0\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + key_state->debounce = DEBOUNCE_NOTPRESSED; + continue; + } + /* key is stable */ + ds->debounce_count--; + if (ds->use_irq) + key_state->debounce |= DEBOUNCE_WAIT_IRQ; + else + key_state->debounce |= DEBOUNCE_POLL; + if (gpio_flags & GPIOEDF_PRINT_KEYS) + pr_info("gpio_keys_scan_keys: key %x-%x, %d (%d) " + "changed to %d\n", ds->info->type, + key_entry->code, i, key_entry->gpio, pressed); + input_event(ds->input_devs->dev[key_entry->dev], ds->info->type, + key_entry->code, pressed); + sync_needed = true; + } + if (sync_needed) { + for (i = 0; i < ds->input_devs->count; i++) + input_sync(ds->input_devs->dev[i]); + } + +#if 0 + key_entry = kp->keys_info->keymap; + key_state = kp->key_state; + for (i = 0; i < nkeys; i++, key_entry++, key_state++) { + pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio, + gpio_read_detect_status(key_entry->gpio)); + } +#endif + + if (ds->debounce_count) + hrtimer_start(timer, ds->info->debounce_time, HRTIMER_MODE_REL); + else if (!ds->use_irq) + hrtimer_start(timer, ds->info->poll_time, HRTIMER_MODE_REL); + else + __pm_relax(ds->ws); + + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + + return HRTIMER_NORESTART; +} + +static irqreturn_t gpio_event_input_irq_handler(int irq, void *dev_id) +{ + struct gpio_key_state *ks = dev_id; + struct gpio_input_state *ds = ks->ds; + int keymap_index = ks - ds->key_state; + const struct gpio_event_direct_entry *key_entry; + unsigned long irqflags; + int pressed; + + if (!ds->use_irq) + return IRQ_HANDLED; + + key_entry = &ds->info->keymap[keymap_index]; + + if (ds->info->debounce_time.tv64) { + spin_lock_irqsave(&ds->irq_lock, irqflags); + if (ks->debounce & DEBOUNCE_WAIT_IRQ) { + ks->debounce = DEBOUNCE_UNKNOWN; + if (ds->debounce_count++ == 0) { + __pm_stay_awake(ds->ws); + hrtimer_start( + &ds->timer, ds->info->debounce_time, + HRTIMER_MODE_REL); + } + if (ds->info->flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_event_input_irq_handler: " + "key %x-%x, %d (%d) start debounce\n", + ds->info->type, key_entry->code, + keymap_index, key_entry->gpio); + } else { + disable_irq_nosync(irq); + ks->debounce = DEBOUNCE_UNSTABLE; + } + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + } else { + pressed = gpio_get_value(key_entry->gpio) ^ + !(ds->info->flags & GPIOEDF_ACTIVE_HIGH); + if (ds->info->flags & GPIOEDF_PRINT_KEYS) + pr_info("gpio_event_input_irq_handler: key %x-%x, %d " + "(%d) changed to %d\n", + ds->info->type, key_entry->code, keymap_index, + key_entry->gpio, pressed); + input_event(ds->input_devs->dev[key_entry->dev], ds->info->type, + key_entry->code, pressed); + input_sync(ds->input_devs->dev[key_entry->dev]); + } + return IRQ_HANDLED; +} + +static int gpio_event_input_request_irqs(struct gpio_input_state *ds) +{ + int i; + int err; + unsigned int irq; + unsigned long req_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + + for (i = 0; i < ds->info->keymap_size; i++) { + err = irq = gpio_to_irq(ds->info->keymap[i].gpio); + if (err < 0) + goto err_gpio_get_irq_num_failed; + err = request_irq(irq, gpio_event_input_irq_handler, + req_flags, "gpio_keys", &ds->key_state[i]); + if (err) { + pr_err("gpio_event_input_request_irqs: request_irq " + "failed for input %d, irq %d\n", + ds->info->keymap[i].gpio, irq); + goto err_request_irq_failed; + } + if (ds->info->info.no_suspend) { + err = enable_irq_wake(irq); + if (err) { + pr_err("gpio_event_input_request_irqs: " + "enable_irq_wake failed for input %d, " + "irq %d\n", + ds->info->keymap[i].gpio, irq); + goto err_enable_irq_wake_failed; + } + } + } + return 0; + + for (i = ds->info->keymap_size - 1; i >= 0; i--) { + irq = gpio_to_irq(ds->info->keymap[i].gpio); + if (ds->info->info.no_suspend) + disable_irq_wake(irq); +err_enable_irq_wake_failed: + free_irq(irq, &ds->key_state[i]); +err_request_irq_failed: +err_gpio_get_irq_num_failed: + ; + } + return err; +} + +int gpio_event_input_func(struct gpio_event_input_devs *input_devs, + struct gpio_event_info *info, void **data, int func) +{ + int ret; + int i; + unsigned long irqflags; + struct gpio_event_input_info *di; + struct gpio_input_state *ds = *data; + char *wlname; + + di = container_of(info, struct gpio_event_input_info, info); + + if (func == GPIO_EVENT_FUNC_SUSPEND) { + if (ds->use_irq) + for (i = 0; i < di->keymap_size; i++) + disable_irq(gpio_to_irq(di->keymap[i].gpio)); + hrtimer_cancel(&ds->timer); + return 0; + } + if (func == GPIO_EVENT_FUNC_RESUME) { + spin_lock_irqsave(&ds->irq_lock, irqflags); + if (ds->use_irq) + for (i = 0; i < di->keymap_size; i++) + enable_irq(gpio_to_irq(di->keymap[i].gpio)); + hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + return 0; + } + + if (func == GPIO_EVENT_FUNC_INIT) { + if (ktime_to_ns(di->poll_time) <= 0) + di->poll_time = ktime_set(0, 20 * NSEC_PER_MSEC); + + *data = ds = kzalloc(sizeof(*ds) + sizeof(ds->key_state[0]) * + di->keymap_size, GFP_KERNEL); + if (ds == NULL) { + ret = -ENOMEM; + pr_err("gpio_event_input_func: " + "Failed to allocate private data\n"); + goto err_ds_alloc_failed; + } + ds->debounce_count = di->keymap_size; + ds->input_devs = input_devs; + ds->info = di; + wlname = kasprintf(GFP_KERNEL, "gpio_input:%s%s", + input_devs->dev[0]->name, + (input_devs->count > 1) ? "..." : ""); + + ds->ws = wakeup_source_register(wlname); + kfree(wlname); + if (!ds->ws) { + ret = -ENOMEM; + pr_err("gpio_event_input_func: " + "Failed to allocate wakeup source\n"); + goto err_ws_failed; + } + + spin_lock_init(&ds->irq_lock); + + for (i = 0; i < di->keymap_size; i++) { + int dev = di->keymap[i].dev; + if (dev >= input_devs->count) { + pr_err("gpio_event_input_func: bad device " + "index %d >= %d for key code %d\n", + dev, input_devs->count, + di->keymap[i].code); + ret = -EINVAL; + goto err_bad_keymap; + } + input_set_capability(input_devs->dev[dev], di->type, + di->keymap[i].code); + ds->key_state[i].ds = ds; + ds->key_state[i].debounce = DEBOUNCE_UNKNOWN; + } + + for (i = 0; i < di->keymap_size; i++) { + ret = gpio_request(di->keymap[i].gpio, "gpio_kp_in"); + if (ret) { + pr_err("gpio_event_input_func: gpio_request " + "failed for %d\n", di->keymap[i].gpio); + goto err_gpio_request_failed; + } + ret = gpio_direction_input(di->keymap[i].gpio); + if (ret) { + pr_err("gpio_event_input_func: " + "gpio_direction_input failed for %d\n", + di->keymap[i].gpio); + goto err_gpio_configure_failed; + } + } + + ret = gpio_event_input_request_irqs(ds); + + spin_lock_irqsave(&ds->irq_lock, irqflags); + ds->use_irq = ret == 0; + + pr_info("GPIO Input Driver: Start gpio inputs for %s%s in %s " + "mode\n", input_devs->dev[0]->name, + (input_devs->count > 1) ? "..." : "", + ret == 0 ? "interrupt" : "polling"); + + hrtimer_init(&ds->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ds->timer.function = gpio_event_input_timer_func; + hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + return 0; + } + + ret = 0; + spin_lock_irqsave(&ds->irq_lock, irqflags); + hrtimer_cancel(&ds->timer); + if (ds->use_irq) { + for (i = di->keymap_size - 1; i >= 0; i--) { + int irq = gpio_to_irq(di->keymap[i].gpio); + if (ds->info->info.no_suspend) + disable_irq_wake(irq); + free_irq(irq, &ds->key_state[i]); + } + } + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + + for (i = di->keymap_size - 1; i >= 0; i--) { +err_gpio_configure_failed: + gpio_free(di->keymap[i].gpio); +err_gpio_request_failed: + ; + } +err_bad_keymap: + wakeup_source_unregister(ds->ws); +err_ws_failed: + kfree(ds); +err_ds_alloc_failed: + return ret; +} diff --git a/drivers/input/misc/gpio_matrix.c b/drivers/input/misc/gpio_matrix.c new file mode 100644 index 00000000000000..eaa9e89d473acb --- /dev/null +++ b/drivers/input/misc/gpio_matrix.c @@ -0,0 +1,441 @@ +/* drivers/input/misc/gpio_matrix.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +struct gpio_kp { + struct gpio_event_input_devs *input_devs; + struct gpio_event_matrix_info *keypad_info; + struct hrtimer timer; + struct wake_lock wake_lock; + int current_output; + unsigned int use_irq:1; + unsigned int key_state_changed:1; + unsigned int last_key_state_changed:1; + unsigned int some_keys_pressed:2; + unsigned int disabled_irq:1; + unsigned long keys_pressed[0]; +}; + +static void clear_phantom_key(struct gpio_kp *kp, int out, int in) +{ + struct gpio_event_matrix_info *mi = kp->keypad_info; + int key_index = out * mi->ninputs + in; + unsigned short keyentry = mi->keymap[key_index]; + unsigned short keycode = keyentry & MATRIX_KEY_MASK; + unsigned short dev = keyentry >> MATRIX_CODE_BITS; + + if (!test_bit(keycode, kp->input_devs->dev[dev]->key)) { + if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS) + pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) " + "cleared\n", keycode, out, in, + mi->output_gpios[out], mi->input_gpios[in]); + __clear_bit(key_index, kp->keys_pressed); + } else { + if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS) + pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) " + "not cleared\n", keycode, out, in, + mi->output_gpios[out], mi->input_gpios[in]); + } +} + +static int restore_keys_for_input(struct gpio_kp *kp, int out, int in) +{ + int rv = 0; + int key_index; + + key_index = out * kp->keypad_info->ninputs + in; + while (out < kp->keypad_info->noutputs) { + if (test_bit(key_index, kp->keys_pressed)) { + rv = 1; + clear_phantom_key(kp, out, in); + } + key_index += kp->keypad_info->ninputs; + out++; + } + return rv; +} + +static void remove_phantom_keys(struct gpio_kp *kp) +{ + int out, in, inp; + int key_index; + + if (kp->some_keys_pressed < 3) + return; + + for (out = 0; out < kp->keypad_info->noutputs; out++) { + inp = -1; + key_index = out * kp->keypad_info->ninputs; + for (in = 0; in < kp->keypad_info->ninputs; in++, key_index++) { + if (test_bit(key_index, kp->keys_pressed)) { + if (inp == -1) { + inp = in; + continue; + } + if (inp >= 0) { + if (!restore_keys_for_input(kp, out + 1, + inp)) + break; + clear_phantom_key(kp, out, inp); + inp = -2; + } + restore_keys_for_input(kp, out, in); + } + } + } +} + +static void report_key(struct gpio_kp *kp, int key_index, int out, int in) +{ + struct gpio_event_matrix_info *mi = kp->keypad_info; + int pressed = test_bit(key_index, kp->keys_pressed); + unsigned short keyentry = mi->keymap[key_index]; + unsigned short keycode = keyentry & MATRIX_KEY_MASK; + unsigned short dev = keyentry >> MATRIX_CODE_BITS; + + if (pressed != test_bit(keycode, kp->input_devs->dev[dev]->key)) { + if (keycode == KEY_RESERVED) { + if (mi->flags & GPIOKPF_PRINT_UNMAPPED_KEYS) + pr_info("gpiomatrix: unmapped key, %d-%d " + "(%d-%d) changed to %d\n", + out, in, mi->output_gpios[out], + mi->input_gpios[in], pressed); + } else { + if (mi->flags & GPIOKPF_PRINT_MAPPED_KEYS) + pr_info("gpiomatrix: key %x, %d-%d (%d-%d) " + "changed to %d\n", keycode, + out, in, mi->output_gpios[out], + mi->input_gpios[in], pressed); + input_report_key(kp->input_devs->dev[dev], keycode, pressed); + } + } +} + +static void report_sync(struct gpio_kp *kp) +{ + int i; + + for (i = 0; i < kp->input_devs->count; i++) + input_sync(kp->input_devs->dev[i]); +} + +static enum hrtimer_restart gpio_keypad_timer_func(struct hrtimer *timer) +{ + int out, in; + int key_index; + int gpio; + struct gpio_kp *kp = container_of(timer, struct gpio_kp, timer); + struct gpio_event_matrix_info *mi = kp->keypad_info; + unsigned gpio_keypad_flags = mi->flags; + unsigned polarity = !!(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH); + + out = kp->current_output; + if (out == mi->noutputs) { + out = 0; + kp->last_key_state_changed = kp->key_state_changed; + kp->key_state_changed = 0; + kp->some_keys_pressed = 0; + } else { + key_index = out * mi->ninputs; + for (in = 0; in < mi->ninputs; in++, key_index++) { + gpio = mi->input_gpios[in]; + if (gpio_get_value(gpio) ^ !polarity) { + if (kp->some_keys_pressed < 3) + kp->some_keys_pressed++; + kp->key_state_changed |= !__test_and_set_bit( + key_index, kp->keys_pressed); + } else + kp->key_state_changed |= __test_and_clear_bit( + key_index, kp->keys_pressed); + } + gpio = mi->output_gpios[out]; + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(gpio, !polarity); + else + gpio_direction_input(gpio); + out++; + } + kp->current_output = out; + if (out < mi->noutputs) { + gpio = mi->output_gpios[out]; + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(gpio, polarity); + else + gpio_direction_output(gpio, polarity); + hrtimer_start(timer, mi->settle_time, HRTIMER_MODE_REL); + return HRTIMER_NORESTART; + } + if (gpio_keypad_flags & GPIOKPF_DEBOUNCE) { + if (kp->key_state_changed) { + hrtimer_start(&kp->timer, mi->debounce_delay, + HRTIMER_MODE_REL); + return HRTIMER_NORESTART; + } + kp->key_state_changed = kp->last_key_state_changed; + } + if (kp->key_state_changed) { + if (gpio_keypad_flags & GPIOKPF_REMOVE_SOME_PHANTOM_KEYS) + remove_phantom_keys(kp); + key_index = 0; + for (out = 0; out < mi->noutputs; out++) + for (in = 0; in < mi->ninputs; in++, key_index++) + report_key(kp, key_index, out, in); + report_sync(kp); + } + if (!kp->use_irq || kp->some_keys_pressed) { + hrtimer_start(timer, mi->poll_time, HRTIMER_MODE_REL); + return HRTIMER_NORESTART; + } + + /* No keys are pressed, reenable interrupt */ + for (out = 0; out < mi->noutputs; out++) { + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(mi->output_gpios[out], polarity); + else + gpio_direction_output(mi->output_gpios[out], polarity); + } + for (in = 0; in < mi->ninputs; in++) + enable_irq(gpio_to_irq(mi->input_gpios[in])); + wake_unlock(&kp->wake_lock); + return HRTIMER_NORESTART; +} + +static irqreturn_t gpio_keypad_irq_handler(int irq_in, void *dev_id) +{ + int i; + struct gpio_kp *kp = dev_id; + struct gpio_event_matrix_info *mi = kp->keypad_info; + unsigned gpio_keypad_flags = mi->flags; + + if (!kp->use_irq) { + /* ignore interrupt while registering the handler */ + kp->disabled_irq = 1; + disable_irq_nosync(irq_in); + return IRQ_HANDLED; + } + + for (i = 0; i < mi->ninputs; i++) + disable_irq_nosync(gpio_to_irq(mi->input_gpios[i])); + for (i = 0; i < mi->noutputs; i++) { + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(mi->output_gpios[i], + !(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH)); + else + gpio_direction_input(mi->output_gpios[i]); + } + wake_lock(&kp->wake_lock); + hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + return IRQ_HANDLED; +} + +static int gpio_keypad_request_irqs(struct gpio_kp *kp) +{ + int i; + int err; + unsigned int irq; + unsigned long request_flags; + struct gpio_event_matrix_info *mi = kp->keypad_info; + + switch (mi->flags & (GPIOKPF_ACTIVE_HIGH|GPIOKPF_LEVEL_TRIGGERED_IRQ)) { + default: + request_flags = IRQF_TRIGGER_FALLING; + break; + case GPIOKPF_ACTIVE_HIGH: + request_flags = IRQF_TRIGGER_RISING; + break; + case GPIOKPF_LEVEL_TRIGGERED_IRQ: + request_flags = IRQF_TRIGGER_LOW; + break; + case GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_ACTIVE_HIGH: + request_flags = IRQF_TRIGGER_HIGH; + break; + } + + for (i = 0; i < mi->ninputs; i++) { + err = irq = gpio_to_irq(mi->input_gpios[i]); + if (err < 0) + goto err_gpio_get_irq_num_failed; + err = request_irq(irq, gpio_keypad_irq_handler, request_flags, + "gpio_kp", kp); + if (err) { + pr_err("gpiomatrix: request_irq failed for input %d, " + "irq %d\n", mi->input_gpios[i], irq); + goto err_request_irq_failed; + } + err = enable_irq_wake(irq); + if (err) { + pr_err("gpiomatrix: set_irq_wake failed for input %d, " + "irq %d\n", mi->input_gpios[i], irq); + } + disable_irq(irq); + if (kp->disabled_irq) { + kp->disabled_irq = 0; + enable_irq(irq); + } + } + return 0; + + for (i = mi->noutputs - 1; i >= 0; i--) { + free_irq(gpio_to_irq(mi->input_gpios[i]), kp); +err_request_irq_failed: +err_gpio_get_irq_num_failed: + ; + } + return err; +} + +int gpio_event_matrix_func(struct gpio_event_input_devs *input_devs, + struct gpio_event_info *info, void **data, int func) +{ + int i; + int err; + int key_count; + struct gpio_kp *kp; + struct gpio_event_matrix_info *mi; + + mi = container_of(info, struct gpio_event_matrix_info, info); + if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME) { + /* TODO: disable scanning */ + return 0; + } + + if (func == GPIO_EVENT_FUNC_INIT) { + if (mi->keymap == NULL || + mi->input_gpios == NULL || + mi->output_gpios == NULL) { + err = -ENODEV; + pr_err("gpiomatrix: Incomplete pdata\n"); + goto err_invalid_platform_data; + } + key_count = mi->ninputs * mi->noutputs; + + *data = kp = kzalloc(sizeof(*kp) + sizeof(kp->keys_pressed[0]) * + BITS_TO_LONGS(key_count), GFP_KERNEL); + if (kp == NULL) { + err = -ENOMEM; + pr_err("gpiomatrix: Failed to allocate private data\n"); + goto err_kp_alloc_failed; + } + kp->input_devs = input_devs; + kp->keypad_info = mi; + for (i = 0; i < key_count; i++) { + unsigned short keyentry = mi->keymap[i]; + unsigned short keycode = keyentry & MATRIX_KEY_MASK; + unsigned short dev = keyentry >> MATRIX_CODE_BITS; + if (dev >= input_devs->count) { + pr_err("gpiomatrix: bad device index %d >= " + "%d for key code %d\n", + dev, input_devs->count, keycode); + err = -EINVAL; + goto err_bad_keymap; + } + if (keycode && keycode <= KEY_MAX) + input_set_capability(input_devs->dev[dev], + EV_KEY, keycode); + } + + for (i = 0; i < mi->noutputs; i++) { + err = gpio_request(mi->output_gpios[i], "gpio_kp_out"); + if (err) { + pr_err("gpiomatrix: gpio_request failed for " + "output %d\n", mi->output_gpios[i]); + goto err_request_output_gpio_failed; + } + if (gpio_cansleep(mi->output_gpios[i])) { + pr_err("gpiomatrix: unsupported output gpio %d," + " can sleep\n", mi->output_gpios[i]); + err = -EINVAL; + goto err_output_gpio_configure_failed; + } + if (mi->flags & GPIOKPF_DRIVE_INACTIVE) + err = gpio_direction_output(mi->output_gpios[i], + !(mi->flags & GPIOKPF_ACTIVE_HIGH)); + else + err = gpio_direction_input(mi->output_gpios[i]); + if (err) { + pr_err("gpiomatrix: gpio_configure failed for " + "output %d\n", mi->output_gpios[i]); + goto err_output_gpio_configure_failed; + } + } + for (i = 0; i < mi->ninputs; i++) { + err = gpio_request(mi->input_gpios[i], "gpio_kp_in"); + if (err) { + pr_err("gpiomatrix: gpio_request failed for " + "input %d\n", mi->input_gpios[i]); + goto err_request_input_gpio_failed; + } + err = gpio_direction_input(mi->input_gpios[i]); + if (err) { + pr_err("gpiomatrix: gpio_direction_input failed" + " for input %d\n", mi->input_gpios[i]); + goto err_gpio_direction_input_failed; + } + } + kp->current_output = mi->noutputs; + kp->key_state_changed = 1; + + hrtimer_init(&kp->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + kp->timer.function = gpio_keypad_timer_func; + wake_lock_init(&kp->wake_lock, WAKE_LOCK_SUSPEND, "gpio_kp"); + err = gpio_keypad_request_irqs(kp); + kp->use_irq = err == 0; + + pr_info("GPIO Matrix Keypad Driver: Start keypad matrix for " + "%s%s in %s mode\n", input_devs->dev[0]->name, + (input_devs->count > 1) ? "..." : "", + kp->use_irq ? "interrupt" : "polling"); + + if (kp->use_irq) + wake_lock(&kp->wake_lock); + hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + + return 0; + } + + err = 0; + kp = *data; + + if (kp->use_irq) + for (i = mi->noutputs - 1; i >= 0; i--) + free_irq(gpio_to_irq(mi->input_gpios[i]), kp); + + hrtimer_cancel(&kp->timer); + wake_lock_destroy(&kp->wake_lock); + for (i = mi->noutputs - 1; i >= 0; i--) { +err_gpio_direction_input_failed: + gpio_free(mi->input_gpios[i]); +err_request_input_gpio_failed: + ; + } + for (i = mi->noutputs - 1; i >= 0; i--) { +err_output_gpio_configure_failed: + gpio_free(mi->output_gpios[i]); +err_request_output_gpio_failed: + ; + } +err_bad_keymap: + kfree(kp); +err_kp_alloc_failed: +err_invalid_platform_data: + return err; +} diff --git a/drivers/input/misc/gpio_output.c b/drivers/input/misc/gpio_output.c new file mode 100644 index 00000000000000..2aac2fad0a17bf --- /dev/null +++ b/drivers/input/misc/gpio_output.c @@ -0,0 +1,97 @@ +/* drivers/input/misc/gpio_output.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include + +int gpio_event_output_event( + struct gpio_event_input_devs *input_devs, struct gpio_event_info *info, + void **data, unsigned int dev, unsigned int type, + unsigned int code, int value) +{ + int i; + struct gpio_event_output_info *oi; + oi = container_of(info, struct gpio_event_output_info, info); + if (type != oi->type) + return 0; + if (!(oi->flags & GPIOEDF_ACTIVE_HIGH)) + value = !value; + for (i = 0; i < oi->keymap_size; i++) + if (dev == oi->keymap[i].dev && code == oi->keymap[i].code) + gpio_set_value(oi->keymap[i].gpio, value); + return 0; +} + +int gpio_event_output_func( + struct gpio_event_input_devs *input_devs, struct gpio_event_info *info, + void **data, int func) +{ + int ret; + int i; + struct gpio_event_output_info *oi; + oi = container_of(info, struct gpio_event_output_info, info); + + if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME) + return 0; + + if (func == GPIO_EVENT_FUNC_INIT) { + int output_level = !(oi->flags & GPIOEDF_ACTIVE_HIGH); + + for (i = 0; i < oi->keymap_size; i++) { + int dev = oi->keymap[i].dev; + if (dev >= input_devs->count) { + pr_err("gpio_event_output_func: bad device " + "index %d >= %d for key code %d\n", + dev, input_devs->count, + oi->keymap[i].code); + ret = -EINVAL; + goto err_bad_keymap; + } + input_set_capability(input_devs->dev[dev], oi->type, + oi->keymap[i].code); + } + + for (i = 0; i < oi->keymap_size; i++) { + ret = gpio_request(oi->keymap[i].gpio, + "gpio_event_output"); + if (ret) { + pr_err("gpio_event_output_func: gpio_request " + "failed for %d\n", oi->keymap[i].gpio); + goto err_gpio_request_failed; + } + ret = gpio_direction_output(oi->keymap[i].gpio, + output_level); + if (ret) { + pr_err("gpio_event_output_func: " + "gpio_direction_output failed for %d\n", + oi->keymap[i].gpio); + goto err_gpio_direction_output_failed; + } + } + return 0; + } + + ret = 0; + for (i = oi->keymap_size - 1; i >= 0; i--) { +err_gpio_direction_output_failed: + gpio_free(oi->keymap[i].gpio); +err_gpio_request_failed: + ; + } +err_bad_keymap: + return ret; +} + diff --git a/drivers/input/misc/hi6421_pwrkey.c b/drivers/input/misc/hi6421_pwrkey.c new file mode 100644 index 00000000000000..31d0145fc6884f --- /dev/null +++ b/drivers/input/misc/hi6421_pwrkey.c @@ -0,0 +1,153 @@ +/* + * hi6421_pwrkey.c - Hisilicon Hi6421 PMIC ONKEY driver + * + * Copyright (C) 2013 Hisilicon Ltd. + * Copyright (C) 2013 Linaro Ltd. + * Author: Haojian Zhuang + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +struct hi6421_onkey_info { + struct input_dev *idev; + int irq[4]; +}; + +static irqreturn_t hi6421_onkey_handler(int irq, void *data) +{ + struct hi6421_onkey_info *info = (struct hi6421_onkey_info *)data; + + /* only handle power down & power up event at here */ + if (irq == info->irq[0]) { + input_report_key(info->idev, KEY_POWER, 1); + input_sync(info->idev); + } else if (irq == info->irq[1]) { + input_report_key(info->idev, KEY_POWER, 0); + input_sync(info->idev); + } + return IRQ_HANDLED; +} + +static int hi6421_onkey_probe(struct platform_device *pdev) +{ + struct hi6421_onkey_info *info; + struct device *dev = &pdev->dev; + int ret; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->idev = input_allocate_device(); + if (!info->idev) { + dev_err(&pdev->dev, "Failed to allocate input device\n"); + return -ENOMEM; + } + info->idev->name = "hi6421_on"; + info->idev->phys = "hi6421_on/input0"; + info->idev->dev.parent = &pdev->dev; + info->idev->evbit[0] = BIT_MASK(EV_KEY); + __set_bit(KEY_POWER, info->idev->keybit); + + info->irq[0] = platform_get_irq_byname(pdev, "down"); + if (info->irq[0] < 0) { + ret = -ENOENT; + goto err_irq0; + } + ret = request_irq(info->irq[0], hi6421_onkey_handler, + IRQF_DISABLED, "down", info); + if (ret < 0) + goto err_irq0; + info->irq[1] = platform_get_irq_byname(pdev, "up"); + if (info->irq[1] < 0) { + ret = -ENOENT; + goto err_irq1; + } + ret = request_irq(info->irq[1], hi6421_onkey_handler, + IRQF_DISABLED, "up", info); + if (ret < 0) + goto err_irq1; + info->irq[2] = platform_get_irq_byname(pdev, "hold 1s"); + if (info->irq[2] < 0) { + ret = -ENOENT; + goto err_irq2; + } + ret = request_irq(info->irq[2], hi6421_onkey_handler, + IRQF_DISABLED, "hold 1s", info); + if (ret < 0) + goto err_irq2; + info->irq[3] = platform_get_irq_byname(pdev, "hold 10s"); + if (info->irq[3] < 0) { + ret = -ENOENT; + goto err_irq3; + } + ret = request_irq(info->irq[3], hi6421_onkey_handler, + IRQF_DISABLED, "hold 10s", info); + if (ret < 0) + goto err_irq3; + + ret = input_register_device(info->idev); + if (ret) { + dev_err(&pdev->dev, "Can't register input device: %d\n", ret); + goto err_reg; + } + + platform_set_drvdata(pdev, info); + return ret; +err_reg: + free_irq(info->irq[3], info); +err_irq3: + free_irq(info->irq[2], info); +err_irq2: + free_irq(info->irq[1], info); +err_irq1: + free_irq(info->irq[0], info); +err_irq0: + input_free_device(info->idev); + return ret; +} + +static int hi6421_onkey_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct of_device_id hi6421_onkey_of_match[] = { + { .compatible = "hisilicon,hi6421-onkey", }, + { }, +}; +MODULE_DEVICE_TABLE(of, hi6421_onkey_of_match); + +static struct platform_driver hi6421_onkey_driver = { + .probe = hi6421_onkey_probe, + .remove = hi6421_onkey_remove, + .driver = { + .owner = THIS_MODULE, + .name = "hi6421-onkey", + .of_match_table = hi6421_onkey_of_match, + }, +}; +module_platform_driver(hi6421_onkey_driver); + +MODULE_AUTHOR("Haojian Zhuang + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KEYCHORD_NAME "keychord" +#define BUFFER_SIZE 16 + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("Key chord input driver"); +MODULE_SUPPORTED_DEVICE("keychord"); +MODULE_LICENSE("GPL"); + +#define NEXT_KEYCHORD(kc) ((struct input_keychord *) \ + ((char *)kc + sizeof(struct input_keychord) + \ + kc->count * sizeof(kc->keycodes[0]))) + +struct keychord_device { + struct input_handler input_handler; + int registered; + + /* list of keychords to monitor */ + struct input_keychord *keychords; + int keychord_count; + + /* bitmask of keys contained in our keychords */ + unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; + /* current state of the keys */ + unsigned long keystate[BITS_TO_LONGS(KEY_CNT)]; + /* number of keys that are currently pressed */ + int key_down; + + /* second input_device_id is needed for null termination */ + struct input_device_id device_ids[2]; + + spinlock_t lock; + wait_queue_head_t waitq; + unsigned char head; + unsigned char tail; + __u16 buff[BUFFER_SIZE]; +}; + +static int check_keychord(struct keychord_device *kdev, + struct input_keychord *keychord) +{ + int i; + + if (keychord->count != kdev->key_down) + return 0; + + for (i = 0; i < keychord->count; i++) { + if (!test_bit(keychord->keycodes[i], kdev->keystate)) + return 0; + } + + /* we have a match */ + return 1; +} + +static void keychord_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + struct keychord_device *kdev = handle->private; + struct input_keychord *keychord; + unsigned long flags; + int i, got_chord = 0; + + if (type != EV_KEY || code >= KEY_MAX) + return; + + spin_lock_irqsave(&kdev->lock, flags); + /* do nothing if key state did not change */ + if (!test_bit(code, kdev->keystate) == !value) + goto done; + __change_bit(code, kdev->keystate); + if (value) + kdev->key_down++; + else + kdev->key_down--; + + /* don't notify on key up */ + if (!value) + goto done; + /* ignore this event if it is not one of the keys we are monitoring */ + if (!test_bit(code, kdev->keybit)) + goto done; + + keychord = kdev->keychords; + if (!keychord) + goto done; + + /* check to see if the keyboard state matches any keychords */ + for (i = 0; i < kdev->keychord_count; i++) { + if (check_keychord(kdev, keychord)) { + kdev->buff[kdev->head] = keychord->id; + kdev->head = (kdev->head + 1) % BUFFER_SIZE; + got_chord = 1; + break; + } + /* skip to next keychord */ + keychord = NEXT_KEYCHORD(keychord); + } + +done: + spin_unlock_irqrestore(&kdev->lock, flags); + + if (got_chord) { + pr_info("keychord: got keychord id %d. Any tasks: %d\n", + keychord->id, + !list_empty_careful(&kdev->waitq.task_list)); + wake_up_interruptible(&kdev->waitq); + } +} + +static int keychord_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + int i, ret; + struct input_handle *handle; + struct keychord_device *kdev = + container_of(handler, struct keychord_device, input_handler); + + /* + * ignore this input device if it does not contain any keycodes + * that we are monitoring + */ + for (i = 0; i < KEY_MAX; i++) { + if (test_bit(i, kdev->keybit) && test_bit(i, dev->keybit)) + break; + } + if (i == KEY_MAX) + return -ENODEV; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = KEYCHORD_NAME; + handle->private = kdev; + + ret = input_register_handle(handle); + if (ret) + goto err_input_register_handle; + + ret = input_open_device(handle); + if (ret) + goto err_input_open_device; + + pr_info("keychord: using input dev %s for fevent\n", dev->name); + + return 0; + +err_input_open_device: + input_unregister_handle(handle); +err_input_register_handle: + kfree(handle); + return ret; +} + +static void keychord_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +/* + * keychord_read is used to read keychord events from the driver + */ +static ssize_t keychord_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct keychord_device *kdev = file->private_data; + __u16 id; + int retval; + unsigned long flags; + + if (count < sizeof(id)) + return -EINVAL; + count = sizeof(id); + + if (kdev->head == kdev->tail && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(kdev->waitq, + kdev->head != kdev->tail); + if (retval) + return retval; + + spin_lock_irqsave(&kdev->lock, flags); + /* pop a keychord ID off the queue */ + id = kdev->buff[kdev->tail]; + kdev->tail = (kdev->tail + 1) % BUFFER_SIZE; + spin_unlock_irqrestore(&kdev->lock, flags); + + if (copy_to_user(buffer, &id, count)) + return -EFAULT; + + return count; +} + +/* + * keychord_write is used to configure the driver + */ +static ssize_t keychord_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct keychord_device *kdev = file->private_data; + struct input_keychord *keychords = 0; + struct input_keychord *keychord, *next, *end; + int ret, i, key; + unsigned long flags; + + if (count < sizeof(struct input_keychord)) + return -EINVAL; + keychords = kzalloc(count, GFP_KERNEL); + if (!keychords) + return -ENOMEM; + + /* read list of keychords from userspace */ + if (copy_from_user(keychords, buffer, count)) { + kfree(keychords); + return -EFAULT; + } + + /* unregister handler before changing configuration */ + if (kdev->registered) { + input_unregister_handler(&kdev->input_handler); + kdev->registered = 0; + } + + spin_lock_irqsave(&kdev->lock, flags); + /* clear any existing configuration */ + kfree(kdev->keychords); + kdev->keychords = 0; + kdev->keychord_count = 0; + kdev->key_down = 0; + memset(kdev->keybit, 0, sizeof(kdev->keybit)); + memset(kdev->keystate, 0, sizeof(kdev->keystate)); + kdev->head = kdev->tail = 0; + + keychord = keychords; + end = (struct input_keychord *)((char *)keychord + count); + + while (keychord < end) { + next = NEXT_KEYCHORD(keychord); + if (keychord->count <= 0 || next > end) { + pr_err("keychord: invalid keycode count %d\n", + keychord->count); + goto err_unlock_return; + } + if (keychord->version != KEYCHORD_VERSION) { + pr_err("keychord: unsupported version %d\n", + keychord->version); + goto err_unlock_return; + } + + /* keep track of the keys we are monitoring in keybit */ + for (i = 0; i < keychord->count; i++) { + key = keychord->keycodes[i]; + if (key < 0 || key >= KEY_CNT) { + pr_err("keychord: keycode %d out of range\n", + key); + goto err_unlock_return; + } + __set_bit(key, kdev->keybit); + } + + kdev->keychord_count++; + keychord = next; + } + + kdev->keychords = keychords; + spin_unlock_irqrestore(&kdev->lock, flags); + + ret = input_register_handler(&kdev->input_handler); + if (ret) { + kfree(keychords); + kdev->keychords = 0; + return ret; + } + kdev->registered = 1; + + return count; + +err_unlock_return: + spin_unlock_irqrestore(&kdev->lock, flags); + kfree(keychords); + return -EINVAL; +} + +static unsigned int keychord_poll(struct file *file, poll_table *wait) +{ + struct keychord_device *kdev = file->private_data; + + poll_wait(file, &kdev->waitq, wait); + + if (kdev->head != kdev->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int keychord_open(struct inode *inode, struct file *file) +{ + struct keychord_device *kdev; + + kdev = kzalloc(sizeof(struct keychord_device), GFP_KERNEL); + if (!kdev) + return -ENOMEM; + + spin_lock_init(&kdev->lock); + init_waitqueue_head(&kdev->waitq); + + kdev->input_handler.event = keychord_event; + kdev->input_handler.connect = keychord_connect; + kdev->input_handler.disconnect = keychord_disconnect; + kdev->input_handler.name = KEYCHORD_NAME; + kdev->input_handler.id_table = kdev->device_ids; + + kdev->device_ids[0].flags = INPUT_DEVICE_ID_MATCH_EVBIT; + __set_bit(EV_KEY, kdev->device_ids[0].evbit); + + file->private_data = kdev; + + return 0; +} + +static int keychord_release(struct inode *inode, struct file *file) +{ + struct keychord_device *kdev = file->private_data; + + if (kdev->registered) + input_unregister_handler(&kdev->input_handler); + kfree(kdev); + + return 0; +} + +static const struct file_operations keychord_fops = { + .owner = THIS_MODULE, + .open = keychord_open, + .release = keychord_release, + .read = keychord_read, + .write = keychord_write, + .poll = keychord_poll, +}; + +static struct miscdevice keychord_misc = { + .fops = &keychord_fops, + .name = KEYCHORD_NAME, + .minor = MISC_DYNAMIC_MINOR, +}; + +static int __init keychord_init(void) +{ + return misc_register(&keychord_misc); +} + +static void __exit keychord_exit(void) +{ + misc_deregister(&keychord_misc); +} + +module_init(keychord_init); +module_exit(keychord_exit); diff --git a/drivers/input/serio/ambakmi.c b/drivers/input/serio/ambakmi.c index 762b08432de003..08055ab0897038 100644 --- a/drivers/input/serio/ambakmi.c +++ b/drivers/input/serio/ambakmi.c @@ -79,7 +79,7 @@ static int amba_kmi_open(struct serio *io) writeb(divisor, KMICLKDIV); writeb(KMICR_EN, KMICR); - ret = request_irq(kmi->irq, amba_kmi_int, 0, "kmi-pl050", kmi); + ret = request_irq(kmi->irq, amba_kmi_int, IRQF_SHARED, "kmi-pl050", kmi); if (ret) { printk(KERN_ERR "kmi: failed to claim IRQ%d\n", kmi->irq); writeb(0, KMICR); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 07e9e82029d190..9ebc0d219be53d 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -943,4 +943,15 @@ config TOUCHSCREEN_ZFORCE To compile this driver as a module, choose M here: the module will be called zforce_ts. +config TOUCHSCREEN_MXT224E + tristate "Atmel mXT224E based touchscreens" + depends on I2C + help + Say Y here if you have a TPS6507x based touchscreen + controller. + + If unsure, say N. + + module will be called atmel_mXT224E. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 62801f213346a8..c343315f04dd52 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -77,3 +77,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o +obj-$(CONFIG_TOUCHSCREEN_MXT224E) += atmel_mXT224E.o diff --git a/drivers/input/touchscreen/atmel_mXT224E.c b/drivers/input/touchscreen/atmel_mXT224E.c new file mode 100644 index 00000000000000..0a7fab242d9811 --- /dev/null +++ b/drivers/input/touchscreen/atmel_mXT224E.c @@ -0,0 +1,1473 @@ +/* drivers/input/touchscreen/atmel_mXT224E.c - ATMEL Touch driver + * + * Copyright (C) 2008 ATMEL + * Copyright (C) 2011 Huawei Corporation. + * + * Based on touchscreen code from Atmel Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef TS_ATMEL_DEBUG +#define TS_DEBUG_ATMEL(fmt, args...) pr_info(fmt, ##args) +#else +#define TS_DEBUG_ATMEL(fmt, args...) +#endif + +#define ATMEL_MXT224E_NAME "atmel_mxt224e" + +#define INFO_BLK_FID 0 +#define INFO_BLK_VID 1 +#define INFO_BLK_VER 2 +#define INFO_BLK_BUILD 3 +#define INFO_BLK_XSIZE 4 +#define INFO_BLK_YSIZE 5 +#define INFO_BLK_OBJS 6 + +#define OBJ_TABLE_TYPE 0 +#define OBJ_TABLE_LSB 1 +#define OBJ_TABLE_MSB 2 +#define OBJ_TABLE_SIZE 3 +#define OBJ_TABLE_INSTANCES 4 +#define OBJ_TABLE_RIDS 5 + +#define RESERVED_T0 0u +#define RESERVED_T1 1u +#define DEBUG_DELTAS_T2 2u +#define DEBUG_REFERENCES_T3 3u +#define DEBUG_SIGNALS_T4 4u +#define GEN_MESSAGEPROCESSOR_T5 5u +#define GEN_COMMANDPROCESSOR_T6 6u +#define GEN_POWERCONFIG_T7 7u +#define GEN_ACQUISITIONCONFIG_T8 8u +#define TOUCH_MULTITOUCHSCREEN_T9 9u +#define TOUCH_SINGLETOUCHSCREEN_T10 10u +#define TOUCH_XSLIDER_T11 11u +#define TOUCH_YSLIDER_T12 12u +#define TOUCH_XWHEEL_T13 13u +#define TOUCH_YWHEEL_T14 14u +#define TOUCH_KEYARRAY_T15 15u +#define PROCG_SIGNALFILTER_T16 16u +#define PROCI_LINEARIZATIONTABLE_T17 17u +#define SPT_COMCONFIG_T18 18u +#define SPT_GPIOPWM_T19 19u +#define PROCI_GRIPFACESUPPRESSION_T20 20u +#define RESERVED_T21 21u +#define PROCG_NOISESUPPRESSION_T22 22u +#define TOUCH_PROXIMITY_T23 23u +#define PROCI_ONETOUCHGESTUREPROCESSOR_T24 24u +#define SPT_SELFTEST_T25 25u +#define DEBUG_CTERANGE_T26 26u +#define PROCI_TWOTOUCHGESTUREPROCESSOR_T27 27u +#define SPT_CTECONFIG_T28 28u +#define SPT_GPI_T29 29u +#define SPT_GATE_T30 30u +#define TOUCH_KEYSET_T31 31u +#define TOUCH_XSLIDERSET_T32 32u +#define DIAGNOSTIC_T37 37u +#define PROCI_GRIPSUPPRESSION_T40 40u +#define PROCI_TOUCHSUPPRESSION_T42 42u +#define SPT_CTECONFIG_T46 46u +#define PROCI_STYLUS_T47 47u +#define PROCG_NOISESUPPRESSION_T48 48u + +#define T37_PAGE_SIZE 128 + +#define T37_TCH_FLAG_SIZE 80 +#define T37_TCH_FLAG_IDX 0 +#define T37_ATCH_FLAG_IDX 40 + +#define T37_MODE 0 +#define T37_PAGE 1 +#define T37_DATA 2 /* n bytes */ + +#define T37_PAGE_NUM0 0 +#define T37_PAGE_NUM1 1 +#define T37_PAGE_NUM2 2 +#define T37_PAGE_NUM3 3 + +#define MSG_RID 0 + +#define T6_CFG_RESET 0 +#define T6_CFG_BACKUPNV 1 +#define T6_CFG_CALIBRATE 2 +#define T6_CFG_REPORTALL 3 +/* Reserved */ +#define T6_CFG_DIAG 5 + +#define T6_CFG_DIAG_CMD_PAGEUP 0x01 +#define T6_CFG_DIAG_CMD_PAGEDOWN 0x02 +#define T6_CFG_DIAG_CMD_DELTAS 0x10 +#define T6_CFG_DIAG_CMD_REF 0x11 +#define T6_CFG_DIAG_CMD_CTE 0x31 +#define T6_CFG_DIAG_CMD_TCH 0xF3 + +#define T6_MSG_STATUS 1 +#define T6_MSG_CHECKSUM 2 /* three bytes */ + +#define T6_MSG_STATUS_COMSERR BIT(2) +#define T6_MSG_STATUS_CFGERR BIT(3) +#define T6_MSG_STATUS_CAL BIT(4) +#define T6_MSG_STATUS_SIGERR BIT(5) +#define T6_MSG_STATUS_OFL BIT(6) +#define T6_MSG_STATUS_RESET BIT(7) + +#define T7_CFG_IDLEACQINT 0 +#define T7_CFG_ACTVACQINT 1 +#define T7_CFG_ACTV2IDLETO 2 + +#define T8_CFG_CHRGTIME 0 +/* Reserved */ +#define T8_CFG_TCHDRIFT 2 +#define T8_CFG_DRIFTST 3 +#define T8_CFG_TCHAUTOCAL 4 +#define T8_CFG_SYNC 5 +#define T8_CFG_ATCHCALST 6 +#define T8_CFG_ATCHCALSTHR 7 +#define T8_CFG_ATCHFRCCALTHR 8 /* FW v2.x */ +#define T8_CFG_ATCHFRCCALRATIO 9 /* FW v2.x */ + +#define T9_CFG_CTRL 0 +#define T9_CFG_XORIGIN 1 +#define T9_CFG_YORIGIN 2 +#define T9_CFG_XSIZE 3 +#define T9_CFG_YSIZE 4 +#define T9_CFG_AKSCFG 5 +#define T9_CFG_BLEN 6 +#define T9_CFG_TCHTHR 7 +#define T9_CFG_TCHDI 8 +#define T9_CFG_ORIENT 9 +#define T9_CFG_MRGTIMEOUT 10 +#define T9_CFG_MOVHYSTI 11 +#define T9_CFG_MOVHYSTN 12 +#define T9_CFG_MOVFILTER 13 +#define T9_CFG_NUMTOUCH 14 +#define T9_CFG_MRGHYST 15 +#define T9_CFG_MRGTHR 16 +#define T9_CFG_AMPHYST 17 +#define T9_CFG_XRANGE 18 /* two bytes */ +#define T9_CFG_YRANGE 20 /* two bytes */ +#define T9_CFG_XLOCLIP 22 +#define T9_CFG_XHICLIP 23 +#define T9_CFG_YLOCLIP 24 +#define T9_CFG_YHICLIP 25 +#define T9_CFG_XEDGECTRL 26 +#define T9_CFG_XEDGEDIST 27 +#define T9_CFG_YEDGECTRL 28 +#define T9_CFG_YEDGEDIST 29 +#define T9_CFG_JUMPLIMIT 30 +#define T9_CFG_TCHHYST 31 /* FW v2.x */ + +#define T9_MSG_STATUS 1 +#define T9_MSG_XPOSMSB 2 +#define T9_MSG_YPOSMSB 3 +#define T9_MSG_XYPOSLSB 4 +#define T9_MSG_TCHAREA 5 +#define T9_MSG_TCHAMPLITUDE 6 +#define T9_MSG_TCHVECTOR 7 + +#define T9_MSG_STATUS_UNGRIP BIT(0) /* FW v2.x */ +#define T9_MSG_STATUS_SUPPRESS BIT(1) +#define T9_MSG_STATUS_AMP BIT(2) +#define T9_MSG_STATUS_VECTOR BIT(3) +#define T9_MSG_STATUS_MOVE BIT(4) +#define T9_MSG_STATUS_RELEASE BIT(5) +#define T9_MSG_STATUS_PRESS BIT(6) +#define T9_MSG_STATUS_DETECT BIT(7) + +#define T20_CFG_CTRL 0 +#define T20_CFG_XLOGRIP 1 +#define T20_CFG_XHIGRIP 2 +#define T20_CFG_YLOGRIP 3 +#define T20_CFG_YHIGRIP 4 +#define T20_CFG_MAXTCHS 5 +/* Reserved */ +#define T20_CFG_SZTHR1 7 +#define T20_CFG_SZTHR2 8 +#define T20_CFG_SHPTHR1 9 +#define T20_CFG_SHPTHR2 10 +#define T20_CFG_SHPEXTTO 11 + +#define T20_MSG_STATUS 1 + +#define T20_MSG_STATUS_FACESUP BIT(0) + +#define T22_CFG_CTRL 0 +/* Reserved */ +#define T22_CFG_GCAFUL 3 /* two bytes */ +#define T22_CFG_GCAFLL 5 /* two bytes */ +#define T22_CFG_ACTVGCAFVALID 7 +#define T22_CFG_NOISETHR 8 +/* Reserved */ +#define T22_CFG_FREQHOPSCALE 10 +#define T22_CFG_FREQ 11 /* five bytes */ +#define T22_CFG_IDLEGCAFVAILD 16 + +#define T22_MSG_STATUS 1 +#define T22_MSG_GCAFDEPTH 2 +#define T22_MSG_FREQINDEX 3 + +#define T22_MSG_STATUS_FHCHG BIT(0) +#define T22_MSG_STATUS_GCAFERR BIT(2) +#define T22_MSG_STATUS_FHERR BIT(3) +#define T22_MSG_STATUS_GCAFCHG BIT(4) + +#define T19_CFG_CTRL 0 +#define T19_CFG_REPORTMASK 1 +#define T19_CFG_DIR 2 +#define T19_CFG_INTPULLUP 3 +#define T19_CFG_OUT 4 +#define T19_CFG_WAKE 5 +#define T19_CFG_PWM 6 +#define T19_CFG_PERIOD 7 +#define T19_CFG_DUTY0 8 +#define T19_CFG_DUTY1 9 +#define T19_CFG_DUTY2 10 +#define T19_CFG_DUTY3 11 +#define T19_CFG_TRIGGER0 12 +#define T19_CFG_TRIGGER1 13 +#define T19_CFG_TRIGGER2 14 +#define T19_CFG_TRIGGER3 15 + +#define T19_CFG_CTRL_ENABLE BIT(0) +#define T19_CFG_CTRL_RPTEN BIT(1) +#define T19_CFG_CTRL_FORCERPT BIT(2) + +#define T19_MSG_STATUS 1 + +#define T25_CFG_CTRL 0 +#define T25_CFG_CMD 1 + +#define T25_MSG_STATUS 1 +#define T25_MSG_INFO 2 /* five bytes */ + +#define T28_CFG_CTRL 0 +#define T28_CFG_CMD 1 +#define T28_CFG_MODE 2 +#define T28_CFG_IDLEGCAFDEPTH 3 +#define T28_CFG_ACTVGCAFDEPTH 4 +#define T28_CFG_VOLTAGE 5 + +#define T28_CFG_MODE0_X 16 +#define T28_CFG_MODE0_Y 14 + +#define T28_MSG_STATUS 1 + +#define T48_NOISESUPPRESSION_CFG 1 + +/* cable_config[] of atmel_i2c_platform_data */ +/* config[] of atmel_config_data */ +#define CB_TCHTHR 0 +#define CB_NOISETHR 1 +#define CB_IDLEGCAFDEPTH 2 +#define CB_ACTVGCAFDEPTH 3 + +#define NC_TCHTHR 0 +#define NC_TCHDI 1 +#define NC_NOISETHR 2 + +/* filter_level */ +#define FL_XLOGRIPMIN 0 +#define FL_XLOGRIPMAX 1 +#define FL_XHIGRIPMIN 2 +#define FL_XHIGRIPMAX 3 + +struct info_id_t { + uint8_t family_id; + uint8_t variant_id; + uint8_t version; + uint8_t build; + uint8_t matrix_x_size; + uint8_t matrix_y_size; + uint8_t num_declared_objects; +}; + +struct object_t { + uint8_t object_type; + uint16_t i2c_address; + uint8_t size; + uint8_t instances; + uint8_t num_report_ids; + uint8_t report_ids; +}; + +struct atmel_virtual_key { + int keycode; + int range_min; + int range_max; +}; + +struct atmel_finger_data { + int x; + int y; + int w; + int z; +}; + +struct atmel_i2c_platform_data { + uint16_t version; + uint16_t source; + uint16_t abs_x_min; + uint16_t abs_x_max; + uint16_t abs_y_min; + uint16_t abs_y_max; + uint8_t abs_pressure_min; + uint8_t abs_pressure_max; + uint8_t abs_width_min; + uint8_t abs_width_max; + uint8_t abs_area_min; + uint8_t abs_area_max; + int gpio_irq; + int gpio_reset; + int (*power)(int on); + u8 config_T6[6]; + u8 config_T7[3]; + u8 config_T8[10]; + u8 config_T9[35]; + u8 config_T15[11]; + u8 config_T19[16]; + u8 config_T20[12]; + u8 config_T22[17]; + u8 config_T23[15]; + u8 config_T24[19]; + u8 config_T25[14]; + u8 config_T27[7]; + u8 config_T28[6]; + u8 config_T40[5]; + u8 config_T42[8]; + u8 config_T46[9]; + u8 config_T47[10]; + u8 config_T48[54]; + u8 object_crc[3]; + u8 cable_config[4]; + u8 cable_config_T7[3]; + u8 cable_config_T8[10]; + u8 cable_config_T9[35]; + u8 cable_config_T22[17]; + u8 cable_config_T28[6]; + u8 cable_config_T46[9]; + u8 cable_config_T48[54]; + u8 noise_config[3]; + u16 filter_level[4]; + u8 GCAF_level[5]; + u8 ATCH_NOR[6]; + u8 ATCH_NOR_20S[6]; +}; + +struct atmel_config_data { + int8_t config[4]; + int8_t *config_T7; + int8_t *config_T8; + int8_t *config_T9; + int8_t *config_T22; + int8_t *config_T28; + int8_t *config_T46; + int8_t *config_T48; +}; + +#define ATMEL_I2C_RETRY_TIMES 10 + +/* config_setting */ +#define NONE 0 +#define CONNECTED 1 +struct atmel_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct atmel_i2c_platform_data *pdata; + struct workqueue_struct *atmel_wq; + struct work_struct work; + int (*power) (int on); + struct info_id_t *id; + struct object_t *object_table; + struct iomux_block *gpio_block; + struct block_config *gpio_block_config; + uint8_t finger_count; + uint16_t abs_x_min; + uint16_t abs_x_max; + uint16_t abs_y_min; + uint16_t abs_y_max; + uint8_t abs_area_min; + uint8_t abs_area_max; + uint8_t abs_width_min; + uint8_t abs_width_max; + uint8_t abs_pressure_min; + uint8_t abs_pressure_max; + uint8_t first_pressed; + struct atmel_finger_data finger_data[10]; + uint8_t finger_type; + uint8_t finger_support; + uint16_t finger_pressed; + uint8_t face_suppression; + uint8_t grip_suppression; + uint8_t noise_status[2]; + uint16_t *filter_level; + uint8_t calibration_confirm; + uint64_t timestamp; + struct atmel_config_data config_setting[2]; + int8_t noise_config[3]; + uint8_t status; + uint8_t GCAF_sample; + uint8_t *GCAF_level; + uint8_t noisethr; + uint8_t noisethr_config; + uint8_t diag_command; + uint8_t *ATCH_EXT; + int8_t *ATCH_NOR; + int8_t *ATCH_NOR_20S; + int pre_data[11]; + /*unlock flag used to indicate calibration after unlock system*/ + int unlock_flag; + + /*For usb detect*/ + struct work_struct usb_work; + struct notifier_block nb; + unsigned long usb_event; + struct mutex lock; +}; + +static struct atmel_ts_data *private_ts; + +#define LDO_POWR_VOLTAGE 2700000 /*2.7v*/ +static struct regulator *LDO; + +int i2c_atmel_read(struct i2c_client *client, uint16_t address, uint8_t *data, uint8_t length) +{ + int retry, ret; + uint8_t addr[2]; + + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + .buf = data, + } + }; + addr[0] = address & 0xFF; + addr[1] = (address >> 8) & 0xFF; + + for (retry = 0; retry < ATMEL_I2C_RETRY_TIMES; retry++) { + ret = i2c_transfer(client->adapter, msg, 2); + if ((ret == 2) || (ret == -ERESTARTSYS)) + break; + mdelay(10); + } + if (retry == ATMEL_I2C_RETRY_TIMES) { + dev_err(&client->dev, "k3ts, %s: i2c_read_block retry over %d\n", __func__, + ATMEL_I2C_RETRY_TIMES); + return -EIO; + } + return 0; +} + +int i2c_atmel_write(struct i2c_client *client, uint16_t address, uint8_t *data, uint8_t length) +{ + int retry, loop_i, ret; + uint8_t buf[length + 2]; + + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = length + 2, + .buf = buf, + } + }; + + buf[0] = address & 0xFF; + buf[1] = (address >> 8) & 0xFF; + + for (loop_i = 0; loop_i < length; loop_i++) + buf[loop_i + 2] = data[loop_i]; + + for (retry = 0; retry < ATMEL_I2C_RETRY_TIMES; retry++) { + ret = i2c_transfer(client->adapter, msg, 1); + if ((ret == 1) || (ret == -ERESTARTSYS)) + break; + mdelay(10); + } + + if (retry == ATMEL_I2C_RETRY_TIMES) { + dev_err(&client->dev, "k3ts, %s: i2c_write_block retry over %d\n", __func__, + ATMEL_I2C_RETRY_TIMES); + return -EIO; + } + return 0; + +} + +int i2c_atmel_write_byte_data(struct i2c_client *client, uint16_t address, uint8_t value) +{ + i2c_atmel_write(client, address, &value, 1); + return 0; +} + +uint16_t get_object_address(struct atmel_ts_data *ts, uint8_t object_type) +{ + uint8_t loop_i; + for (loop_i = 0; loop_i < ts->id->num_declared_objects; loop_i++) { + if (ts->object_table[loop_i].object_type == object_type) + return ts->object_table[loop_i].i2c_address; + } + return 0; +} +uint8_t get_object_size(struct atmel_ts_data *ts, uint8_t object_type) +{ + uint8_t loop_i; + for (loop_i = 0; loop_i < ts->id->num_declared_objects; loop_i++) { + if (ts->object_table[loop_i].object_type == object_type) + return ts->object_table[loop_i].size; + } + return 0; +} + +uint8_t get_object_size_from_address(struct atmel_ts_data *ts, int address) +{ + uint8_t loop_i; + for (loop_i = 0; loop_i < ts->id->num_declared_objects; loop_i++) { + if (ts->object_table[loop_i].i2c_address == address) + return ts->object_table[loop_i].size; + } + return 0; +} + +uint8_t get_rid(struct atmel_ts_data *ts, uint8_t object_type) +{ + uint8_t loop_i; + for (loop_i = 0; loop_i < ts->id->num_declared_objects; loop_i++) { + if (ts->object_table[loop_i].object_type == object_type) + return ts->object_table[loop_i].report_ids; + } + return 0; +} + +static void check_calibration(struct atmel_ts_data *ts) +{ + uint8_t data[T37_DATA + T37_TCH_FLAG_SIZE]; + uint8_t loop_i, loop_j, x_limit = 0, check_mask, tch_ch = 0, atch_ch = 0; + + memset(data, 0xFF, sizeof(data)); + i2c_atmel_write_byte_data(ts->client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_DIAG, T6_CFG_DIAG_CMD_TCH); + + for (loop_i = 0; + !(data[T37_MODE] == T6_CFG_DIAG_CMD_TCH && data[T37_PAGE] == T37_PAGE_NUM0) && loop_i < 10; loop_i++) { + msleep(5); + i2c_atmel_read(ts->client, + get_object_address(ts, DIAGNOSTIC_T37), data, 2); + } + + if (loop_i == 10) + dev_err(&ts->client->dev, "k3ts, %s: Diag data not ready\n", __func__); + + i2c_atmel_read(ts->client, get_object_address(ts, DIAGNOSTIC_T37), data, + T37_DATA + T37_TCH_FLAG_SIZE); + if (data[T37_MODE] == T6_CFG_DIAG_CMD_TCH && + data[T37_PAGE] == T37_PAGE_NUM0) { + x_limit = T28_CFG_MODE0_X + ts->config_setting[NONE].config_T28[T28_CFG_MODE]; + x_limit = x_limit << 1; + if (x_limit <= 40) { + for (loop_i = 0; loop_i < x_limit; loop_i += 2) { + for (loop_j = 0; loop_j < BITS_PER_BYTE; loop_j++) { + check_mask = BIT_MASK(loop_j); + if (data[T37_DATA + T37_TCH_FLAG_IDX + loop_i] & + check_mask) + tch_ch++; + if (data[T37_DATA + T37_TCH_FLAG_IDX + loop_i + 1] & + check_mask) + tch_ch++; + if (data[T37_DATA + T37_ATCH_FLAG_IDX + loop_i] & + check_mask) + atch_ch++; + if (data[T37_DATA + T37_ATCH_FLAG_IDX + loop_i + 1] & + check_mask) + atch_ch++; + } + } + } + } + i2c_atmel_write_byte_data(ts->client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_DIAG, T6_CFG_DIAG_CMD_PAGEUP); + + if (tch_ch && (atch_ch == 0)) { + if (jiffies > (ts->timestamp + HZ/2) && (ts->calibration_confirm == 1)) { + ts->calibration_confirm = 2; + } + if (ts->calibration_confirm < 2) + ts->calibration_confirm = 1; + ts->timestamp = jiffies; + } else if (atch_ch > 1 || tch_ch > 8) { + ts->calibration_confirm = 0; + i2c_atmel_write_byte_data(ts->client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_CALIBRATE, 0x55); + } +} + +static void confirm_calibration(struct atmel_ts_data *ts) +{ + i2c_atmel_write(ts->client, + get_object_address(ts, GEN_ACQUISITIONCONFIG_T8) + + T8_CFG_TCHAUTOCAL, ts->ATCH_NOR_20S, 6); + ts->pre_data[0] = 2; +} + +static void msg_process_finger_data_x10y10bit(struct atmel_finger_data *fdata, uint8_t *data) +{ + fdata->x = data[T9_MSG_XPOSMSB] << 2 | data[T9_MSG_XYPOSLSB] >> 6; + fdata->y = data[T9_MSG_YPOSMSB] << 2 | (data[T9_MSG_XYPOSLSB] & 0x0C) >>2; + fdata->w = data[T9_MSG_TCHAREA]; + fdata->z = data[T9_MSG_TCHAMPLITUDE]; +} +static void msg_process_finger_data_x10y12bit(struct atmel_finger_data *fdata, uint8_t *data) +{ + fdata->x = data[T9_MSG_XPOSMSB] << 2 | data[T9_MSG_XYPOSLSB] >> 6; + fdata->y = data[T9_MSG_YPOSMSB] << 4 | (data[T9_MSG_XYPOSLSB] & 0x0F) ; + fdata->w = data[T9_MSG_TCHAREA]; + fdata->z = data[T9_MSG_TCHAMPLITUDE]; +} + +static void msg_process_multitouch(struct atmel_ts_data *ts, uint8_t *data, uint8_t idx) +{ + if (ts->calibration_confirm < 2 && ts->id->version == 0x10) + check_calibration(ts); + if(ts->abs_y_max >= 1024) { + msg_process_finger_data_x10y12bit(&ts->finger_data[idx], data); + } else { + msg_process_finger_data_x10y10bit(&ts->finger_data[idx], data); + } + if (data[T9_MSG_STATUS] & T9_MSG_STATUS_RELEASE) { + if (ts->grip_suppression & BIT(idx)) + ts->grip_suppression &= ~BIT(idx); + if (ts->finger_pressed & BIT(idx)) { + ts->finger_count--; + ts->finger_pressed &= ~BIT(idx); + if (!ts->first_pressed) { + if (!ts->finger_count) + ts->first_pressed = 1; + } + if (ts->pre_data[0] < 2 && ts->unlock_flag != 1) { + + if (ts->finger_count) { + i2c_atmel_write_byte_data(ts->client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_CALIBRATE, 0x55); + } else if (!ts->finger_count && ts->pre_data[0] == 1) + ts->pre_data[0] = 0; + } + } + } else if ((data[T9_MSG_STATUS] & (T9_MSG_STATUS_DETECT | T9_MSG_STATUS_PRESS)) + && !(ts->finger_pressed & BIT(idx))) { + if (ts->id->version >= 0x10 && ts->pre_data[0] < 2) { + if (jiffies > (ts->timestamp + 20 * HZ)) { + confirm_calibration(ts); + } + } + if (!(ts->grip_suppression & BIT(idx))) { + ts->finger_count++; + ts->finger_pressed |= BIT(idx); + if (ts->id->version >= 0x10 && ts->pre_data[0] < 2) { + ts->pre_data[idx + 1] = ts->finger_data[idx].x; + ts->pre_data[idx + 2] = ts->finger_data[idx].y; + if (ts->finger_count == ts->finger_support) { + i2c_atmel_write_byte_data(ts->client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_CALIBRATE, 0x55); + } else if (!ts->pre_data[0] && ts->finger_count == 1) + ts->pre_data[0] = 1; + } + } + } else if ((data[T9_MSG_STATUS] & (T9_MSG_STATUS_DETECT|T9_MSG_STATUS_PRESS)) + && ts->pre_data[0] < 2 && ts->unlock_flag != 1) { + if (ts->finger_count == 1 && ts->pre_data[0] && + (idx == 0 && ((abs(ts->finger_data[idx].y - ts->pre_data[idx + 2]) > 50) + || (abs(ts->finger_data[idx].x - ts->pre_data[idx + 1]) > 50)))) + { + ts->unlock_flag = 1; + ts->calibration_confirm = 2; + } + } + +} + +static void compatible_input_report(struct input_dev *idev, + struct atmel_finger_data *fdata, uint8_t press, uint8_t last) +{ + if (!press) { + input_mt_sync(idev); + /*input_report_key(idev, BTN_TOUCH, 0);*/ + input_report_key(idev, BTN_TOUCH, 1); + + } else { + TS_DEBUG_ATMEL("k3ts, %s: Touch report_key x = %d, y = %d, z = %d, w = %d\n ", __func__, + fdata->x, fdata->y, fdata->z, fdata->w); + input_report_abs(idev, ABS_MT_TOUCH_MAJOR, fdata->z); + input_report_abs(idev, ABS_MT_WIDTH_MAJOR, fdata->w); + input_report_abs(idev, ABS_MT_POSITION_X, fdata->x); + input_report_abs(idev, ABS_MT_POSITION_Y, fdata->y); + input_mt_sync(idev); + } +} + + + +static void multi_input_report(struct atmel_ts_data *ts) +{ + uint8_t loop_i, finger_report = 0; + + for (loop_i = 0; loop_i < ts->finger_support; loop_i++) { + if (ts->finger_pressed & BIT(loop_i)) { + compatible_input_report(ts->input_dev, &ts->finger_data[loop_i], + 1, (ts->finger_count == ++finger_report)); + } + } +} +static irqreturn_t atmel_interrupt_fun(int irq, void *dev_id) +{ + int ret; + struct atmel_ts_data *ts = dev_id; + uint8_t data[7]; + int8_t report_type; + uint8_t msg_byte_num = 7; + + memset(data, 0x0, sizeof(data)); + + mutex_lock(&ts->lock); + ret = i2c_atmel_read(ts->client, get_object_address(ts, + GEN_MESSAGEPROCESSOR_T5), data, 7); + + report_type = data[MSG_RID] - ts->finger_type; + if (report_type >= 0 && report_type < ts->finger_support) { + msg_process_multitouch(ts, data, report_type); + } else { + if (data[MSG_RID] == get_rid(ts, GEN_COMMANDPROCESSOR_T6)) { + if (data[1] & 0x10) { + ts->timestamp = jiffies; + } + if (data[1] & 0x80) { + msleep(100); + + i2c_atmel_write_byte_data(ts->client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_CALIBRATE, 0x55); + } + msg_byte_num = 5; + } + if (data[MSG_RID] == get_rid(ts, PROCI_TOUCHSUPPRESSION_T42)) { + if (ts->calibration_confirm < 2 && ts->id->version == 0x10) { + i2c_atmel_write_byte_data(ts->client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_CALIBRATE, 0x55); + } + ts->face_suppression = data[T20_MSG_STATUS]; + printk(KERN_INFO "Touch Face suppression %s: ", + ts->face_suppression ? "Active" : "Inactive"); + msg_byte_num = 2; + } + } + if (!ts->finger_count || ts->face_suppression) { + ts->finger_pressed = 0; + ts->finger_count = 0; + compatible_input_report(ts->input_dev, NULL, 0, 1); + } else { + multi_input_report(ts); + } + input_sync(ts->input_dev); + mutex_unlock(&ts->lock); + + return IRQ_HANDLED; +} + +static int read_object_table(struct atmel_ts_data *ts) +{ + uint8_t i, type_count = 0; + uint8_t data[6]; + memset(data, 0x0, sizeof(data)); + + ts->object_table = kzalloc(sizeof(struct object_t)*ts->id->num_declared_objects, GFP_KERNEL); + if (ts->object_table == NULL) { + dev_err(&ts->client->dev, "k3ts, %s: allocate object_table failed\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < ts->id->num_declared_objects; i++) { + i2c_atmel_read(ts->client, i * 6 + 0x07, data, 6); + ts->object_table[i].object_type = data[OBJ_TABLE_TYPE]; + ts->object_table[i].i2c_address = + data[OBJ_TABLE_LSB] | data[OBJ_TABLE_MSB] << 8; + ts->object_table[i].size = data[OBJ_TABLE_SIZE] + 1; + ts->object_table[i].instances = data[OBJ_TABLE_INSTANCES]; + ts->object_table[i].num_report_ids = data[OBJ_TABLE_RIDS]; + if (data[OBJ_TABLE_RIDS]) { + ts->object_table[i].report_ids = type_count + 1; + type_count += data[OBJ_TABLE_RIDS]; + } + if (data[OBJ_TABLE_TYPE] == TOUCH_MULTITOUCHSCREEN_T9) + ts->finger_type = ts->object_table[i].report_ids; + } + + return 0; +} + +struct atmel_i2c_platform_data *atmel_ts_get_pdata(struct i2c_client *client) +{ + struct device_node *node = client->dev.of_node; + struct atmel_i2c_platform_data *pdata = client->dev.platform_data; + u32 data[8]; + + if (pdata) + return pdata; + + if (!node) + return NULL; + + pdata = devm_kzalloc(&client->dev, sizeof(struct atmel_i2c_platform_data), + GFP_KERNEL); + pdata->gpio_irq = of_get_named_gpio(node, "atmel-ts,gpio-irq", 0); + pdata->gpio_reset = of_get_named_gpio(node, "atmel-ts,gpio-reset", 0); + + of_property_read_u32_array(node, "atmel-ts,abs", &data[0], 8); + pdata->abs_x_min = data[0]; + pdata->abs_x_max = data[1]; + pdata->abs_y_min = data[2]; + pdata->abs_y_max = data[3]; + pdata->abs_pressure_min = data[4]; + pdata->abs_pressure_max = data[5]; + pdata->abs_width_min = data[6]; + pdata->abs_width_max = data[7]; + + of_property_read_u8_array(node, "atmel-ts,cfg_t6", &pdata->config_T6[0], 6); + of_property_read_u8_array(node, "atmel-ts,cfg_t7", &pdata->config_T7[0], 3); + of_property_read_u8_array(node, "atmel-ts,cfg_t8", &pdata->config_T8[0], 10); + of_property_read_u8_array(node, "atmel-ts,cfg_t9", &pdata->config_T9[0], 35); + of_property_read_u8_array(node, "atmel-ts,cfg_t15", &pdata->config_T15[0], 11); + of_property_read_u8_array(node, "atmel-ts,cfg_t19", &pdata->config_T19[0], 16); + of_property_read_u8_array(node, "atmel-ts,cfg_t23", &pdata->config_T23[0], 15); + of_property_read_u8_array(node, "atmel-ts,cfg_t25", &pdata->config_T25[0], 14); + of_property_read_u8_array(node, "atmel-ts,cfg_t40", &pdata->config_T40[0], 5); + of_property_read_u8_array(node, "atmel-ts,cfg_t42", &pdata->config_T42[0], 8); + of_property_read_u8_array(node, "atmel-ts,cfg_t46", &pdata->config_T46[0], 9); + of_property_read_u8_array(node, "atmel-ts,cfg_t47", &pdata->config_T47[0], 10); + of_property_read_u8_array(node, "atmel-ts,cfg_t48", &pdata->config_T48[0], 54); + of_property_read_u8_array(node, "atmel-ts,object_crc", &pdata->object_crc[0], 3); + of_property_read_u8_array(node, "atmel-ts,cable_config", &pdata->cable_config[0], 4); + of_property_read_u8_array(node, "atmel-ts,cable_config_t7", &pdata->cable_config_T7[0], 3); + of_property_read_u8_array(node, "atmel-ts,cable_config_t8", &pdata->cable_config_T8[0], 10); + of_property_read_u8_array(node, "atmel-ts,cable_config_t46", &pdata->cable_config_T46[0], 9); + of_property_read_u8_array(node, "atmel-ts,cable_config_t48", &pdata->cable_config_T48[0], 54); + of_property_read_u8_array(node, "atmel-ts,noise_config", &pdata->noise_config[0], 3); + of_property_read_u16_array(node, "atmel-ts,filter_level", &pdata->filter_level[0], 4); + of_property_read_u8_array(node, "atmel-ts,gcaf_level", &pdata->GCAF_level[0], 5); + of_property_read_u8_array(node, "atmel-ts,atch_nor", &pdata->ATCH_NOR[0], 6); + of_property_read_u8_array(node, "atmel-ts,atch_nor_20s", &pdata->ATCH_NOR_20S[0], 6); + + return pdata; +} + +static int atmel_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct atmel_ts_data *ts; + struct atmel_i2c_platform_data *pdata; + int ret = 0, intr = 0; + uint8_t loop_i; + struct i2c_msg msg[2]; + uint8_t data[16]; + uint8_t CRC_check = 0; + + client->dev.init_name = "atmel-ts"; + LDO = regulator_get(&client->dev, "ldo"); + if (IS_ERR(LDO)) { + dev_err(&client->dev, "no regulator found\n"); + LDO = NULL; + } else { + ret = regulator_enable(LDO); + if (!ret) + ret = regulator_set_voltage(LDO, LDO_POWR_VOLTAGE, LDO_POWR_VOLTAGE); + if (ret) + dev_err(&client->dev, "k3ts, %s: failed to set LDO\n", __func__); + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "k3ts, %s: need I2C_FUNC_I2C\n", __func__); + ret = -ENODEV; + goto err_check_functionality_failed; + } + + ts = kzalloc(sizeof(struct atmel_ts_data), GFP_KERNEL); + if (ts == NULL) { + ret = -ENOMEM; + goto err_alloc_data_failed; + } + + ts->unlock_flag = 0; + mutex_init(&ts->lock); + + ts->atmel_wq = create_singlethread_workqueue("atmel_wq"); + if (!ts->atmel_wq) { + dev_err(&client->dev, "k3ts, %s: create workqueue failed\n", __func__); + ret = -ENOMEM; + goto err_cread_wq_failed; + } + + ts->client = client; + i2c_set_clientdata(client, ts); + + pdata = atmel_ts_get_pdata(client); + ts->pdata = pdata; + + if (pdata) { + ts->power = pdata->power; + intr = pdata->gpio_irq; + client->irq = gpio_to_irq(intr); + } + if (ts->power) + ret = ts->power(1); + ret = gpio_request(intr, "gpio_tp_intr"); + if (ret) { + dev_err(&client->dev, "gpio_request %d failed\n", intr); + goto err_request_gpio_failed; + } + ret = gpio_direction_input(intr); + if (ret) { + dev_err(&client->dev, "k3ts, %s: gpio_direction_input failed %d\n", __func__, intr); + goto err_gpio_direction_failed; + } + + ret = gpio_request(ts->pdata->gpio_reset, "gpio_tp_reset"); + if (ret) { + dev_err(&client->dev, "k3ts, %s: gpio_request failed %d, ret = %d\n", + __func__, ts->pdata->gpio_reset, ret); + goto err_request_gpio_reset_failed; + } + + gpio_direction_output(ts->pdata->gpio_reset, 1); + mdelay(5); + gpio_direction_output(ts->pdata->gpio_reset, 0); + mdelay(10); + gpio_direction_output(ts->pdata->gpio_reset, 1); + mdelay(50); + + for (loop_i = 0; loop_i < 10; loop_i++) { + if (!gpio_get_value(intr)) + break; + msleep(10); + } + + if (loop_i == 10) + dev_err(&client->dev, "k3ts, %s: No Messages\n", __func__); + + /* read message*/ + msg[0].addr = ts->client->addr; + msg[0].flags = I2C_M_RD; + msg[0].len = 7; + msg[0].buf = data; + ret = i2c_transfer(client->adapter, msg, 1); + + if (ret < 0) { + dev_err(&client->dev, "k3ts, %s: No Atmel chip inside\n", __func__); + goto err_detect_failed; + } + if (ts->power) + ret = ts->power(2); + + if (data[MSG_RID] == 0x01 && + (data[T6_MSG_STATUS] & (T6_MSG_STATUS_SIGERR|T6_MSG_STATUS_COMSERR))) { + dev_err(&client->dev, "k3ts, %s: init err: %x\n", __func__, data[1]); + goto err_detect_failed; + } else { + for (loop_i = 0; loop_i < 10; loop_i++) { + if (gpio_get_value(intr)) { + dev_err(&client->dev, "k3ts, %s: No more message\n", __func__); + break; + } + ret = i2c_transfer(client->adapter, msg, 1); + msleep(10); + } + } + + /* Read the info block data. */ + ts->id = kzalloc(sizeof(struct info_id_t), GFP_KERNEL); + if (ts->id == NULL) { + dev_err(&client->dev, "k3ts, %s: allocate info_id_t failed\n", __func__); + goto err_alloc_failed; + } + ret = i2c_atmel_read(client, 0x00, data, 7); + + ts->id->family_id = data[INFO_BLK_FID]; + ts->id->variant_id = data[INFO_BLK_VID]; + if (ts->id->family_id == 0x80 && ts->id->variant_id == 0x10) + ts->id->version = data[INFO_BLK_VER] + 6; + else + ts->id->version = data[INFO_BLK_VER]; + + ts->id->build = data[INFO_BLK_BUILD]; + ts->id->matrix_x_size = data[INFO_BLK_XSIZE]; + ts->id->matrix_y_size = data[INFO_BLK_YSIZE]; + ts->id->num_declared_objects = data[INFO_BLK_OBJS]; + + /* Read object table. */ + ret = read_object_table(ts); + if (ret < 0) + goto err_read_table_failed; + + + if (pdata) { + ts->finger_support = pdata->config_T9[T9_CFG_NUMTOUCH]; + + /* OBJECT CONFIG CRC check */ + if (pdata->object_crc[0]) { + ret = i2c_atmel_write_byte_data(client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_CALIBRATE, 0x55); + for (loop_i = 0; loop_i < 10; loop_i++) { + if (!gpio_get_value(intr)) { + ret = i2c_atmel_read(ts->client, get_object_address(ts, + GEN_MESSAGEPROCESSOR_T5), data, 5); + if (data[MSG_RID] == get_rid(ts, GEN_COMMANDPROCESSOR_T6)) + break; + } + msleep(10); + } + if (loop_i == 10) + dev_err(&client->dev, "k3ts, %s: No checksum read\n", __func__); + else { + dev_info(&client->dev, "k3ts, %s: CRC print : %x, %x, %x\n", __func__, + data[T6_MSG_CHECKSUM + 0], data[T6_MSG_CHECKSUM + 1], data[T6_MSG_CHECKSUM + 2]); + for (loop_i = 0; loop_i < 3; loop_i++) { + if (pdata->object_crc[loop_i] != data[T6_MSG_CHECKSUM + loop_i]) { + dev_err(&client->dev, + "k3ts, %s: CRC Error: %x, %x\n", __func__, + pdata->object_crc[loop_i], + data[T6_MSG_CHECKSUM + loop_i]); + break; + } + } + if (loop_i == 3) { + dev_info(&client->dev, "k3ts, %s: CRC passed: ", __func__); + for (loop_i = 0; loop_i < 3; loop_i++) + pr_info("0x%2.2X ", pdata->object_crc[loop_i]); + pr_info("\n"); + CRC_check = 1;/*means CRC check OK*/ + } + } + } + ts->abs_x_min = pdata->abs_x_min; + ts->abs_x_max = pdata->abs_x_max; + ts->abs_y_min = pdata->abs_y_min; + ts->abs_y_max = pdata->abs_y_max; + ts->abs_pressure_min = pdata->abs_pressure_min; + ts->abs_pressure_max = pdata->abs_pressure_max; + ts->abs_width_min = pdata->abs_width_min; + ts->abs_width_max = pdata->abs_width_max; + + ts->GCAF_level = pdata->GCAF_level; + if (ts->id->version >= 0x10) { + ts->ATCH_EXT = &pdata->config_T8[6]; + ts->timestamp = jiffies + 60 * HZ; + } + ts->ATCH_NOR = pdata->ATCH_NOR; + ts->ATCH_NOR_20S = pdata->ATCH_NOR_20S; + ts->filter_level = pdata->filter_level; + + ts->config_setting[NONE].config_T7 + = ts->config_setting[CONNECTED].config_T7 + = pdata->config_T7; + ts->config_setting[NONE].config_T8 = pdata->config_T8; + ts->config_setting[CONNECTED].config_T8 = pdata->cable_config_T8; + ts->config_setting[NONE].config_T9 = pdata->config_T9; + ts->config_setting[NONE].config_T22 = pdata->config_T22; + ts->config_setting[NONE].config_T28 = pdata->config_T28; + ts->config_setting[NONE].config_T46 = pdata->config_T46; + ts->config_setting[NONE].config_T48 = pdata->config_T48; + ts->config_setting[CONNECTED].config_T46 = pdata->cable_config_T46; + ts->config_setting[CONNECTED].config_T48 = pdata->cable_config_T48; + + if (pdata->noise_config[0]) + for (loop_i = 0; loop_i < 3; loop_i++) + ts->noise_config[loop_i] = pdata->noise_config[loop_i]; + + if (pdata->cable_config[0]) { + ts->config_setting[NONE].config[CB_TCHTHR] = + pdata->config_T9[T9_CFG_TCHTHR]; + ts->config_setting[NONE].config[CB_NOISETHR] = + pdata->config_T22[T22_CFG_NOISETHR]; + ts->config_setting[NONE].config[CB_IDLEGCAFDEPTH] = + pdata->config_T28[T28_CFG_IDLEGCAFDEPTH]; + ts->config_setting[NONE].config[CB_ACTVGCAFDEPTH] = + pdata->config_T28[T28_CFG_ACTVGCAFDEPTH]; + for (loop_i = 0; loop_i < 4; loop_i++) + ts->config_setting[CONNECTED].config[loop_i] = + pdata->cable_config[loop_i]; + ts->GCAF_sample = + ts->config_setting[CONNECTED].config[CB_ACTVGCAFDEPTH]; + if (ts->id->version >= 0x20) + ts->noisethr = pdata->cable_config[CB_TCHTHR] - + pdata->config_T9[T9_CFG_TCHHYST]; + else + ts->noisethr = pdata->cable_config[CB_TCHTHR]; + ts->noisethr_config = + ts->config_setting[CONNECTED].config[CB_NOISETHR]; + } else { + if (pdata->cable_config_T7[0]) + ts->config_setting[CONNECTED].config_T7 = + pdata->cable_config_T7; + if (pdata->cable_config_T8[0]) + ts->config_setting[CONNECTED].config_T8 = + pdata->cable_config_T8; + if (pdata->cable_config_T9[0]) { + ts->config_setting[CONNECTED].config_T9 = + pdata->cable_config_T9; + ts->config_setting[CONNECTED].config_T22 = + pdata->cable_config_T22; + ts->config_setting[CONNECTED].config_T28 = + pdata->cable_config_T28; + ts->GCAF_sample = + ts->config_setting[CONNECTED].config_T28[T28_CFG_ACTVGCAFDEPTH]; + } + if (ts->status == CONNECTED) + ts->noisethr = (ts->id->version >= 0x20) ? + pdata->cable_config_T9[T9_CFG_TCHTHR] - pdata->cable_config_T9[T9_CFG_TCHHYST] : + pdata->cable_config_T9[T9_CFG_TCHTHR]; + else + ts->noisethr = (ts->id->version >= 0x20) ? + pdata->config_T9[T9_CFG_TCHTHR] - pdata->config_T9[T9_CFG_TCHHYST] : + pdata->config_T9[T9_CFG_TCHTHR]; + ts->noisethr_config = pdata->cable_config_T22[T22_CFG_NOISETHR]; + + } + + i2c_atmel_write(ts->client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6), + pdata->config_T6, + get_object_size(ts, GEN_COMMANDPROCESSOR_T6)); + i2c_atmel_write(ts->client, + get_object_address(ts, GEN_POWERCONFIG_T7), + pdata->config_T7, + get_object_size(ts, GEN_POWERCONFIG_T7)); + i2c_atmel_write(ts->client, + get_object_address(ts, GEN_ACQUISITIONCONFIG_T8), + pdata->config_T8, + get_object_size(ts, GEN_ACQUISITIONCONFIG_T8)); + i2c_atmel_write(ts->client, + get_object_address(ts, TOUCH_MULTITOUCHSCREEN_T9), + pdata->config_T9, + get_object_size(ts, TOUCH_MULTITOUCHSCREEN_T9)); + i2c_atmel_write(ts->client, + get_object_address(ts, TOUCH_KEYARRAY_T15), + pdata->config_T15, + get_object_size(ts, TOUCH_KEYARRAY_T15)); + i2c_atmel_write(ts->client, + get_object_address(ts, SPT_GPIOPWM_T19), + pdata->config_T19, + get_object_size(ts, SPT_GPIOPWM_T19)); + + i2c_atmel_write(ts->client, + get_object_address(ts, PROCI_GRIPSUPPRESSION_T40), + pdata->config_T40, + get_object_size(ts, PROCI_GRIPSUPPRESSION_T40)); + + i2c_atmel_write(ts->client, + get_object_address(ts, PROCI_TOUCHSUPPRESSION_T42), + pdata->config_T42, + get_object_size(ts, PROCI_TOUCHSUPPRESSION_T42)); + i2c_atmel_write(ts->client, + get_object_address(ts, PROCG_NOISESUPPRESSION_T48), + pdata->config_T48, + get_object_size(ts, PROCG_NOISESUPPRESSION_T48)); + i2c_atmel_write(ts->client, + get_object_address(ts, TOUCH_PROXIMITY_T23), + pdata->config_T23, + get_object_size(ts, TOUCH_PROXIMITY_T23)); + i2c_atmel_write(ts->client, + get_object_address(ts, SPT_SELFTEST_T25), + pdata->config_T25, + get_object_size(ts, SPT_SELFTEST_T25)); + i2c_atmel_write(ts->client, + get_object_address(ts, SPT_CTECONFIG_T46), + pdata->config_T46, + get_object_size(ts, SPT_CTECONFIG_T46)); + i2c_atmel_write(ts->client, + get_object_address(ts, PROCI_STYLUS_T47), + pdata->config_T47, + get_object_size(ts, PROCI_STYLUS_T47)); + + ret = i2c_atmel_write_byte_data(client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_BACKUPNV, 0x55); + + for (loop_i = 0; loop_i < 10; loop_i++) { + if (!gpio_get_value(intr)) + break; + dev_err(&client->dev, "k3ts, %s: wait for Message(%d)\n", __func__, loop_i + 1); + msleep(10); + } + + i2c_atmel_read(client, + get_object_address(ts, GEN_MESSAGEPROCESSOR_T5), data, 7); + + ret = i2c_atmel_write_byte_data(client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_RESET, 0x11);/*reset*/ + msleep(100); + + if (ts->status == CONNECTED) { + if (ts->config_setting[CONNECTED].config_T8 != NULL) + i2c_atmel_write(ts->client, + get_object_address(ts, GEN_ACQUISITIONCONFIG_T8), + ts->config_setting[CONNECTED].config_T8, + get_object_size(ts, GEN_ACQUISITIONCONFIG_T8)); + if (ts->config_setting[CONNECTED].config_T46 != NULL) + i2c_atmel_write(ts->client, + get_object_address(ts, SPT_CTECONFIG_T46), + ts->config_setting[CONNECTED].config_T46, + get_object_size(ts, SPT_CTECONFIG_T46)); + if (ts->config_setting[CONNECTED].config_T48 != NULL) { + i2c_atmel_write(ts->client, + get_object_address(ts, PROCG_NOISESUPPRESSION_T48), + ts->config_setting[CONNECTED].config_T48, + get_object_size(ts, PROCG_NOISESUPPRESSION_T48)); + } + } + } + ts->calibration_confirm = 0; + ts->input_dev = input_allocate_device(); + if (ts->input_dev == NULL) { + ret = -ENOMEM; + dev_err(&client->dev, "k3ts, %s: Failed to allocate input device\n", __func__); + goto err_input_dev_alloc_failed; + } + /*Modified by z181527 for Debug Only*/ + ts->input_dev->name = "synaptics"/*"atmel-touchscreen"*/; + set_bit(EV_SYN, ts->input_dev->evbit); + set_bit(EV_KEY, ts->input_dev->evbit); + set_bit(BTN_TOUCH, ts->input_dev->keybit); + set_bit(BTN_2, ts->input_dev->keybit); + set_bit(EV_ABS, ts->input_dev->evbit); + set_bit(INPUT_PROP_DIRECT, ts->input_dev->propbit); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, + ts->abs_x_min, ts->abs_x_max, 0, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, + ts->abs_y_min, ts->abs_y_max, 0, 0); + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, + ts->abs_pressure_min, ts->abs_pressure_max, + 0, 0); + input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, + ts->abs_width_min, ts->abs_width_max, 0, 0); + + ret = input_register_device(ts->input_dev); + if (ret) { + dev_err(&client->dev, + "k3ts, %s: atmel_ts_probe: Unable to register %s input device\n", __func__, + ts->input_dev->name); + goto err_input_register_device_failed; + } + + ret = request_threaded_irq(client->irq, NULL, atmel_interrupt_fun, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + client->name, ts); + if (ret) + dev_err(&client->dev, "k3ts, %s: request_irq failed\n", __func__); + + private_ts = ts; + + dev_info(&client->dev, "k3ts, %s: probe %s successfully\n", __func__, + ts->input_dev->name); + + return 0; + +err_input_register_device_failed: + input_free_device(ts->input_dev); +err_input_dev_alloc_failed: +err_read_table_failed: + kfree(ts->id); +err_alloc_failed: +err_detect_failed: +err_gpio_direction_failed: +err_request_gpio_reset_failed: + gpio_free(ts->pdata->gpio_reset); + gpio_free(intr); +err_request_gpio_failed: + destroy_workqueue(ts->atmel_wq); +err_cread_wq_failed: + kfree(ts); +err_alloc_data_failed: +err_check_functionality_failed: + if (LDO != NULL) { + regulator_disable(LDO); + regulator_put(LDO); + } + return ret; +} + +static int atmel_ts_remove(struct i2c_client *client) +{ + struct atmel_ts_data *ts = i2c_get_clientdata(client); + + free_irq(client->irq, ts); + + destroy_workqueue(ts->atmel_wq); + input_unregister_device(ts->input_dev); + kfree(ts); + + regulator_disable(LDO); + regulator_put(LDO); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int atmel_ts_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct atmel_ts_data *ts = i2c_get_clientdata(client); + struct atmel_i2c_platform_data *pdata = ts->pdata; + uint8_t data[7]; + int ret = 0; + + mutex_lock(&ts->lock); + ts->finger_pressed = 0; + ts->finger_count = 0; + ts->first_pressed = 0; + + if (ts->id->version >= 0x10) { + ts->pre_data[0] = 0; + ret = i2c_atmel_write(ts->client, + get_object_address(ts, GEN_ACQUISITIONCONFIG_T8) + T8_CFG_ATCHCALST, + ts->ATCH_EXT, 4); + if (ret < 0) + pr_err("k3ts, %s: failed to write config T8\n", __func__); + } + + ret = i2c_atmel_write_byte_data(client, + get_object_address(ts, GEN_POWERCONFIG_T7) + T7_CFG_IDLEACQINT, 0x0); + if (ret < 0) + pr_err("k3ts, %s: failed to write config T7\n", __func__); + + ret = i2c_atmel_write_byte_data(client, + get_object_address(ts, GEN_POWERCONFIG_T7) + T7_CFG_ACTVACQINT, 0x0); + if (ret < 0) + pr_err("k3ts, %s: failed to write config T7\n", __func__); + + /* Read T5 until gpio_irq is HIGH level */ + if (!gpio_get_value(pdata->gpio_irq)) { + ret = i2c_atmel_read(ts->client, get_object_address(ts, + GEN_MESSAGEPROCESSOR_T5), data, 7); + if (ret < 0) { + pr_err("k3ts, %s: failed to read T5\n", __func__); + } + } + + mutex_unlock(&ts->lock); + + pr_info("[%s]: -\n", __func__); + return 0; +} + +static int atmel_ts_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct atmel_ts_data *ts = i2c_get_clientdata(client); + int ret = 0; + + pr_info("[%s]: +\n", __func__); + + mutex_lock(&ts->lock); + if (ts->id->version >= 0x10) + ts->timestamp = jiffies; + + ts->unlock_flag = 0; + + ret = i2c_atmel_write(ts->client, + get_object_address(ts, GEN_POWERCONFIG_T7), + ts->config_setting[ts->status].config_T7, + get_object_size(ts, GEN_POWERCONFIG_T7)); + if (ret < 0) + pr_err("k3ts, %s: failed to write config T7\n", __func__); + + ts->calibration_confirm = 0; + msleep(1); + + ret = i2c_atmel_write(ts->client, + get_object_address(ts, GEN_ACQUISITIONCONFIG_T8) + + T8_CFG_TCHAUTOCAL, ts->ATCH_NOR, 6); + if (ret < 0) + pr_err("k3ts, %s: failed to write config T8\n", __func__); + + ret = i2c_atmel_write_byte_data(client, + get_object_address(ts, GEN_COMMANDPROCESSOR_T6) + + T6_CFG_CALIBRATE, 0x55); + if (ret < 0) + pr_err("k3ts, %s: failed to write config T6\n", __func__); + + mutex_unlock(&ts->lock); + + pr_info("[%s]: -\n", __func__); + + return 0; +} +#endif +static SIMPLE_DEV_PM_OPS(atmel_ts_pm_ops, atmel_ts_suspend, atmel_ts_resume); + +static const struct i2c_device_id atml_ts_i2c_id[] = { + { ATMEL_MXT224E_NAME, 0 }, + { } +}; + +#ifdef CONFIG_OF +static const struct of_device_id atmel_ts_dt_ids[] = { + { .compatible = "atmel,ts-mxt224e", }, + { } +}; +MODULE_DEVICE_TABLE(of, atmel_ts_dt_ids); +#endif + +static struct i2c_driver atmel_ts_driver = { + .id_table = atml_ts_i2c_id, + .probe = atmel_ts_probe, + .remove = atmel_ts_remove, + .driver = { + .of_match_table = of_match_ptr(atmel_ts_dt_ids), + .name = ATMEL_MXT224E_NAME, + .pm = &atmel_ts_pm_ops, + }, +}; +module_i2c_driver(atmel_ts_driver); + +MODULE_DESCRIPTION("ATMEL Touch driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 79bbc21c1d0167..763bf3a5fffa1d 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -178,16 +178,15 @@ config TEGRA_IOMMU_SMMU config EXYNOS_IOMMU bool "Exynos IOMMU Support" - depends on ARCH_EXYNOS && EXYNOS_DEV_SYSMMU + depends on ARCH_EXYNOS select IOMMU_API + default n help Support for the IOMMU(System MMU) of Samsung Exynos application processor family. This enables H/W multimedia accellerators to see non-linear physical memory chunks as a linear memory in their address spaces - If unsure, say N here. - config EXYNOS_IOMMU_DEBUG bool "Debugging log for Exynos IOMMU" depends on EXYNOS_IOMMU diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 074018979cdfb0..b7f77318ed08f6 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -12,6 +12,7 @@ #define DEBUG #endif +#include #include #include #include @@ -25,12 +26,14 @@ #include #include #include +#include +#include +#include +#include #include #include -#include - /* We does not consider super section mapping (16MB) */ #define SECT_ORDER 20 #define LPAGE_ORDER 16 @@ -44,8 +47,12 @@ #define LPAGE_MASK (~(LPAGE_SIZE - 1)) #define SPAGE_MASK (~(SPAGE_SIZE - 1)) -#define lv1ent_fault(sent) (((*(sent) & 3) == 0) || ((*(sent) & 3) == 3)) -#define lv1ent_page(sent) ((*(sent) & 3) == 1) +#define lv1ent_fault(sent) ((*(sent) == ZERO_LV2LINK) || \ + ((*(sent) & 3) == 0) || ((*(sent) & 3) == 3)) +#define lv1ent_zero(sent) (*(sent) == ZERO_LV2LINK) +#define lv1ent_page_zero(sent) ((*(sent) & 3) == 1) +#define lv1ent_page(sent) ((*(sent) != ZERO_LV2LINK) && \ + ((*(sent) & 3) == 1)) #define lv1ent_section(sent) ((*(sent) & 3) == 2) #define lv2ent_fault(pent) ((*(pent) & 3) == 0) @@ -53,19 +60,19 @@ #define lv2ent_large(pent) ((*(pent) & 3) == 1) #define section_phys(sent) (*(sent) & SECT_MASK) -#define section_offs(iova) ((iova) & 0xFFFFF) +#define section_offs(iova) ((sysmmu_iova_t)(iova) & 0xFFFFF) #define lpage_phys(pent) (*(pent) & LPAGE_MASK) -#define lpage_offs(iova) ((iova) & 0xFFFF) +#define lpage_offs(iova) ((sysmmu_iova_t)(iova) & 0xFFFF) #define spage_phys(pent) (*(pent) & SPAGE_MASK) -#define spage_offs(iova) ((iova) & 0xFFF) +#define spage_offs(iova) ((sysmmu_iova_t)(iova) & 0xFFF) -#define lv1ent_offset(iova) ((iova) >> SECT_ORDER) -#define lv2ent_offset(iova) (((iova) & 0xFF000) >> SPAGE_ORDER) +#define lv1ent_offset(iova) ((sysmmu_iova_t)(iova) >> SECT_ORDER) +#define lv2ent_offset(iova) (((sysmmu_iova_t)(iova) & 0xFF000) >> SPAGE_ORDER) #define NUM_LV1ENTRIES 4096 -#define NUM_LV2ENTRIES 256 +#define NUM_LV2ENTRIES (SECT_SIZE / SPAGE_SIZE) -#define LV2TABLE_SIZE (NUM_LV2ENTRIES * sizeof(long)) +#define LV2TABLE_SIZE (NUM_LV2ENTRIES * sizeof(sysmmu_pte_t)) #define SPAGES_PER_LPAGE (LPAGE_SIZE / SPAGE_SIZE) @@ -80,6 +87,13 @@ #define CTRL_BLOCK 0x7 #define CTRL_DISABLE 0x0 +#define CFG_LRU 0x1 +#define CFG_QOS(n) ((n & 0xF) << 7) +#define CFG_MASK 0x0150FFFF /* Selecting bit 0-15, 20, 22 and 24 */ +#define CFG_ACGEN (1 << 24) /* System MMU 3.3 only */ +#define CFG_SYSSEL (1 << 22) /* System MMU 3.2 only */ +#define CFG_FLPDCACHE (1 << 20) /* System MMU 3.2+ only */ + #define REG_MMU_CTRL 0x000 #define REG_MMU_CFG 0x004 #define REG_MMU_STATUS 0x008 @@ -96,19 +110,49 @@ #define REG_MMU_VERSION 0x034 +#define MMU_MAJ_VER(val) ((val) >> 7) +#define MMU_MIN_VER(val) ((val) & 0x7F) +#define MMU_RAW_VER(reg) (((reg) >> 21) & ((1 << 11) - 1)) /* 11 bits */ + +#define MAKE_MMU_VER(maj, min) ((((maj) & 0xF) << 7) | ((min) & 0x7F)) + #define REG_PB0_SADDR 0x04C #define REG_PB0_EADDR 0x050 #define REG_PB1_SADDR 0x054 #define REG_PB1_EADDR 0x058 -static unsigned long *section_entry(unsigned long *pgtable, unsigned long iova) +#define __clk_gate_ctrl(data, clk, en) do { \ + if (data->clk) \ + clk_##en##able(data->clk); \ + } while (0) + +#define __sysmmu_clk_enable(data) __clk_gate_ctrl(data, clk, en) +#define __sysmmu_clk_disable(data) __clk_gate_ctrl(data, clk, dis) +#define __master_clk_enable(data) __clk_gate_ctrl(data, clk_master, en) +#define __master_clk_disable(data) __clk_gate_ctrl(data, clk_master, dis) + +#define has_sysmmu(dev) (dev->archdata.iommu != NULL) +#define for_each_sysmmu_list(dev, list_data) \ + list_for_each_entry(list_data, \ + &((struct exynos_iommu_owner *)dev->archdata.iommu)->mmu_list, \ + entry) + +typedef u32 sysmmu_iova_t; +typedef u32 sysmmu_pte_t; + +static struct kmem_cache *lv2table_kmem_cache; +static sysmmu_pte_t *zero_lv2_table; +#define ZERO_LV2LINK mk_lv1ent_page(virt_to_phys(zero_lv2_table)) + +static sysmmu_pte_t *section_entry(sysmmu_pte_t *pgtable, sysmmu_iova_t iova) { return pgtable + lv1ent_offset(iova); } -static unsigned long *page_entry(unsigned long *sent, unsigned long iova) +static sysmmu_pte_t *page_entry(sysmmu_pte_t *sent, sysmmu_iova_t iova) { - return (unsigned long *)__va(lv2table_base(sent)) + lv2ent_offset(iova); + return (sysmmu_pte_t *)phys_to_virt( + lv2table_base(sent)) + lv2ent_offset(iova); } enum exynos_sysmmu_inttype { @@ -124,16 +168,6 @@ enum exynos_sysmmu_inttype { SYSMMU_FAULTS_NUM }; -/* - * @itype: type of fault. - * @pgtable_base: the physical address of page table base. This is 0 if @itype - * is SYSMMU_BUSERROR. - * @fault_addr: the device (virtual) address that the System MMU tried to - * translated. This is 0 if @itype is SYSMMU_BUSERROR. - */ -typedef int (*sysmmu_fault_handler_t)(enum exynos_sysmmu_inttype itype, - unsigned long pgtable_base, unsigned long fault_addr); - static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = { REG_PAGE_FAULT_ADDR, REG_AR_FAULT_ADDR, @@ -157,27 +191,41 @@ static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = { "UNKNOWN FAULT" }; +/* attached to dev.archdata.iommu of the master device */ +struct exynos_iommu_owner { + struct list_head client; /* entry of exynos_iommu_domain.clients */ + struct device *dev; + struct list_head mmu_list; /* list of sysmmu_list_data.entry */ + struct iommu_domain *domain; + void *vmm_data; /* IO virtual memory manager's data */ + spinlock_t lock; /* Lock to preserve consistency of System MMU */ +}; + struct exynos_iommu_domain { struct list_head clients; /* list of sysmmu_drvdata.node */ - unsigned long *pgtable; /* lv1 page table, 16KB */ + sysmmu_pte_t *pgtable; /* lv1 page table, 16KB */ short *lv2entcnt; /* free lv2 entry counter for each section */ spinlock_t lock; /* lock for this structure */ spinlock_t pgtablelock; /* lock for modifying page table @ pgtable */ }; +struct sysmmu_list_data { + struct list_head entry; /* entry of exynos_iommu_owner.mmu_list */ + struct device *sysmmu; +}; + struct sysmmu_drvdata { - struct list_head node; /* entry of exynos_iommu_domain.clients */ struct device *sysmmu; /* System MMU's device descriptor */ - struct device *dev; /* Owner of system MMU */ - char *dbgname; - int nsfrs; - void __iomem **sfrbases; - struct clk *clk[2]; + struct device *master; /* Owner of system MMU */ + void __iomem *sfrbase; + struct clk *clk; + struct clk *clk_master; int activations; - rwlock_t lock; + spinlock_t lock; struct iommu_domain *domain; - sysmmu_fault_handler_t fault_handler; - unsigned long pgtable; + bool runtime_active; + bool suspended; + phys_addr_t pgtable; }; static bool set_sysmmu_active(struct sysmmu_drvdata *data) @@ -204,6 +252,11 @@ static void sysmmu_unblock(void __iomem *sfrbase) __raw_writel(CTRL_ENABLE, sfrbase + REG_MMU_CTRL); } +static unsigned int __raw_sysmmu_version(struct sysmmu_drvdata *data) +{ + return MMU_RAW_VER(__raw_readl(data->sfrbase + REG_MMU_VERSION)); +} + static bool sysmmu_block(void __iomem *sfrbase) { int i = 120; @@ -226,434 +279,591 @@ static void __sysmmu_tlb_invalidate(void __iomem *sfrbase) } static void __sysmmu_tlb_invalidate_entry(void __iomem *sfrbase, - unsigned long iova) + sysmmu_iova_t iova, unsigned int num_inv) { - __raw_writel((iova & SPAGE_MASK) | 1, sfrbase + REG_MMU_FLUSH_ENTRY); + unsigned int i; + for (i = 0; i < num_inv; i++) { + __raw_writel((iova & SPAGE_MASK) | 1, + sfrbase + REG_MMU_FLUSH_ENTRY); + iova += SPAGE_SIZE; + } } static void __sysmmu_set_ptbase(void __iomem *sfrbase, - unsigned long pgd) + phys_addr_t pgd) { - __raw_writel(0x1, sfrbase + REG_MMU_CFG); /* 16KB LV1, LRU */ __raw_writel(pgd, sfrbase + REG_PT_BASE_ADDR); __sysmmu_tlb_invalidate(sfrbase); } -static void __sysmmu_set_prefbuf(void __iomem *sfrbase, unsigned long base, - unsigned long size, int idx) +static void show_fault_information(const char *name, + enum exynos_sysmmu_inttype itype, + phys_addr_t pgtable_base, sysmmu_iova_t fault_addr) { - __raw_writel(base, sfrbase + REG_PB0_SADDR + idx * 8); - __raw_writel(size - 1 + base, sfrbase + REG_PB0_EADDR + idx * 8); -} - -static void __set_fault_handler(struct sysmmu_drvdata *data, - sysmmu_fault_handler_t handler) -{ - unsigned long flags; - - write_lock_irqsave(&data->lock, flags); - data->fault_handler = handler; - write_unlock_irqrestore(&data->lock, flags); -} - -void exynos_sysmmu_set_fault_handler(struct device *dev, - sysmmu_fault_handler_t handler) -{ - struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu); - - __set_fault_handler(data, handler); -} - -static int default_fault_handler(enum exynos_sysmmu_inttype itype, - unsigned long pgtable_base, unsigned long fault_addr) -{ - unsigned long *ent; + sysmmu_pte_t *ent; if ((itype >= SYSMMU_FAULTS_NUM) || (itype < SYSMMU_PAGEFAULT)) itype = SYSMMU_FAULT_UNKNOWN; - pr_err("%s occurred at 0x%lx(Page table base: 0x%lx)\n", - sysmmu_fault_name[itype], fault_addr, pgtable_base); + pr_err("%s occurred at %#x by %s(Page table base: %pa)\n", + sysmmu_fault_name[itype], fault_addr, name, &pgtable_base); - ent = section_entry(__va(pgtable_base), fault_addr); - pr_err("\tLv1 entry: 0x%lx\n", *ent); + ent = section_entry(phys_to_virt(pgtable_base), fault_addr); + pr_err("\tLv1 entry: %#x\n", *ent); if (lv1ent_page(ent)) { ent = page_entry(ent, fault_addr); - pr_err("\t Lv2 entry: 0x%lx\n", *ent); + pr_err("\t Lv2 entry: %#x\n", *ent); } - - pr_err("Generating Kernel OOPS... because it is unrecoverable.\n"); - - BUG(); - - return 0; } static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id) { /* SYSMMU is in blocked when interrupt occurred. */ struct sysmmu_drvdata *data = dev_id; - struct resource *irqres; - struct platform_device *pdev; enum exynos_sysmmu_inttype itype; - unsigned long addr = -1; - - int i, ret = -ENOSYS; - - read_lock(&data->lock); + sysmmu_iova_t addr = -1; + int ret = -ENOSYS; WARN_ON(!is_sysmmu_active(data)); - pdev = to_platform_device(data->sysmmu); - for (i = 0; i < (pdev->num_resources / 2); i++) { - irqres = platform_get_resource(pdev, IORESOURCE_IRQ, i); - if (irqres && ((int)irqres->start == irq)) - break; - } + spin_lock(&data->lock); - if (i == pdev->num_resources) { + __master_clk_enable(data); + + itype = (enum exynos_sysmmu_inttype) + __ffs(__raw_readl(data->sfrbase + REG_INT_STATUS)); + if (WARN_ON(!((itype >= 0) && (itype < SYSMMU_FAULT_UNKNOWN)))) itype = SYSMMU_FAULT_UNKNOWN; + else + addr = __raw_readl(data->sfrbase + fault_reg_offset[itype]); + + if (itype == SYSMMU_FAULT_UNKNOWN) { + pr_err("%s: Fault is not occurred by System MMU '%s'!\n", + __func__, dev_name(data->sysmmu)); + pr_err("%s: Please check if IRQ is correctly configured.\n", + __func__); + BUG(); } else { - itype = (enum exynos_sysmmu_inttype) - __ffs(__raw_readl(data->sfrbases[i] + REG_INT_STATUS)); - if (WARN_ON(!((itype >= 0) && (itype < SYSMMU_FAULT_UNKNOWN)))) - itype = SYSMMU_FAULT_UNKNOWN; - else - addr = __raw_readl( - data->sfrbases[i] + fault_reg_offset[itype]); + unsigned int base = + __raw_readl(data->sfrbase + REG_PT_BASE_ADDR); + show_fault_information(dev_name(data->sysmmu), + itype, base, addr); + if (data->domain) + ret = report_iommu_fault(data->domain, + data->master, addr, itype); } - if (data->domain) - ret = report_iommu_fault(data->domain, data->dev, - addr, itype); + /* fault is not recovered by fault handler */ + BUG_ON(ret != 0); - if ((ret == -ENOSYS) && data->fault_handler) { - unsigned long base = data->pgtable; - if (itype != SYSMMU_FAULT_UNKNOWN) - base = __raw_readl( - data->sfrbases[i] + REG_PT_BASE_ADDR); - ret = data->fault_handler(itype, base, addr); - } + __raw_writel(1 << itype, data->sfrbase + REG_INT_CLEAR); - if (!ret && (itype != SYSMMU_FAULT_UNKNOWN)) - __raw_writel(1 << itype, data->sfrbases[i] + REG_INT_CLEAR); - else - dev_dbg(data->sysmmu, "(%s) %s is not handled.\n", - data->dbgname, sysmmu_fault_name[itype]); + sysmmu_unblock(data->sfrbase); - if (itype != SYSMMU_FAULT_UNKNOWN) - sysmmu_unblock(data->sfrbases[i]); + __master_clk_disable(data); - read_unlock(&data->lock); + spin_unlock(&data->lock); return IRQ_HANDLED; } -static bool __exynos_sysmmu_disable(struct sysmmu_drvdata *data) +static void __sysmmu_disable_nocount(struct sysmmu_drvdata *data) { + clk_enable(data->clk_master); + + __raw_writel(CTRL_DISABLE, data->sfrbase + REG_MMU_CTRL); + __raw_writel(0, data->sfrbase + REG_MMU_CFG); + + __sysmmu_clk_disable(data); + __master_clk_disable(data); +} + +static bool __sysmmu_disable(struct sysmmu_drvdata *data) +{ + bool disabled; unsigned long flags; - bool disabled = false; - int i; - write_lock_irqsave(&data->lock, flags); + spin_lock_irqsave(&data->lock, flags); - if (!set_sysmmu_inactive(data)) - goto finish; + disabled = set_sysmmu_inactive(data); - for (i = 0; i < data->nsfrs; i++) - __raw_writel(CTRL_DISABLE, data->sfrbases[i] + REG_MMU_CTRL); + if (disabled) { + data->pgtable = 0; + data->domain = NULL; - if (data->clk[1]) - clk_disable(data->clk[1]); - if (data->clk[0]) - clk_disable(data->clk[0]); + if (data->runtime_active) + __sysmmu_disable_nocount(data); - disabled = true; - data->pgtable = 0; - data->domain = NULL; -finish: - write_unlock_irqrestore(&data->lock, flags); + dev_dbg(data->sysmmu, "Disabled\n"); + } else { + dev_dbg(data->sysmmu, "%d times left to disable\n", + data->activations); + } - if (disabled) - dev_dbg(data->sysmmu, "(%s) Disabled\n", data->dbgname); - else - dev_dbg(data->sysmmu, "(%s) %d times left to be disabled\n", - data->dbgname, data->activations); + spin_unlock_irqrestore(&data->lock, flags); return disabled; } -/* __exynos_sysmmu_enable: Enables System MMU - * - * returns -error if an error occurred and System MMU is not enabled, - * 0 if the System MMU has been just enabled and 1 if System MMU was already - * enabled before. - */ -static int __exynos_sysmmu_enable(struct sysmmu_drvdata *data, - unsigned long pgtable, struct iommu_domain *domain) +static void __sysmmu_init_config(struct sysmmu_drvdata *data) { - int i, ret = 0; - unsigned long flags; + unsigned int cfg = CFG_LRU | CFG_QOS(15); + unsigned int ver; + + ver = __raw_sysmmu_version(data); + if (MMU_MAJ_VER(ver) == 3) { + if (MMU_MIN_VER(ver) >= 2) { + cfg |= CFG_FLPDCACHE; + if (MMU_MIN_VER(ver) == 3) { + cfg |= CFG_ACGEN; + cfg &= ~CFG_LRU; + } else { + cfg |= CFG_SYSSEL; + } + } + } - write_lock_irqsave(&data->lock, flags); + __raw_writel(cfg, data->sfrbase + REG_MMU_CFG); +} - if (!set_sysmmu_active(data)) { - if (WARN_ON(pgtable != data->pgtable)) { - ret = -EBUSY; - set_sysmmu_inactive(data); - } else { - ret = 1; - } +static void __sysmmu_enable_nocount(struct sysmmu_drvdata *data) +{ + __master_clk_enable(data); + __sysmmu_clk_enable(data); - dev_dbg(data->sysmmu, "(%s) Already enabled\n", data->dbgname); - goto finish; - } + __raw_writel(CTRL_BLOCK, data->sfrbase + REG_MMU_CTRL); - if (data->clk[0]) - clk_enable(data->clk[0]); - if (data->clk[1]) - clk_enable(data->clk[1]); + __sysmmu_init_config(data); - data->pgtable = pgtable; + __sysmmu_set_ptbase(data->sfrbase, data->pgtable); - for (i = 0; i < data->nsfrs; i++) { - __sysmmu_set_ptbase(data->sfrbases[i], pgtable); + __raw_writel(CTRL_ENABLE, data->sfrbase + REG_MMU_CTRL); - if ((readl(data->sfrbases[i] + REG_MMU_VERSION) >> 28) == 3) { - /* System MMU version is 3.x */ - __raw_writel((1 << 12) | (2 << 28), - data->sfrbases[i] + REG_MMU_CFG); - __sysmmu_set_prefbuf(data->sfrbases[i], 0, -1, 0); - __sysmmu_set_prefbuf(data->sfrbases[i], 0, -1, 1); - } + __master_clk_disable(data); +} + +static int __sysmmu_enable(struct sysmmu_drvdata *data, + phys_addr_t pgtable, struct iommu_domain *domain) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + if (set_sysmmu_active(data)) { + data->pgtable = pgtable; + data->domain = domain; - __raw_writel(CTRL_ENABLE, data->sfrbases[i] + REG_MMU_CTRL); + if (data->runtime_active) + __sysmmu_enable_nocount(data); + + dev_dbg(data->sysmmu, "Enabled\n"); + } else { + ret = (pgtable == data->pgtable) ? 1 : -EBUSY; + + dev_dbg(data->sysmmu, "already enabled\n"); } - data->domain = domain; + if (WARN_ON(ret < 0)) + set_sysmmu_inactive(data); /* decrement count */ - dev_dbg(data->sysmmu, "(%s) Enabled\n", data->dbgname); -finish: - write_unlock_irqrestore(&data->lock, flags); + spin_unlock_irqrestore(&data->lock, flags); return ret; } -int exynos_sysmmu_enable(struct device *dev, unsigned long pgtable) +/* __exynos_sysmmu_enable: Enables System MMU + * + * returns -error if an error occurred and System MMU is not enabled, + * 0 if the System MMU has been just enabled and 1 if System MMU was already + * enabled before. + */ +static int __exynos_sysmmu_enable(struct device *dev, phys_addr_t pgtable, + struct iommu_domain *domain) { - struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu); - int ret; - - BUG_ON(!memblock_is_memory(pgtable)); - - ret = pm_runtime_get_sync(data->sysmmu); - if (ret < 0) { - dev_dbg(data->sysmmu, "(%s) Failed to enable\n", data->dbgname); - return ret; + int ret = 0; + unsigned long flags; + struct exynos_iommu_owner *owner = dev->archdata.iommu; + struct sysmmu_list_data *list; + + BUG_ON(!has_sysmmu(dev)); + + spin_lock_irqsave(&owner->lock, flags); + + for_each_sysmmu_list(dev, list) { + struct sysmmu_drvdata *data = dev_get_drvdata(list->sysmmu); + data->master = dev; + ret = __sysmmu_enable(data, pgtable, domain); + if (ret < 0) { + struct sysmmu_list_data *iter; + for_each_sysmmu_list(dev, iter) { + if (iter->sysmmu == list->sysmmu) + break; + data = dev_get_drvdata(iter->sysmmu); + __sysmmu_disable(data); + data->master = NULL; + } + break; + } } - ret = __exynos_sysmmu_enable(data, pgtable, NULL); - if (WARN_ON(ret < 0)) { - pm_runtime_put(data->sysmmu); - dev_err(data->sysmmu, - "(%s) Already enabled with page table %#lx\n", - data->dbgname, data->pgtable); - } else { - data->dev = dev; - } + spin_unlock_irqrestore(&owner->lock, flags); return ret; } +int exynos_sysmmu_enable(struct device *dev, phys_addr_t pgtable) +{ + BUG_ON(!memblock_is_memory(pgtable)); + + return __exynos_sysmmu_enable(dev, pgtable, NULL); +} + static bool exynos_sysmmu_disable(struct device *dev) { - struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu); - bool disabled; + unsigned long flags; + bool disabled = true; + struct exynos_iommu_owner *owner = dev->archdata.iommu; + struct sysmmu_list_data *list; + + BUG_ON(!has_sysmmu(dev)); + + spin_lock_irqsave(&owner->lock, flags); + + /* Every call to __sysmmu_disable() must return same result */ + for_each_sysmmu_list(dev, list) { + struct sysmmu_drvdata *data = dev_get_drvdata(list->sysmmu); + disabled = __sysmmu_disable(data); + if (disabled) + data->master = NULL; + } - disabled = __exynos_sysmmu_disable(data); - pm_runtime_put(data->sysmmu); + spin_unlock_irqrestore(&owner->lock, flags); return disabled; } -static void sysmmu_tlb_invalidate_entry(struct device *dev, unsigned long iova) +static void __sysmmu_tlb_invalidate_flpdcache(struct sysmmu_drvdata *data, + sysmmu_iova_t iova) { - unsigned long flags; - struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu); + if (__raw_sysmmu_version(data) == MAKE_MMU_VER(3, 3)) + __raw_writel(iova | 0x1, data->sfrbase + REG_MMU_FLUSH_ENTRY); +} + +static void sysmmu_tlb_invalidate_flpdcache(struct device *dev, + sysmmu_iova_t iova) +{ + struct sysmmu_list_data *list; - read_lock_irqsave(&data->lock, flags); + for_each_sysmmu_list(dev, list) { + unsigned long flags; + struct sysmmu_drvdata *data = dev_get_drvdata(list->sysmmu); - if (is_sysmmu_active(data)) { - int i; - for (i = 0; i < data->nsfrs; i++) { - if (sysmmu_block(data->sfrbases[i])) { - __sysmmu_tlb_invalidate_entry( - data->sfrbases[i], iova); - sysmmu_unblock(data->sfrbases[i]); + __master_clk_enable(data); + + spin_lock_irqsave(&data->lock, flags); + if (is_sysmmu_active(data) && data->runtime_active) + __sysmmu_tlb_invalidate_flpdcache(data, iova); + spin_unlock_irqrestore(&data->lock, flags); + + __master_clk_disable(data); + } +} + +static void sysmmu_tlb_invalidate_entry(struct device *dev, sysmmu_iova_t iova, + size_t size) +{ + struct exynos_iommu_owner *owner = dev->archdata.iommu; + unsigned long flags; + struct sysmmu_list_data *list; + + spin_lock_irqsave(&owner->lock, flags); + + for_each_sysmmu_list(dev, list) { + struct sysmmu_drvdata *data = dev_get_drvdata(list->sysmmu); + spin_lock(&data->lock); + if (is_sysmmu_active(data) && data->runtime_active) { + unsigned int num_inv = 1; + + __master_clk_enable(data); + + /* + * L2TLB invalidation required + * 4KB page: 1 invalidation + * 64KB page: 16 invalidation + * 1MB page: 64 invalidation + * because it is set-associative TLB + * with 8-way and 64 sets. + * 1MB page can be cached in one of all sets. + * 64KB page can be one of 16 consecutive sets. + */ + if (MMU_MAJ_VER(__raw_sysmmu_version(data)) == 2) + num_inv = min_t(unsigned int, + size / PAGE_SIZE, 64); + + if (sysmmu_block(data->sfrbase)) { + __sysmmu_tlb_invalidate_entry(data->sfrbase, + iova, num_inv); + sysmmu_unblock(data->sfrbase); } + __master_clk_disable(data); + } else { + dev_dbg(dev, + "disabled. Skipping TLB invalidation @ %#x\n", + iova); } - } else { - dev_dbg(data->sysmmu, - "(%s) Disabled. Skipping invalidating TLB.\n", - data->dbgname); + + spin_unlock(&data->lock); } - read_unlock_irqrestore(&data->lock, flags); + spin_unlock_irqrestore(&owner->lock, flags); } void exynos_sysmmu_tlb_invalidate(struct device *dev) { + struct exynos_iommu_owner *owner = dev->archdata.iommu; unsigned long flags; - struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu); - - read_lock_irqsave(&data->lock, flags); - - if (is_sysmmu_active(data)) { - int i; - for (i = 0; i < data->nsfrs; i++) { - if (sysmmu_block(data->sfrbases[i])) { - __sysmmu_tlb_invalidate(data->sfrbases[i]); - sysmmu_unblock(data->sfrbases[i]); + struct sysmmu_list_data *list; + + spin_lock_irqsave(&owner->lock, flags); + + for_each_sysmmu_list(dev, list) { + struct sysmmu_drvdata *data = dev_get_drvdata(list->sysmmu); + spin_lock(&data->lock); + if (is_sysmmu_active(data) && data->runtime_active) { + __master_clk_enable(data); + if (sysmmu_block(data->sfrbase)) { + __sysmmu_tlb_invalidate(data->sfrbase); + sysmmu_unblock(data->sfrbase); } + __master_clk_disable(data); + } else { + dev_dbg(dev, "disabled. Skipping TLB invalidation\n"); } - } else { - dev_dbg(data->sysmmu, - "(%s) Disabled. Skipping invalidating TLB.\n", - data->dbgname); + spin_unlock(&data->lock); } - read_unlock_irqrestore(&data->lock, flags); + spin_unlock_irqrestore(&owner->lock, flags); } -static int exynos_sysmmu_probe(struct platform_device *pdev) +static int __init __sysmmu_init_clock(struct device *sysmmu, + struct sysmmu_drvdata *data) { - int i, ret; - struct device *dev; - struct sysmmu_drvdata *data; + int ret; - dev = &pdev->dev; + data->clk = devm_clk_get(sysmmu, "sysmmu"); + if (IS_ERR(data->clk)) { + if (PTR_ERR(data->clk) == -ENOENT) { + dev_info(sysmmu, "No gating clock found.\n"); + data->clk = NULL; + return 0; + } - data = kzalloc(sizeof(*data), GFP_KERNEL); - if (!data) { - dev_dbg(dev, "Not enough memory\n"); - ret = -ENOMEM; - goto err_alloc; + dev_err(sysmmu, "Failed get sysmmu clock\n"); + return PTR_ERR(data->clk); } - ret = dev_set_drvdata(dev, data); + ret = clk_prepare(data->clk); if (ret) { - dev_dbg(dev, "Unabled to initialize driver data\n"); - goto err_init; + dev_err(sysmmu, "Failed to prepare sysmmu clock\n"); + return ret; } - data->nsfrs = pdev->num_resources / 2; - data->sfrbases = kmalloc(sizeof(*data->sfrbases) * data->nsfrs, - GFP_KERNEL); - if (data->sfrbases == NULL) { - dev_dbg(dev, "Not enough memory\n"); - ret = -ENOMEM; - goto err_init; + data->clk_master = devm_clk_get(sysmmu, "master"); + if (PTR_ERR(data->clk_master) == -ENOENT) { + data->clk_master = NULL; + return 0; + } else if (IS_ERR(data->clk_master)) { + dev_err(sysmmu, "Failed to get master clock\n"); + clk_unprepare(data->clk); + return PTR_ERR(data->clk_master); + } + + ret = clk_prepare(data->clk_master); + if (ret) { + clk_unprepare(data->clk); + dev_err(sysmmu, "Failed to prepare master clock\n"); + return ret; } - for (i = 0; i < data->nsfrs; i++) { - struct resource *res; - res = platform_get_resource(pdev, IORESOURCE_MEM, i); - if (!res) { - dev_dbg(dev, "Unable to find IOMEM region\n"); - ret = -ENOENT; - goto err_res; + return 0; +} + +static int __init __sysmmu_init_master(struct device *dev) +{ + int ret; + int i = 0; + struct device_node *node; + + while ((node = of_parse_phandle(dev->of_node, "mmu-masters", i++))) { + struct platform_device *master = of_find_device_by_node(node); + struct exynos_iommu_owner *owner; + struct sysmmu_list_data *list_data; + + if (!master) { + dev_err(dev, "%s: mmu-master '%s' not found\n", + __func__, node->name); + ret = -EINVAL; + goto err; } - data->sfrbases[i] = ioremap(res->start, resource_size(res)); - if (!data->sfrbases[i]) { - dev_dbg(dev, "Unable to map IOMEM @ PA:%#x\n", - res->start); - ret = -ENOENT; - goto err_res; + owner = master->dev.archdata.iommu; + if (!owner) { + owner = devm_kzalloc(dev, sizeof(*owner), GFP_KERNEL); + if (!owner) { + dev_err(dev, + "%s: Failed to allocate owner structure\n", + __func__); + ret = -ENOMEM; + goto err; + } + + INIT_LIST_HEAD(&owner->mmu_list); + INIT_LIST_HEAD(&owner->client); + owner->dev = &master->dev; + spin_lock_init(&owner->lock); + + master->dev.archdata.iommu = owner; } - } - for (i = 0; i < data->nsfrs; i++) { - ret = platform_get_irq(pdev, i); - if (ret <= 0) { - dev_dbg(dev, "Unable to find IRQ resource\n"); - goto err_irq; + list_data = devm_kzalloc(dev, sizeof(*list_data), GFP_KERNEL); + if (!list_data) { + dev_err(dev, + "%s: Failed to allocate sysmmu_list_data\n", + __func__); + ret = -ENOMEM; + goto err; } - ret = request_irq(ret, exynos_sysmmu_irq, 0, - dev_name(dev), data); - if (ret) { - dev_dbg(dev, "Unabled to register interrupt handler\n"); - goto err_irq; + INIT_LIST_HEAD(&list_data->entry); + list_data->sysmmu = dev; + + /* + * System MMUs are attached in the order of the presence + * in device tree + */ + list_add_tail(&list_data->entry, &owner->mmu_list); + } + + return 0; +err: + while ((node = of_parse_phandle(dev->of_node, "mmu-masters", i++))) { + struct platform_device *master = of_find_device_by_node(node); + struct exynos_iommu_owner *owner; + struct sysmmu_list_data *list_data; + + if (!master) + continue; + + owner = master->dev.archdata.iommu; + if (!owner) + continue; + + for_each_sysmmu_list(owner->dev, list_data) { + if (list_data->sysmmu == dev) { + list_del(&list_data->entry); + kfree(list_data); + break; + } } } - if (dev_get_platdata(dev)) { - char *deli, *beg; - struct sysmmu_platform_data *platdata = dev_get_platdata(dev); + return ret; +} - beg = platdata->clockname; +static int __init __sysmmu_setup(struct device *sysmmu, + struct sysmmu_drvdata *data) +{ + int ret; - for (deli = beg; (*deli != '\0') && (*deli != ','); deli++) - /* NOTHING */; + ret = __sysmmu_init_clock(sysmmu, data); + if (ret) { + dev_err(sysmmu, "Failed to initialize gating clocks\n"); + return ret; + } - if (*deli == '\0') - deli = NULL; - else - *deli = '\0'; + ret = __sysmmu_init_master(sysmmu); + if (ret) { + if (data->clk) + clk_unprepare(data->clk); + if (data->clk_master) + clk_unprepare(data->clk_master); + dev_err(sysmmu, "Failed to initialize master device.\n"); + } - data->clk[0] = clk_get(dev, beg); - if (IS_ERR(data->clk[0])) { - data->clk[0] = NULL; - dev_dbg(dev, "No clock descriptor registered\n"); - } + return ret; +} - if (data->clk[0] && deli) { - *deli = ','; - data->clk[1] = clk_get(dev, deli + 1); - if (IS_ERR(data->clk[1])) - data->clk[1] = NULL; - } +static int __init exynos_sysmmu_probe(struct platform_device *pdev) +{ + int irq, ret; + struct device *dev = &pdev->dev; + struct sysmmu_drvdata *data; + struct resource *res; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(dev, "Not enough memory for driver data\n"); + return -ENOMEM; + } - data->dbgname = platdata->dbgname; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Unable to find IOMEM region\n"); + return -ENOENT; } - data->sysmmu = dev; - rwlock_init(&data->lock); - INIT_LIST_HEAD(&data->node); + data->sfrbase = devm_request_and_ioremap(dev, res); + if (!data->sfrbase) { + dev_err(dev, "Unable to map IOMEM @ PA: %pa\n", &res->start); + return -EBUSY; + } - __set_fault_handler(data, &default_fault_handler); + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(dev, "Unable to find IRQ resource\n"); + return irq; + } - if (dev->parent) - pm_runtime_enable(dev); + ret = devm_request_irq(dev, irq, exynos_sysmmu_irq, 0, + dev_name(dev), data); + if (ret) { + dev_err(dev, "Unabled to register handler of irq %d\n", irq); + return ret; + } - dev_dbg(dev, "(%s) Initialized\n", data->dbgname); - return 0; -err_irq: - while (i-- > 0) { - int irq; + pm_runtime_enable(dev); - irq = platform_get_irq(pdev, i); - free_irq(irq, data); + ret = __sysmmu_setup(dev, data); + if (!ret) { + data->runtime_active = !pm_runtime_enabled(dev); + data->sysmmu = dev; + spin_lock_init(&data->lock); + + platform_set_drvdata(pdev, data); } -err_res: - while (data->nsfrs-- > 0) - iounmap(data->sfrbases[data->nsfrs]); - kfree(data->sfrbases); -err_init: - kfree(data); -err_alloc: - dev_err(dev, "Failed to initialize\n"); + return ret; } -static struct platform_driver exynos_sysmmu_driver = { - .probe = exynos_sysmmu_probe, - .driver = { +#ifdef CONFIG_OF +static struct of_device_id sysmmu_of_match[] __initconst = { + { .compatible = "samsung,sysmmu-v1", }, + { .compatible = "samsung,sysmmu-v2", }, + { .compatible = "samsung,sysmmu-v3.1", }, + { .compatible = "samsung,sysmmu-v3.2", }, + { .compatible = "samsung,sysmmu-v3.3", }, + { }, +}; +#endif + +static struct platform_driver exynos_sysmmu_driver __refdata = { + .probe = exynos_sysmmu_probe, + .driver = { .owner = THIS_MODULE, .name = "exynos-sysmmu", + .of_match_table = of_match_ptr(sysmmu_of_match), } }; @@ -667,21 +877,32 @@ static inline void pgtable_flush(void *vastart, void *vaend) static int exynos_iommu_domain_init(struct iommu_domain *domain) { struct exynos_iommu_domain *priv; + int i; priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - priv->pgtable = (unsigned long *)__get_free_pages( - GFP_KERNEL | __GFP_ZERO, 2); + priv->pgtable = (sysmmu_pte_t *)__get_free_pages(GFP_KERNEL, 2); if (!priv->pgtable) goto err_pgtable; - priv->lv2entcnt = (short *)__get_free_pages( - GFP_KERNEL | __GFP_ZERO, 1); + priv->lv2entcnt = (short *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 1); if (!priv->lv2entcnt) goto err_counter; + /* w/a of System MMU v3.3 to prevent caching 1MiB mapping */ + for (i = 0; i < NUM_LV1ENTRIES; i += 8) { + priv->pgtable[i + 0] = ZERO_LV2LINK; + priv->pgtable[i + 1] = ZERO_LV2LINK; + priv->pgtable[i + 2] = ZERO_LV2LINK; + priv->pgtable[i + 3] = ZERO_LV2LINK; + priv->pgtable[i + 4] = ZERO_LV2LINK; + priv->pgtable[i + 5] = ZERO_LV2LINK; + priv->pgtable[i + 6] = ZERO_LV2LINK; + priv->pgtable[i + 7] = ZERO_LV2LINK; + } + pgtable_flush(priv->pgtable, priv->pgtable + NUM_LV1ENTRIES); spin_lock_init(&priv->lock); @@ -705,7 +926,7 @@ static int exynos_iommu_domain_init(struct iommu_domain *domain) static void exynos_iommu_domain_destroy(struct iommu_domain *domain) { struct exynos_iommu_domain *priv = domain->priv; - struct sysmmu_drvdata *data; + struct exynos_iommu_owner *owner; unsigned long flags; int i; @@ -713,16 +934,20 @@ static void exynos_iommu_domain_destroy(struct iommu_domain *domain) spin_lock_irqsave(&priv->lock, flags); - list_for_each_entry(data, &priv->clients, node) { - while (!exynos_sysmmu_disable(data->dev)) + list_for_each_entry(owner, &priv->clients, client) { + while (!exynos_sysmmu_disable(owner->dev)) ; /* until System MMU is actually disabled */ } + while (!list_empty(&priv->clients)) + list_del_init(priv->clients.next); + spin_unlock_irqrestore(&priv->lock, flags); for (i = 0; i < NUM_LV1ENTRIES; i++) if (lv1ent_page(priv->pgtable + i)) - kfree(__va(lv2table_base(priv->pgtable + i))); + kmem_cache_free(lv2table_kmem_cache, + phys_to_virt(lv2table_base(priv->pgtable + i))); free_pages((unsigned long)priv->pgtable, 2); free_pages((unsigned long)priv->lv2entcnt, 1); @@ -733,117 +958,149 @@ static void exynos_iommu_domain_destroy(struct iommu_domain *domain) static int exynos_iommu_attach_device(struct iommu_domain *domain, struct device *dev) { - struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu); + struct exynos_iommu_owner *owner = dev->archdata.iommu; struct exynos_iommu_domain *priv = domain->priv; unsigned long flags; + phys_addr_t pgtable = virt_to_phys(priv->pgtable); int ret; - ret = pm_runtime_get_sync(data->sysmmu); - if (ret < 0) - return ret; - - ret = 0; - spin_lock_irqsave(&priv->lock, flags); - ret = __exynos_sysmmu_enable(data, __pa(priv->pgtable), domain); - + ret = __exynos_sysmmu_enable(dev, pgtable, domain); if (ret == 0) { - /* 'data->node' must not be appeared in priv->clients */ - BUG_ON(!list_empty(&data->node)); - data->dev = dev; - list_add_tail(&data->node, &priv->clients); + list_add_tail(&owner->client, &priv->clients); + owner->domain = domain; } spin_unlock_irqrestore(&priv->lock, flags); if (ret < 0) { - dev_err(dev, "%s: Failed to attach IOMMU with pgtable %#lx\n", - __func__, __pa(priv->pgtable)); - pm_runtime_put(data->sysmmu); - } else if (ret > 0) { - dev_dbg(dev, "%s: IOMMU with pgtable 0x%lx already attached\n", - __func__, __pa(priv->pgtable)); - } else { - dev_dbg(dev, "%s: Attached new IOMMU with pgtable 0x%lx\n", - __func__, __pa(priv->pgtable)); + dev_err(dev, "%s: Failed to attach IOMMU with pgtable %pa\n", + __func__, &pgtable); + return ret; } + dev_dbg(dev, "%s: Attached IOMMU with pgtable %pa %s\n", + __func__, &pgtable, (ret == 0) ? "" : ", again"); + return ret; } static void exynos_iommu_detach_device(struct iommu_domain *domain, struct device *dev) { - struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu); + struct exynos_iommu_owner *owner; struct exynos_iommu_domain *priv = domain->priv; - struct list_head *pos; unsigned long flags; - bool found = false; spin_lock_irqsave(&priv->lock, flags); - list_for_each(pos, &priv->clients) { - if (list_entry(pos, struct sysmmu_drvdata, node) == data) { - found = true; + list_for_each_entry(owner, &priv->clients, client) { + if (owner == dev->archdata.iommu) { + if (exynos_sysmmu_disable(dev)) { + list_del_init(&owner->client); + owner->domain = NULL; + } break; } } - if (!found) - goto finish; - - if (__exynos_sysmmu_disable(data)) { - dev_dbg(dev, "%s: Detached IOMMU with pgtable %#lx\n", - __func__, __pa(priv->pgtable)); - list_del_init(&data->node); + spin_unlock_irqrestore(&priv->lock, flags); + if (owner == dev->archdata.iommu) { + phys_addr_t pgtable = virt_to_phys(priv->pgtable); + dev_dbg(dev, "%s: Detached IOMMU with pgtable %pa\n", + __func__, &pgtable); } else { - dev_dbg(dev, "%s: Detaching IOMMU with pgtable %#lx delayed", - __func__, __pa(priv->pgtable)); + dev_err(dev, "%s: No IOMMU is attached\n", __func__); } - -finish: - spin_unlock_irqrestore(&priv->lock, flags); - - if (found) - pm_runtime_put(data->sysmmu); } -static unsigned long *alloc_lv2entry(unsigned long *sent, unsigned long iova, - short *pgcounter) +static sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *priv, + sysmmu_pte_t *sent, sysmmu_iova_t iova, short *pgcounter) { + if (lv1ent_section(sent)) { + WARN(1, "Trying mapping on %#08x mapped with 1MiB page", iova); + return ERR_PTR(-EADDRINUSE); + } + if (lv1ent_fault(sent)) { - unsigned long *pent; + sysmmu_pte_t *pent; + bool need_flush_flpd_cache = lv1ent_zero(sent); - pent = kzalloc(LV2TABLE_SIZE, GFP_ATOMIC); - BUG_ON((unsigned long)pent & (LV2TABLE_SIZE - 1)); + pent = kmem_cache_zalloc(lv2table_kmem_cache, GFP_ATOMIC); + BUG_ON((unsigned int)pent & (LV2TABLE_SIZE - 1)); if (!pent) - return NULL; + return ERR_PTR(-ENOMEM); - *sent = mk_lv1ent_page(__pa(pent)); + *sent = mk_lv1ent_page(virt_to_phys(pent)); *pgcounter = NUM_LV2ENTRIES; pgtable_flush(pent, pent + NUM_LV2ENTRIES); pgtable_flush(sent, sent + 1); + + /* + * If pretched SLPD is a fault SLPD in zero_l2_table, FLPD cache + * may caches the address of zero_l2_table. This function + * replaces the zero_l2_table with new L2 page table to write + * valid mappings. + * Accessing the valid area may cause page fault since FLPD + * cache may still caches zero_l2_table for the valid area + * instead of new L2 page table that have the mapping + * information of the valid area + * Thus any replacement of zero_l2_table with other valid L2 + * page table must involve FLPD cache invalidation for System + * MMU v3.3. + * FLPD cache invalidation is performed with TLB invalidation + * by VPN without blocking. It is safe to invalidate TLB without + * blocking because the target address of TLB invalidation is + * not currently mapped. + */ + if (need_flush_flpd_cache) { + struct exynos_iommu_owner *owner; + spin_lock(&priv->lock); + list_for_each_entry(owner, &priv->clients, client) + sysmmu_tlb_invalidate_flpdcache( + owner->dev, iova); + spin_unlock(&priv->lock); + } } return page_entry(sent, iova); } -static int lv1set_section(unsigned long *sent, phys_addr_t paddr, short *pgcnt) +static int lv1set_section(struct exynos_iommu_domain *priv, + sysmmu_pte_t *sent, sysmmu_iova_t iova, + phys_addr_t paddr, short *pgcnt) { - if (lv1ent_section(sent)) + if (lv1ent_section(sent)) { + WARN(1, "Trying mapping on 1MiB@%#08x that is mapped", + iova); return -EADDRINUSE; + } if (lv1ent_page(sent)) { - if (*pgcnt != NUM_LV2ENTRIES) + if (*pgcnt != NUM_LV2ENTRIES) { + WARN(1, "Trying mapping on 1MiB@%#08x that is mapped", + iova); return -EADDRINUSE; + } - kfree(page_entry(sent, 0)); - + kmem_cache_free(lv2table_kmem_cache, page_entry(sent, 0)); *pgcnt = 0; } + spin_lock(&priv->lock); + if (lv1ent_page_zero(sent)) { + struct exynos_iommu_owner *owner; + /* + * Flushing FLPD cache in System MMU v3.3 that may cache a FLPD + * entry by speculative prefetch of SLPD which has no mapping. + */ + list_for_each_entry(owner, &priv->clients, client) + sysmmu_tlb_invalidate_flpdcache(owner->dev, iova); + } + spin_unlock(&priv->lock); + *sent = mk_lv1ent_sect(paddr); pgtable_flush(sent, sent + 1); @@ -851,11 +1108,11 @@ static int lv1set_section(unsigned long *sent, phys_addr_t paddr, short *pgcnt) return 0; } -static int lv2set_page(unsigned long *pent, phys_addr_t paddr, size_t size, +static int lv2set_page(sysmmu_pte_t *pent, phys_addr_t paddr, size_t size, short *pgcnt) { if (size == SPAGE_SIZE) { - if (!lv2ent_fault(pent)) + if (WARN_ON(!lv2ent_fault(pent))) return -EADDRINUSE; *pent = mk_lv2ent_spage(paddr); @@ -864,8 +1121,9 @@ static int lv2set_page(unsigned long *pent, phys_addr_t paddr, size_t size, } else { /* size == LPAGE_SIZE */ int i; for (i = 0; i < SPAGES_PER_LPAGE; i++, pent++) { - if (!lv2ent_fault(pent)) { - memset(pent, 0, sizeof(*pent) * i); + if (WARN_ON(!lv2ent_fault(pent))) { + if (i > 0) + memset(pent - i, 0, sizeof(*pent) * i); return -EADDRINUSE; } @@ -878,11 +1136,38 @@ static int lv2set_page(unsigned long *pent, phys_addr_t paddr, size_t size, return 0; } -static int exynos_iommu_map(struct iommu_domain *domain, unsigned long iova, +/* + * *CAUTION* to the I/O virtual memory managers that support exynos-iommu: + * + * System MMU v3.x have an advanced logic to improve address translation + * performance with caching more page table entries by a page table walk. + * However, the logic has a bug that caching fault page table entries and System + * MMU reports page fault if the cached fault entry is hit even though the fault + * entry is updated to a valid entry after the entry is cached. + * To prevent caching fault page table entries which may be updated to valid + * entries later, the virtual memory manager should care about the w/a about the + * problem. The followings describe w/a. + * + * Any two consecutive I/O virtual address regions must have a hole of 128KiB + * in maximum to prevent misbehavior of System MMU 3.x. (w/a of h/w bug) + * + * Precisely, any start address of I/O virtual region must be aligned by + * the following sizes for System MMU v3.1 and v3.2. + * System MMU v3.1: 128KiB + * System MMU v3.2: 256KiB + * + * Because System MMU v3.3 caches page table entries more aggressively, it needs + * more w/a. + * - Any two consecutive I/O virtual regions must be have a hole of larger size + * than or equal size to 128KiB. + * - Start address of an I/O virtual region must be aligned by 128KiB. + */ +static int exynos_iommu_map(struct iommu_domain *domain, unsigned long l_iova, phys_addr_t paddr, size_t size, int prot) { struct exynos_iommu_domain *priv = domain->priv; - unsigned long *entry; + sysmmu_pte_t *entry; + sysmmu_iova_t iova = (sysmmu_iova_t)l_iova; unsigned long flags; int ret = -ENOMEM; @@ -893,38 +1178,52 @@ static int exynos_iommu_map(struct iommu_domain *domain, unsigned long iova, entry = section_entry(priv->pgtable, iova); if (size == SECT_SIZE) { - ret = lv1set_section(entry, paddr, + ret = lv1set_section(priv, entry, iova, paddr, &priv->lv2entcnt[lv1ent_offset(iova)]); } else { - unsigned long *pent; + sysmmu_pte_t *pent; - pent = alloc_lv2entry(entry, iova, + pent = alloc_lv2entry(priv, entry, iova, &priv->lv2entcnt[lv1ent_offset(iova)]); - if (!pent) - ret = -ENOMEM; + if (IS_ERR(pent)) + ret = PTR_ERR(pent); else ret = lv2set_page(pent, paddr, size, &priv->lv2entcnt[lv1ent_offset(iova)]); } - if (ret) { - pr_debug("%s: Failed to map iova 0x%lx/0x%x bytes\n", - __func__, iova, size); - } + if (ret) + pr_err("%s: Failed(%d) to map %#zx bytes @ %#x\n", + __func__, ret, size, iova); spin_unlock_irqrestore(&priv->pgtablelock, flags); return ret; } +static void exynos_iommu_tlb_invalidate_entry(struct exynos_iommu_domain *priv, + sysmmu_iova_t iova, size_t size) +{ + struct exynos_iommu_owner *owner; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + list_for_each_entry(owner, &priv->clients, client) + sysmmu_tlb_invalidate_entry(owner->dev, iova, size); + + spin_unlock_irqrestore(&priv->lock, flags); +} + static size_t exynos_iommu_unmap(struct iommu_domain *domain, - unsigned long iova, size_t size) + unsigned long l_iova, size_t size) { struct exynos_iommu_domain *priv = domain->priv; - struct sysmmu_drvdata *data; + sysmmu_iova_t iova = (sysmmu_iova_t)l_iova; + sysmmu_pte_t *ent; + size_t err_pgsize; unsigned long flags; - unsigned long *ent; BUG_ON(priv->pgtable == NULL); @@ -933,9 +1232,12 @@ static size_t exynos_iommu_unmap(struct iommu_domain *domain, ent = section_entry(priv->pgtable, iova); if (lv1ent_section(ent)) { - BUG_ON(size < SECT_SIZE); + if (WARN_ON(size < SECT_SIZE)) { + err_pgsize = SECT_SIZE; + goto err; + } - *ent = 0; + *ent = ZERO_LV2LINK; /* w/a for h/w bug in Sysmem MMU v3.3 */ pgtable_flush(ent, ent + 1); size = SECT_SIZE; goto done; @@ -959,34 +1261,42 @@ static size_t exynos_iommu_unmap(struct iommu_domain *domain, if (lv2ent_small(ent)) { *ent = 0; size = SPAGE_SIZE; + pgtable_flush(ent, ent + 1); priv->lv2entcnt[lv1ent_offset(iova)] += 1; goto done; } /* lv1ent_large(ent) == true here */ - BUG_ON(size < LPAGE_SIZE); + if (WARN_ON(size < LPAGE_SIZE)) { + err_pgsize = LPAGE_SIZE; + goto err; + } memset(ent, 0, sizeof(*ent) * SPAGES_PER_LPAGE); + pgtable_flush(ent, ent + SPAGES_PER_LPAGE); size = LPAGE_SIZE; priv->lv2entcnt[lv1ent_offset(iova)] += SPAGES_PER_LPAGE; done: spin_unlock_irqrestore(&priv->pgtablelock, flags); - spin_lock_irqsave(&priv->lock, flags); - list_for_each_entry(data, &priv->clients, node) - sysmmu_tlb_invalidate_entry(data->dev, iova); - spin_unlock_irqrestore(&priv->lock, flags); - + exynos_iommu_tlb_invalidate_entry(priv, iova, size); return size; +err: + spin_unlock_irqrestore(&priv->pgtablelock, flags); + + pr_err("%s: Failed: size(%#zx) @ %#x is smaller than page size %#zx\n", + __func__, size, iova, err_pgsize); + + return 0; } static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova) { struct exynos_iommu_domain *priv = domain->priv; - unsigned long *entry; + sysmmu_pte_t *entry; unsigned long flags; phys_addr_t phys = 0; @@ -1010,6 +1320,32 @@ static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain, return phys; } +static int exynos_iommu_add_device(struct device *dev) +{ + struct iommu_group *group; + int ret; + + group = iommu_group_get(dev); + + if (!group) { + group = iommu_group_alloc(); + if (IS_ERR(group)) { + dev_err(dev, "Failed to allocate IOMMU group\n"); + return PTR_ERR(group); + } + } + + ret = iommu_group_add_device(group, dev); + iommu_group_put(group); + + return ret; +} + +static void exynos_iommu_remove_device(struct device *dev) +{ + iommu_group_remove_device(dev); +} + static struct iommu_ops exynos_iommu_ops = { .domain_init = &exynos_iommu_domain_init, .domain_destroy = &exynos_iommu_domain_destroy, @@ -1018,6 +1354,8 @@ static struct iommu_ops exynos_iommu_ops = { .map = &exynos_iommu_map, .unmap = &exynos_iommu_unmap, .iova_to_phys = &exynos_iommu_iova_to_phys, + .add_device = &exynos_iommu_add_device, + .remove_device = &exynos_iommu_remove_device, .pgsize_bitmap = SECT_SIZE | LPAGE_SIZE | SPAGE_SIZE, }; @@ -1025,11 +1363,266 @@ static int __init exynos_iommu_init(void) { int ret; + lv2table_kmem_cache = kmem_cache_create("exynos-iommu-lv2table", + LV2TABLE_SIZE, LV2TABLE_SIZE, 0, NULL); + if (!lv2table_kmem_cache) { + pr_err("%s: Failed to create kmem cache\n", __func__); + return -ENOMEM; + } + ret = platform_driver_register(&exynos_sysmmu_driver); + if (ret) { + pr_err("%s: Failed to register driver\n", __func__); + goto err_reg_driver; + } - if (ret == 0) - bus_set_iommu(&platform_bus_type, &exynos_iommu_ops); + zero_lv2_table = kmem_cache_zalloc(lv2table_kmem_cache, GFP_KERNEL); + if (zero_lv2_table == NULL) { + pr_err("%s: Failed to allocate zero level2 page table\n", + __func__); + ret = -ENOMEM; + goto err_zero_lv2; + } + ret = bus_set_iommu(&platform_bus_type, &exynos_iommu_ops); + if (ret) { + pr_err("%s: Failed to register exynos-iommu driver.\n", + __func__); + goto err_set_iommu; + } + + return 0; +err_set_iommu: + kmem_cache_free(lv2table_kmem_cache, zero_lv2_table); +err_zero_lv2: + platform_driver_unregister(&exynos_sysmmu_driver); +err_reg_driver: + kmem_cache_destroy(lv2table_kmem_cache); return ret; } subsys_initcall(exynos_iommu_init); + +#ifdef CONFIG_PM_SLEEP +static int sysmmu_pm_genpd_suspend(struct device *dev) +{ + struct sysmmu_list_data *list; + int ret; + + ret = pm_generic_suspend(dev); + if (ret) + return ret; + + for_each_sysmmu_list(dev, list) { + struct sysmmu_drvdata *data = dev_get_drvdata(list->sysmmu); + unsigned long flags; + spin_lock_irqsave(&data->lock, flags); + if (!data->suspended && is_sysmmu_active(data) && + (!pm_runtime_enabled(dev) || data->runtime_active)) + __sysmmu_disable_nocount(data); + data->suspended = true; + spin_unlock_irqrestore(&data->lock, flags); + } + + return 0; +} + +static int sysmmu_pm_genpd_resume(struct device *dev) +{ + + struct sysmmu_list_data *list; + + for_each_sysmmu_list(dev, list) { + struct sysmmu_drvdata *data = dev_get_drvdata(list->sysmmu); + unsigned long flags; + spin_lock_irqsave(&data->lock, flags); + if (data->suspended && is_sysmmu_active(data) && + (!pm_runtime_enabled(dev) || data->runtime_active)) + __sysmmu_enable_nocount(data); + data->suspended = false; + spin_unlock_irqrestore(&data->lock, flags); + } + + return pm_generic_resume(dev); +} +#endif + +#ifdef CONFIG_PM_RUNTIME +static void sysmmu_restore_state(struct device *dev) +{ + struct sysmmu_list_data *list; + + for_each_sysmmu_list(dev, list) { + struct sysmmu_drvdata *data = dev_get_drvdata(list->sysmmu); + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + if (!data->runtime_active && is_sysmmu_active(data)) + __sysmmu_enable_nocount(data); + data->runtime_active = true; + spin_unlock_irqrestore(&data->lock, flags); + } +} + +static void sysmmu_save_state(struct device *dev) +{ + struct sysmmu_list_data *list; + + for_each_sysmmu_list(dev, list) { + struct sysmmu_drvdata *data = dev_get_drvdata(list->sysmmu); + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + if (data->runtime_active && is_sysmmu_active(data)) + __sysmmu_disable_nocount(data); + data->runtime_active = false; + spin_unlock_irqrestore(&data->lock, flags); + } +} + +static int sysmmu_pm_genpd_save_state(struct device *dev) +{ + struct exynos_iommu_client *client = dev->archdata.iommu; + int (*cb)(struct device *__dev); + int ret = 0; + + if (dev->type && dev->type->pm) + cb = dev->type->pm->runtime_suspend; + else if (dev->class && dev->class->pm) + cb = dev->class->pm->runtime_suspend; + else if (dev->bus && dev->bus->pm) + cb = dev->bus->pm->runtime_suspend; + else + cb = NULL; + + if (!cb && dev->driver && dev->driver->pm) + cb = dev->driver->pm->runtime_suspend; + + if (cb) + ret = cb(dev); + + if (ret == 0) + sysmmu_save_state(dev); + + return ret; +} + +static int sysmmu_pm_genpd_restore_state(struct device *dev) +{ + struct exynos_iommu_client *client = dev->archdata.iommu; + int (*cb)(struct device *__dev); + int ret = 0; + + if (dev->type && dev->type->pm) + cb = dev->type->pm->runtime_resume; + else if (dev->class && dev->class->pm) + cb = dev->class->pm->runtime_resume; + else if (dev->bus && dev->bus->pm) + cb = dev->bus->pm->runtime_resume; + else + cb = NULL; + + if (!cb && dev->driver && dev->driver->pm) + cb = dev->driver->pm->runtime_resume; + + sysmmu_restore_state(dev); + + if (cb) + ret = cb(dev); + + if (ret) + sysmmu_restore_state(dev); + + return ret; +} +#endif + +#ifdef CONFIG_PM_GENERIC_DOMAINS +struct gpd_dev_ops sysmmu_devpm_ops = { +#ifdef CONFIG_PM_RUNTIME + .save_state = &sysmmu_pm_genpd_save_state, + .restore_state = &sysmmu_pm_genpd_restore_state, +#endif +#ifdef CONFIG_PM_SLEEP + .suspend = &sysmmu_pm_genpd_suspend, + .resume = &sysmmu_pm_genpd_resume, +#endif +}; +#endif /* CONFIG_PM_GENERIC_DOMAINS */ + +static int sysmmu_hook_driver_register(struct notifier_block *nb, + unsigned long val, + void *p) +{ + struct device *dev = p; + + /* + * No System MMU assigned even though in the initial state. + * See exynos_sysmmu_probe(). + */ + if (dev->archdata.iommu == NULL) + return 0; + + switch (val) { + case BUS_NOTIFY_BIND_DRIVER: + { + if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS) && dev->pm_domain) { + int ret = pm_genpd_add_callbacks( + dev, &sysmmu_devpm_ops, NULL); + if (ret && (ret != -ENOSYS)) { + dev_err(dev, + "Failed to register 'dev_pm_ops' for iommu\n"); + return ret; + } + } + + break; + } + case BUS_NOTIFY_BOUND_DRIVER: + { + struct sysmmu_list_data *list; + + /* OK if runtime PM is enabled with genpd for dev */ + if (pm_runtime_enabled(dev) && dev->pm_domain) + break; + + /* + * System MMU will be permanently enabled if the master H/W is + * neither registered to a power domain nor runtime PM enabled. + */ + for_each_sysmmu_list(dev, list) { + struct sysmmu_drvdata *data = + dev_get_drvdata(list->sysmmu); + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + if (is_sysmmu_active(data) && !data->runtime_active) + __sysmmu_enable_nocount(data); + data->runtime_active = true; + pm_runtime_disable(data->sysmmu); + spin_unlock_irqrestore(&data->lock, flags); + } + + break; + } + case BUS_NOTIFY_UNBOUND_DRIVER: + { + struct exynos_iommu_owner *owner = dev->archdata.iommu; + if (WARN_ON(!list_empty(&owner->client))) + iommu_detach_device(owner->domain, dev); + __pm_genpd_remove_callbacks(dev, false); + break; + } + } /* switch (val) */ + + return 0; +} + +static struct notifier_block sysmmu_notifier = { + .notifier_call = &sysmmu_hook_driver_register, +}; + +static int __init exynos_iommu_prepare(void) +{ + return bus_register_notifier(&platform_bus_type, &sysmmu_notifier); +} +subsys_initcall_sync(exynos_iommu_prepare); diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index 40e6440348ff5d..73fb6993680ac3 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -50,14 +50,14 @@ static void combiner_mask_irq(struct irq_data *data) { u32 mask = 1 << (data->hwirq % 32); - __raw_writel(mask, combiner_base(data) + COMBINER_ENABLE_CLEAR); + writel_relaxed(mask, combiner_base(data) + COMBINER_ENABLE_CLEAR); } static void combiner_unmask_irq(struct irq_data *data) { u32 mask = 1 << (data->hwirq % 32); - __raw_writel(mask, combiner_base(data) + COMBINER_ENABLE_SET); + writel_relaxed(mask, combiner_base(data) + COMBINER_ENABLE_SET); } static void combiner_handle_cascade_irq(unsigned int irq, struct irq_desc *desc) @@ -70,7 +70,7 @@ static void combiner_handle_cascade_irq(unsigned int irq, struct irq_desc *desc) chained_irq_enter(chip, desc); spin_lock(&irq_controller_lock); - status = __raw_readl(chip_data->base + COMBINER_INT_STATUS); + status = readl_relaxed(chip_data->base + COMBINER_INT_STATUS); spin_unlock(&irq_controller_lock); status &= chip_data->irq_mask; @@ -131,7 +131,7 @@ static void __init combiner_init_one(struct combiner_chip_data *combiner_data, combiner_data->parent_irq = irq; /* Disable all interrupts */ - __raw_writel(combiner_data->irq_mask, base + COMBINER_ENABLE_CLEAR); + writel_relaxed(combiner_data->irq_mask, base + COMBINER_ENABLE_CLEAR); } static int combiner_irq_domain_xlate(struct irq_domain *d, diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 784695d22fde1a..8b45281f335870 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -543,29 +543,29 @@ static int crypt_iv_lmk_one(struct crypt_config *cc, u8 *iv, u8 *data) { struct iv_lmk_private *lmk = &cc->iv_gen_private.lmk; - struct { - struct shash_desc desc; - char ctx[crypto_shash_descsize(lmk->hash_tfm)]; - } sdesc; + char sdesc[sizeof(struct shash_desc) + + crypto_shash_descsize(lmk->hash_tfm) + + CRYPTO_MINALIGN] CRYPTO_MINALIGN_ATTR; + struct shash_desc *desc = (struct shash_desc *)sdesc; struct md5_state md5state; __le32 buf[4]; int i, r; - sdesc.desc.tfm = lmk->hash_tfm; - sdesc.desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP; + desc->tfm = lmk->hash_tfm; + desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; - r = crypto_shash_init(&sdesc.desc); + r = crypto_shash_init(desc); if (r) return r; if (lmk->seed) { - r = crypto_shash_update(&sdesc.desc, lmk->seed, LMK_SEED_SIZE); + r = crypto_shash_update(desc, lmk->seed, LMK_SEED_SIZE); if (r) return r; } /* Sector is always 512B, block size 16, add data of blocks 1-31 */ - r = crypto_shash_update(&sdesc.desc, data + 16, 16 * 31); + r = crypto_shash_update(desc, data + 16, 16 * 31); if (r) return r; @@ -574,12 +574,12 @@ static int crypt_iv_lmk_one(struct crypt_config *cc, u8 *iv, buf[1] = cpu_to_le32((((u64)dmreq->iv_sector >> 32) & 0x00FFFFFF) | 0x80000000); buf[2] = cpu_to_le32(4024); buf[3] = 0; - r = crypto_shash_update(&sdesc.desc, (u8 *)buf, sizeof(buf)); + r = crypto_shash_update(desc, (u8 *)buf, sizeof(buf)); if (r) return r; /* No MD5 padding here */ - r = crypto_shash_export(&sdesc.desc, &md5state); + r = crypto_shash_export(desc, &md5state); if (r) return r; diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 49bb445d846aa7..0f02e2efde9d31 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -59,6 +59,14 @@ config MFD_AAT2870_CORE additional drivers must be enabled in order to use the functionality of the device. +config MFD_BCM590XX + tristate "Broadcom BCM590xx PMUs" + select MFD_CORE + select REGMAP_I2C + depends on I2C + help + Support for the BCM590xx PMUs from Broadcom + config MFD_CROS_EC tristate "ChromeOS Embedded Controller" select MFD_CORE @@ -343,6 +351,20 @@ config MFD_MAX77686 additional drivers must be enabled in order to use the functionality of the device. +config MFD_MAX77XXX + bool "Maxim Semiconductor MAX77686/802 PMIC Support" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select IRQ_DOMAIN + help + Say yes here to support for Maxim Semiconductor MAX77xxx. The + driver supports MAX77686 and MAX77802. These are companion + Power Management ICs with RTC. + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the functionality + of the device. + config MFD_MAX77693 bool "Maxim Semiconductor MAX77693 PMIC Support" depends on I2C=y @@ -1193,6 +1215,14 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series. +config MFD_HI6421_PMIC + tristate "HiSilicon Hi6421 PMU/Codec IC" + depends on OF + help + This driver supports HiSilicon Hi6421 power management and codec IC, + including regulators, codec, ADCs, Coulomb counter, etc. Memory + mapped I/O ports are the way of communication with it. + endmenu endif diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 5aea5ef0a62f51..2b8eb8ddbc8d46 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_MFD_88PM800) += 88pm800.o 88pm80x.o obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o +obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o obj-$(CONFIG_MFD_CROS_EC) += cros_ec.o obj-$(CONFIG_MFD_CROS_EC_I2C) += cros_ec_i2c.o obj-$(CONFIG_MFD_CROS_EC_SPI) += cros_ec_spi.o @@ -115,6 +116,7 @@ obj-$(CONFIG_MFD_MAX14577) += max14577.o obj-$(CONFIG_MFD_MAX77686) += max77686.o max77686-irq.o obj-$(CONFIG_MFD_MAX77693) += max77693.o max77693-irq.o obj-$(CONFIG_MFD_MAX8907) += max8907.o +obj-$(CONFIG_MFD_MAX77XXX) += max77xxx.o max77xxx-irq.o max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o obj-$(CONFIG_MFD_MAX8997) += max8997.o max8997-irq.o @@ -166,3 +168,4 @@ obj-$(CONFIG_MFD_RETU) += retu-mfd.o obj-$(CONFIG_MFD_AS3711) += as3711.o obj-$(CONFIG_MFD_AS3722) += as3722.o obj-$(CONFIG_MFD_STW481X) += stw481x.o +obj-$(CONFIG_MFD_HI6421_PMIC) += hi6421-pmic-core.o diff --git a/drivers/mfd/bcm590xx.c b/drivers/mfd/bcm590xx.c new file mode 100644 index 00000000000000..926a57e4d533df --- /dev/null +++ b/drivers/mfd/bcm590xx.c @@ -0,0 +1,93 @@ +/* + * Broadcom BCM590xx PMU + * + * Copyright 2014 Linaro Limited + * Author: Matt Porter + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct mfd_cell bcm590xx_devs[] = { + { + .name = "bcm590xx-vregs", + }, +}; + +static const struct regmap_config bcm590xx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BCM590XX_MAX_REGISTER, + .cache_type = REGCACHE_RBTREE, +}; + +static int bcm590xx_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct bcm590xx *bcm590xx; + int ret; + + bcm590xx = devm_kzalloc(&i2c->dev, sizeof(*bcm590xx), GFP_KERNEL); + if (!bcm590xx) + return -ENOMEM; + + i2c_set_clientdata(i2c, bcm590xx); + bcm590xx->dev = &i2c->dev; + bcm590xx->i2c_client = i2c; + + bcm590xx->regmap = devm_regmap_init_i2c(i2c, &bcm590xx_regmap_config); + if (IS_ERR(bcm590xx->regmap)) { + ret = PTR_ERR(bcm590xx->regmap); + dev_err(&i2c->dev, "regmap initialization failed: %d\n", ret); + return ret; + } + + ret = mfd_add_devices(&i2c->dev, -1, bcm590xx_devs, + ARRAY_SIZE(bcm590xx_devs), NULL, 0, NULL); + if (ret < 0) + dev_err(&i2c->dev, "failed to add sub-devices: %d\n", ret); + + return ret; +} + +static const struct of_device_id bcm590xx_of_match[] = { + { .compatible = "brcm,bcm59056" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bcm590xx_of_match); + +static const struct i2c_device_id bcm590xx_i2c_id[] = { + { "bcm59056" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bcm590xx_i2c_id); + +static struct i2c_driver bcm590xx_i2c_driver = { + .driver = { + .name = "bcm590xx", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(bcm590xx_of_match), + }, + .probe = bcm590xx_i2c_probe, + .id_table = bcm590xx_i2c_id, +}; +module_i2c_driver(bcm590xx_i2c_driver); + +MODULE_AUTHOR("Matt Porter "); +MODULE_DESCRIPTION("BCM590xx multi-function driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bcm590xx"); diff --git a/drivers/mfd/hi6421-pmic-core.c b/drivers/mfd/hi6421-pmic-core.c new file mode 100644 index 00000000000000..1ef6d4e161720f --- /dev/null +++ b/drivers/mfd/hi6421-pmic-core.c @@ -0,0 +1,310 @@ +/* + * Device driver for regulators in Hi6421 IC + * + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2011 Hisilicon. + * + * Guodong Xu + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* 8-bit register offset in PMIC */ +#define HI6421_REG_IRQ1 1 +#define HI6421_REG_IRQ2 2 +#define HI6421_REG_IRQ3 3 +#define HI6421_REG_IRQM1 4 +#define HI6421_REG_IRQM2 5 +#define HI6421_REG_IRQM3 6 + +static struct of_device_id of_hi6421_pmic_child_match_tbl[] = { + /* regulators */ + { + .compatible = "hisilicon,hi6421-ldo", + }, + { + .compatible = "hisilicon,hi6421-buck012", + }, + { + .compatible = "hisilicon,hi6421-buck345", + }, + { /* end */ } +}; + +static struct of_device_id of_hi6421_pmic_match_tbl[] = { + { + .compatible = "hisilicon,hi6421-pmic", + }, + { /* end */ } +}; + +/* + * The PMIC register is only 8-bit. + * Hisilicon SoC use hardware to map PMIC register into SoC mapping. + * At here, we are accessing SoC register with 32-bit. + */ +u32 hi6421_pmic_read(struct hi6421_pmic *pmic, int reg) +{ + unsigned long flags; + u32 ret; + spin_lock_irqsave(&pmic->lock, flags); + ret = readl_relaxed(pmic->regs + (reg << 2)); + spin_unlock_irqrestore(&pmic->lock, flags); + return ret; +} +EXPORT_SYMBOL(hi6421_pmic_read); + +void hi6421_pmic_write(struct hi6421_pmic *pmic, int reg, u32 val) +{ + unsigned long flags; + spin_lock_irqsave(&pmic->lock, flags); + writel_relaxed(val, pmic->regs + (reg << 2)); + spin_unlock_irqrestore(&pmic->lock, flags); +} +EXPORT_SYMBOL(hi6421_pmic_write); + +void hi6421_pmic_rmw(struct hi6421_pmic *pmic, int reg, + u32 mask, u32 bits) +{ + u32 data; + + spin_lock(&pmic->lock); + data = readl_relaxed(pmic->regs + (reg << 2)) & ~mask; + data |= mask & bits; + writel_relaxed(data, pmic->regs + (reg << 2)); + spin_unlock(&pmic->lock); +} +EXPORT_SYMBOL(hi6421_pmic_rmw); + +static int hi6421_to_irq(struct hi6421_pmic *pmic, unsigned offset) +{ + return irq_find_mapping(pmic->domain, offset); +} + +static irqreturn_t hi6421_irq_handler(int irq, void *data) +{ + struct hi6421_pmic *pmic = (struct hi6421_pmic *)data; + unsigned long pending; + int i, offset, index; + + + for (i = HI6421_REG_IRQ1; i <= HI6421_REG_IRQ3; i++) { + spin_lock(&pmic->lock); + pending = readl_relaxed(pmic->regs + (i << 2)); + pending &= HI6421_MASK_FIELD; + writel_relaxed(pending, pmic->regs + ((i + 3) << 2)); + spin_unlock(&pmic->lock); + + if (pending) { + for_each_set_bit(offset, &pending, HI6421_BITS) { + index = offset + (i - HI6421_REG_IRQ1) * HI6421_BITS; + generic_handle_irq(hi6421_to_irq(pmic, index)); + } + } + + spin_lock(&pmic->lock); + writel_relaxed(0, pmic->regs + ((i + 3) << 2)); + writel_relaxed(pending, pmic->regs + (i << 2)); + spin_unlock(&pmic->lock); + } + + return IRQ_HANDLED; +} + +static void hi6421_irq_mask(struct irq_data *d) +{ + struct hi6421_pmic *pmic = irq_data_get_irq_chip_data(d); + u32 data, offset; + + offset = ((irqd_to_hwirq(d) >> 3) + HI6421_REG_IRQM1) << 2; + spin_lock(&pmic->lock); + data = readl_relaxed(pmic->regs + offset); + data |= irqd_to_hwirq(d) % 8; + writel_relaxed(data, pmic->regs + offset); + spin_unlock(&pmic->lock); +} + +static void hi6421_irq_unmask(struct irq_data *d) +{ + struct hi6421_pmic *pmic = irq_data_get_irq_chip_data(d); + u32 data, offset; + + offset = ((irqd_to_hwirq(d) >> 3) + HI6421_REG_IRQM1) << 2; + spin_lock(&pmic->lock); + data = readl_relaxed(pmic->regs + offset); + data &= ~(irqd_to_hwirq(d) % 8); + writel_relaxed(data, pmic->regs + offset); + spin_unlock(&pmic->lock); +} + +static struct irq_chip hi6421_irqchip = { + .name = "pmic", + .irq_mask = hi6421_irq_mask, + .irq_unmask = hi6421_irq_unmask, +}; + +static int hi6421_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + struct hi6421_pmic *pmic = d->host_data; + + irq_set_chip_and_handler_name(virq, &hi6421_irqchip, + handle_simple_irq, "hi6421"); + irq_set_chip_data(virq, pmic); + irq_set_irq_type(virq, IRQ_TYPE_NONE); + + return 0; +} + +static struct irq_domain_ops hi6421_domain_ops = { + .map = hi6421_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int hi6421_pmic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct hi6421_pmic *pmic = NULL; + enum of_gpio_flags flags; + int i, ret; + + pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) { + dev_err(dev, "cannot allocate hi6421_pmic device info\n"); + return -ENOMEM; + } + + mutex_init(&pmic->enable_mutex); + /* get resources */ + pmic->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!pmic->res) { + dev_err(dev, "platform_get_resource err\n"); + return -ENOENT; + } + + if (!devm_request_mem_region(dev, pmic->res->start, + resource_size(pmic->res), + pdev->name)) { + dev_err(dev, "cannot claim register memory\n"); + return -ENOMEM; + } + + pmic->regs = devm_ioremap(dev, pmic->res->start, + resource_size(pmic->res)); + if (!pmic->regs) { + dev_err(dev, "cannot map register memory\n"); + return -ENOMEM; + } + + /* TODO: get and enable clk request */ + + spin_lock_init(&pmic->lock); + + pmic->gpio = of_get_gpio_flags(np, 0, &flags); + if (pmic->gpio < 0) + return pmic->gpio; + if (!gpio_is_valid(pmic->gpio)) + return -EINVAL; + ret = gpio_request_one(pmic->gpio, GPIOF_IN, "pmic"); + if (ret < 0) { + dev_err(dev, "failed to request gpio%d\n", pmic->gpio); + return ret; + } + pmic->irq = gpio_to_irq(pmic->gpio); + /* clear IRQ status */ + spin_lock(&pmic->lock); + writel_relaxed(0xff, pmic->regs + (HI6421_REG_IRQ1 << 2)); + writel_relaxed(0xff, pmic->regs + (HI6421_REG_IRQ2 << 2)); + writel_relaxed(0xff, pmic->regs + (HI6421_REG_IRQ3 << 2)); + spin_unlock(&pmic->lock); + + pmic->domain = irq_domain_add_simple(np, HI6421_NR_IRQ, 0, + &hi6421_domain_ops, pmic); + if (!pmic->domain) + return -ENODEV; + + for (i = 0; i < HI6421_NR_IRQ; i++) { + ret = irq_create_mapping(pmic->domain, i); + if (ret == NO_IRQ) { + dev_err(dev, "failed mapping hwirq %d\n", i); + return -ENOMEM; + } + } + + ret = request_threaded_irq(pmic->irq, hi6421_irq_handler, NULL, + IRQF_TRIGGER_LOW | IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND, + "pmic", pmic); + + platform_set_drvdata(pdev, pmic); + + /* set over-current protection debounce 8ms*/ + hi6421_pmic_rmw(pmic, OCP_DEB_CTRL_REG, \ + OCP_DEB_SEL_MASK | OCP_EN_DEBOUNCE_MASK | OCP_AUTO_STOP_MASK, \ + OCP_DEB_SEL_8MS | OCP_EN_DEBOUNCE_ENABLE); + + /* populate sub nodes */ + of_platform_populate(np, of_hi6421_pmic_child_match_tbl, NULL, dev); + + return 0; +} + +static int hi6421_pmic_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hi6421_pmic *pmic = platform_get_drvdata(pdev); + + free_irq(pmic->irq, pmic); + gpio_free(pmic->gpio); + devm_iounmap(dev, pmic->regs); + devm_release_mem_region(dev, pmic->res->start, + resource_size(pmic->res)); + devm_kfree(dev, pmic); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver hi6421_pmic_driver = { + .driver = { + .name = "hi6421_pmic", + .owner = THIS_MODULE, + .of_match_table = of_hi6421_pmic_match_tbl, + }, + .probe = hi6421_pmic_probe, + .remove = hi6421_pmic_remove, +}; +module_platform_driver(hi6421_pmic_driver); + +MODULE_AUTHOR("Guodong Xu "); +MODULE_DESCRIPTION("Hi6421 PMIC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/max77xxx-irq.c b/drivers/mfd/max77xxx-irq.c new file mode 100644 index 00000000000000..6283630ef64518 --- /dev/null +++ b/drivers/mfd/max77xxx-irq.c @@ -0,0 +1,379 @@ +/* + * max77xxx-irq.c - Interrupt controller support for MAX77XXX + * + * Copyright (C) 2013 Google, Inc + * + * Copyright (C) 2012 Samsung Electronics Co.Ltd + * Chiwoong Byun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8997-irq.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + MAX77XXX_DEBUG_IRQ_INFO = 1 << 0, + MAX77XXX_DEBUG_IRQ_MASK = 1 << 1, + MAX77XXX_DEBUG_IRQ_INT = 1 << 2, +}; + +static int debug_mask; +module_param(debug_mask, int, 0); +MODULE_PARM_DESC(debug_mask, "Set debug_mask : 0x0=off 0x1=IRQ_INFO 0x2=IRQ_MASK 0x4=IRQ_INI)"); + +static const u8 max77686_mask_reg[] = { + [PMIC_INT1] = MAX77XXX_REG_INT1MSK, + [PMIC_INT2] = MAX77XXX_REG_INT2MSK, + [RTC_INT] = MAX77686_RTC_INTM, +}; + +static const u8 max77802_mask_reg[] = { + [PMIC_INT1] = MAX77XXX_REG_INT1MSK, + [PMIC_INT2] = MAX77XXX_REG_INT2MSK, + [RTC_INT] = MAX77802_RTC_INTM, +}; + +static const u8 max77686_irq_reg[] = { + [PMIC_INT1] = MAX77XXX_REG_INT1, + [PMIC_INT2] = MAX77XXX_REG_INT2, + [RTC_INT] = MAX77686_RTC_INT, +}; + +static const u8 max77802_irq_reg[] = { + [PMIC_INT1] = MAX77XXX_REG_INT1, + [PMIC_INT2] = MAX77XXX_REG_INT2, + [RTC_INT] = MAX77802_RTC_INT, +}; + +static struct regmap *max77xxx_get_regmap(struct max77xxx_dev *max77xxx, + enum max77xxx_irq_source src) +{ + switch (src) { + case PMIC_INT1 ... PMIC_INT2: + return max77xxx->regmap; + case RTC_INT: + return max77xxx->rtc_regmap; + default: + return ERR_PTR(-EINVAL); + } +} + +struct max77xxx_irq_data { + int mask; + enum max77xxx_irq_source group; +}; + +#define DECLARE_IRQ(idx, _group, _mask) \ + [(idx)] = { .group = (_group), .mask = (_mask) } +static const struct max77xxx_irq_data max77xxx_irqs[] = { + DECLARE_IRQ(MAX77XXX_PMICIRQ_PWRONF, PMIC_INT1, 1 << 0), + DECLARE_IRQ(MAX77XXX_PMICIRQ_PWRONR, PMIC_INT1, 1 << 1), + DECLARE_IRQ(MAX77XXX_PMICIRQ_JIGONBF, PMIC_INT1, 1 << 2), + DECLARE_IRQ(MAX77XXX_PMICIRQ_JIGONBR, PMIC_INT1, 1 << 3), + DECLARE_IRQ(MAX77XXX_PMICIRQ_ACOKBF, PMIC_INT1, 1 << 4), + DECLARE_IRQ(MAX77XXX_PMICIRQ_ACOKBR, PMIC_INT1, 1 << 5), + DECLARE_IRQ(MAX77XXX_PMICIRQ_ONKEY1S, PMIC_INT1, 1 << 6), + DECLARE_IRQ(MAX77XXX_PMICIRQ_MRSTB, PMIC_INT1, 1 << 7), + DECLARE_IRQ(MAX77XXX_PMICIRQ_140C, PMIC_INT2, 1 << 0), + DECLARE_IRQ(MAX77XXX_PMICIRQ_120C, PMIC_INT2, 1 << 1), + DECLARE_IRQ(MAX77XXX_RTCIRQ_RTC60S, RTC_INT, 1 << 0), + DECLARE_IRQ(MAX77XXX_RTCIRQ_RTCA1, RTC_INT, 1 << 1), + DECLARE_IRQ(MAX77XXX_RTCIRQ_RTCA2, RTC_INT, 1 << 2), + DECLARE_IRQ(MAX77XXX_RTCIRQ_SMPL, RTC_INT, 1 << 3), + DECLARE_IRQ(MAX77XXX_RTCIRQ_RTC1S, RTC_INT, 1 << 4), + DECLARE_IRQ(MAX77XXX_RTCIRQ_WTSR, RTC_INT, 1 << 5), +}; + +static void max77xxx_irq_lock(struct irq_data *data) +{ + struct max77xxx_dev *max77xxx = irq_get_chip_data(data->irq); + + if (debug_mask & MAX77XXX_DEBUG_IRQ_MASK) + pr_info("%s\n", __func__); + + mutex_lock(&max77xxx->irqlock); +} + +static void max77xxx_irq_sync_unlock(struct irq_data *data) +{ + struct max77xxx_dev *max77xxx = irq_get_chip_data(data->irq); + int i; + + for (i = 0; i < MAX77XXX_IRQ_GROUP_NR; i++) { + u8 mask_reg = max77xxx->irq_mask_regs[i]; + struct regmap *map = max77xxx_get_regmap(max77xxx, i); + + if (debug_mask & MAX77XXX_DEBUG_IRQ_MASK) + pr_debug("%s: mask_reg[%d]=0x%x, cur=0x%x\n", + __func__, i, mask_reg, max77xxx->irq_masks_cur[i]); + + if (mask_reg == MAX77XXX_REG_INVALID || + IS_ERR_OR_NULL(map)) + continue; + + max77xxx->irq_masks_cache[i] = max77xxx->irq_masks_cur[i]; + + regmap_write(map, mask_reg, max77xxx->irq_masks_cur[i]); + } + + mutex_unlock(&max77xxx->irqlock); +} + +static const inline struct max77xxx_irq_data *to_max77xxx_irq(int irq) +{ + struct irq_data *data = irq_get_irq_data(irq); + return &max77xxx_irqs[data->hwirq]; +} + +static void max77xxx_irq_mask(struct irq_data *data) +{ + struct max77xxx_dev *max77xxx = irq_get_chip_data(data->irq); + const struct max77xxx_irq_data *irq_data = to_max77xxx_irq(data->irq); + + max77xxx->irq_masks_cur[irq_data->group] |= irq_data->mask; + + if (debug_mask & MAX77XXX_DEBUG_IRQ_MASK) + pr_info("%s: group=%d, cur=0x%x\n", + __func__, irq_data->group, + max77xxx->irq_masks_cur[irq_data->group]); +} + +static void max77xxx_irq_unmask(struct irq_data *data) +{ + struct max77xxx_dev *max77xxx = irq_get_chip_data(data->irq); + const struct max77xxx_irq_data *irq_data = to_max77xxx_irq(data->irq); + + max77xxx->irq_masks_cur[irq_data->group] &= ~irq_data->mask; + + if (debug_mask & MAX77XXX_DEBUG_IRQ_MASK) + pr_info("%s: group=%d, cur=0x%x\n", + __func__, irq_data->group, + max77xxx->irq_masks_cur[irq_data->group]); +} + +static int max77xxx_irq_set_wake(struct irq_data *data, unsigned int on) +{ + struct max77xxx_dev *max77xxx = irq_get_chip_data(data->irq); + + if (device_may_wakeup(max77xxx->dev)) { + if (on) + return enable_irq_wake(max77xxx->irq); + else + return disable_irq_wake(max77xxx->irq); + } else if (on) { + dev_warn(max77xxx->dev, + "Child requested wakeup but wakeup disabled\n"); + return -ENXIO; + } + return 0; +} + +static struct irq_chip max77xxx_irq_chip = { + .name = "max77xxx", + .irq_bus_lock = max77xxx_irq_lock, + .irq_bus_sync_unlock = max77xxx_irq_sync_unlock, + .irq_mask = max77xxx_irq_mask, + .irq_unmask = max77xxx_irq_unmask, + .irq_set_wake = max77xxx_irq_set_wake, +}; + +static irqreturn_t max77xxx_irq_thread(int irq, void *data) +{ + struct max77xxx_dev *max77xxx = data; + unsigned int irq_reg[MAX77XXX_IRQ_GROUP_NR] = {}; + unsigned int irq_src; + int ret; + int i, cur_irq; + + ret = regmap_read(max77xxx->regmap, MAX77XXX_REG_INTSRC, &irq_src); + if (ret < 0) { + dev_err(max77xxx->dev, "Failed to read interrupt source: %d\n", + ret); + return IRQ_NONE; + } + + if (debug_mask & MAX77XXX_DEBUG_IRQ_INT) + pr_info("%s: irq_src=0x%x\n", __func__, irq_src); + + if (irq_src == MAX77XXX_IRQSRC_PMIC) { + ret = regmap_bulk_read(max77xxx->regmap, + max77xxx->irq_regs[PMIC_INT1], + irq_reg, 2); + if (ret < 0) { + dev_err(max77xxx->dev, "Failed to read interrupt source: %d\n", + ret); + return IRQ_NONE; + } + + if (debug_mask & MAX77XXX_DEBUG_IRQ_INT) + pr_info("%s: int1=0x%x, int2=0x%x\n", __func__, + irq_reg[PMIC_INT1], irq_reg[PMIC_INT2]); + } + + if (irq_src & MAX77XXX_IRQSRC_RTC) { + ret = regmap_read(max77xxx->rtc_regmap, + max77xxx->irq_regs[RTC_INT], + &irq_reg[RTC_INT]); + if (ret < 0) { + dev_err(max77xxx->dev, "Failed to read interrupt source: %d\n", + ret); + return IRQ_NONE; + } + + if (debug_mask & MAX77XXX_DEBUG_IRQ_INT) + pr_info("%s: rtc int=0x%x\n", __func__, + irq_reg[RTC_INT]); + + } + + for (i = 0; i < MAX77XXX_IRQ_GROUP_NR; i++) + irq_reg[i] &= ~max77xxx->irq_masks_cur[i]; + + for (i = 0; i < MAX77XXX_IRQ_NR; i++) { + if (irq_reg[max77xxx_irqs[i].group] & max77xxx_irqs[i].mask) { + cur_irq = irq_find_mapping(max77xxx->irq_domain, i); + if (cur_irq) + handle_nested_irq(cur_irq); + } + } + + return IRQ_HANDLED; +} + +static int max77xxx_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct max77xxx_dev *max77xxx = d->host_data; + + irq_set_chip_data(irq, max77xxx); + irq_set_chip_and_handler(irq, &max77xxx_irq_chip, handle_simple_irq); + irq_set_nested_thread(irq, 1); + irq_set_parent(irq, max77xxx->irq); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + return 0; +} + +static struct irq_domain_ops max77xxx_irq_domain_ops = { + .map = max77xxx_irq_domain_map, +}; + +int max77xxx_irq_init(struct max77xxx_dev *max77xxx) +{ + struct irq_domain *domain; + int i; + int ret; + int val; + struct regmap *map; + + mutex_init(&max77xxx->irqlock); + + if (max77xxx->irq_gpio && !max77xxx->irq) { + max77xxx->irq = gpio_to_irq(max77xxx->irq_gpio); + + if (debug_mask & MAX77XXX_DEBUG_IRQ_INT) { + ret = gpio_request(max77xxx->irq_gpio, "pmic_irq"); + if (ret < 0) { + dev_err(max77xxx->dev, + "Failed to request gpio %d with ret:" + "%d\n", max77xxx->irq_gpio, ret); + return IRQ_NONE; + } + + gpio_direction_input(max77xxx->irq_gpio); + val = gpio_get_value(max77xxx->irq_gpio); + gpio_free(max77xxx->irq_gpio); + pr_info("%s: gpio_irq=%x\n", __func__, val); + } + } + + if (!max77xxx->irq) { + dev_err(max77xxx->dev, "irq is not specified\n"); + return -ENODEV; + } + + if (max77xxx->type == TYPE_MAX77686) { + max77xxx->irq_regs = max77686_irq_reg; + max77xxx->irq_mask_regs = max77686_mask_reg; + } else { + max77xxx->irq_regs = max77802_irq_reg; + max77xxx->irq_mask_regs = max77802_mask_reg; + } + + /* Mask individual interrupt sources */ + for (i = 0; i < MAX77XXX_IRQ_GROUP_NR; i++) { + max77xxx->irq_masks_cur[i] = 0xff; + max77xxx->irq_masks_cache[i] = 0xff; + map = max77xxx_get_regmap(max77xxx, i); + + if (IS_ERR_OR_NULL(map)) + continue; + if (max77xxx->irq_mask_regs[i] == MAX77XXX_REG_INVALID) + continue; + + regmap_write(map, max77xxx->irq_mask_regs[i], 0xff); + } + domain = irq_domain_add_linear(NULL, MAX77XXX_IRQ_NR, + &max77xxx_irq_domain_ops, max77xxx); + if (!domain) { + dev_err(max77xxx->dev, "could not create irq domain\n"); + return -ENODEV; + } + max77xxx->irq_domain = domain; + + ret = request_threaded_irq(max77xxx->irq, NULL, max77xxx_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max77xxx-irq", max77xxx); + + if (ret) + dev_err(max77xxx->dev, "Failed to request IRQ %d: %d\n", + max77xxx->irq, ret); + + + if (debug_mask & MAX77XXX_DEBUG_IRQ_INFO) + pr_info("%s-\n", __func__); + + return 0; +} + +void max77xxx_irq_exit(struct max77xxx_dev *max77xxx) +{ + if (max77xxx->irq) + free_irq(max77xxx->irq, max77xxx); +} + +int max77xxx_irq_resume(struct max77xxx_dev *max77xxx) +{ + /* + * The IRQ that woke us up may still need to be ACK'ed on resume. + * If it isn't ever ACK'ed, future IRQs may not be delivered. + */ + if (max77xxx->irq) + max77xxx_irq_thread(0, max77xxx); + + return 0; +} diff --git a/drivers/mfd/max77xxx.c b/drivers/mfd/max77xxx.c new file mode 100644 index 00000000000000..b25a1286a870d4 --- /dev/null +++ b/drivers/mfd/max77xxx.c @@ -0,0 +1,309 @@ +/* + * max77xxx.c - mfd core driver for the Maxim 77686 and 77802 + * + * Copyright (C) 2013 Google, Inc + * + * Copyright (C) 2012 Samsung Electronics + * Chiwoong Byun + * Jonghwa Lee + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8997.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct mfd_cell max77xxx_devs[TYPE_COUNT][2] = { + { + { .name = "max77686-pmic", }, + { .name = "max77686-rtc", }, + }, + { + { .name = "max77802-pmic", }, + { .name = "max77802-rtc", }, + }, +}; + +static bool max77686_is_accessible_reg(struct device *dev, unsigned int reg) +{ + return (reg >= MAX77XXX_REG_DEVICE_ID && reg < MAX77686_REG_PMIC_END); +} + +static bool max77802_is_accessible_reg(struct device *dev, unsigned int reg) +{ + return (reg >= MAX77XXX_REG_DEVICE_ID && reg < MAX77802_REG_PMIC_END); +} + +static bool max77xxx_is_precious_reg(struct device *dev, unsigned int reg) +{ + return (reg == MAX77XXX_REG_INTSRC || reg == MAX77XXX_REG_INT1 || + reg == MAX77XXX_REG_INT2); +} + +static bool max77xxx_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (max77xxx_is_precious_reg(dev, reg) || + reg == MAX77XXX_REG_STATUS1 || reg == MAX77XXX_REG_STATUS2 || + reg == MAX77XXX_REG_PWRON); +} + +static const struct regmap_config max77686_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = max77686_is_accessible_reg, + .readable_reg = max77686_is_accessible_reg, + .precious_reg = max77xxx_is_precious_reg, + .volatile_reg = max77xxx_is_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config max77802_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = max77802_is_accessible_reg, + .readable_reg = max77802_is_accessible_reg, + .precious_reg = max77xxx_is_precious_reg, + .volatile_reg = max77xxx_is_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +#ifdef CONFIG_OF +static const struct of_device_id max77xxx_pmic_dt_match[] = { + {.compatible = "maxim,max77686", .data = (void *)TYPE_MAX77686}, + {.compatible = "maxim,max77802", .data = (void *)TYPE_MAX77802}, + {}, +}; + +static void max77xxx_dt_parse_dvs_gpio(struct device *dev, + struct max77xxx_platform_data *pd, + struct device_node *np) +{ + int i; + + /* + * NOTE: we don't consder GPIO errors fatal; board may have some lines + * directly pulled high or low and thus doesn't specify them. + */ + for (i = 0; i < ARRAY_SIZE(pd->buck_gpio_dvs); i++) + pd->buck_gpio_dvs[i] = + of_get_named_gpio(np, + "max77xxx,pmic-buck-dvs-gpios", i); + + for (i = 0; i < ARRAY_SIZE(pd->buck_gpio_selb); i++) + pd->buck_gpio_selb[i] = + of_get_named_gpio(np, + "max77xxx,pmic-buck-selb-gpios", i); +} + +static struct max77xxx_platform_data *max77xxx_i2c_parse_dt_pdata(struct device + *dev) +{ + struct device_node *np = dev->of_node; + struct max77xxx_platform_data *pd; + + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + if (!pd) { + dev_err(dev, "could not allocate memory for pdata\n"); + return NULL; + } + + /* Read default index and ignore errors, since default is 0 */ + of_property_read_u32(np, "max77xxx,pmic-buck-default-dvs-idx", + &pd->buck_default_idx); + + max77xxx_dt_parse_dvs_gpio(dev, pd, np); + + dev->platform_data = pd; + return pd; +} +#else +static struct max77xxx_platform_data *max77xxx_i2c_parse_dt_pdata(struct device + *dev) +{ + return 0; +} +#endif + +static int max77xxx_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max77xxx_dev *max77xxx = NULL; + struct max77xxx_platform_data *pdata = i2c->dev.platform_data; + struct device_node *np; + unsigned int data; + int num_devs; + int ret = 0; + const struct regmap_config *config; + + if (i2c->dev.of_node) + pdata = max77xxx_i2c_parse_dt_pdata(&i2c->dev); + + if (!pdata) { + ret = -EIO; + dev_err(&i2c->dev, "No platform data found.\n"); + goto err; + } + + max77xxx = devm_kzalloc(&i2c->dev, sizeof(struct max77xxx_dev), + GFP_KERNEL); + if (max77xxx == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max77xxx); + max77xxx->dev = &i2c->dev; + max77xxx->i2c = i2c; + max77xxx->type = id->driver_data; + + max77xxx->wakeup = pdata->wakeup; + max77xxx->irq_gpio = pdata->irq_gpio; + max77xxx->irq = i2c->irq; + + if (max77xxx->type == TYPE_MAX77686) + config = &max77686_regmap_config; + else + config = &max77802_regmap_config; + max77xxx->regmap = devm_regmap_init_i2c(i2c, config); + if (IS_ERR(max77xxx->regmap)) { + ret = PTR_ERR(max77xxx->regmap); + dev_err(max77xxx->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + if (regmap_read(max77xxx->regmap, + MAX77XXX_REG_DEVICE_ID, &data) < 0) { + dev_err(max77xxx->dev, + "device not found on this channel (this is not an error)\n"); + ret = -ENODEV; + goto err; + } else + dev_info(max77xxx->dev, "device found\n"); + + /* If there is an rtc, use it */ + num_devs = 1; + np = of_find_node_by_name(i2c->dev.of_node, "rtc"); + if (np) { + u32 addr; + + /* Just to be safe, add some checks on this */ + if (!of_property_read_u32(np, "reg", &addr)) { + if (max77xxx->type == TYPE_MAX77802) { + dev_err(max77xxx->dev, + "Do not specify slave address for MAX77802 RTC\n"); + ret = -ENODEV; + goto err; + } + max77xxx->rtc = i2c_new_dummy(i2c->adapter, addr); + i2c_set_clientdata(max77xxx->rtc, max77xxx); + } else if (max77xxx->type == TYPE_MAX77686) { + dev_err(max77xxx->dev, "Must specify slave address for MAX77686 RTC\n"); + ret = -ENODEV; + goto err; + } + num_devs++; + } + + ret = max77xxx_irq_init(max77xxx); + if (ret) { + dev_err(max77xxx->dev, "Failed to init IRQs\n"); + return ret; + } + + ret = mfd_add_devices(max77xxx->dev, -1, max77xxx_devs[max77xxx->type], + num_devs, NULL, 0, NULL); + + if (ret < 0) + goto err_mfd; + + return 0; + +err_mfd: + mfd_remove_devices(max77xxx->dev); + i2c_unregister_device(max77xxx->rtc); +err: + return ret; +} + +static int max77xxx_i2c_remove(struct i2c_client *i2c) +{ + struct max77xxx_dev *max77xxx = i2c_get_clientdata(i2c); + + mfd_remove_devices(max77xxx->dev); + i2c_unregister_device(max77xxx->rtc); + + return 0; +} + +static const struct i2c_device_id max77xxx_i2c_id[] = { + { "max77686", TYPE_MAX77686 }, + { "max77802", TYPE_MAX77802 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77xxx_i2c_id); + +#ifdef CONFIG_PM_SLEEP +static int max77xxx_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77xxx_dev *max77xxx = i2c_get_clientdata(i2c); + + /* TODO: Shouldn't max77xxx-irq do this itself? */ + max77xxx_irq_resume(max77xxx); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(max77xxx_pm_ops, NULL, max77xxx_resume); + +static struct i2c_driver max77xxx_i2c_driver = { + .driver = { + .name = "max77xxx", + .owner = THIS_MODULE, + .pm = &max77xxx_pm_ops, + .of_match_table = of_match_ptr(max77xxx_pmic_dt_match), + }, + .probe = max77xxx_i2c_probe, + .remove = max77xxx_i2c_remove, + .id_table = max77xxx_i2c_id, +}; + +static int __init max77xxx_i2c_init(void) +{ + return i2c_add_driver(&max77xxx_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max77xxx_i2c_init); + +static void __exit max77xxx_i2c_exit(void) +{ + i2c_del_driver(&max77xxx_i2c_driver); +} +module_exit(max77xxx_i2c_exit); + +MODULE_DESCRIPTION("MAXIM 77xxx multi-function core driver"); +MODULE_AUTHOR("Simon Glass "); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 6cb388e8fb7d5a..e6a95e352f6c4b 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -51,6 +51,21 @@ config AD525X_DPOT_SPI To compile this driver as a module, choose M here: the module will be called ad525x_dpot-spi. +config ARM_SCPI_MHU + tristate "ARM SCPI MHU driver" + depends on ARM || ARM64 + default n + help + This driver provides support for communication with the + System Control Processor (SCP) device present in some ARM based + systems via the SCPI Message Handling Unit. + + SCP offers access to DVFS tables, control of certain system clocks, + enabling or disabling computing cores and clusters, watchdog patting + and even system reset. + + If you know your system has one, say Y here. + config ATMEL_PWM tristate "Atmel AT32/AT91 PWM support" depends on HAVE_CLK @@ -411,6 +426,10 @@ config TI_DAC7512 This driver can also be built as a module. If so, the module will be called ti_dac7512. +config UID_STAT + bool "UID based statistics tracking exported to /proc/uid_stat" + default n + config VMWARE_BALLOON tristate "VMware Balloon Driver" depends on X86 && HYPERVISOR_GUEST diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 99b9424ce31d80..d15b02a0170fc7 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_IBM_ASM) += ibmasm/ obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o obj-$(CONFIG_AD525X_DPOT_I2C) += ad525x_dpot-i2c.o obj-$(CONFIG_AD525X_DPOT_SPI) += ad525x_dpot-spi.o +obj-$(CONFIG_ARM_SCPI_MHU) += scpi-mhu.o obj-$(CONFIG_INTEL_MID_PTI) += pti.o obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o @@ -35,6 +36,7 @@ obj-$(CONFIG_ISL29020) += isl29020.o obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o obj-$(CONFIG_DS1682) += ds1682.o obj-$(CONFIG_TI_DAC7512) += ti_dac7512.o +obj-$(CONFIG_UID_STAT) += uid_stat.o obj-$(CONFIG_C2PORT) += c2port/ obj-$(CONFIG_HMC6352) += hmc6352.o obj-y += eeprom/ diff --git a/drivers/misc/scpi-mhu.c b/drivers/misc/scpi-mhu.c new file mode 100644 index 00000000000000..5a2c7f0f1c64cd --- /dev/null +++ b/drivers/misc/scpi-mhu.c @@ -0,0 +1,451 @@ +/* + * Driver for the communication with System Control and Power processor using + * Message Handling Unit present on some AArch64 chips. The driver speaks + * the SCPI protocol. + * + * Copyright (C) 2014 ARM Ltd. + * Author: Liviu Dudau + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include + +#define SCPI_MHU_CHANNEL_SIZE 0x200 + +#define SCPI_MHU_CHANNEL_LOW 0x0 +#define SCPI_MHU_CHANNEL_HIGH 0x1 + +#define SCPI_MHU_SCP_INT_LOW_OFFSET 0x000 +#define SCPI_MHU_SCP_INT_HIGH_OFFSET 0x020 +#define SCPI_MHU_CPU_INT_LOW_OFFSET 0x100 +#define SCPI_MHU_CPU_INT_HIGH_OFFSET 0x120 + +#define CMD_HEADER_ID_MASK 0xff +#define CMD_HEADER_SENDER_MASK 0xff00 +#define CMD_HEADER_DATA_SIZE_MASK 0x7fe00000 + +#define SCPI_PID0_OFFSET 0xfe0 +#define SCPI_PID1_OFFSET 0xfe4 +#define SCPI_PID2_OFFSET 0xfe8 +#define SCPI_PID3_OFFSET 0xfec +#define SCPI_PID4_OFFSET 0xfd0 + +#define SCPI_VER 0x001bb098 + +#define SCPI_CLOCKS_MAX 5 + +struct scpi_dev { + struct device *dev; + void __iomem *regs; + void __iomem *payload; + int irq[SCPI_MHU_CHANNEL_MAX]; + struct work_struct clocks_work; +}; + +/* + * Each slot in the MHU has this set of registers: + * @status - interrupt status. Each asserted interrupt will set a bit + * in this register. + * @set - Setting a bit in this register will set the corresponding bit + * in the status register. + * @clear - Setting a bit in this register will clear the corresponding + * bit in the status register. + */ +struct scpi_mhu_regs { + uint32_t status; + uint32_t reserved1; + uint32_t set; + uint32_t reserved2; + uint32_t clear; +}; + + +/* + * The payload area is divided in slots of SCPI_MHU_CHANNEL_SIZE size. + * The SCP->AP slot comes first, followed by the AP->SCP slot. The high + * priority channel slots follow the low priority channel slots in the + * normal payload area, with the secure payload being in a different + * region (secure ram). + */ + +struct scpi_command { + uint8_t id; + uint8_t channels; /* channels where this command is valid */ + bool needs_reply; /* if the command has an answer from SCP */ +}; + +struct scpi_command commands[] = { + { SCPI_CMD_SCP_CAPABILITIES, 1 << SCPI_MHU_CHANNEL_LOW, true }, + { SCPI_CMD_GET_CLOCKS, 1 << SCPI_MHU_CHANNEL_LOW, true }, + { SCPI_CMD_SET_CLOCK_FREQ_INDEX, 1 << SCPI_MHU_CHANNEL_LOW | \ + 1 << SCPI_MHU_CHANNEL_HIGH, false }, + { SCPI_CMD_SET_CLOCK_FREQ, 1 << SCPI_MHU_CHANNEL_LOW | \ + 1 << SCPI_MHU_CHANNEL_HIGH, false }, + { SCPI_CMD_GET_CLOCK_FREQ, 1 << SCPI_MHU_CHANNEL_LOW | \ + 1 << SCPI_MHU_CHANNEL_HIGH, true }, +}; + +struct scpi_request { + unsigned int id; + struct completion done; + struct list_head list; + void *data; +}; + +struct list_head request_list; +unsigned int last_sender_id; + +struct workqueue_struct *mhu_wq; + +struct scpi_dev *scpi_dev; + +#define SCP_COMMAND(cmd, sender, size) \ + ((cmd & CMD_HEADER_ID_MASK) | \ + ((sender << 8) & CMD_HEADER_SENDER_MASK) | \ + ((size << 20) & CMD_HEADER_DATA_SIZE_MASK)) + +static inline int scpi_get_command(uint32_t header) +{ + return header & CMD_HEADER_ID_MASK; +} + +static inline int scpi_get_sender(uint32_t header) +{ + return (header & CMD_HEADER_SENDER_MASK) >> 8; +} + +static inline int scpi_get_size(uint32_t header) +{ + return (header & CMD_HEADER_DATA_SIZE_MASK) >> 20; +} + +/* + * Note: The following implementation is missing support for secure channel + */ + +/* + * Send a payload from AP to SCP + */ +static int scpi_send(struct scpi_dev *scpi, uint8_t channel, uint8_t command, + uint8_t sender, char* payload, int size) +{ + /* for communication with SCP we can only use slots 1 or 3 + corresponding to channel 0 or 1 */ + uint32_t offset = (channel * 2 + 1) * SCPI_MHU_CHANNEL_SIZE; + struct scpi_mhu_regs *regs; + + regs = (struct scpi_mhu_regs *)(scpi->regs + (channel ? + SCPI_MHU_CPU_INT_HIGH_OFFSET : SCPI_MHU_CPU_INT_LOW_OFFSET)); + + memcpy(scpi->payload + offset, payload, size); + wmb(); + writel(SCP_COMMAND(command, sender, size), ®s->set); + return 0; +} + +/* + * Receive a payload from SCP to AP + */ +static int scpi_receive(struct scpi_dev *scpi, uint8_t channel, void *payload, int size) +{ + /* SCP will use slots 0 or 2 corresponding to channel 0 or 1 */ + uint32_t offset = (channel * 2) * SCPI_MHU_CHANNEL_SIZE; + + memcpy(payload, scpi->payload + offset, size); + return 0; +} + +static int scpi_execute_command(struct scpi_dev *scpi, uint8_t cmd, void *payload, + int size, void *reply_payload, int rsize) +{ + int i, ret = 0; + uint8_t channel; + struct scpi_request *req; + + for (i = 0; i < ARRAY_SIZE(commands); i++) { + if (commands[i].id == cmd) { + break; + } + } + + if (i >= ARRAY_SIZE(commands)) + return -EINVAL; + + channel = ffs(commands[i].channels) - 1; + if (commands[i].needs_reply) { + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + /* allocate space for status code */ + req->data = kzalloc(rsize + 4, GFP_KERNEL); + if (!req->data) { + ret = -ENOMEM; + goto free_req; + } + req->id = last_sender_id; + init_completion(&req->done); + + disable_irq(scpi->irq[channel]); + list_add(&req->list, &request_list); + enable_irq(scpi->irq[channel]); + } + + scpi_send(scpi, channel, cmd, last_sender_id, payload, size); + + last_sender_id = (last_sender_id + 1) & CMD_HEADER_SENDER_MASK; + + if (commands[i].needs_reply) { + wait_for_completion(&req->done); + /* get the response code */ + memcpy((char*)&ret, req->data, 4); + /* then the rest of the payload */ + memcpy(reply_payload, req->data + 4, rsize); + kfree(req->data); + } +free_req: + kfree(req); + return ret; +} + +int scpi_exec_command(uint8_t cmd, void *payload, int size, + void *reply_payload, int rsize) +{ + if (scpi_dev) + return scpi_execute_command(scpi_dev, cmd, payload, size, + reply_payload, rsize); + + return -EAGAIN; +} +EXPORT_SYMBOL_GPL(scpi_exec_command); + +static irqreturn_t scpi_irq_handler(int irq, void *arg) +{ + int i, sender, size; + struct scpi_dev *scpi = arg; + uint32_t response; + struct scpi_request *req, *n; + struct scpi_mhu_regs *regs = NULL; + + uint32_t offset[] = { SCPI_MHU_SCP_INT_LOW_OFFSET, + SCPI_MHU_SCP_INT_HIGH_OFFSET }; + + for (i = 0; i < SCPI_MHU_CHANNEL_MAX; i++) { + if (scpi->irq[i] == irq) { + regs = (struct scpi_mhu_regs *)(scpi->regs + offset[i]); + break; + } + } + + if (!regs) + return IRQ_NONE; + + response = readl(®s->status); + sender = scpi_get_sender(response); + size = scpi_get_size(response); + + list_for_each_entry_safe(req, n, &request_list, list) { + if (req->id == sender) { + scpi_receive(scpi, i, req->data, size); + list_del(&req->list); + complete(&req->done); + } + } + writel(~0, ®s->clear); + return IRQ_HANDLED; +} + +static void scpi_cleanup(struct scpi_dev *scpi) +{ + int i; + + destroy_workqueue(mhu_wq); + + for (i = 0; i < SCPI_MHU_CHANNEL_MAX; i++) { + if (scpi->irq[i]) + devm_free_irq(scpi->dev, scpi->irq[i], scpi); + } + + if (scpi->regs) + iounmap(scpi->regs); + + kfree(scpi); + scpi_dev = NULL; +} + +static void scpi_init_clocks(struct work_struct *work) +{ + int err, i; + uint16_t clock_count = 0; + uint32_t clock_freq; + char buf[4]; + struct scpi_dev *scpi = container_of(work, struct scpi_dev, clocks_work); + + memset(buf, 0, 4); + err = scpi_execute_command(scpi, SCPI_CMD_GET_CLOCKS, NULL, 0, buf, 2); + if (err) { + dev_info(scpi->dev, "command failed to execute: %d\n", err); + return; + } + clock_count = (buf[1] << 8) | buf[0]; + if (clock_count > SCPI_CLOCKS_MAX) { + dev_info(scpi->dev, "truncating number of supported clocks from %d to %d\n", + clock_count, SCPI_CLOCKS_MAX); + clock_count = SCPI_CLOCKS_MAX; + } + + dev_info(scpi->dev, "initialising %d clocks\n", clock_count); + for (i = 3; i < clock_count; i++) { + buf[0] = i; + buf[1] = 0; + err = scpi_execute_command(scpi, SCPI_CMD_GET_CLOCK_FREQ, + &buf, 2, &buf, 4); + if (err) + dev_info(scpi->dev, "failed to get freq for clock %d: %d\n", i, err); + else { + clock_freq = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + dev_info(scpi->dev, "clock id %d = %d\n", i, clock_freq); + } + } + + return; +} + +static int scpi_mhu_probe(struct platform_device *pdev) +{ + struct resource *res; + struct scpi_dev *scpi; + int err, irq, i; + uint32_t ver; + + scpi = kzalloc(sizeof(*scpi), GFP_KERNEL); + if (!scpi) + return -ENOMEM; + + scpi->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get memory resource\n"); + err = -EINVAL; + goto fail; + } + scpi->regs = ioremap_nocache(res->start, resource_size(res)); + if (!scpi->regs) { + dev_err(&pdev->dev, "failed to map scpi control block memory\n"); + err = -ENOMEM; + goto fail; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "failed to get memory resource\n"); + err = -EINVAL; + goto fail; + } + scpi->payload = devm_ioremap_resource(scpi->dev, res); + if (!scpi->payload) { + dev_err(&pdev->dev, "failed to map scpi payload memory\n"); + err = -ENOMEM; + goto fail; + } + + for (i = 0; i < SCPI_MHU_CHANNEL_MAX; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get irq for channel %d\n", i); + err = -EINVAL; + goto fail; + } + err = devm_request_irq(&pdev->dev, irq, scpi_irq_handler, + 0, dev_name(&pdev->dev), scpi); + if (err < 0) { + dev_err(&pdev->dev, "failed to request irq for channel %d\n", i); + goto fail; + } + scpi->irq[i] = irq; + } + + /* check that we are talking with the correct MHU part */ + ver = readl(scpi->regs + SCPI_PID0_OFFSET) & 0xff; + ver = ((readl(scpi->regs + SCPI_PID1_OFFSET) & 0xff) << 8) | ver; + ver = ((readl(scpi->regs + SCPI_PID2_OFFSET) & 0xff) << 16) | ver; + ver = ((readl(scpi->regs + SCPI_PID3_OFFSET) & 0xff) << 24) | ver; + + if (ver != SCPI_VER || readl(scpi->regs + SCPI_PID4_OFFSET) != 0x4) { + dev_err(&pdev->dev, "invalid MHU version found: 0x%x\n", ver); + err = -ENXIO; + goto fail; + } + + dev_info(&pdev->dev, "MHU version 0x%x using interrupts %d and %d\n", + ver, scpi->irq[0], scpi->irq[1]); + platform_set_drvdata(pdev, scpi); + + INIT_LIST_HEAD(&request_list); + last_sender_id = 0; + + mhu_wq = alloc_workqueue("scpi_mhu", WQ_UNBOUND | WQ_SYSFS, 0); + if (!mhu_wq) { + dev_err(&pdev->dev, "failed to create workqueue\n"); + } else { + INIT_WORK(&scpi->clocks_work, scpi_init_clocks); + queue_work(mhu_wq, &scpi->clocks_work); + } + + scpi_dev = scpi; + + return 0; +fail: + scpi_cleanup(scpi); + return err; +} + +static int scpi_mhu_remove(struct platform_device *pdev) +{ + struct scpi_dev *scpi = platform_get_drvdata(pdev); + if (scpi) + scpi_cleanup(scpi); + + return 0; +} + +static struct of_device_id scpi_mhu_of_match[] = { + { .compatible = "arm,scpi-mhu" }, + {}, +}; +MODULE_DEVICE_TABLE(of, scpi_mhu_of_match); + +static struct platform_driver scpi_mhu_driver = { + .probe = scpi_mhu_probe, + .remove = scpi_mhu_remove, + .driver = { + .name = "scpi-mhu", + .owner = THIS_MODULE, + .of_match_table = scpi_mhu_of_match, + }, +}; + +static int __init scpi_mhu_init(void) +{ + return platform_driver_register(&scpi_mhu_driver); +} + +static void __exit scpi_mhu_exit(void) +{ + platform_driver_unregister(&scpi_mhu_driver); +} + +module_init(scpi_mhu_init); +module_exit(scpi_mhu_exit); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("ARM SCPI MHU driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/uid_stat.c b/drivers/misc/uid_stat.c new file mode 100644 index 00000000000000..4766c1f83b94ee --- /dev/null +++ b/drivers/misc/uid_stat.c @@ -0,0 +1,152 @@ +/* drivers/misc/uid_stat.c + * + * Copyright (C) 2008 - 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(uid_lock); +static LIST_HEAD(uid_list); +static struct proc_dir_entry *parent; + +struct uid_stat { + struct list_head link; + uid_t uid; + atomic_t tcp_rcv; + atomic_t tcp_snd; +}; + +static struct uid_stat *find_uid_stat(uid_t uid) { + struct uid_stat *entry; + + list_for_each_entry(entry, &uid_list, link) { + if (entry->uid == uid) { + return entry; + } + } + return NULL; +} + +static int uid_stat_atomic_int_show(struct seq_file *m, void *v) +{ + unsigned int bytes; + atomic_t *counter = m->private; + + bytes = (unsigned int) (atomic_read(counter) + INT_MIN); + return seq_printf(m, "%u\n", bytes); +} + +static int uid_stat_read_atomic_int_open(struct inode *inode, struct file *file) +{ + return single_open(file, uid_stat_atomic_int_show, PDE_DATA(inode)); +} + +static const struct file_operations uid_stat_read_atomic_int_fops = { + .open = uid_stat_read_atomic_int_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/* Create a new entry for tracking the specified uid. */ +static struct uid_stat *create_stat(uid_t uid) { + struct uid_stat *new_uid; + /* Create the uid stat struct and append it to the list. */ + new_uid = kmalloc(sizeof(struct uid_stat), GFP_ATOMIC); + if (!new_uid) + return NULL; + + new_uid->uid = uid; + /* Counters start at INT_MIN, so we can track 4GB of network traffic. */ + atomic_set(&new_uid->tcp_rcv, INT_MIN); + atomic_set(&new_uid->tcp_snd, INT_MIN); + + list_add_tail(&new_uid->link, &uid_list); + return new_uid; +} + +static void create_stat_proc(struct uid_stat *new_uid) +{ + char uid_s[32]; + struct proc_dir_entry *entry; + sprintf(uid_s, "%d", new_uid->uid); + entry = proc_mkdir(uid_s, parent); + + /* Keep reference to uid_stat so we know what uid to read stats from. */ + proc_create_data("tcp_snd", S_IRUGO, entry, + &uid_stat_read_atomic_int_fops, &new_uid->tcp_snd); + + proc_create_data("tcp_rcv", S_IRUGO, entry, + &uid_stat_read_atomic_int_fops, &new_uid->tcp_rcv); +} + +static struct uid_stat *find_or_create_uid_stat(uid_t uid) +{ + struct uid_stat *entry; + unsigned long flags; + spin_lock_irqsave(&uid_lock, flags); + entry = find_uid_stat(uid); + if (entry) { + spin_unlock_irqrestore(&uid_lock, flags); + return entry; + } + entry = create_stat(uid); + spin_unlock_irqrestore(&uid_lock, flags); + if (entry) + create_stat_proc(entry); + return entry; +} + +int uid_stat_tcp_snd(uid_t uid, int size) { + struct uid_stat *entry; + activity_stats_update(); + entry = find_or_create_uid_stat(uid); + if (!entry) + return -1; + atomic_add(size, &entry->tcp_snd); + return 0; +} + +int uid_stat_tcp_rcv(uid_t uid, int size) { + struct uid_stat *entry; + activity_stats_update(); + entry = find_or_create_uid_stat(uid); + if (!entry) + return -1; + atomic_add(size, &entry->tcp_rcv); + return 0; +} + +static int __init uid_stat_init(void) +{ + parent = proc_mkdir("uid_stat", NULL); + if (!parent) { + pr_err("uid_stat: failed to create proc entry\n"); + return -1; + } + return 0; +} + +__initcall(uid_stat_init); diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index 5562308699bc29..79d82124413f1c 100644 --- a/drivers/mmc/card/Kconfig +++ b/drivers/mmc/card/Kconfig @@ -50,6 +50,15 @@ config MMC_BLOCK_BOUNCE If unsure, say Y here. +config MMC_BLOCK_DEFERRED_RESUME + bool "Deferr MMC layer resume until I/O is requested" + depends on MMC_BLOCK + default n + help + Say Y here to enable deferred MMC resume until I/O + is requested. This will reduce overall resume latency and + save power when theres an SD card inserted but not being used. + config SDIO_UART tristate "SDIO UART/GPS class support" depends on TTY diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 7b5424f398ace0..feb9bf5e4c17e9 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -36,6 +36,9 @@ #include #include +#define CREATE_TRACE_POINTS +#include + #include #include #include @@ -166,11 +169,7 @@ static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk) static inline int mmc_get_devidx(struct gendisk *disk) { - int devmaj = MAJOR(disk_devt(disk)); - int devidx = MINOR(disk_devt(disk)) / perdev_minors; - - if (!devmaj) - devidx = disk->first_minor / perdev_minors; + int devidx = disk->first_minor / perdev_minors; return devidx; } @@ -426,9 +425,11 @@ static int ioctl_do_sanitize(struct mmc_card *card) pr_debug("%s: %s - SANITIZE IN PROGRESS...\n", mmc_hostname(card->host), __func__); + trace_mmc_blk_erase_start(EXT_CSD_SANITIZE_START, 0, 0); err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_SANITIZE_START, 1, MMC_SANITIZE_REQ_TIMEOUT); + trace_mmc_blk_erase_end(EXT_CSD_SANITIZE_START, 0, 0); if (err) pr_err("%s: %s - EXT_CSD_SANITIZE_START failed. err=%d\n", @@ -771,18 +772,22 @@ static int mmc_blk_cmd_error(struct request *req, const char *name, int error, req->rq_disk->disk_name, "timed out", name, status); /* If the status cmd initially failed, retry the r/w cmd */ - if (!status_valid) + if (!status_valid) { + pr_err("%s: status not valid, retrying timeout\n", req->rq_disk->disk_name); return ERR_RETRY; - + } /* * If it was a r/w cmd crc error, or illegal command * (eg, issued in wrong state) then retry - we should * have corrected the state problem above. */ - if (status & (R1_COM_CRC_ERROR | R1_ILLEGAL_COMMAND)) + if (status & (R1_COM_CRC_ERROR | R1_ILLEGAL_COMMAND)) { + pr_err("%s: command error, retrying timeout\n", req->rq_disk->disk_name); return ERR_RETRY; + } /* Otherwise abort the command */ + pr_err("%s: not retrying timeout\n", req->rq_disk->disk_name); return ERR_ABORT; default: @@ -1961,6 +1966,11 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) unsigned long flags; unsigned int cmd_flags = req ? req->cmd_flags : 0; +#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME + if (mmc_bus_needs_resume(card->host)) + mmc_resume_bus(card->host); +#endif + if (req && !mq->mqrq_prev->req) /* claim host only for the first request */ mmc_get_card(card); @@ -2083,6 +2093,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, md->disk->queue = md->queue.queue; md->disk->driverfs_dev = parent; set_disk_ro(md->disk, md->read_only || default_ro); + md->disk->flags = GENHD_FL_EXT_DEVT; if (area_type & MMC_BLK_DATA_AREA_RPMB) md->disk->flags |= GENHD_FL_NO_PART_SCAN; @@ -2399,6 +2410,9 @@ static int mmc_blk_probe(struct mmc_card *card) mmc_set_drvdata(card, md); mmc_fixup_device(card, blk_fixups); +#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME + mmc_set_bus_resume_policy(card->host, 1); +#endif if (mmc_add_disk(md)) goto out; @@ -2441,6 +2455,9 @@ static void mmc_blk_remove(struct mmc_card *card) pm_runtime_put_noidle(&card->dev); mmc_blk_remove_req(md); mmc_set_drvdata(card, NULL); +#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME + mmc_set_bus_resume_policy(card->host, 0); +#endif } static int _mmc_blk_suspend(struct mmc_card *card) diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index 269d072ef55eab..ae10a372af0f92 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -26,3 +26,18 @@ config MMC_CLKGATE support handling this in order for it to be of any use. If unsure, say N. + +config MMC_EMBEDDED_SDIO + boolean "MMC embedded SDIO device support (EXPERIMENTAL)" + help + If you say Y here, support will be added for embedded SDIO + devices which do not contain the necessary enumeration + support in hardware to be properly detected. + +config MMC_PARANOID_SD_INIT + bool "Enable paranoid SD card initialization (EXPERIMENTAL)" + help + If you say Y here, the MMC layer will be extra paranoid + about re-trying SD init requests. This can be a useful + work-around for buggy controllers and hardware. Enable + if you are experiencing issues with SD detection. diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 098374b1ab2b99..7ee8abeea12776 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -29,6 +29,9 @@ #include #include #include +#include + +#include #include #include @@ -54,6 +57,7 @@ #define MMC_BKOPS_MAX_TIMEOUT (4 * 60 * 1000) /* max time to wait in ms */ static struct workqueue_struct *workqueue; +static struct wake_lock mmc_delayed_work_wake_lock; static const unsigned freqs[] = { 400000, 300000, 200000, 100000 }; /* @@ -87,6 +91,7 @@ MODULE_PARM_DESC( static int mmc_schedule_delayed_work(struct delayed_work *work, unsigned long delay) { + wake_lock(&mmc_delayed_work_wake_lock); return queue_delayed_work(workqueue, work, delay); } @@ -174,6 +179,7 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) pr_debug("%s: %d bytes transferred: %d\n", mmc_hostname(host), mrq->data->bytes_xfered, mrq->data->error); + trace_mmc_blk_rw_end(cmd->opcode, cmd->arg, mrq->data); } if (mrq->stop) { @@ -557,8 +563,12 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, mmc_start_bkops(host->card, true); } - if (!err && areq) + if (!err && areq) { + trace_mmc_blk_rw_start(areq->mrq->cmd->opcode, + areq->mrq->cmd->arg, + areq->mrq->data); start_err = __mmc_start_data_req(host, areq->mrq); + } if (host->areq) mmc_post_req(host, host->areq->mrq, 0); @@ -1885,8 +1895,13 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from, struct mmc_command cmd = {0}; unsigned int qty = 0; unsigned long timeout; + unsigned int fr, nr; int err; + fr = from; + nr = to - from + 1; + trace_mmc_blk_erase_start(arg, fr, nr); + /* * qty is used to calculate the erase timeout which depends on how many * erase groups (or allocation units in SD terminology) are affected. @@ -1990,6 +2005,8 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from, } while (!(cmd.resp[0] & R1_READY_FOR_DATA) || (R1_CURRENT_STATE(cmd.resp[0]) == R1_STATE_PRG)); out: + + trace_mmc_blk_erase_end(arg, fr, nr); return err; } @@ -2420,6 +2437,7 @@ void mmc_rescan(struct work_struct *work) struct mmc_host *host = container_of(work, struct mmc_host, detect.work); int i; + bool extend_wakelock = false; if (host->rescan_disable) return; @@ -2441,6 +2459,12 @@ void mmc_rescan(struct work_struct *work) host->detect_change = 0; + /* If the card was removed the bus will be marked + * as dead - extend the wakelock so userspace + * can respond */ + if (host->bus_dead) + extend_wakelock = 1; + /* * Let mmc_bus_put() free the bus/bus_ops if we've found that * the card is no longer present. @@ -2470,14 +2494,20 @@ void mmc_rescan(struct work_struct *work) mmc_claim_host(host); for (i = 0; i < ARRAY_SIZE(freqs); i++) { - if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) + if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) { + extend_wakelock = true; break; + } if (freqs[i] <= host->f_min) break; } mmc_release_host(host); out: + if (extend_wakelock) + wake_lock_timeout(&mmc_delayed_work_wake_lock, HZ / 2); + else + wake_unlock(&mmc_delayed_work_wake_lock); if (host->caps & MMC_CAP_NEEDS_POLL) mmc_schedule_delayed_work(&host->detect, HZ); } @@ -2712,6 +2742,22 @@ void mmc_init_context_info(struct mmc_host *host) init_waitqueue_head(&host->context_info.wait); } +#ifdef CONFIG_MMC_EMBEDDED_SDIO +void mmc_set_embedded_sdio_data(struct mmc_host *host, + struct sdio_cis *cis, + struct sdio_cccr *cccr, + struct sdio_embedded_func *funcs, + int num_funcs) +{ + host->embedded_sdio_data.cis = cis; + host->embedded_sdio_data.cccr = cccr; + host->embedded_sdio_data.funcs = funcs; + host->embedded_sdio_data.num_funcs = num_funcs; +} + +EXPORT_SYMBOL(mmc_set_embedded_sdio_data); +#endif + static int __init mmc_init(void) { int ret; @@ -2720,6 +2766,9 @@ static int __init mmc_init(void) if (!workqueue) return -ENOMEM; + wake_lock_init(&mmc_delayed_work_wake_lock, WAKE_LOCK_SUSPEND, + "mmc_delayed_work"); + ret = mmc_register_bus(); if (ret) goto destroy_workqueue; @@ -2740,6 +2789,7 @@ static int __init mmc_init(void) mmc_unregister_bus(); destroy_workqueue: destroy_workqueue(workqueue); + wake_lock_destroy(&mmc_delayed_work_wake_lock); return ret; } @@ -2750,6 +2800,7 @@ static void __exit mmc_exit(void) mmc_unregister_host_class(); mmc_unregister_bus(); destroy_workqueue(workqueue); + wake_lock_destroy(&mmc_delayed_work_wake_lock); } subsys_initcall(mmc_init); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 49bc403e31f02f..cc3164a939eb94 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -533,7 +533,8 @@ int mmc_add_host(struct mmc_host *host) mmc_host_clk_sysfs_init(host); mmc_start_host(host); - register_pm_notifier(&host->pm_notify); + if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY)) + register_pm_notifier(&host->pm_notify); return 0; } @@ -550,7 +551,9 @@ EXPORT_SYMBOL(mmc_add_host); */ void mmc_remove_host(struct mmc_host *host) { - unregister_pm_notifier(&host->pm_notify); + if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY)) + unregister_pm_notifier(&host->pm_notify); + mmc_stop_host(host); #ifdef CONFIG_DEBUG_FS diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 692fdb177294c2..9d016f6e29dfc2 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -823,6 +823,9 @@ int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card, bool reinit) { int err; +#ifdef CONFIG_MMC_PARANOID_SD_INIT + int retries; +#endif if (!reinit) { /* @@ -849,7 +852,26 @@ int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card, /* * Fetch switch information from card. */ +#ifdef CONFIG_MMC_PARANOID_SD_INIT + for (retries = 1; retries <= 3; retries++) { + err = mmc_read_switch(card); + if (!err) { + if (retries > 1) { + printk(KERN_WARNING + "%s: recovered\n", + mmc_hostname(host)); + } + break; + } else { + printk(KERN_WARNING + "%s: read switch failed (attempt %d)\n", + mmc_hostname(host), retries); + } + } +#else err = mmc_read_switch(card); +#endif + if (err) return err; } @@ -1051,7 +1073,10 @@ static int mmc_sd_alive(struct mmc_host *host) */ static void mmc_sd_detect(struct mmc_host *host) { - int err; + int err = 0; +#ifdef CONFIG_MMC_PARANOID_SD_INIT + int retries = 5; +#endif BUG_ON(!host); BUG_ON(!host->card); @@ -1061,7 +1086,23 @@ static void mmc_sd_detect(struct mmc_host *host) /* * Just check if our card has been removed. */ +#ifdef CONFIG_MMC_PARANOID_SD_INIT + while(retries) { + err = mmc_send_status(host->card, NULL); + if (err) { + retries--; + udelay(5); + continue; + } + break; + } + if (!retries) { + printk(KERN_ERR "%s(%s): Unable to re-detect card (%d)\n", + __func__, mmc_hostname(host), err); + } +#else err = _mmc_detect_card_removed(host); +#endif mmc_put_card(host->card); @@ -1123,6 +1164,9 @@ static int mmc_sd_suspend(struct mmc_host *host) static int _mmc_sd_resume(struct mmc_host *host) { int err = 0; +#ifdef CONFIG_MMC_PARANOID_SD_INIT + int retries; +#endif BUG_ON(!host); BUG_ON(!host->card); @@ -1133,7 +1177,23 @@ static int _mmc_sd_resume(struct mmc_host *host) goto out; mmc_power_up(host, host->card->ocr); +#ifdef CONFIG_MMC_PARANOID_SD_INIT + retries = 5; + while (retries) { + err = mmc_sd_init_card(host, host->card->ocr, host->card); + + if (err) { + printk(KERN_ERR "%s: Re-init card rc = %d (retries = %d)\n", + mmc_hostname(host), err, retries); + mdelay(5); + retries--; + continue; + } + break; + } +#else err = mmc_sd_init_card(host, host->card->ocr, host->card); +#endif mmc_card_clr_suspended(host->card); out: @@ -1246,6 +1306,9 @@ int mmc_attach_sd(struct mmc_host *host) { int err; u32 ocr, rocr; +#ifdef CONFIG_MMC_PARANOID_SD_INIT + int retries; +#endif BUG_ON(!host); WARN_ON(!host->claimed); @@ -1282,9 +1345,27 @@ int mmc_attach_sd(struct mmc_host *host) /* * Detect and init the card. */ +#ifdef CONFIG_MMC_PARANOID_SD_INIT + retries = 5; + while (retries) { + err = mmc_sd_init_card(host, rocr, NULL); + if (err) { + retries--; + continue; + } + break; + } + + if (!retries) { + printk(KERN_ERR "%s: mmc_sd_init_card() failure (err = %d)\n", + mmc_hostname(host), err); + goto err; + } +#else err = mmc_sd_init_card(host, rocr, NULL); if (err) goto err; +#endif mmc_release_host(host); err = mmc_add_card(host->card); diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 4d721c6e2af010..e1197ae5378259 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -10,6 +10,7 @@ */ #include +#include #include #include @@ -28,6 +29,10 @@ #include "sdio_ops.h" #include "sdio_cis.h" +#ifdef CONFIG_MMC_EMBEDDED_SDIO +#include +#endif + static int sdio_read_fbr(struct sdio_func *func) { int ret; @@ -740,19 +745,35 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, goto finish; } - /* - * Read the common registers. - */ - err = sdio_read_cccr(card, ocr); - if (err) - goto remove; +#ifdef CONFIG_MMC_EMBEDDED_SDIO + if (host->embedded_sdio_data.cccr) + memcpy(&card->cccr, host->embedded_sdio_data.cccr, sizeof(struct sdio_cccr)); + else { +#endif + /* + * Read the common registers. + */ + err = sdio_read_cccr(card, ocr); + if (err) + goto remove; +#ifdef CONFIG_MMC_EMBEDDED_SDIO + } +#endif - /* - * Read the common CIS tuples. - */ - err = sdio_read_common_cis(card); - if (err) - goto remove; +#ifdef CONFIG_MMC_EMBEDDED_SDIO + if (host->embedded_sdio_data.cis) + memcpy(&card->cis, host->embedded_sdio_data.cis, sizeof(struct sdio_cis)); + else { +#endif + /* + * Read the common CIS tuples. + */ + err = sdio_read_common_cis(card); + if (err) + goto remove; +#ifdef CONFIG_MMC_EMBEDDED_SDIO + } +#endif if (oldcard) { int same = (card->cis.vendor == oldcard->cis.vendor && @@ -1177,14 +1198,36 @@ int mmc_attach_sdio(struct mmc_host *host) funcs = (ocr & 0x70000000) >> 28; card->sdio_funcs = 0; +#ifdef CONFIG_MMC_EMBEDDED_SDIO + if (host->embedded_sdio_data.funcs) + card->sdio_funcs = funcs = host->embedded_sdio_data.num_funcs; +#endif + /* * Initialize (but don't add) all present functions. */ for (i = 0; i < funcs; i++, card->sdio_funcs++) { - err = sdio_init_func(host->card, i + 1); - if (err) - goto remove; - +#ifdef CONFIG_MMC_EMBEDDED_SDIO + if (host->embedded_sdio_data.funcs) { + struct sdio_func *tmp; + + tmp = sdio_alloc_func(host->card); + if (IS_ERR(tmp)) + goto remove; + tmp->num = (i + 1); + card->sdio_func[i] = tmp; + tmp->class = host->embedded_sdio_data.funcs[i].f_class; + tmp->max_blksize = host->embedded_sdio_data.funcs[i].f_maxblksize; + tmp->vendor = card->cis.vendor; + tmp->device = card->cis.device; + } else { +#endif + err = sdio_init_func(host->card, i + 1); + if (err) + goto remove; +#ifdef CONFIG_MMC_EMBEDDED_SDIO + } +#endif /* * Enable Runtime PM for this func (if supported) */ @@ -1232,3 +1275,40 @@ int mmc_attach_sdio(struct mmc_host *host) return err; } +int sdio_reset_comm(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + u32 ocr; + u32 rocr; + int err; + + printk("%s():\n", __func__); + mmc_claim_host(host); + + mmc_go_idle(host); + + mmc_set_clock(host, host->f_min); + + err = mmc_send_io_op_cond(host, 0, &ocr); + if (err) + goto err; + + rocr = mmc_select_voltage(host, ocr); + if (!rocr) { + err = -EINVAL; + goto err; + } + + err = mmc_sdio_init_card(host, rocr, card, 0); + if (err) + goto err; + + mmc_release_host(host); + return 0; +err: + printk("%s: Error resetting SDIO communications (%d)\n", + mmc_hostname(host), err); + mmc_release_host(host); + return err; +} +EXPORT_SYMBOL(sdio_reset_comm); diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index 92d1ba8e8153ab..872f45dd798246 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -25,6 +25,10 @@ #include "sdio_cis.h" #include "sdio_bus.h" +#ifdef CONFIG_MMC_EMBEDDED_SDIO +#include +#endif + /* show configuration fields */ #define sdio_config_attr(field, format_string) \ static ssize_t \ @@ -273,7 +277,14 @@ static void sdio_release_func(struct device *dev) { struct sdio_func *func = dev_to_sdio_func(dev); - sdio_free_func_cis(func); +#ifdef CONFIG_MMC_EMBEDDED_SDIO + /* + * If this device is embedded then we never allocated + * cis tables for this func + */ + if (!func->card->host->embedded_sdio_data.funcs) +#endif + sdio_free_func_cis(func); kfree(func->info); diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c old mode 100644 new mode 100755 index 78cb4d5d9d5818..8fdeb07723a67b --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -383,6 +383,39 @@ u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret) } EXPORT_SYMBOL_GPL(sdio_readb); +/** + * sdio_readb_ext - read a single byte from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * @in: value to add to argument + * + * Reads a single byte from the address space of a given SDIO + * function. If there is a problem reading the address, 0xff + * is returned and @err_ret will contain the error code. + */ +unsigned char sdio_readb_ext(struct sdio_func *func, unsigned int addr, + int *err_ret, unsigned in) +{ + int ret; + unsigned char val; + + BUG_ON(!func); + + if (err_ret) + *err_ret = 0; + + ret = mmc_io_rw_direct(func->card, 0, func->num, addr, (u8)in, &val); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFF; + } + + return val; +} +EXPORT_SYMBOL_GPL(sdio_readb_ext); + /** * sdio_writeb - write a single byte to a SDIO function * @func: SDIO function to access diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 55cd110a49c400..6c64dac41efddb 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1448,7 +1448,7 @@ static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt) buf += len; cnt -= len; if (host->part_buf_count == 2) { - mci_writew(host, DATA(host->data_offset), + mci_writew_data(host, DATA(host->data_offset), host->part_buf16); host->part_buf_count = 0; } @@ -1466,7 +1466,7 @@ static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt) cnt -= len; /* push data from aligned buffer into fifo */ for (i = 0; i < items; ++i) - mci_writew(host, DATA(host->data_offset), + mci_writew_data(host, DATA(host->data_offset), aligned_buf[i]); } } else @@ -1474,7 +1474,7 @@ static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt) { u16 *pdata = buf; for (; cnt >= 2; cnt -= 2) - mci_writew(host, DATA(host->data_offset), *pdata++); + mci_writew_data(host, DATA(host->data_offset), *pdata++); buf = pdata; } /* put anything remaining in the part_buf */ @@ -1483,7 +1483,7 @@ static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt) /* Push data if we have reached the expected data length */ if ((data->bytes_xfered + init_cnt) == (data->blksz * data->blocks)) - mci_writew(host, DATA(host->data_offset), + mci_writew_data(host, DATA(host->data_offset), host->part_buf16); } } @@ -1499,7 +1499,7 @@ static void dw_mci_pull_data16(struct dw_mci *host, void *buf, int cnt) int items = len >> 1; int i; for (i = 0; i < items; ++i) - aligned_buf[i] = mci_readw(host, + aligned_buf[i] = mci_readw_data(host, DATA(host->data_offset)); /* memcpy from aligned buffer into output buffer */ memcpy(buf, aligned_buf, len); @@ -1511,11 +1511,11 @@ static void dw_mci_pull_data16(struct dw_mci *host, void *buf, int cnt) { u16 *pdata = buf; for (; cnt >= 2; cnt -= 2) - *pdata++ = mci_readw(host, DATA(host->data_offset)); + *pdata++ = mci_readw_data(host, DATA(host->data_offset)); buf = pdata; } if (cnt) { - host->part_buf16 = mci_readw(host, DATA(host->data_offset)); + host->part_buf16 = mci_readw_data(host, DATA(host->data_offset)); dw_mci_pull_final_bytes(host, buf, cnt); } } @@ -1531,7 +1531,7 @@ static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt) buf += len; cnt -= len; if (host->part_buf_count == 4) { - mci_writel(host, DATA(host->data_offset), + mci_writel_data(host, DATA(host->data_offset), host->part_buf32); host->part_buf_count = 0; } @@ -1549,7 +1549,7 @@ static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt) cnt -= len; /* push data from aligned buffer into fifo */ for (i = 0; i < items; ++i) - mci_writel(host, DATA(host->data_offset), + mci_writel_data(host, DATA(host->data_offset), aligned_buf[i]); } } else @@ -1557,7 +1557,7 @@ static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt) { u32 *pdata = buf; for (; cnt >= 4; cnt -= 4) - mci_writel(host, DATA(host->data_offset), *pdata++); + mci_writel_data(host, DATA(host->data_offset), *pdata++); buf = pdata; } /* put anything remaining in the part_buf */ @@ -1566,7 +1566,7 @@ static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt) /* Push data if we have reached the expected data length */ if ((data->bytes_xfered + init_cnt) == (data->blksz * data->blocks)) - mci_writel(host, DATA(host->data_offset), + mci_writel_data(host, DATA(host->data_offset), host->part_buf32); } } @@ -1582,7 +1582,7 @@ static void dw_mci_pull_data32(struct dw_mci *host, void *buf, int cnt) int items = len >> 2; int i; for (i = 0; i < items; ++i) - aligned_buf[i] = mci_readl(host, + aligned_buf[i] = mci_readl_data(host, DATA(host->data_offset)); /* memcpy from aligned buffer into output buffer */ memcpy(buf, aligned_buf, len); @@ -1594,11 +1594,11 @@ static void dw_mci_pull_data32(struct dw_mci *host, void *buf, int cnt) { u32 *pdata = buf; for (; cnt >= 4; cnt -= 4) - *pdata++ = mci_readl(host, DATA(host->data_offset)); + *pdata++ = mci_readl_data(host, DATA(host->data_offset)); buf = pdata; } if (cnt) { - host->part_buf32 = mci_readl(host, DATA(host->data_offset)); + host->part_buf32 = mci_readl_data(host, DATA(host->data_offset)); dw_mci_pull_final_bytes(host, buf, cnt); } } @@ -1615,7 +1615,7 @@ static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt) cnt -= len; if (host->part_buf_count == 8) { - mci_writeq(host, DATA(host->data_offset), + mci_writeq_data(host, DATA(host->data_offset), host->part_buf); host->part_buf_count = 0; } @@ -1633,7 +1633,7 @@ static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt) cnt -= len; /* push data from aligned buffer into fifo */ for (i = 0; i < items; ++i) - mci_writeq(host, DATA(host->data_offset), + mci_writeq_data(host, DATA(host->data_offset), aligned_buf[i]); } } else @@ -1641,7 +1641,7 @@ static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt) { u64 *pdata = buf; for (; cnt >= 8; cnt -= 8) - mci_writeq(host, DATA(host->data_offset), *pdata++); + mci_writeq_data(host, DATA(host->data_offset), *pdata++); buf = pdata; } /* put anything remaining in the part_buf */ @@ -1650,7 +1650,7 @@ static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt) /* Push data if we have reached the expected data length */ if ((data->bytes_xfered + init_cnt) == (data->blksz * data->blocks)) - mci_writeq(host, DATA(host->data_offset), + mci_writeq_data(host, DATA(host->data_offset), host->part_buf); } } @@ -1666,7 +1666,7 @@ static void dw_mci_pull_data64(struct dw_mci *host, void *buf, int cnt) int items = len >> 3; int i; for (i = 0; i < items; ++i) - aligned_buf[i] = mci_readq(host, + aligned_buf[i] = mci_readq_data(host, DATA(host->data_offset)); /* memcpy from aligned buffer into output buffer */ memcpy(buf, aligned_buf, len); @@ -1678,11 +1678,11 @@ static void dw_mci_pull_data64(struct dw_mci *host, void *buf, int cnt) { u64 *pdata = buf; for (; cnt >= 8; cnt -= 8) - *pdata++ = mci_readq(host, DATA(host->data_offset)); + *pdata++ = mci_readq_data(host, DATA(host->data_offset)); buf = pdata; } if (cnt) { - host->part_buf = mci_readq(host, DATA(host->data_offset)); + host->part_buf = mci_readq_data(host, DATA(host->data_offset)); dw_mci_pull_final_bytes(host, buf, cnt); } } diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 6bf24ab917e645..6e3ed89b5688b9 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -152,21 +152,27 @@ /* Register access macros */ #define mci_readl(dev, reg) \ - __raw_readl((dev)->regs + SDMMC_##reg) + readl_relaxed((dev)->regs + SDMMC_##reg) #define mci_writel(dev, reg, value) \ - __raw_writel((value), (dev)->regs + SDMMC_##reg) + writel_relaxed((value), (dev)->regs + SDMMC_##reg) /* 16-bit FIFO access macros */ -#define mci_readw(dev, reg) \ +#define mci_readw_data(dev, reg) \ __raw_readw((dev)->regs + SDMMC_##reg) -#define mci_writew(dev, reg, value) \ +#define mci_writew_data(dev, reg, value) \ __raw_writew((value), (dev)->regs + SDMMC_##reg) +/* 32-bit FIFO access macros */ +#define mci_readl_data(dev, reg) \ + __raw_readl((dev)->regs + SDMMC_##reg) +#define mci_writel_data(dev, reg, value) \ + __raw_writel((value), (dev)->regs + SDMMC_##reg) + /* 64-bit FIFO access macros */ #ifdef readq -#define mci_readq(dev, reg) \ +#define mci_readq_data(dev, reg) \ __raw_readq((dev)->regs + SDMMC_##reg) -#define mci_writeq(dev, reg, value) \ +#define mci_writeq_data(dev, reg, value) \ __raw_writeq((value), (dev)->regs + SDMMC_##reg) #else /* @@ -177,10 +183,10 @@ * executed on those machines. Defining these macros here keeps the * rest of the code free from ifdefs. */ -#define mci_readq(dev, reg) \ - (*(volatile u64 __force *)((dev)->regs + SDMMC_##reg)) -#define mci_writeq(dev, reg, value) \ - (*(volatile u64 __force *)((dev)->regs + SDMMC_##reg) = (value)) +#define mci_readq_data(dev, reg) \ + ({ u64 __r = le64_to_cpu(*(volatile u64 __force *)((dev)->regs + SDMMC_##reg)); __r; }) +#define mci_writeq_data(dev, reg, value) \ + (*(volatile u64 __force *)((dev)->regs + SDMMC_##reg) = (cpu_to_le64(value))) #endif extern int dw_mci_probe(struct dw_mci *host); diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 98b6b6ef7e5c9d..90bf4f00f94cab 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -80,8 +80,8 @@ #define mmc_omap2() (!mmc_omap1()) #define OMAP_MMC_REG(host, reg) (OMAP_MMC_REG_##reg << (host)->reg_shift) -#define OMAP_MMC_READ(host, reg) __raw_readw((host)->virt_base + OMAP_MMC_REG(host, reg)) -#define OMAP_MMC_WRITE(host, reg, val) __raw_writew((val), (host)->virt_base + OMAP_MMC_REG(host, reg)) +#define OMAP_MMC_READ(host, reg) readw_relaxed((host)->virt_base + OMAP_MMC_REG(host, reg)) +#define OMAP_MMC_WRITE(host, reg, val) writew_relaxed((val), (host)->virt_base + OMAP_MMC_REG(host, reg)) /* * Command types diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index dbd32ad3b74933..849d15ff67a079 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -137,10 +137,10 @@ * MMC Host controller read/write API's */ #define OMAP_HSMMC_READ(base, reg) \ - __raw_readl((base) + OMAP_HSMMC_##reg) + readl_relaxed((base) + OMAP_HSMMC_##reg) #define OMAP_HSMMC_WRITE(base, reg, val) \ - __raw_writel((val), (base) + OMAP_HSMMC_##reg) + writel_relaxed((val), (base) + OMAP_HSMMC_##reg) struct omap_hsmmc_next { unsigned int dma_len; diff --git a/drivers/mmc/host/sdhci-bcm-kona.c b/drivers/mmc/host/sdhci-bcm-kona.c index 7a190fe4dff1b7..6f166e63b8172f 100644 --- a/drivers/mmc/host/sdhci-bcm-kona.c +++ b/drivers/mmc/host/sdhci-bcm-kona.c @@ -54,6 +54,7 @@ struct sdhci_bcm_kona_dev { struct mutex write_lock; /* protect back to back writes */ + struct clk *external_clk; }; @@ -257,6 +258,24 @@ static int sdhci_bcm_kona_probe(struct platform_device *pdev) goto err_pltfm_free; } + /* Get and enable the external clock */ + kona_dev->external_clk = devm_clk_get(dev, NULL); + if (IS_ERR(kona_dev->external_clk)) { + dev_err(dev, "Failed to get external clock\n"); + ret = PTR_ERR(kona_dev->external_clk); + goto err_pltfm_free; + } + + if (clk_set_rate(kona_dev->external_clk, host->mmc->f_max) != 0) { + dev_err(dev, "Failed to set rate external clock\n"); + goto err_pltfm_free; + } + + if (clk_prepare_enable(kona_dev->external_clk) != 0) { + dev_err(dev, "Failed to enable external clock\n"); + goto err_pltfm_free; + } + dev_dbg(dev, "non-removable=%c\n", (host->mmc->caps & MMC_CAP_NONREMOVABLE) ? 'Y' : 'N'); dev_dbg(dev, "cd_gpio %c, wp_gpio %c\n", @@ -271,7 +290,7 @@ static int sdhci_bcm_kona_probe(struct platform_device *pdev) ret = sdhci_bcm_kona_sd_reset(host); if (ret) - goto err_pltfm_free; + goto err_clk_disable; sdhci_bcm_kona_sd_init(host); @@ -307,6 +326,9 @@ static int sdhci_bcm_kona_probe(struct platform_device *pdev) err_reset: sdhci_bcm_kona_sd_reset(host); +err_clk_disable: + clk_disable_unprepare(kona_dev->external_clk); + err_pltfm_free: sdhci_pltfm_free(pdev); @@ -314,9 +336,20 @@ static int sdhci_bcm_kona_probe(struct platform_device *pdev) return ret; } -static int __exit sdhci_bcm_kona_remove(struct platform_device *pdev) +static int sdhci_bcm_kona_remove(struct platform_device *pdev) { - return sdhci_pltfm_unregister(pdev); + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host); + struct sdhci_bcm_kona_dev *kona_dev = sdhci_pltfm_priv(pltfm_priv); + int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); + + sdhci_remove_host(host, dead); + + clk_disable_unprepare(kona_dev->external_clk); + + sdhci_pltfm_free(pdev); + + return 0; } static struct platform_driver sdhci_bcm_kona_driver = { diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 90ff447bf04377..997726d9a270d5 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -1,3 +1,10 @@ +config MTD_NAND_IDS + tristate "Include chip ids for known NAND devices." + depends on MTD + help + Useful for NAND drivers that do not use the NAND subsystem but + still like to take advantage of the known chip information. + config MTD_NAND_ECC tristate @@ -106,9 +113,6 @@ config MTD_NAND_OMAP_BCH legacy OMAP families like OMAP2xxx, OMAP3xxx do not have ELM engine so they should not enable this config symbol. -config MTD_NAND_IDS - tristate - config MTD_NAND_RICOH tristate "Ricoh xD card reader" default n diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index 506b0248c4001b..7b8115a8621722 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -166,5 +166,6 @@ source "drivers/net/ethernet/via/Kconfig" source "drivers/net/ethernet/wiznet/Kconfig" source "drivers/net/ethernet/xilinx/Kconfig" source "drivers/net/ethernet/xircom/Kconfig" +source "drivers/net/ethernet/hisilicon/Kconfig" endif # ETHERNET diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index c0b8789952e711..34104a6fd13555 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -77,3 +77,4 @@ obj-$(CONFIG_NET_VENDOR_VIA) += via/ obj-$(CONFIG_NET_VENDOR_WIZNET) += wiznet/ obj-$(CONFIG_NET_VENDOR_XILINX) += xilinx/ obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/ +obj-$(CONFIG_NET_VENDOR_HISILICON) += hisilicon/ diff --git a/drivers/net/ethernet/hisilicon/Kconfig b/drivers/net/ethernet/hisilicon/Kconfig new file mode 100644 index 00000000000000..c1885e57db0e88 --- /dev/null +++ b/drivers/net/ethernet/hisilicon/Kconfig @@ -0,0 +1,32 @@ +# +# HISILICON device configuration +# + +config NET_VENDOR_HISILICON + bool "Hisilicon devices" + default y + depends on (ARM && ARCH_HIP04) + ---help--- + If you have a network (Ethernet) card belonging to this class, say Y + and read the Ethernet-HOWTO, available from + . + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about MOXA ART devices. If you say Y, you will be asked + for your specific card in the following questions. + +if NET_VENDOR_HISILICON + +config HIP04_ETH + tristate "HISILICON P04 Ethernet support" + depends on ARM && ARCH_HIP04 + select NET_CORE + select PHYLIB + select MARVELL_PHY + ---help--- + If you wish to compile a kernel for a hardware with hisilicon p04 SoC and + want to use the internal ethernet then you should answer Y to this. + + +endif # NET_VENDOR_HISILICON diff --git a/drivers/net/ethernet/hisilicon/Makefile b/drivers/net/ethernet/hisilicon/Makefile new file mode 100644 index 00000000000000..e6fe7af439d9e8 --- /dev/null +++ b/drivers/net/ethernet/hisilicon/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the HISILICON network device drivers. +# + +obj-$(CONFIG_HIP04_ETH) += hip04_eth.o hip04_mdio.o diff --git a/drivers/net/ethernet/hisilicon/hip04_eth.c b/drivers/net/ethernet/hisilicon/hip04_eth.c new file mode 100644 index 00000000000000..9621e1b77a40ad --- /dev/null +++ b/drivers/net/ethernet/hisilicon/hip04_eth.c @@ -0,0 +1,806 @@ + +/* Copyright (c) 2014 Linaro Ltd. + * Copyright (c) 2014 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PPE_CFG_RX_CFF_ADDR 0x100 +#define PPE_CFG_POOL_GRP 0x300 +#define PPE_CFG_RX_BUF_SIZE 0x400 +#define PPE_CFG_RX_FIFO_SIZE 0x500 +#define PPE_CURR_BUF_CNT_REG 0xa200 + +#define GE_DUPLEX_TYPE 0x8 +#define GE_PORT_MODE 0x40 +#define GE_PORT_EN 0x44 +#define GE_MODE_CHANGE_EN 0x1b4 +#define GE_STATION_MAC_ADDRESS 0x210 +#define PPE_CFG_TX_PKT_BD_ADDR 0x420 +#define PPE_CFG_RX_CTRL_REG 0x428 +#define PPE_CFG_QOS_VMID_GEN 0x500 +#define PPE_CFG_RX_PKT_INT 0x538 +#define PPE_INTEN 0x600 +#define PPE_INTSTS 0x608 +#define PPE_RINT 0x604 +#define PPE_CFG_STS_MODE 0x700 +#define PPE_HIS_RX_PKT_CNT 0x804 + +#define GE_MAX_FRM_SIZE_REG 0x3c +#define GE_SHORT_RUNTS_THR_REG 0x50 +#define GE_TX_LOCAL_PAGE_REG 0x5c +#define GE_TRANSMIT_CONTROL_REG 0x60 +#define GE_CF_CRC_STRIP_REG 0x1b0 +#define GE_RECV_CONTROL_REG 0x1e0 +#define PPE_CFG_MAX_FRAME_LEN_REG 0x408 +#define PPE_CFG_BUS_CTRL_REG 0x424 +#define PPE_CFG_RX_PKT_MODE_REG 0x438 +#define GMAC_PPE_RX_PKT_MAX_LEN (379) +#define GMAC_MAX_PKT_LEN 1516 + +/* REG_INTERRUPT_MASK */ +#define RCV_INT BIT(10) +#define RCV_NOBUF BIT(8) +#define DEF_INT_MASK 0x41fdf + +#define RX_DESC_NUM 64 +#define TX_DESC_NUM 64 +#define TX_NEXT(N) (((N) + 1) & (TX_DESC_NUM-1)) +#define RX_NEXT(N) (((N) + 1) & (RX_DESC_NUM-1)) + +#define DESC_DEF_CFG 0x14 +#define RX_BUF_SIZE 1600 +#define TX_TIMEOUT (6 * HZ) +#define DRV_NAME "hip04-ether" + +#define OBSOLETE_BUFFER + +struct tx_desc { + u32 send_addr; + u16 send_size; + u16 reserved[3]; + u32 cfg; + u32 wb_addr; +}; + +struct rx_desc { + u16 pkt_len; + u16 reserved_16; + u32 reserve[8]; /* simplified */ +}; + +struct hip04_priv { + void __iomem *base; + unsigned int port; + unsigned int speed; + unsigned int duplex; + unsigned int id; + unsigned int reg_inten; + + struct napi_struct napi; + struct net_device *ndev; + struct dma_pool *desc_pool; + + struct sk_buff *tx_skb[TX_DESC_NUM]; + struct tx_desc *td_ring[TX_DESC_NUM]; + dma_addr_t td_phys[TX_DESC_NUM]; + spinlock_t txlock; + unsigned int tx_head; + unsigned int tx_tail; + unsigned int tx_count; + + struct sk_buff *rx_skb[RX_DESC_NUM]; + unsigned char *rx_buf[RX_DESC_NUM]; + unsigned int rx_head; + unsigned int rx_buf_size; + + struct device_node *phy_node; + struct phy_device *phy; +}; + +static void __iomem *ppebase; + +static void hip04_config_port(struct hip04_priv *priv, u32 speed, u32 duplex) +{ + u32 val, reg; + + priv->speed = speed; + priv->duplex = duplex; + + switch (speed) { + case SPEED_1000: + reg = 8; + break; + case SPEED_100: + if (!priv->id) + reg = 1; + else + reg = 7; + break; + default: + reg = 0; + break; + } + val = readl_relaxed(priv->base + GE_PORT_MODE); + val &= ~(0xf); + val |= reg; + writel_relaxed(val, priv->base + GE_PORT_MODE); + + reg = (duplex) ? BIT(0) : 0; + val = readl_relaxed(priv->base + GE_DUPLEX_TYPE); + val &= ~(0x1); + val |= reg; + writel_relaxed(val, priv->base + GE_DUPLEX_TYPE); + + val = readl_relaxed(priv->base + GE_MODE_CHANGE_EN); + val |= BIT(0); + writel_relaxed(val, priv->base + GE_MODE_CHANGE_EN); +} + +static void hip04_reset_ppe(struct hip04_priv *priv) +{ + u32 val; + + do { + val = + readl_relaxed(ppebase + priv->port * 4 + PPE_CURR_BUF_CNT_REG); + readl_relaxed(ppebase + priv->port * 4 + PPE_CFG_RX_CFF_ADDR); + } while (val & 0xfff); +} + +static void hip04_config_fifo(struct hip04_priv *priv) +{ + u32 val; + + val = readl_relaxed(priv->base + PPE_CFG_STS_MODE); + val |= BIT(12); /* PPE_HIS_RX_PKT_CNT read clear */ + writel_relaxed(val, priv->base + PPE_CFG_STS_MODE); + + val = readl_relaxed(ppebase + priv->port * 4 + PPE_CFG_POOL_GRP); + val |= BIT(priv->port); + writel_relaxed(val, ppebase + priv->port * 4 + PPE_CFG_POOL_GRP); + + val = readl_relaxed(priv->base + PPE_CFG_QOS_VMID_GEN); + val &= ~(0x7f00); /* [14:8]*/ + val |= BIT(14); + val |= priv->port << 8; + writel_relaxed(val, priv->base + PPE_CFG_QOS_VMID_GEN); + + val = readl_relaxed(ppebase + priv->port * 4 + PPE_CFG_RX_BUF_SIZE); + val &= ~(0xffff); /* [15:0]*/ + val |= RX_BUF_SIZE; + writel_relaxed(val, ppebase + priv->port * 4 + PPE_CFG_RX_BUF_SIZE); + + val = readl_relaxed(ppebase + priv->port * 4 + PPE_CFG_RX_FIFO_SIZE); + val &= ~(0xfff0fff); /* [27:16] [11:0]*/ + val |= RX_DESC_NUM << 16; /* depth */ + val |= BIT(11); /* seq: first set first ues */ + val |= RX_DESC_NUM * priv->id; /* start_addr */ + writel_relaxed(val, ppebase + priv->port * 4 + PPE_CFG_RX_FIFO_SIZE); + + /* pkt store format */ + val = readl_relaxed(priv->base + PPE_CFG_RX_CTRL_REG); + val &= ~(0x1f80f); /* [16:11] [3:0]*/ + val |= 2 << 11; /* align */ + writel_relaxed(val, priv->base + PPE_CFG_RX_CTRL_REG); + + /* following cfg required for 1000M */ + /* pkt mode */ + val = readl_relaxed(priv->base + PPE_CFG_RX_PKT_MODE_REG); + val &= ~(0xc0000); /* [19:18] */ + val |= 1 << 18; /* align */ + writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_MODE_REG); + + /* set bus ctrl */ + val = 1 << 14; /* buffer locally release */ + val |= 1; /* big endian */ + writel_relaxed(val, priv->base + PPE_CFG_BUS_CTRL_REG); + + /* set max pkt len, curtail if exceed */ + val = readl_relaxed(priv->base + PPE_CFG_MAX_FRAME_LEN_REG); + val &= ~(0x3fff); /* [13:0]*/ + val |= GMAC_PPE_RX_PKT_MAX_LEN; /* max buffer len */ + writel_relaxed(val, priv->base + PPE_CFG_MAX_FRAME_LEN_REG); + + /* set max len of each pkt */ + val = readl_relaxed(priv->base + GE_MAX_FRM_SIZE_REG); + val &= ~(0xffff); /* [15:0]*/ + val |= GMAC_MAX_PKT_LEN; /* max buffer len */ + writel_relaxed(val, priv->base + GE_MAX_FRM_SIZE_REG); + + /* set min len of each pkt */ + val = readl_relaxed(priv->base + GE_SHORT_RUNTS_THR_REG); + val |= 31; /* min buffer len */ + writel_relaxed(val, priv->base + GE_SHORT_RUNTS_THR_REG); + + /* tx */ + val = readl_relaxed(priv->base + GE_TRANSMIT_CONTROL_REG); + val |= 1 << 5; /* tx auto neg */ + val |= 1 << 6; /* tx add crc */ + val |= 1 << 7; /* tx short pad through */ + writel_relaxed(val, priv->base + GE_TRANSMIT_CONTROL_REG); + + /* rx crc */ + val = readl_relaxed(priv->base + GE_CF_CRC_STRIP_REG); + val |= 1; /* rx strip crc */ + writel_relaxed(val, priv->base + GE_CF_CRC_STRIP_REG); + + /* rx */ + val = readl_relaxed(priv->base + GE_RECV_CONTROL_REG); + val |= 1 << 3; /* rx strip pad */ + val |= 1 << 4; /* run pkt en */ + writel_relaxed(val, priv->base + GE_RECV_CONTROL_REG); + + /* auto neg control */ + val = readl_relaxed(priv->base + GE_TX_LOCAL_PAGE_REG); + val |= 1; + val &= ~(0x1e0); /* [8:5] = 0*/ + val &= ~(0x3c0); /* [13:10] = 0*/ + val &= ~(0x8000); /* [15] = 0*/ + writel_relaxed(val, priv->base + GE_TX_LOCAL_PAGE_REG); +} + +static void hip04_mac_enable(struct net_device *ndev, bool enable) +{ + struct hip04_priv *priv = netdev_priv(ndev); + u32 val; + + if (enable) { + /* enable tx & rx */ + val = readl_relaxed(priv->base + GE_PORT_EN); + val |= 0x1 << 1; /* rx*/ + val |= 0x1 << 2; /* tx*/ + writel_relaxed(val, priv->base + GE_PORT_EN); + + /* enable interrupt */ + priv->reg_inten = DEF_INT_MASK; + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN); + + /* clear rx int */ + val = RCV_INT; + writel_relaxed(val, priv->base + PPE_RINT); + + /* config recv int*/ + val = readl_relaxed(priv->base + PPE_CFG_RX_PKT_INT); + val &= ~(0x0fff); /* [11:0] */ + val |= 0x1 << 6; /* int threshold 1 package */ + val |= 0x4; /* recv timeout */ + writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_INT); + } else { + /* disable int */ + priv->reg_inten &= ~(RCV_INT | RCV_NOBUF); + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN); + + /* disable tx & rx */ + val = readl_relaxed(priv->base + GE_PORT_EN); + val &= ~(0x1 << 1); /* rx*/ + val &= ~(0x1 << 2); /* tx*/ + writel_relaxed(val, priv->base + GE_PORT_EN); + } +} + +static void hip04_set_xmit_desc(struct hip04_priv *priv, dma_addr_t phys) +{ + writel_relaxed(phys, priv->base + PPE_CFG_TX_PKT_BD_ADDR); +} + +static void hip04_set_recv_desc(struct hip04_priv *priv, dma_addr_t phys) +{ + writel_relaxed(phys, ppebase + priv->port * 4 + PPE_CFG_RX_CFF_ADDR); +} + +static u32 hip04_recv_cnt(struct hip04_priv *priv) +{ + return readl_relaxed(priv->base + PPE_HIS_RX_PKT_CNT); +} + +static void hip04_update_mac_address(struct net_device *ndev) +{ + struct hip04_priv *priv = netdev_priv(ndev); + + writel_relaxed(((ndev->dev_addr[0] << 8) | (ndev->dev_addr[1])), + priv->base + GE_STATION_MAC_ADDRESS); + writel_relaxed(((ndev->dev_addr[2] << 24) | (ndev->dev_addr[3] << 16) | + (ndev->dev_addr[4] << 8) | (ndev->dev_addr[5])), + priv->base + GE_STATION_MAC_ADDRESS + 4); +} + +static int hip04_set_mac_address(struct net_device *ndev, void *addr) +{ + struct sockaddr *address = addr; + + if (!is_valid_ether_addr(address->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(ndev->dev_addr, address->sa_data, ndev->addr_len); + hip04_update_mac_address(ndev); + + return 0; +} + +static void endian_change(void *p, int size) +{ + unsigned int *to_cover = (unsigned int *)p; + int i; + + size = size >> 2; + for (i = 0; i < size; i++) + *(to_cover+i) = htonl(*(to_cover+i)); +} + +#ifdef OBSOLETE_BUFFER +static int +hip04_rx_refill_one_buffer(struct net_device *ndev, unsigned int index) +{ + struct hip04_priv *priv = netdev_priv(ndev); + struct sk_buff *skb; + + skb = dev_alloc_skb(RX_BUF_SIZE + NET_SKB_PAD); + if (NULL == skb) + return -ENOMEM; + + memset(skb->data, 0x0, RX_BUF_SIZE); + priv->rx_skb[index] = skb; + dma_map_single(&(ndev->dev), skb->data, RX_BUF_SIZE, DMA_TO_DEVICE); + hip04_set_recv_desc(priv, virt_to_phys(skb->data)); + + return 0; +} +#endif + +static int hip04_rx_poll(struct napi_struct *napi, int budget) +{ + struct hip04_priv *priv = container_of(napi, + struct hip04_priv, napi); + struct net_device *ndev = priv->ndev; + struct sk_buff *skb; + struct rx_desc *desc; + unsigned char *buf; + int rx = 0; + unsigned int cnt = hip04_recv_cnt(priv); + unsigned int len, tmp[16]; + + while (cnt) { +#ifdef OBSOLETE_BUFFER + skb = priv->rx_skb[priv->rx_head]; +#else + buf = priv->rx_buf[priv->rx_head]; + skb = build_skb(buf, priv->rx_buf_size); + if (unlikely(!skb)) + net_dbg_ratelimited("build_skb failed\n"); +#endif + dma_map_single(&ndev->dev, skb->data, + RX_BUF_SIZE, DMA_FROM_DEVICE); + memcpy(tmp, skb->data, 64); + endian_change((void *)tmp, 64); + desc = (struct rx_desc *)tmp; + len = desc->pkt_len; + + if (len > RX_BUF_SIZE) + len = RX_BUF_SIZE; + if (0 == len) + break; + + skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN); + skb_put(skb, len); + skb->protocol = eth_type_trans(skb, ndev); + napi_gro_receive(&priv->napi, skb); + +#ifdef OBSOLETE_BUFFER + hip04_rx_refill_one_buffer(ndev, priv->rx_head); +#else + dma_map_single(&ndev->dev, buf, RX_BUF_SIZE, DMA_TO_DEVICE); + /* reuse the buffer after data exhausted */ + hip04_set_recv_desc(priv, virt_to_phys(buf)); +#endif + priv->rx_head = RX_NEXT(priv->rx_head); + + if (rx++ >= budget) + break; + + if (--cnt == 0) + cnt = hip04_recv_cnt(priv); + } + + if (rx < budget) { + napi_gro_flush(napi, false); + __napi_complete(napi); + } + + /* enable rx interrupt */ + priv->reg_inten |= RCV_INT | RCV_NOBUF; + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN); + + return rx; +} + +static irqreturn_t hip04_mac_interrupt(int irq, void *dev_id) +{ + struct net_device *ndev = (struct net_device *) dev_id; + struct hip04_priv *priv = netdev_priv(ndev); + unsigned int ists = readl(priv->base + PPE_INTSTS); + u32 val; + + val = DEF_INT_MASK; + writel_relaxed(val, priv->base + PPE_RINT); + + if ((ists & RCV_INT) || (ists & RCV_NOBUF)) { + if (napi_schedule_prep(&priv->napi)) { + /* disable rx interrupt */ + priv->reg_inten &= ~(RCV_INT | RCV_NOBUF); + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN); + __napi_schedule(&priv->napi); + } + } + + return IRQ_HANDLED; +} + +static void hip04_tx_reclaim(struct net_device *ndev, bool force) +{ + struct hip04_priv *priv = netdev_priv(ndev); + unsigned tx_head = priv->tx_head; + unsigned tx_tail = priv->tx_tail; + struct tx_desc *desc = priv->td_ring[priv->tx_tail]; + + spin_lock_irq(&priv->txlock); + while (tx_tail != tx_head) { + if (desc->send_addr != 0) { + if (force) + desc->send_addr = 0; + else + break; + } + dev_kfree_skb_irq(priv->tx_skb[tx_tail]); + priv->tx_skb[tx_tail] = NULL; + tx_tail = TX_NEXT(tx_tail); + priv->tx_count--; + } + priv->tx_tail = tx_tail; + spin_unlock_irq(&priv->txlock); +} + +static int hip04_mac_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct hip04_priv *priv = netdev_priv(ndev); + struct tx_desc *desc = priv->td_ring[priv->tx_head]; + unsigned int tx_head = priv->tx_head; + int ret; + + hip04_tx_reclaim(ndev, false); + + spin_lock_irq(&priv->txlock); + if (priv->tx_count++ >= TX_DESC_NUM) { + net_dbg_ratelimited("no TX space for packet\n"); + netif_stop_queue(ndev); + ret = NETDEV_TX_BUSY; + goto out_unlock; + } + + priv->tx_skb[tx_head] = skb; + dma_map_single(&ndev->dev, skb->data, skb->len, DMA_TO_DEVICE); + memset((void *)desc, 0, sizeof(*desc)); + desc->send_addr = (unsigned int)virt_to_phys(skb->data); + desc->send_size = skb->len; + desc->cfg = DESC_DEF_CFG; + desc->wb_addr = priv->td_phys[tx_head]; + endian_change(desc, 64); + skb_tx_timestamp(skb); + hip04_set_xmit_desc(priv, priv->td_phys[tx_head]); + + priv->tx_head = TX_NEXT(tx_head); + ret = NETDEV_TX_OK; +out_unlock: + spin_unlock_irq(&priv->txlock); + + return ret; +} + +static void hip04_adjust_link(struct net_device *ndev) +{ + struct hip04_priv *priv = netdev_priv(ndev); + struct phy_device *phy = priv->phy; + + if ((priv->speed != phy->speed) || (priv->duplex != phy->duplex)) + hip04_config_port(priv, phy->speed, phy->duplex); +} + +static int hip04_mac_open(struct net_device *ndev) +{ + struct hip04_priv *priv = netdev_priv(ndev); + int i; + +#ifndef OBSOLETE_BUFFER + hip04_reset_ppe(priv); + for (i = 0; i < RX_DESC_NUM; i++) { + dma_map_single(&ndev->dev, priv->rx_buf[i], + RX_BUF_SIZE, DMA_TO_DEVICE); + hip04_set_recv_desc(priv, virt_to_phys(priv->rx_buf[i])); + } + priv->rx_head = 0; +#endif + priv->tx_head = 0; + priv->tx_tail = 0; + priv->tx_count = 0; + + if (priv->phy_node) { + priv->phy = of_phy_connect(ndev, priv->phy_node, + &hip04_adjust_link, 0, PHY_INTERFACE_MODE_GMII); + if (!priv->phy) + return -ENODEV; + phy_start(priv->phy); + } + + netif_start_queue(ndev); + hip04_mac_enable(ndev, true); + napi_enable(&priv->napi); + return 0; +} + +static int hip04_mac_stop(struct net_device *ndev) +{ + struct hip04_priv *priv = netdev_priv(ndev); + + napi_disable(&priv->napi); + netif_stop_queue(ndev); + hip04_mac_enable(ndev, false); + hip04_tx_reclaim(ndev, true); +#ifndef OBSOLETE_BUFFER + hip04_reset_ppe(priv); +#endif + return 0; +} + +static void hip04_timeout(struct net_device *ndev) +{ + netif_wake_queue(ndev); + return; +} + +static struct net_device_ops hip04_netdev_ops = { + .ndo_open = hip04_mac_open, + .ndo_stop = hip04_mac_stop, + .ndo_start_xmit = hip04_mac_start_xmit, + .ndo_set_mac_address = hip04_set_mac_address, + .ndo_tx_timeout = hip04_timeout, + .ndo_validate_addr = eth_validate_addr, + .ndo_change_mtu = eth_change_mtu, +}; + +#ifdef OBSOLETE_BUFFER +static int hip04_rx_ring_init(struct net_device *dev) +{ + struct hip04_priv *priv = netdev_priv(dev); + struct sk_buff *skb = NULL; + unsigned int i; + + for (i = 0; i < RX_DESC_NUM; i++) { + skb = dev_alloc_skb(RX_BUF_SIZE); + if (NULL == skb) + return -ENOMEM; + memset(skb->data, 0x0, RX_BUF_SIZE); + dma_map_single(&dev->dev, (skb->data), + RX_BUF_SIZE, DMA_TO_DEVICE); + hip04_set_recv_desc(priv, virt_to_phys(skb->data)); + priv->rx_skb[i] = skb; + } + priv->rx_head = 0; + return 0; +} +#endif + +static int hip04_alloc_ring(struct net_device *ndev, struct device *d) +{ + struct hip04_priv *priv = netdev_priv(ndev); + void *base; + int i; + + priv->desc_pool = dma_pool_create(DRV_NAME, d, sizeof(struct tx_desc), + SKB_DATA_ALIGN(sizeof(struct tx_desc)), 0); + if (!priv->desc_pool) + return -ENOMEM; + + for (i = 0; i < TX_DESC_NUM; i++) { + priv->td_ring[i] = dma_pool_alloc(priv->desc_pool, + GFP_ATOMIC, &priv->td_phys[i]); + if (!priv->td_ring[i]) + return -ENOMEM; + } + +#ifndef OBSOLETE_BUFFER + priv->rx_head = 0; + priv->rx_buf_size = RX_BUF_SIZE + + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); + /* mallco one more buffer for SKB_DATA_ALIGN */ + base = devm_kzalloc(d, priv->rx_buf_size * (RX_DESC_NUM + 1), + GFP_ATOMIC); + if (!base) + return -ENOMEM; + base = (void *)SKB_DATA_ALIGN((unsigned int)base); + for (i = 0; i < RX_DESC_NUM; i++) + priv->rx_buf[i] = base + i * priv->rx_buf_size; +#endif + return 0; +} + +static void hip04_free_ring(struct net_device *dev) +{ + struct hip04_priv *priv = netdev_priv(dev); + int i; + + for (i = 0; i < TX_DESC_NUM; i++) { + if (priv->tx_skb[i]) + dev_kfree_skb_any(priv->tx_skb[i]); + if ((priv->desc_pool) && (priv->td_ring[i])) + dma_pool_free(priv->desc_pool, priv->td_ring[i], + priv->td_phys[i]); + } + + if (priv->desc_pool) + dma_pool_destroy(priv->desc_pool); +} + +static int hip04_mac_probe(struct platform_device *pdev) +{ + struct device *d = &pdev->dev; + struct device_node *node = d->of_node; + struct net_device *ndev; + struct hip04_priv *priv; + struct resource *res; + unsigned int irq, val; + int ret; + + ndev = alloc_etherdev(sizeof(struct hip04_priv)); + if (!ndev) + return -ENOMEM; + + priv = netdev_priv(ndev); + priv->ndev = ndev; + platform_set_drvdata(pdev, ndev); + spin_lock_init(&priv->txlock); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -EINVAL; + goto init_fail; + } + ndev->base_addr = res->start; + priv->base = devm_ioremap_resource(d, res); + ret = IS_ERR(priv->base); + if (ret) { + dev_err(d, "devm_ioremap_resource failed\n"); + goto init_fail; + } + + if (!ppebase) { + struct device_node *n; + + n = of_find_compatible_node(NULL, NULL, "hisilicon,hip04-ppebase"); + if (!n) { + ret = -EINVAL; + netdev_err(ndev, "not find hisilicon,ppebase\n"); + goto init_fail; + } + ppebase = of_iomap(n, 0); + } + + ret = of_property_read_u32(node, "port", &val); + if (ret) { + dev_warn(d, "not find port info\n"); + goto init_fail; + } + priv->port = val & 0x1f; + + ret = of_property_read_u32(node, "speed", &val); + if (ret) { + dev_warn(d, "not find speed info\n"); + priv->speed = SPEED_1000; + } + + if (SPEED_100 == val) + priv->speed = SPEED_100; + else + priv->speed = SPEED_1000; + priv->duplex = DUPLEX_FULL; + + /* fixme any id can be used directly? */ + ret = of_property_read_u32(node, "id", &priv->id); + if (ret) { + dev_warn(d, "not find id info\n"); + goto init_fail; + } + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + ret = -EINVAL; + goto init_fail; + } + ether_setup(ndev); + ndev->netdev_ops = &hip04_netdev_ops; + ndev->watchdog_timeo = TX_TIMEOUT; + ndev->priv_flags |= IFF_UNICAST_FLT; + ndev->irq = irq; + netif_napi_add(ndev, &priv->napi, hip04_rx_poll, RX_DESC_NUM); + SET_NETDEV_DEV(ndev, &pdev->dev); + + hip04_reset_ppe(priv); + hip04_config_port(priv, priv->speed, priv->duplex); + hip04_config_fifo(priv); + random_ether_addr(ndev->dev_addr); + hip04_update_mac_address(ndev); +#ifdef OBSOLETE_BUFFER + hip04_rx_ring_init(ndev); +#endif + ret = hip04_alloc_ring(ndev, d); + if (ret) { + netdev_err(ndev, "alloc ring fail\n"); + goto alloc_fail; + } + + ret = devm_request_irq(d, irq, hip04_mac_interrupt, + 0, pdev->name, ndev); + if (ret) { + netdev_err(ndev, "devm_request_irq failed\n"); + goto init_fail; + } + + priv->phy_node = of_parse_phandle(node, "phy-handle", 0); + + ret = register_netdev(ndev); + if (ret) { + free_netdev(ndev); + goto init_fail; + } + + return 0; +alloc_fail: + hip04_free_ring(ndev); +init_fail: + of_node_put(priv->phy_node); + free_netdev(ndev); + return ret; +} + +static int hip04_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct hip04_priv *priv = netdev_priv(ndev); + + unregister_netdev(ndev); + free_irq(ndev->irq, ndev); + free_netdev(ndev); + free_netdev(ndev); + of_node_put(priv->phy_node); + + return 0; +} + +static const struct of_device_id hip04_mac_match[] = { + { .compatible = "hisilicon,hip04-mac" }, + { } +}; + +static struct platform_driver hip04_mac_driver = { + .probe = hip04_mac_probe, + .remove = hip04_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = hip04_mac_match, + }, +}; +module_platform_driver(hip04_mac_driver); + +MODULE_DESCRIPTION("HISILICON P04 Ethernet driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:hip04-ether"); diff --git a/drivers/net/ethernet/hisilicon/hip04_mdio.c b/drivers/net/ethernet/hisilicon/hip04_mdio.c new file mode 100644 index 00000000000000..d9764e306d799a --- /dev/null +++ b/drivers/net/ethernet/hisilicon/hip04_mdio.c @@ -0,0 +1,206 @@ + +/* Copyright (c) 2014 Linaro Ltd. + * Copyright (c) 2014 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MDIO_CMD_REG 0x0 +#define MDIO_ADDR_REG 0x4 +#define MDIO_WDATA_REG 0x8 +#define MDIO_RDATA_REG 0xc +#define MDIO_STA_REG 0x10 + +#define MDIO_START BIT(14) +#define MDIO_R_VALID BIT(1) +#define MDIO_READ (BIT(12) | BIT(11) | MDIO_START) +#define MDIO_WRITE (BIT(12) | BIT(10) | MDIO_START) + +struct hip04_mdio_priv { + struct mutex lock; + void __iomem *base; +}; + +#define WAIT_TIMEOUT 10 +static int hip04_mdio_wait_ready(struct mii_bus *bus) +{ + struct hip04_mdio_priv *priv = bus->priv; + int i; + + for (i = 0; readl_relaxed(priv->base + MDIO_CMD_REG) & MDIO_START; i++) { + if (i == WAIT_TIMEOUT) + return -ETIMEDOUT; + msleep(20); + } + + return 0; +} + +static int hip04_mdio_read(struct mii_bus *bus, int mii_id, int regnum) +{ + struct hip04_mdio_priv *priv = bus->priv; + u32 val; + int ret; + mutex_lock(&priv->lock); + + ret = hip04_mdio_wait_ready(bus); + if (ret < 0) + goto out; + + val = (regnum & 0x1f) | ((mii_id & 0x1f) << 5) | MDIO_READ; + writel_relaxed(val, priv->base + MDIO_CMD_REG); + + ret = hip04_mdio_wait_ready(bus); + if (ret < 0) + goto out; + val = readl_relaxed(priv->base + MDIO_STA_REG); + if (val & MDIO_R_VALID) { + dev_err(bus->parent, "SMI bus read not valid\n"); + ret = -ENODEV; + goto out; + } + val = readl_relaxed(priv->base + MDIO_RDATA_REG); + ret = val & 0xFFFF; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int hip04_mdio_write(struct mii_bus *bus, int mii_id, + int regnum, u16 value) +{ + struct hip04_mdio_priv *priv = bus->priv; + u32 val; + int ret; + + mutex_lock(&priv->lock); + + ret = hip04_mdio_wait_ready(bus); + if (ret < 0) + goto out; + + writel_relaxed(value, priv->base + MDIO_WDATA_REG); + val = (regnum & 0x1f) | ((mii_id & 0x1f) << 5) | MDIO_WRITE; + writel_relaxed(val, priv->base + MDIO_CMD_REG); +out: + mutex_unlock(&priv->lock); + return ret; +} + + +static int hip04_mdio_reset(struct mii_bus *bus) +{ + int temp, err, i; + + for (i = 0; i < 2; i++) { + hip04_mdio_write(bus, i, 22, 0); + temp = hip04_mdio_read(bus, i, MII_BMCR); + temp |= BMCR_RESET; + err = hip04_mdio_write(bus, i, MII_BMCR, temp); + if (err < 0) + return err; + } + + mdelay(500); + return 0; +} + +static int hip04_mdio_probe(struct platform_device *pdev) +{ + struct resource *r; + struct mii_bus *bus; + struct hip04_mdio_priv *priv; + int i, ret; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "No SMI register address given\n"); + return -ENODEV; + } + + bus = mdiobus_alloc_size(sizeof(struct hip04_mdio_priv)); + if (!bus) { + dev_err(&pdev->dev, "Cannot allocate MDIO bus\n"); + return -ENOMEM; + } + + bus->name = "hip04_mdio_bus"; + bus->read = hip04_mdio_read; + bus->write = hip04_mdio_write; + bus->reset = hip04_mdio_reset; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", + dev_name(&pdev->dev)); + bus->parent = &pdev->dev; + priv = bus->priv; + priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!priv->base) { + dev_err(&pdev->dev, "Unable to remap SMI register\n"); + ret = -ENODEV; + goto out_mdio; + } + + mutex_init(&priv->lock); + + ret = of_mdiobus_register(bus, pdev->dev.of_node); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret); + goto out_mdio; + } + + platform_set_drvdata(pdev, bus); + + return 0; + +out_mdio: + mdiobus_free(bus); + return ret; +} + +static int hip04_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); + mdiobus_free(bus); + + return 0; +} + +static const struct of_device_id hip04_mdio_match[] = { + { .compatible = "hisilicon,hip04-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, hip04_mdio_match); + +static struct platform_driver hip04_mdio_driver = { + .probe = hip04_mdio_probe, + .remove = hip04_mdio_remove, + .driver = { + .name = "hip04-mdio", + .owner = THIS_MODULE, + .of_match_table = hip04_mdio_match, + }, +}; + +module_platform_driver(hip04_mdio_driver); + +MODULE_DESCRIPTION("HISILICON P04 MDIO interface driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:hip04-mdio"); diff --git a/drivers/net/ppp/Kconfig b/drivers/net/ppp/Kconfig index 1373c6d7278d84..282aec4860ebe5 100644 --- a/drivers/net/ppp/Kconfig +++ b/drivers/net/ppp/Kconfig @@ -149,6 +149,23 @@ config PPPOL2TP tunnels. L2TP is replacing PPTP for VPN uses. if TTY +config PPPOLAC + tristate "PPP on L2TP Access Concentrator" + depends on PPP && INET + help + L2TP (RFC 2661) is a tunneling protocol widely used in virtual private + networks. This driver handles L2TP data packets between a UDP socket + and a PPP channel, but only permits one session per socket. Thus it is + fairly simple and suited for clients. + +config PPPOPNS + tristate "PPP on PPTP Network Server" + depends on PPP && INET + help + PPTP (RFC 2637) is a tunneling protocol widely used in virtual private + networks. This driver handles PPTP data packets between a RAW socket + and a PPP channel. It is fairly simple and easy to use. + config PPP_ASYNC tristate "PPP support for async serial ports" depends on PPP diff --git a/drivers/net/ppp/Makefile b/drivers/net/ppp/Makefile index a6b6297b00669a..d283d03c4683a9 100644 --- a/drivers/net/ppp/Makefile +++ b/drivers/net/ppp/Makefile @@ -11,3 +11,5 @@ obj-$(CONFIG_PPP_SYNC_TTY) += ppp_synctty.o obj-$(CONFIG_PPPOE) += pppox.o pppoe.o obj-$(CONFIG_PPPOL2TP) += pppox.o obj-$(CONFIG_PPTP) += pppox.o pptp.o +obj-$(CONFIG_PPPOLAC) += pppox.o pppolac.o +obj-$(CONFIG_PPPOPNS) += pppox.o pppopns.o diff --git a/drivers/net/ppp/pppolac.c b/drivers/net/ppp/pppolac.c new file mode 100644 index 00000000000000..a5d3d634fd9a5d --- /dev/null +++ b/drivers/net/ppp/pppolac.c @@ -0,0 +1,449 @@ +/* drivers/net/pppolac.c + * + * Driver for PPP on L2TP Access Concentrator / PPPoLAC Socket (RFC 2661) + * + * Copyright (C) 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +/* This driver handles L2TP data packets between a UDP socket and a PPP channel. + * The socket must keep connected, and only one session per socket is permitted. + * Sequencing of outgoing packets is controlled by LNS. Incoming packets with + * sequences are reordered within a sliding window of one second. Currently + * reordering only happens when a packet is received. It is done for simplicity + * since no additional locks or threads are required. This driver only works on + * IPv4 due to the lack of UDP encapsulation support in IPv6. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define L2TP_CONTROL_BIT 0x80 +#define L2TP_LENGTH_BIT 0x40 +#define L2TP_SEQUENCE_BIT 0x08 +#define L2TP_OFFSET_BIT 0x02 +#define L2TP_VERSION 0x02 +#define L2TP_VERSION_MASK 0x0F + +#define PPP_ADDR 0xFF +#define PPP_CTRL 0x03 + +union unaligned { + __u32 u32; +} __attribute__((packed)); + +static inline union unaligned *unaligned(void *ptr) +{ + return (union unaligned *)ptr; +} + +struct meta { + __u32 sequence; + __u32 timestamp; +}; + +static inline struct meta *skb_meta(struct sk_buff *skb) +{ + return (struct meta *)skb->cb; +} + +/******************************************************************************/ + +static int pppolac_recv_core(struct sock *sk_udp, struct sk_buff *skb) +{ + struct sock *sk = (struct sock *)sk_udp->sk_user_data; + struct pppolac_opt *opt = &pppox_sk(sk)->proto.lac; + struct meta *meta = skb_meta(skb); + __u32 now = jiffies; + __u8 bits; + __u8 *ptr; + + /* Drop the packet if L2TP header is missing. */ + if (skb->len < sizeof(struct udphdr) + 6) + goto drop; + + /* Put it back if it is a control packet. */ + if (skb->data[sizeof(struct udphdr)] & L2TP_CONTROL_BIT) + return opt->backlog_rcv(sk_udp, skb); + + /* Skip UDP header. */ + skb_pull(skb, sizeof(struct udphdr)); + + /* Check the version. */ + if ((skb->data[1] & L2TP_VERSION_MASK) != L2TP_VERSION) + goto drop; + bits = skb->data[0]; + ptr = &skb->data[2]; + + /* Check the length if it is present. */ + if (bits & L2TP_LENGTH_BIT) { + if ((ptr[0] << 8 | ptr[1]) != skb->len) + goto drop; + ptr += 2; + } + + /* Skip all fields including optional ones. */ + if (!skb_pull(skb, 6 + (bits & L2TP_SEQUENCE_BIT ? 4 : 0) + + (bits & L2TP_LENGTH_BIT ? 2 : 0) + + (bits & L2TP_OFFSET_BIT ? 2 : 0))) + goto drop; + + /* Skip the offset padding if it is present. */ + if (bits & L2TP_OFFSET_BIT && + !skb_pull(skb, skb->data[-2] << 8 | skb->data[-1])) + goto drop; + + /* Check the tunnel and the session. */ + if (unaligned(ptr)->u32 != opt->local) + goto drop; + + /* Check the sequence if it is present. */ + if (bits & L2TP_SEQUENCE_BIT) { + meta->sequence = ptr[4] << 8 | ptr[5]; + if ((__s16)(meta->sequence - opt->recv_sequence) < 0) + goto drop; + } + + /* Skip PPP address and control if they are present. */ + if (skb->len >= 2 && skb->data[0] == PPP_ADDR && + skb->data[1] == PPP_CTRL) + skb_pull(skb, 2); + + /* Fix PPP protocol if it is compressed. */ + if (skb->len >= 1 && skb->data[0] & 1) + skb_push(skb, 1)[0] = 0; + + /* Drop the packet if PPP protocol is missing. */ + if (skb->len < 2) + goto drop; + + /* Perform reordering if sequencing is enabled. */ + atomic_set(&opt->sequencing, bits & L2TP_SEQUENCE_BIT); + if (bits & L2TP_SEQUENCE_BIT) { + struct sk_buff *skb1; + + /* Insert the packet into receive queue in order. */ + skb_set_owner_r(skb, sk); + skb_queue_walk(&sk->sk_receive_queue, skb1) { + struct meta *meta1 = skb_meta(skb1); + __s16 order = meta->sequence - meta1->sequence; + if (order == 0) + goto drop; + if (order < 0) { + meta->timestamp = meta1->timestamp; + skb_insert(skb1, skb, &sk->sk_receive_queue); + skb = NULL; + break; + } + } + if (skb) { + meta->timestamp = now; + skb_queue_tail(&sk->sk_receive_queue, skb); + } + + /* Remove packets from receive queue as long as + * 1. the receive buffer is full, + * 2. they are queued longer than one second, or + * 3. there are no missing packets before them. */ + skb_queue_walk_safe(&sk->sk_receive_queue, skb, skb1) { + meta = skb_meta(skb); + if (atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf && + now - meta->timestamp < HZ && + meta->sequence != opt->recv_sequence) + break; + skb_unlink(skb, &sk->sk_receive_queue); + opt->recv_sequence = (__u16)(meta->sequence + 1); + skb_orphan(skb); + ppp_input(&pppox_sk(sk)->chan, skb); + } + return NET_RX_SUCCESS; + } + + /* Flush receive queue if sequencing is disabled. */ + skb_queue_purge(&sk->sk_receive_queue); + skb_orphan(skb); + ppp_input(&pppox_sk(sk)->chan, skb); + return NET_RX_SUCCESS; +drop: + kfree_skb(skb); + return NET_RX_DROP; +} + +static int pppolac_recv(struct sock *sk_udp, struct sk_buff *skb) +{ + sock_hold(sk_udp); + sk_receive_skb(sk_udp, skb, 0); + return 0; +} + +static struct sk_buff_head delivery_queue; + +static void pppolac_xmit_core(struct work_struct *delivery_work) +{ + mm_segment_t old_fs = get_fs(); + struct sk_buff *skb; + + set_fs(KERNEL_DS); + while ((skb = skb_dequeue(&delivery_queue))) { + struct sock *sk_udp = skb->sk; + struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len}; + struct msghdr msg = { + .msg_iov = (struct iovec *)&iov, + .msg_iovlen = 1, + .msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT, + }; + sk_udp->sk_prot->sendmsg(NULL, sk_udp, &msg, skb->len); + kfree_skb(skb); + } + set_fs(old_fs); +} + +static DECLARE_WORK(delivery_work, pppolac_xmit_core); + +static int pppolac_xmit(struct ppp_channel *chan, struct sk_buff *skb) +{ + struct sock *sk_udp = (struct sock *)chan->private; + struct pppolac_opt *opt = &pppox_sk(sk_udp->sk_user_data)->proto.lac; + + /* Install PPP address and control. */ + skb_push(skb, 2); + skb->data[0] = PPP_ADDR; + skb->data[1] = PPP_CTRL; + + /* Install L2TP header. */ + if (atomic_read(&opt->sequencing)) { + skb_push(skb, 10); + skb->data[0] = L2TP_SEQUENCE_BIT; + skb->data[6] = opt->xmit_sequence >> 8; + skb->data[7] = opt->xmit_sequence; + skb->data[8] = 0; + skb->data[9] = 0; + opt->xmit_sequence++; + } else { + skb_push(skb, 6); + skb->data[0] = 0; + } + skb->data[1] = L2TP_VERSION; + unaligned(&skb->data[2])->u32 = opt->remote; + + /* Now send the packet via the delivery queue. */ + skb_set_owner_w(skb, sk_udp); + skb_queue_tail(&delivery_queue, skb); + schedule_work(&delivery_work); + return 1; +} + +/******************************************************************************/ + +static struct ppp_channel_ops pppolac_channel_ops = { + .start_xmit = pppolac_xmit, +}; + +static int pppolac_connect(struct socket *sock, struct sockaddr *useraddr, + int addrlen, int flags) +{ + struct sock *sk = sock->sk; + struct pppox_sock *po = pppox_sk(sk); + struct sockaddr_pppolac *addr = (struct sockaddr_pppolac *)useraddr; + struct socket *sock_udp = NULL; + struct sock *sk_udp; + int error; + + if (addrlen != sizeof(struct sockaddr_pppolac) || + !addr->local.tunnel || !addr->local.session || + !addr->remote.tunnel || !addr->remote.session) { + return -EINVAL; + } + + lock_sock(sk); + error = -EALREADY; + if (sk->sk_state != PPPOX_NONE) + goto out; + + sock_udp = sockfd_lookup(addr->udp_socket, &error); + if (!sock_udp) + goto out; + sk_udp = sock_udp->sk; + lock_sock(sk_udp); + + /* Remove this check when IPv6 supports UDP encapsulation. */ + error = -EAFNOSUPPORT; + if (sk_udp->sk_family != AF_INET) + goto out; + error = -EPROTONOSUPPORT; + if (sk_udp->sk_protocol != IPPROTO_UDP) + goto out; + error = -EDESTADDRREQ; + if (sk_udp->sk_state != TCP_ESTABLISHED) + goto out; + error = -EBUSY; + if (udp_sk(sk_udp)->encap_type || sk_udp->sk_user_data) + goto out; + if (!sk_udp->sk_bound_dev_if) { + struct dst_entry *dst = sk_dst_get(sk_udp); + error = -ENODEV; + if (!dst) + goto out; + sk_udp->sk_bound_dev_if = dst->dev->ifindex; + dst_release(dst); + } + + po->chan.hdrlen = 12; + po->chan.private = sk_udp; + po->chan.ops = &pppolac_channel_ops; + po->chan.mtu = PPP_MRU - 80; + po->proto.lac.local = unaligned(&addr->local)->u32; + po->proto.lac.remote = unaligned(&addr->remote)->u32; + atomic_set(&po->proto.lac.sequencing, 1); + po->proto.lac.backlog_rcv = sk_udp->sk_backlog_rcv; + + error = ppp_register_channel(&po->chan); + if (error) + goto out; + + sk->sk_state = PPPOX_CONNECTED; + udp_sk(sk_udp)->encap_type = UDP_ENCAP_L2TPINUDP; + udp_sk(sk_udp)->encap_rcv = pppolac_recv; + sk_udp->sk_backlog_rcv = pppolac_recv_core; + sk_udp->sk_user_data = sk; +out: + if (sock_udp) { + release_sock(sk_udp); + if (error) + sockfd_put(sock_udp); + } + release_sock(sk); + return error; +} + +static int pppolac_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + if (!sk) + return 0; + + lock_sock(sk); + if (sock_flag(sk, SOCK_DEAD)) { + release_sock(sk); + return -EBADF; + } + + if (sk->sk_state != PPPOX_NONE) { + struct sock *sk_udp = (struct sock *)pppox_sk(sk)->chan.private; + lock_sock(sk_udp); + skb_queue_purge(&sk->sk_receive_queue); + pppox_unbind_sock(sk); + udp_sk(sk_udp)->encap_type = 0; + udp_sk(sk_udp)->encap_rcv = NULL; + sk_udp->sk_backlog_rcv = pppox_sk(sk)->proto.lac.backlog_rcv; + sk_udp->sk_user_data = NULL; + release_sock(sk_udp); + sockfd_put(sk_udp->sk_socket); + } + + sock_orphan(sk); + sock->sk = NULL; + release_sock(sk); + sock_put(sk); + return 0; +} + +/******************************************************************************/ + +static struct proto pppolac_proto = { + .name = "PPPOLAC", + .owner = THIS_MODULE, + .obj_size = sizeof(struct pppox_sock), +}; + +static struct proto_ops pppolac_proto_ops = { + .family = PF_PPPOX, + .owner = THIS_MODULE, + .release = pppolac_release, + .bind = sock_no_bind, + .connect = pppolac_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = sock_no_getname, + .poll = sock_no_poll, + .ioctl = pppox_ioctl, + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = sock_no_setsockopt, + .getsockopt = sock_no_getsockopt, + .sendmsg = sock_no_sendmsg, + .recvmsg = sock_no_recvmsg, + .mmap = sock_no_mmap, +}; + +static int pppolac_create(struct net *net, struct socket *sock) +{ + struct sock *sk; + + sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pppolac_proto); + if (!sk) + return -ENOMEM; + + sock_init_data(sock, sk); + sock->state = SS_UNCONNECTED; + sock->ops = &pppolac_proto_ops; + sk->sk_protocol = PX_PROTO_OLAC; + sk->sk_state = PPPOX_NONE; + return 0; +} + +/******************************************************************************/ + +static struct pppox_proto pppolac_pppox_proto = { + .create = pppolac_create, + .owner = THIS_MODULE, +}; + +static int __init pppolac_init(void) +{ + int error; + + error = proto_register(&pppolac_proto, 0); + if (error) + return error; + + error = register_pppox_proto(PX_PROTO_OLAC, &pppolac_pppox_proto); + if (error) + proto_unregister(&pppolac_proto); + else + skb_queue_head_init(&delivery_queue); + return error; +} + +static void __exit pppolac_exit(void) +{ + unregister_pppox_proto(PX_PROTO_OLAC); + proto_unregister(&pppolac_proto); +} + +module_init(pppolac_init); +module_exit(pppolac_exit); + +MODULE_DESCRIPTION("PPP on L2TP Access Concentrator (PPPoLAC)"); +MODULE_AUTHOR("Chia-chi Yeh "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ppp/pppopns.c b/drivers/net/ppp/pppopns.c new file mode 100644 index 00000000000000..6016d29c0660c5 --- /dev/null +++ b/drivers/net/ppp/pppopns.c @@ -0,0 +1,428 @@ +/* drivers/net/pppopns.c + * + * Driver for PPP on PPTP Network Server / PPPoPNS Socket (RFC 2637) + * + * Copyright (C) 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +/* This driver handles PPTP data packets between a RAW socket and a PPP channel. + * The socket is created in the kernel space and connected to the same address + * of the control socket. Outgoing packets are always sent with sequences but + * without acknowledgements. Incoming packets with sequences are reordered + * within a sliding window of one second. Currently reordering only happens when + * a packet is received. It is done for simplicity since no additional locks or + * threads are required. This driver should work on both IPv4 and IPv6. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GRE_HEADER_SIZE 8 + +#define PPTP_GRE_BITS htons(0x2001) +#define PPTP_GRE_BITS_MASK htons(0xEF7F) +#define PPTP_GRE_SEQ_BIT htons(0x1000) +#define PPTP_GRE_ACK_BIT htons(0x0080) +#define PPTP_GRE_TYPE htons(0x880B) + +#define PPP_ADDR 0xFF +#define PPP_CTRL 0x03 + +struct header { + __u16 bits; + __u16 type; + __u16 length; + __u16 call; + __u32 sequence; +} __attribute__((packed)); + +struct meta { + __u32 sequence; + __u32 timestamp; +}; + +static inline struct meta *skb_meta(struct sk_buff *skb) +{ + return (struct meta *)skb->cb; +} + +/******************************************************************************/ + +static int pppopns_recv_core(struct sock *sk_raw, struct sk_buff *skb) +{ + struct sock *sk = (struct sock *)sk_raw->sk_user_data; + struct pppopns_opt *opt = &pppox_sk(sk)->proto.pns; + struct meta *meta = skb_meta(skb); + __u32 now = jiffies; + struct header *hdr; + + /* Skip transport header */ + skb_pull(skb, skb_transport_header(skb) - skb->data); + + /* Drop the packet if GRE header is missing. */ + if (skb->len < GRE_HEADER_SIZE) + goto drop; + hdr = (struct header *)skb->data; + + /* Check the header. */ + if (hdr->type != PPTP_GRE_TYPE || hdr->call != opt->local || + (hdr->bits & PPTP_GRE_BITS_MASK) != PPTP_GRE_BITS) + goto drop; + + /* Skip all fields including optional ones. */ + if (!skb_pull(skb, GRE_HEADER_SIZE + + (hdr->bits & PPTP_GRE_SEQ_BIT ? 4 : 0) + + (hdr->bits & PPTP_GRE_ACK_BIT ? 4 : 0))) + goto drop; + + /* Check the length. */ + if (skb->len != ntohs(hdr->length)) + goto drop; + + /* Check the sequence if it is present. */ + if (hdr->bits & PPTP_GRE_SEQ_BIT) { + meta->sequence = ntohl(hdr->sequence); + if ((__s32)(meta->sequence - opt->recv_sequence) < 0) + goto drop; + } + + /* Skip PPP address and control if they are present. */ + if (skb->len >= 2 && skb->data[0] == PPP_ADDR && + skb->data[1] == PPP_CTRL) + skb_pull(skb, 2); + + /* Fix PPP protocol if it is compressed. */ + if (skb->len >= 1 && skb->data[0] & 1) + skb_push(skb, 1)[0] = 0; + + /* Drop the packet if PPP protocol is missing. */ + if (skb->len < 2) + goto drop; + + /* Perform reordering if sequencing is enabled. */ + if (hdr->bits & PPTP_GRE_SEQ_BIT) { + struct sk_buff *skb1; + + /* Insert the packet into receive queue in order. */ + skb_set_owner_r(skb, sk); + skb_queue_walk(&sk->sk_receive_queue, skb1) { + struct meta *meta1 = skb_meta(skb1); + __s32 order = meta->sequence - meta1->sequence; + if (order == 0) + goto drop; + if (order < 0) { + meta->timestamp = meta1->timestamp; + skb_insert(skb1, skb, &sk->sk_receive_queue); + skb = NULL; + break; + } + } + if (skb) { + meta->timestamp = now; + skb_queue_tail(&sk->sk_receive_queue, skb); + } + + /* Remove packets from receive queue as long as + * 1. the receive buffer is full, + * 2. they are queued longer than one second, or + * 3. there are no missing packets before them. */ + skb_queue_walk_safe(&sk->sk_receive_queue, skb, skb1) { + meta = skb_meta(skb); + if (atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf && + now - meta->timestamp < HZ && + meta->sequence != opt->recv_sequence) + break; + skb_unlink(skb, &sk->sk_receive_queue); + opt->recv_sequence = meta->sequence + 1; + skb_orphan(skb); + ppp_input(&pppox_sk(sk)->chan, skb); + } + return NET_RX_SUCCESS; + } + + /* Flush receive queue if sequencing is disabled. */ + skb_queue_purge(&sk->sk_receive_queue); + skb_orphan(skb); + ppp_input(&pppox_sk(sk)->chan, skb); + return NET_RX_SUCCESS; +drop: + kfree_skb(skb); + return NET_RX_DROP; +} + +static void pppopns_recv(struct sock *sk_raw, int length) +{ + struct sk_buff *skb; + while ((skb = skb_dequeue(&sk_raw->sk_receive_queue))) { + sock_hold(sk_raw); + sk_receive_skb(sk_raw, skb, 0); + } +} + +static struct sk_buff_head delivery_queue; + +static void pppopns_xmit_core(struct work_struct *delivery_work) +{ + mm_segment_t old_fs = get_fs(); + struct sk_buff *skb; + + set_fs(KERNEL_DS); + while ((skb = skb_dequeue(&delivery_queue))) { + struct sock *sk_raw = skb->sk; + struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len}; + struct msghdr msg = { + .msg_iov = (struct iovec *)&iov, + .msg_iovlen = 1, + .msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT, + }; + sk_raw->sk_prot->sendmsg(NULL, sk_raw, &msg, skb->len); + kfree_skb(skb); + } + set_fs(old_fs); +} + +static DECLARE_WORK(delivery_work, pppopns_xmit_core); + +static int pppopns_xmit(struct ppp_channel *chan, struct sk_buff *skb) +{ + struct sock *sk_raw = (struct sock *)chan->private; + struct pppopns_opt *opt = &pppox_sk(sk_raw->sk_user_data)->proto.pns; + struct header *hdr; + __u16 length; + + /* Install PPP address and control. */ + skb_push(skb, 2); + skb->data[0] = PPP_ADDR; + skb->data[1] = PPP_CTRL; + length = skb->len; + + /* Install PPTP GRE header. */ + hdr = (struct header *)skb_push(skb, 12); + hdr->bits = PPTP_GRE_BITS | PPTP_GRE_SEQ_BIT; + hdr->type = PPTP_GRE_TYPE; + hdr->length = htons(length); + hdr->call = opt->remote; + hdr->sequence = htonl(opt->xmit_sequence); + opt->xmit_sequence++; + + /* Now send the packet via the delivery queue. */ + skb_set_owner_w(skb, sk_raw); + skb_queue_tail(&delivery_queue, skb); + schedule_work(&delivery_work); + return 1; +} + +/******************************************************************************/ + +static struct ppp_channel_ops pppopns_channel_ops = { + .start_xmit = pppopns_xmit, +}; + +static int pppopns_connect(struct socket *sock, struct sockaddr *useraddr, + int addrlen, int flags) +{ + struct sock *sk = sock->sk; + struct pppox_sock *po = pppox_sk(sk); + struct sockaddr_pppopns *addr = (struct sockaddr_pppopns *)useraddr; + struct sockaddr_storage ss; + struct socket *sock_tcp = NULL; + struct socket *sock_raw = NULL; + struct sock *sk_tcp; + struct sock *sk_raw; + int error; + + if (addrlen != sizeof(struct sockaddr_pppopns)) + return -EINVAL; + + lock_sock(sk); + error = -EALREADY; + if (sk->sk_state != PPPOX_NONE) + goto out; + + sock_tcp = sockfd_lookup(addr->tcp_socket, &error); + if (!sock_tcp) + goto out; + sk_tcp = sock_tcp->sk; + error = -EPROTONOSUPPORT; + if (sk_tcp->sk_protocol != IPPROTO_TCP) + goto out; + addrlen = sizeof(struct sockaddr_storage); + error = kernel_getpeername(sock_tcp, (struct sockaddr *)&ss, &addrlen); + if (error) + goto out; + if (!sk_tcp->sk_bound_dev_if) { + struct dst_entry *dst = sk_dst_get(sk_tcp); + error = -ENODEV; + if (!dst) + goto out; + sk_tcp->sk_bound_dev_if = dst->dev->ifindex; + dst_release(dst); + } + + error = sock_create(ss.ss_family, SOCK_RAW, IPPROTO_GRE, &sock_raw); + if (error) + goto out; + sk_raw = sock_raw->sk; + sk_raw->sk_bound_dev_if = sk_tcp->sk_bound_dev_if; + error = kernel_connect(sock_raw, (struct sockaddr *)&ss, addrlen, 0); + if (error) + goto out; + + po->chan.hdrlen = 14; + po->chan.private = sk_raw; + po->chan.ops = &pppopns_channel_ops; + po->chan.mtu = PPP_MRU - 80; + po->proto.pns.local = addr->local; + po->proto.pns.remote = addr->remote; + po->proto.pns.data_ready = sk_raw->sk_data_ready; + po->proto.pns.backlog_rcv = sk_raw->sk_backlog_rcv; + + error = ppp_register_channel(&po->chan); + if (error) + goto out; + + sk->sk_state = PPPOX_CONNECTED; + lock_sock(sk_raw); + sk_raw->sk_data_ready = pppopns_recv; + sk_raw->sk_backlog_rcv = pppopns_recv_core; + sk_raw->sk_user_data = sk; + release_sock(sk_raw); +out: + if (sock_tcp) + sockfd_put(sock_tcp); + if (error && sock_raw) + sock_release(sock_raw); + release_sock(sk); + return error; +} + +static int pppopns_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + if (!sk) + return 0; + + lock_sock(sk); + if (sock_flag(sk, SOCK_DEAD)) { + release_sock(sk); + return -EBADF; + } + + if (sk->sk_state != PPPOX_NONE) { + struct sock *sk_raw = (struct sock *)pppox_sk(sk)->chan.private; + lock_sock(sk_raw); + skb_queue_purge(&sk->sk_receive_queue); + pppox_unbind_sock(sk); + sk_raw->sk_data_ready = pppox_sk(sk)->proto.pns.data_ready; + sk_raw->sk_backlog_rcv = pppox_sk(sk)->proto.pns.backlog_rcv; + sk_raw->sk_user_data = NULL; + release_sock(sk_raw); + sock_release(sk_raw->sk_socket); + } + + sock_orphan(sk); + sock->sk = NULL; + release_sock(sk); + sock_put(sk); + return 0; +} + +/******************************************************************************/ + +static struct proto pppopns_proto = { + .name = "PPPOPNS", + .owner = THIS_MODULE, + .obj_size = sizeof(struct pppox_sock), +}; + +static struct proto_ops pppopns_proto_ops = { + .family = PF_PPPOX, + .owner = THIS_MODULE, + .release = pppopns_release, + .bind = sock_no_bind, + .connect = pppopns_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = sock_no_getname, + .poll = sock_no_poll, + .ioctl = pppox_ioctl, + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = sock_no_setsockopt, + .getsockopt = sock_no_getsockopt, + .sendmsg = sock_no_sendmsg, + .recvmsg = sock_no_recvmsg, + .mmap = sock_no_mmap, +}; + +static int pppopns_create(struct net *net, struct socket *sock) +{ + struct sock *sk; + + sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pppopns_proto); + if (!sk) + return -ENOMEM; + + sock_init_data(sock, sk); + sock->state = SS_UNCONNECTED; + sock->ops = &pppopns_proto_ops; + sk->sk_protocol = PX_PROTO_OPNS; + sk->sk_state = PPPOX_NONE; + return 0; +} + +/******************************************************************************/ + +static struct pppox_proto pppopns_pppox_proto = { + .create = pppopns_create, + .owner = THIS_MODULE, +}; + +static int __init pppopns_init(void) +{ + int error; + + error = proto_register(&pppopns_proto, 0); + if (error) + return error; + + error = register_pppox_proto(PX_PROTO_OPNS, &pppopns_pppox_proto); + if (error) + proto_unregister(&pppopns_proto); + else + skb_queue_head_init(&delivery_queue); + return error; +} + +static void __exit pppopns_exit(void) +{ + unregister_pppox_proto(PX_PROTO_OPNS); + proto_unregister(&pppopns_proto); +} + +module_init(pppopns_init); +module_exit(pppopns_exit); + +MODULE_DESCRIPTION("PPP on PPTP Network Server (PPPoPNS)"); +MODULE_AUTHOR("Chia-chi Yeh "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 26f8635b027d3f..ce399514e0b1c8 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1889,6 +1889,12 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, unsigned int ifindex; int ret; +#ifdef CONFIG_ANDROID_PARANOID_NETWORK + if (cmd != TUNGETIFF && !capable(CAP_NET_ADMIN)) { + return -EPERM; + } +#endif + if (cmd == TUNSETIFF || cmd == TUNSETQUEUE || _IOC_TYPE(cmd) == 0x89) { if (copy_from_user(&ifr, argp, ifreq_len)) return -EFAULT; diff --git a/drivers/net/usb/asix_devices.c b/drivers/net/usb/asix_devices.c index 5d194093f3e1f3..90ce609513a6ef 100644 --- a/drivers/net/usb/asix_devices.c +++ b/drivers/net/usb/asix_devices.c @@ -43,6 +43,35 @@ struct ax88172_int_data { __le16 res3; } __packed; +static char asix_mac_addr[6]; +static int __init asix_setup_mac(char *macstr) +{ + int i, h, l; + + if (!macstr) + return 0; + + for (i = 0; i < 6; i++) { + if (i != 5 && *(macstr + 2) != ':') + return 0; + + h = hex_to_bin(*macstr++); + if (h == -1) + return 0; + + l = hex_to_bin(*macstr++); + if (l == -1) + return 0; + + macstr++; + asix_mac_addr[i] = (h << 4) + l; + } + + return 0; +} + +__setup("mac=", asix_setup_mac); + static void asix_status(struct usbnet *dev, struct urb *urb) { struct ax88172_int_data *event; @@ -61,6 +90,13 @@ static void asix_status(struct usbnet *dev, struct urb *urb) static void asix_set_netdev_dev_addr(struct usbnet *dev, u8 *addr) { + static int if_count = 0; + + if (!is_valid_ether_addr(addr)) { + memcpy(addr, asix_mac_addr, ETH_ALEN); + addr[0] |= (if_count++) << 4; + } + if (is_valid_ether_addr(addr)) { memcpy(dev->net->dev_addr, addr, ETH_ALEN); } else { diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index 200020eb30059d..eba19fe8eaf8eb 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -264,6 +264,11 @@ config MWL8K To compile this driver as a module, choose M here: the module will be called mwl8k. If unsure, say N. +config WIFI_CONTROL_FUNC + bool "Enable WiFi control function abstraction" + help + Enables Power/Reset/Carddetect function abstraction + source "drivers/net/wireless/ath/Kconfig" source "drivers/net/wireless/b43/Kconfig" source "drivers/net/wireless/b43legacy/Kconfig" diff --git a/drivers/pinctrl/pinctrl-exynos.c b/drivers/pinctrl/pinctrl-exynos.c index 155b1b3a0e7a71..02741aeae727cc 100644 --- a/drivers/pinctrl/pinctrl-exynos.c +++ b/drivers/pinctrl/pinctrl-exynos.c @@ -1042,6 +1042,132 @@ struct samsung_pin_ctrl exynos5250_pin_ctrl[] = { }, }; +/* pin banks of exynos5410 pin-controller 0 */ +static struct samsung_pin_bank exynos5410_pin_banks0[] = { + EXYNOS_PIN_BANK_EINTG(8, 0x000, "gpa0", 0x00), + EXYNOS_PIN_BANK_EINTG(6, 0x020, "gpa1", 0x04), + EXYNOS_PIN_BANK_EINTG(8, 0x040, "gpa2", 0x08), + EXYNOS_PIN_BANK_EINTG(5, 0x060, "gpb0", 0x0c), + EXYNOS_PIN_BANK_EINTG(5, 0x080, "gpb1", 0x10), + EXYNOS_PIN_BANK_EINTG(4, 0x0A0, "gpb2", 0x14), + EXYNOS_PIN_BANK_EINTG(4, 0x0C0, "gpb3", 0x18), + EXYNOS_PIN_BANK_EINTG(7, 0x0E0, "gpc0", 0x1c), + EXYNOS_PIN_BANK_EINTG(4, 0x100, "gpc3", 0x20), + EXYNOS_PIN_BANK_EINTG(7, 0x120, "gpc1", 0x24), + EXYNOS_PIN_BANK_EINTG(7, 0x140, "gpc2", 0x28), + EXYNOS_PIN_BANK_EINTN(2, 0x160, "gpm5"), + EXYNOS_PIN_BANK_EINTG(8, 0x180, "gpd1", 0x2c), + EXYNOS_PIN_BANK_EINTG(8, 0x1A0, "gpe0", 0x30), + EXYNOS_PIN_BANK_EINTG(2, 0x1C0, "gpe1", 0x34), + EXYNOS_PIN_BANK_EINTG(6, 0x1E0, "gpf0", 0x38), + EXYNOS_PIN_BANK_EINTG(8, 0x200, "gpf1", 0x3c), + EXYNOS_PIN_BANK_EINTG(8, 0x220, "gpg0", 0x40), + EXYNOS_PIN_BANK_EINTG(8, 0x240, "gpg1", 0x44), + EXYNOS_PIN_BANK_EINTG(2, 0x260, "gpg2", 0x48), + EXYNOS_PIN_BANK_EINTG(4, 0x280, "gph0", 0x4c), + EXYNOS_PIN_BANK_EINTG(8, 0x2A0, "gph1", 0x50), + EXYNOS_PIN_BANK_EINTN(8, 0x2C0, "gpm7"), + EXYNOS_PIN_BANK_EINTN(6, 0x2E0, "gpy0"), + EXYNOS_PIN_BANK_EINTN(4, 0x300, "gpy1"), + EXYNOS_PIN_BANK_EINTN(6, 0x320, "gpy2"), + EXYNOS_PIN_BANK_EINTN(8, 0x340, "gpy3"), + EXYNOS_PIN_BANK_EINTN(8, 0x360, "gpy4"), + EXYNOS_PIN_BANK_EINTN(8, 0x380, "gpy5"), + EXYNOS_PIN_BANK_EINTN(8, 0x3A0, "gpy6"), + EXYNOS_PIN_BANK_EINTN(8, 0x3C0, "gpy7"), + EXYNOS_PIN_BANK_EINTW(8, 0xC00, "gpx0", 0x00), + EXYNOS_PIN_BANK_EINTW(8, 0xC20, "gpx1", 0x04), + EXYNOS_PIN_BANK_EINTW(8, 0xC40, "gpx2", 0x08), + EXYNOS_PIN_BANK_EINTW(8, 0xC60, "gpx3", 0x0c), +}; + +/* pin banks of exynos5410 pin-controller 1 */ +static struct samsung_pin_bank exynos5410_pin_banks1[] = { + EXYNOS_PIN_BANK_EINTG(5, 0x000, "gpj0", 0x00), + EXYNOS_PIN_BANK_EINTG(8, 0x020, "gpj1", 0x04), + EXYNOS_PIN_BANK_EINTG(8, 0x040, "gpj2", 0x08), + EXYNOS_PIN_BANK_EINTG(8, 0x060, "gpj3", 0x0c), + EXYNOS_PIN_BANK_EINTG(2, 0x080, "gpj4", 0x10), + EXYNOS_PIN_BANK_EINTG(8, 0x0A0, "gpk0", 0x14), + EXYNOS_PIN_BANK_EINTG(8, 0x0C0, "gpk1", 0x18), + EXYNOS_PIN_BANK_EINTG(8, 0x0E0, "gpk2", 0x1c), + EXYNOS_PIN_BANK_EINTG(7, 0x100, "gpk3", 0x20), +}; + +/* pin banks of exynos5410 pin-controller 2 */ +static struct samsung_pin_bank exynos5410_pin_banks2[] = { + EXYNOS_PIN_BANK_EINTG(8, 0x000, "gpv0", 0x00), + EXYNOS_PIN_BANK_EINTG(8, 0x020, "gpv1", 0x04), + EXYNOS_PIN_BANK_EINTG(8, 0x060, "gpv2", 0x08), + EXYNOS_PIN_BANK_EINTG(8, 0x080, "gpv3", 0x0c), + EXYNOS_PIN_BANK_EINTG(2, 0x0C0, "gpv4", 0x10), +}; + +/* pin banks of exynos5410 pin-controller 3 */ +static struct samsung_pin_bank exynos5410_pin_banks3[] = { + EXYNOS_PIN_BANK_EINTG(7, 0x000, "gpz", 0x00), +}; + +/* + * Samsung pinctrl driver data for Exynos5410 SoC. Exynos5410 SoC includes + * four gpio/pin-mux/pinconfig controllers. + */ +struct samsung_pin_ctrl exynos5410_pin_ctrl[] = { + { + /* pin-controller instance 0 data */ + .pin_banks = exynos5410_pin_banks0, + .nr_banks = ARRAY_SIZE(exynos5410_pin_banks0), + .geint_con = EXYNOS_GPIO_ECON_OFFSET, + .geint_mask = EXYNOS_GPIO_EMASK_OFFSET, + .geint_pend = EXYNOS_GPIO_EPEND_OFFSET, + .weint_con = EXYNOS_WKUP_ECON_OFFSET, + .weint_mask = EXYNOS_WKUP_EMASK_OFFSET, + .weint_pend = EXYNOS_WKUP_EPEND_OFFSET, + .svc = EXYNOS_SVC_OFFSET, + .eint_gpio_init = exynos_eint_gpio_init, + .eint_wkup_init = exynos_eint_wkup_init, + .suspend = exynos_pinctrl_suspend, + .resume = exynos_pinctrl_resume, + .label = "exynos5410-gpio-ctrl0", + }, { + /* pin-controller instance 1 data */ + .pin_banks = exynos5410_pin_banks1, + .nr_banks = ARRAY_SIZE(exynos5410_pin_banks1), + .geint_con = EXYNOS_GPIO_ECON_OFFSET, + .geint_mask = EXYNOS_GPIO_EMASK_OFFSET, + .geint_pend = EXYNOS_GPIO_EPEND_OFFSET, + .svc = EXYNOS_SVC_OFFSET, + .eint_gpio_init = exynos_eint_gpio_init, + .suspend = exynos_pinctrl_suspend, + .resume = exynos_pinctrl_resume, + .label = "exynos5410-gpio-ctrl1", + }, { + /* pin-controller instance 2 data */ + .pin_banks = exynos5410_pin_banks2, + .nr_banks = ARRAY_SIZE(exynos5410_pin_banks2), + .geint_con = EXYNOS_GPIO_ECON_OFFSET, + .geint_mask = EXYNOS_GPIO_EMASK_OFFSET, + .geint_pend = EXYNOS_GPIO_EPEND_OFFSET, + .svc = EXYNOS_SVC_OFFSET, + .eint_gpio_init = exynos_eint_gpio_init, + .suspend = exynos_pinctrl_suspend, + .resume = exynos_pinctrl_resume, + .label = "exynos5410-gpio-ctrl2", + }, { + /* pin-controller instance 3 data */ + .pin_banks = exynos5410_pin_banks3, + .nr_banks = ARRAY_SIZE(exynos5410_pin_banks3), + .geint_con = EXYNOS_GPIO_ECON_OFFSET, + .geint_mask = EXYNOS_GPIO_EMASK_OFFSET, + .geint_pend = EXYNOS_GPIO_EPEND_OFFSET, + .svc = EXYNOS_SVC_OFFSET, + .eint_gpio_init = exynos_eint_gpio_init, + .suspend = exynos_pinctrl_suspend, + .resume = exynos_pinctrl_resume, + .label = "exynos5410-gpio-ctrl3", + }, +}; + /* pin banks of exynos5420 pin-controller 0 */ static struct samsung_pin_bank exynos5420_pin_banks0[] = { EXYNOS_PIN_BANK_EINTG(8, 0x000, "gpy7", 0x00), diff --git a/drivers/pinctrl/pinctrl-samsung.c b/drivers/pinctrl/pinctrl-samsung.c index 47ec2e8741e422..8fe7d4b15d944e 100644 --- a/drivers/pinctrl/pinctrl-samsung.c +++ b/drivers/pinctrl/pinctrl-samsung.c @@ -1120,6 +1120,8 @@ static const struct of_device_id samsung_pinctrl_dt_match[] = { .data = (void *)exynos4x12_pin_ctrl }, { .compatible = "samsung,exynos5250-pinctrl", .data = (void *)exynos5250_pin_ctrl }, + { .compatible = "samsung,exynos5410-pinctrl", + .data = (void *)exynos5410_pin_ctrl }, { .compatible = "samsung,exynos5420-pinctrl", .data = (void *)exynos5420_pin_ctrl }, { .compatible = "samsung,s5pv210-pinctrl", diff --git a/drivers/pinctrl/pinctrl-samsung.h b/drivers/pinctrl/pinctrl-samsung.h index 30622d9afa2ed8..a4b31474dd6804 100644 --- a/drivers/pinctrl/pinctrl-samsung.h +++ b/drivers/pinctrl/pinctrl-samsung.h @@ -254,6 +254,7 @@ struct samsung_pmx_func { extern struct samsung_pin_ctrl exynos4210_pin_ctrl[]; extern struct samsung_pin_ctrl exynos4x12_pin_ctrl[]; extern struct samsung_pin_ctrl exynos5250_pin_ctrl[]; +extern struct samsung_pin_ctrl exynos5410_pin_ctrl[]; extern struct samsung_pin_ctrl exynos5420_pin_ctrl[]; extern struct samsung_pin_ctrl s3c64xx_pin_ctrl[]; extern struct samsung_pin_ctrl s3c2412_pin_ctrl[]; diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index defb6afc1409cc..e6e068ec1ad430 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2321,53 +2321,55 @@ static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m) } } -static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, - struct tp_nvram_state *newn, - const u32 event_mask) -{ - #define TPACPI_COMPARE_KEY(__scancode, __member) \ - do { \ - if ((event_mask & (1 << __scancode)) && \ - oldn->__member != newn->__member) \ - tpacpi_hotkey_send_key(__scancode); \ - } while (0) +do { \ + if ((event_mask & (1 << __scancode)) && \ + oldn->__member != newn->__member) \ + tpacpi_hotkey_send_key(__scancode); \ +} while (0) #define TPACPI_MAY_SEND_KEY(__scancode) \ - do { \ - if (event_mask & (1 << __scancode)) \ - tpacpi_hotkey_send_key(__scancode); \ - } while (0) +do { \ + if (event_mask & (1 << __scancode)) \ + tpacpi_hotkey_send_key(__scancode); \ +} while (0) - void issue_volchange(const unsigned int oldvol, - const unsigned int newvol) - { - unsigned int i = oldvol; +static void issue_volchange(const unsigned int oldvol, + const unsigned int newvol, + const u32 event_mask) +{ + unsigned int i = oldvol; - while (i > newvol) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); - i--; - } - while (i < newvol) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); - i++; - } + while (i > newvol) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); + i--; + } + while (i < newvol) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); + i++; } +} - void issue_brightnesschange(const unsigned int oldbrt, - const unsigned int newbrt) - { - unsigned int i = oldbrt; +static void issue_brightnesschange(const unsigned int oldbrt, + const unsigned int newbrt, + const u32 event_mask) +{ + unsigned int i = oldbrt; - while (i > newbrt) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); - i--; - } - while (i < newbrt) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); - i++; - } + while (i > newbrt) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); + i--; } + while (i < newbrt) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); + i++; + } +} + +static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, + struct tp_nvram_state *newn, + const u32 event_mask) +{ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle); TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle); @@ -2402,7 +2404,8 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, oldn->volume_level != newn->volume_level) { /* recently muted, or repeated mute keypress, or * multiple presses ending in mute */ - issue_volchange(oldn->volume_level, newn->volume_level); + issue_volchange(oldn->volume_level, newn->volume_level, + event_mask); TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE); } } else { @@ -2412,7 +2415,8 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); } if (oldn->volume_level != newn->volume_level) { - issue_volchange(oldn->volume_level, newn->volume_level); + issue_volchange(oldn->volume_level, newn->volume_level, + event_mask); } else if (oldn->volume_toggle != newn->volume_toggle) { /* repeated vol up/down keypress at end of scale ? */ if (newn->volume_level == 0) @@ -2425,7 +2429,7 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, /* handle brightness */ if (oldn->brightness_level != newn->brightness_level) { issue_brightnesschange(oldn->brightness_level, - newn->brightness_level); + newn->brightness_level, event_mask); } else if (oldn->brightness_toggle != newn->brightness_toggle) { /* repeated key presses that didn't change state */ if (newn->brightness_level == 0) diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 44420d1e9094cb..7566e15701e50a 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -189,6 +189,10 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(time_to_full_avg), POWER_SUPPLY_ATTR(type), POWER_SUPPLY_ATTR(scope), + /* Local extensions */ + POWER_SUPPLY_ATTR(usb_hc), + POWER_SUPPLY_ATTR(usb_otg), + POWER_SUPPLY_ATTR(charge_enabled), /* Properties of type `const char *' */ POWER_SUPPLY_ATTR(model_name), POWER_SUPPLY_ATTR(manufacturer), diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 22f2f2857b8206..1fd42afb5588b9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -62,6 +62,16 @@ config PWM_ATMEL_TCB To compile this driver as a module, choose M here: the module will be called pwm-atmel-tcb. +config PWM_BCM_KONA + tristate "Kona PWM support" + depends on ARCH_BCM_MOBILE + default y + help + Generic PWM framework driver for Broadcom Kona PWM block. + + To compile this driver as a module, choose M here: the module + will be called pwm-bcm-kona. + config PWM_BFIN tristate "Blackfin PWM support" depends on BFIN_GPTIMERS diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index d8906ec699761a..7413090a0863fc 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_PWM_SYSFS) += sysfs.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o +obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o diff --git a/drivers/pwm/pwm-bcm-kona.c b/drivers/pwm/pwm-bcm-kona.c new file mode 100644 index 00000000000000..374e9adbafcebd --- /dev/null +++ b/drivers/pwm/pwm-bcm-kona.c @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PWM_CONTROL_OFFSET (0x00000000) +#define PWM_CONTROL_SMOOTH_SHIFT(chan) (24 + (chan)) +#define PWM_CONTROL_TYPE_SHIFT(chan) (16 + (chan)) +#define PWM_CONTROL_POLARITY_SHIFT(chan) (8 + (chan)) +#define PWM_CONTROL_ENABLE_SHIFT(chan) (chan) + +#define PRESCALE_OFFSET (0x00000004) +#define PRESCALE_SHIFT(chan) ((chan) << 2) +#define PRESCALE_MASK(chan) (0x7 << PRESCALE_SHIFT(chan)) +#define PRESCALE_MIN (0x00000000) +#define PRESCALE_MAX (0x00000007) + +#define PERIOD_COUNT_OFFSET(chan) (0x00000008 + ((chan) << 3)) +#define PERIOD_COUNT_MIN (0x00000002) +#define PERIOD_COUNT_MAX (0x00ffffff) + +#define DUTY_CYCLE_HIGH_OFFSET(chan) (0x0000000c + ((chan) << 3)) +#define DUTY_CYCLE_HIGH_MIN (0x00000000) +#define DUTY_CYCLE_HIGH_MAX (0x00ffffff) + +struct kona_pwmc { + struct pwm_chip chip; + void __iomem *base; + struct clk *clk; +}; + +static void kona_pwmc_apply_settings(struct kona_pwmc *kp, unsigned int chan) +{ + unsigned long value = readl(kp->base + PWM_CONTROL_OFFSET); + + /* + * New duty and period settings are only reflected in the PWM output + * after a rising edge of the enable bit. When smooth bit is set, the + * new settings are delayed until the prior PWM period has completed. + * Furthermore, if the smooth bit is set, the PWM continues to output a + * waveform based on the old settings during the time that the enable + * bit is low. Otherwise the output is a constant high signal while + * the enable bit is low. + */ + + /* clear enable bit but set smooth bit to maintain old output */ + value |= (1 << PWM_CONTROL_SMOOTH_SHIFT(chan)); + value &= ~(1 << PWM_CONTROL_ENABLE_SHIFT(chan)); + writel(value, kp->base + PWM_CONTROL_OFFSET); + + /* set enable bit and clear smooth bit to apply new settings */ + value &= ~(1 << PWM_CONTROL_SMOOTH_SHIFT(chan)); + value |= (1 << PWM_CONTROL_ENABLE_SHIFT(chan)); + writel(value, kp->base + PWM_CONTROL_OFFSET); +} + +static int kona_pwmc_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct kona_pwmc *kp = dev_get_drvdata(chip->dev); + u64 val, div, clk_rate; + unsigned long prescale = PRESCALE_MIN, pc, dc; + unsigned int value, chan = pwm->hwpwm; + + /* + * Find period count, duty count and prescale to suit duty_ns and + * period_ns. This is done according to formulas described below: + * + * period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + * + * PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1)) + * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1)) + */ + + clk_rate = clk_get_rate(kp->clk); + + /* There is polarity support in HW but it is easier to manage in SW */ + if (pwm->polarity == PWM_POLARITY_INVERSED) + duty_ns = period_ns - duty_ns; + + while (1) { + div = 1000000000; + div *= 1 + prescale; + val = clk_rate * period_ns; + pc = div64_u64(val, div); + val = clk_rate * duty_ns; + dc = div64_u64(val, div); + + /* If duty_ns or period_ns are not achievable then return */ + if (pc < PERIOD_COUNT_MIN || dc < DUTY_CYCLE_HIGH_MIN) + return -EINVAL; + + /* If pc and dc are in bounds, the calculation is done */ + if (pc <= PERIOD_COUNT_MAX && dc <= DUTY_CYCLE_HIGH_MAX) + break; + + /* Otherwise, increase prescale and recalculate pc and dc */ + if (++prescale > PRESCALE_MAX) + return -EINVAL; + } + + /* If the PWM channel is enabled, write the settings to the HW */ + if (test_bit(PWMF_ENABLED, &pwm->flags)) { + value = readl(kp->base + PRESCALE_OFFSET); + value &= ~PRESCALE_MASK(chan); + value |= prescale << PRESCALE_SHIFT(chan); + writel(value, kp->base + PRESCALE_OFFSET); + + writel(pc, kp->base + PERIOD_COUNT_OFFSET(chan)); + + writel(dc, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan)); + + kona_pwmc_apply_settings(kp, chan); + } + + return 0; +} + +static int kona_pwmc_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + /* + * The framework only allows the polarity to be changed when a PWM is + * disabled so no immediate action is required here. When a channel is + * enabled, the polarity gets handled as part of the re-config step. + */ + + return 0; +} + +static int kona_pwmc_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct kona_pwmc *kp = dev_get_drvdata(chip->dev); + int ret; + + /* + * The PWM framework does not clear the enable bit in the flags if an + * error is returned from a PWM driver's enable function so it must be + * cleared here if any trouble is encountered. + */ + + ret = clk_prepare_enable(kp->clk); + if (ret < 0) { + dev_err(chip->dev, "failed to enable clock: %d\n", ret); + clear_bit(PWMF_ENABLED, &pwm->flags); + return ret; + } + + ret = kona_pwmc_config(chip, pwm, pwm->duty_cycle, pwm->period); + if (ret < 0) { + clk_disable_unprepare(kp->clk); + clear_bit(PWMF_ENABLED, &pwm->flags); + return ret; + } + + return 0; +} + +static void kona_pwmc_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct kona_pwmc *kp = dev_get_drvdata(chip->dev); + unsigned int chan = pwm->hwpwm; + + /* + * The "enable" bits in the control register only affect when settings + * start to take effect so the only real way to disable the PWM output + * is to program a zero duty cycle. + */ + + writel(0, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan)); + kona_pwmc_apply_settings(kp, chan); + + /* + * When the PWM clock is disabled, the output is pegged high or low + * depending on its state at that instant. To guarantee that the new + * settings have taken effect and the output is low a delay of 400ns is + * required. + */ + + ndelay(400); + + clk_disable_unprepare(kp->clk); +} + +static const struct pwm_ops kona_pwm_ops = { + .config = kona_pwmc_config, + .set_polarity = kona_pwmc_set_polarity, + .enable = kona_pwmc_enable, + .disable = kona_pwmc_disable, + .owner = THIS_MODULE, +}; + +static int kona_pwmc_probe(struct platform_device *pdev) +{ + struct kona_pwmc *kp; + struct resource *res; + unsigned int chan, value; + int ret = 0; + + kp = devm_kzalloc(&pdev->dev, sizeof(*kp), GFP_KERNEL); + if (kp == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, kp); + + kp->chip.dev = &pdev->dev; + kp->chip.ops = &kona_pwm_ops; + kp->chip.base = -1; + kp->chip.npwm = 6; + kp->chip.of_xlate = of_pwm_xlate_with_flags; + kp->chip.of_pwm_n_cells = 3; + kp->chip.can_sleep = true; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + kp->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(kp->base)) + return PTR_ERR(kp->base); + + kp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(kp->clk)) { + dev_err(&pdev->dev, "failed to get clock: %ld\n", + PTR_ERR(kp->clk)); + return PTR_ERR(kp->clk); + } + + ret = clk_prepare_enable(kp->clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + /* Set smooth mode, push/pull, and normal polarity for all channels */ + for (value = 0, chan = 0; chan < kp->chip.npwm; chan++) { + value |= (1 << PWM_CONTROL_SMOOTH_SHIFT(chan)); + value |= (1 << PWM_CONTROL_TYPE_SHIFT(chan)); + value |= (1 << PWM_CONTROL_POLARITY_SHIFT(chan)); + } + writel(value, kp->base + PWM_CONTROL_OFFSET); + + clk_disable_unprepare(kp->clk); + + ret = pwmchip_add(&kp->chip); + if (ret < 0) + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + + return ret; +} + +static int kona_pwmc_remove(struct platform_device *pdev) +{ + struct kona_pwmc *kp = platform_get_drvdata(pdev); + unsigned int chan; + + for (chan = 0; chan < kp->chip.npwm; chan++) + if (test_bit(PWMF_ENABLED, &kp->chip.pwms[chan].flags)) + clk_disable_unprepare(kp->clk); + + return pwmchip_remove(&kp->chip); +} + +static const struct of_device_id bcm_kona_pwmc_dt[] = { + { .compatible = "brcm,kona-pwm" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bcm_kona_pwmc_dt); + +static struct platform_driver kona_pwmc_driver = { + + .driver = { + .name = "bcm-kona-pwm", + .of_match_table = bcm_kona_pwmc_dt, + }, + .probe = kona_pwmc_probe, + .remove = kona_pwmc_remove, +}; + +module_platform_driver(kona_pwmc_driver); + +MODULE_AUTHOR("Broadcom Corporation "); +MODULE_AUTHOR("Tim Kryger "); +MODULE_DESCRIPTION("Driver for Kona PWM controller"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 6a7932822e3733..7de07aa5f801a7 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -139,6 +139,14 @@ config REGULATOR_AS3722 AS3722 PMIC. This will enable support for all the software controllable DCDC/LDO regulators. +config REGULATOR_BCM590XX + tristate "Broadcom BCM590xx PMU Regulators" + depends on MFD_BCM590XX + help + This driver provides support for the voltage regulators on the + BCM590xx PMUs. This will enable support for the software + controllable LDO/Switching regulators. + config REGULATOR_DA903X tristate "Dialog Semiconductor DA9030/DA9034 regulators" depends on PMIC_DA903X @@ -353,6 +361,15 @@ config REGULATOR_MAX77693 and one current regulator 'CHARGER'. This is suitable for Exynos-4x12 chips. +config REGULATOR_MAX77XXX + tristate "Maxim 77xxx regulator" + depends on MFD_MAX77XXX + help + This driver controls a Maxim 77686/802 regulator + via I2C bus. The provided regulator is suitable for + Exynos-5 chips to control various voltages. It includes + support for control of voltage and ramp speed. + config REGULATOR_MC13XXX_CORE tristate @@ -592,5 +609,12 @@ config REGULATOR_WM8994 This driver provides support for the voltage regulators on the WM8994 CODEC. +config REGULATOR_HI6421 + tristate "HiSilicon Hi6421 PMIC regulators" + depends on MFD_HI6421_PMIC + help + This driver provides support for the voltage regulators on the + HiSilicon Hi6421 PMU / Codec IC. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 979f9ddcf259bd..46076d45779bca 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o obj-$(CONFIG_REGULATOR_ARIZONA) += arizona-micsupp.o arizona-ldo1.o obj-$(CONFIG_REGULATOR_AS3711) += as3711-regulator.o obj-$(CONFIG_REGULATOR_AS3722) += as3722-regulator.o +obj-$(CONFIG_REGULATOR_BCM590XX) += bcm590xx-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_DA9052) += da9052-regulator.o obj-$(CONFIG_REGULATOR_DA9055) += da9055-regulator.o @@ -48,6 +49,7 @@ obj-$(CONFIG_REGULATOR_MAX8997) += max8997.o obj-$(CONFIG_REGULATOR_MAX8998) += max8998.o obj-$(CONFIG_REGULATOR_MAX77686) += max77686.o obj-$(CONFIG_REGULATOR_MAX77693) += max77693.o +obj-$(CONFIG_REGULATOR_MAX77XXX) += max77xxx-regulator.o obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o @@ -80,6 +82,6 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o - +obj-$(CONFIG_REGULATOR_HI6421) += hi6421-regulator.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/bcm590xx-regulator.c b/drivers/regulator/bcm590xx-regulator.c new file mode 100644 index 00000000000000..e6b2e8e08c4113 --- /dev/null +++ b/drivers/regulator/bcm590xx-regulator.c @@ -0,0 +1,413 @@ +/* + * Broadcom BCM590xx regulator driver + * + * Copyright 2014 Linaro Limited + * Author: Matt Porter + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register defs */ +#define BCM590XX_RFLDOPMCTRL1 0x60 +#define BCM590XX_IOSR1PMCTRL1 0x7a +#define BCM590XX_IOSR2PMCTRL1 0x7c +#define BCM590XX_CSRPMCTRL1 0x7e +#define BCM590XX_SDSR1PMCTRL1 0x82 +#define BCM590XX_SDSR2PMCTRL1 0x86 +#define BCM590XX_MSRPMCTRL1 0x8a +#define BCM590XX_VSRPMCTRL1 0x8e +#define BCM590XX_REG_ENABLE BIT(7) + +#define BCM590XX_RFLDOCTRL 0x96 +#define BCM590XX_CSRVOUT1 0xc0 +#define BCM590XX_LDO_VSEL_MASK GENMASK(5, 3) +#define BCM590XX_SR_VSEL_MASK GENMASK(5, 0) + +/* LDO regulator IDs */ +#define BCM590XX_REG_RFLDO 0 +#define BCM590XX_REG_CAMLDO1 1 +#define BCM590XX_REG_CAMLDO2 2 +#define BCM590XX_REG_SIMLDO1 3 +#define BCM590XX_REG_SIMLDO2 4 +#define BCM590XX_REG_SDLDO 5 +#define BCM590XX_REG_SDXLDO 6 +#define BCM590XX_REG_MMCLDO1 7 +#define BCM590XX_REG_MMCLDO2 8 +#define BCM590XX_REG_AUDLDO 9 +#define BCM590XX_REG_MICLDO 10 +#define BCM590XX_REG_USBLDO 11 +#define BCM590XX_REG_VIBLDO 12 + +/* DCDC regulator IDs */ +#define BCM590XX_REG_CSR 13 +#define BCM590XX_REG_IOSR1 14 +#define BCM590XX_REG_IOSR2 15 +#define BCM590XX_REG_MSR 16 +#define BCM590XX_REG_SDSR1 17 +#define BCM590XX_REG_SDSR2 18 +#define BCM590XX_REG_VSR 19 + +#define BCM590XX_NUM_REGS 20 + +#define BCM590XX_REG_IS_LDO(n) (n < BCM590XX_REG_CSR) + +struct bcm590xx_board { + struct regulator_init_data *bcm590xx_pmu_init_data[BCM590XX_NUM_REGS]; +}; + +/* LDO group A: supported voltages in microvolts */ +static const unsigned int ldo_a_table[] = { + 1200000, 1800000, 2500000, 2700000, 2800000, + 2900000, 3000000, 3300000, +}; + +/* LDO group C: supported voltages in microvolts */ +static const unsigned int ldo_c_table[] = { + 3100000, 1800000, 2500000, 2700000, 2800000, + 2900000, 3000000, 3300000, +}; + +/* DCDC group CSR: supported voltages in microvolts */ +static const struct regulator_linear_range dcdc_csr_ranges[] = { + REGULATOR_LINEAR_RANGE(860000, 2, 50, 10000), + REGULATOR_LINEAR_RANGE(1360000, 51, 55, 20000), + REGULATOR_LINEAR_RANGE(900000, 56, 63, 0), +}; + +/* DCDC group IOSR1: supported voltages in microvolts */ +static const struct regulator_linear_range dcdc_iosr1_ranges[] = { + REGULATOR_LINEAR_RANGE(860000, 2, 51, 10000), + REGULATOR_LINEAR_RANGE(1500000, 52, 52, 0), + REGULATOR_LINEAR_RANGE(1800000, 53, 53, 0), + REGULATOR_LINEAR_RANGE(900000, 54, 63, 0), +}; + +/* DCDC group SDSR1: supported voltages in microvolts */ +static const struct regulator_linear_range dcdc_sdsr1_ranges[] = { + REGULATOR_LINEAR_RANGE(860000, 2, 50, 10000), + REGULATOR_LINEAR_RANGE(1340000, 51, 51, 0), + REGULATOR_LINEAR_RANGE(900000, 52, 63, 0), +}; + +struct bcm590xx_info { + const char *name; + const char *vin_name; + u8 n_voltages; + const unsigned int *volt_table; + u8 n_linear_ranges; + const struct regulator_linear_range *linear_ranges; +}; + +#define BCM590XX_REG_TABLE(_name, _table) \ + { \ + .name = #_name, \ + .n_voltages = ARRAY_SIZE(_table), \ + .volt_table = _table, \ + } + +#define BCM590XX_REG_RANGES(_name, _ranges) \ + { \ + .name = #_name, \ + .n_linear_ranges = ARRAY_SIZE(_ranges), \ + .linear_ranges = _ranges, \ + } + +static struct bcm590xx_info bcm590xx_regs[] = { + BCM590XX_REG_TABLE(rfldo, ldo_a_table), + BCM590XX_REG_TABLE(camldo1, ldo_c_table), + BCM590XX_REG_TABLE(camldo2, ldo_c_table), + BCM590XX_REG_TABLE(simldo1, ldo_a_table), + BCM590XX_REG_TABLE(simldo2, ldo_a_table), + BCM590XX_REG_TABLE(sdldo, ldo_c_table), + BCM590XX_REG_TABLE(sdxldo, ldo_a_table), + BCM590XX_REG_TABLE(mmcldo1, ldo_a_table), + BCM590XX_REG_TABLE(mmcldo2, ldo_a_table), + BCM590XX_REG_TABLE(audldo, ldo_a_table), + BCM590XX_REG_TABLE(micldo, ldo_a_table), + BCM590XX_REG_TABLE(usbldo, ldo_a_table), + BCM590XX_REG_TABLE(vibldo, ldo_c_table), + BCM590XX_REG_RANGES(csr, dcdc_csr_ranges), + BCM590XX_REG_RANGES(iosr1, dcdc_iosr1_ranges), + BCM590XX_REG_RANGES(iosr2, dcdc_iosr1_ranges), + BCM590XX_REG_RANGES(msr, dcdc_iosr1_ranges), + BCM590XX_REG_RANGES(sdsr1, dcdc_sdsr1_ranges), + BCM590XX_REG_RANGES(sdsr2, dcdc_iosr1_ranges), + BCM590XX_REG_RANGES(vsr, dcdc_iosr1_ranges), +}; + +struct bcm590xx_reg { + struct regulator_desc *desc; + struct bcm590xx *mfd; + struct regulator_dev **rdev; + struct bcm590xx_info **info; +}; + +static int bcm590xx_get_vsel_register(int id) +{ + if (BCM590XX_REG_IS_LDO(id)) + return BCM590XX_RFLDOCTRL + id; + else + return BCM590XX_CSRVOUT1 + (id - BCM590XX_REG_CSR) * 3; +} + +static int bcm590xx_get_enable_register(int id) +{ + int reg = 0; + + if (BCM590XX_REG_IS_LDO(id)) + reg = BCM590XX_RFLDOPMCTRL1 + id * 2; + else + switch (id) { + case BCM590XX_REG_CSR: + reg = BCM590XX_CSRPMCTRL1; + break; + case BCM590XX_REG_IOSR1: + reg = BCM590XX_IOSR1PMCTRL1; + break; + case BCM590XX_REG_IOSR2: + reg = BCM590XX_IOSR2PMCTRL1; + break; + case BCM590XX_REG_MSR: + reg = BCM590XX_MSRPMCTRL1; + break; + case BCM590XX_REG_SDSR1: + reg = BCM590XX_SDSR1PMCTRL1; + break; + case BCM590XX_REG_SDSR2: + reg = BCM590XX_SDSR2PMCTRL1; + break; + }; + + return reg; +} + +static struct regulator_ops bcm590xx_ops_ldo = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, +}; + +static struct regulator_ops bcm590xx_ops_dcdc = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, +}; + +#define BCM590XX_MATCH(_name, _id) \ + { \ + .name = #_name, \ + .driver_data = (void *)&bcm590xx_regs[BCM590XX_REG_##_id], \ + } + +static struct of_regulator_match bcm590xx_matches[] = { + BCM590XX_MATCH(rfldo, RFLDO), + BCM590XX_MATCH(camldo1, CAMLDO1), + BCM590XX_MATCH(camldo2, CAMLDO2), + BCM590XX_MATCH(simldo1, SIMLDO1), + BCM590XX_MATCH(simldo2, SIMLDO2), + BCM590XX_MATCH(sdldo, SDLDO), + BCM590XX_MATCH(sdxldo, SDXLDO), + BCM590XX_MATCH(mmcldo1, MMCLDO1), + BCM590XX_MATCH(mmcldo2, MMCLDO2), + BCM590XX_MATCH(audldo, AUDLDO), + BCM590XX_MATCH(micldo, MICLDO), + BCM590XX_MATCH(usbldo, USBLDO), + BCM590XX_MATCH(vibldo, VIBLDO), + BCM590XX_MATCH(csr, CSR), + BCM590XX_MATCH(iosr1, IOSR1), + BCM590XX_MATCH(iosr2, IOSR2), + BCM590XX_MATCH(msr, MSR), + BCM590XX_MATCH(sdsr1, SDSR1), + BCM590XX_MATCH(sdsr2, SDSR2), + BCM590XX_MATCH(vsr, VSR), +}; + +static struct bcm590xx_board *bcm590xx_parse_dt_reg_data( + struct platform_device *pdev, + struct of_regulator_match **bcm590xx_reg_matches) +{ + struct bcm590xx_board *data; + struct device_node *np = pdev->dev.parent->of_node; + struct device_node *regulators; + struct of_regulator_match *matches = bcm590xx_matches; + int count = ARRAY_SIZE(bcm590xx_matches); + int idx = 0; + int ret; + + if (!np) { + dev_err(&pdev->dev, "of node not found\n"); + return NULL; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "failed to allocate regulator board data\n"); + return NULL; + } + + np = of_node_get(np); + regulators = of_get_child_by_name(np, "regulators"); + if (!regulators) { + dev_warn(&pdev->dev, "regulator node not found\n"); + return NULL; + } + + ret = of_regulator_match(&pdev->dev, regulators, matches, count); + of_node_put(regulators); + if (ret < 0) { + dev_err(&pdev->dev, "Error parsing regulator init data: %d\n", + ret); + return NULL; + } + + *bcm590xx_reg_matches = matches; + + for (idx = 0; idx < count; idx++) { + if (!matches[idx].init_data || !matches[idx].of_node) + continue; + + data->bcm590xx_pmu_init_data[idx] = matches[idx].init_data; + } + + return data; +} + +static int bcm590xx_probe(struct platform_device *pdev) +{ + struct bcm590xx *bcm590xx = dev_get_drvdata(pdev->dev.parent); + struct bcm590xx_board *pmu_data = NULL; + struct bcm590xx_reg *pmu; + struct regulator_config config = { }; + struct bcm590xx_info *info; + struct regulator_init_data *reg_data; + struct regulator_dev *rdev; + struct of_regulator_match *bcm590xx_reg_matches = NULL; + int i; + + pmu_data = bcm590xx_parse_dt_reg_data(pdev, + &bcm590xx_reg_matches); + + pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL); + if (!pmu) { + dev_err(&pdev->dev, "Memory allocation failed for pmu\n"); + return -ENOMEM; + } + + pmu->mfd = bcm590xx; + + platform_set_drvdata(pdev, pmu); + + pmu->desc = devm_kzalloc(&pdev->dev, BCM590XX_NUM_REGS * + sizeof(struct regulator_desc), GFP_KERNEL); + if (!pmu->desc) { + dev_err(&pdev->dev, "Memory alloc fails for desc\n"); + return -ENOMEM; + } + + pmu->info = devm_kzalloc(&pdev->dev, BCM590XX_NUM_REGS * + sizeof(struct bcm590xx_info *), GFP_KERNEL); + if (!pmu->info) { + dev_err(&pdev->dev, "Memory alloc fails for info\n"); + return -ENOMEM; + } + + pmu->rdev = devm_kzalloc(&pdev->dev, BCM590XX_NUM_REGS * + sizeof(struct regulator_dev *), GFP_KERNEL); + if (!pmu->rdev) { + dev_err(&pdev->dev, "Memory alloc fails for rdev\n"); + return -ENOMEM; + } + + info = bcm590xx_regs; + + for (i = 0; i < BCM590XX_NUM_REGS; i++, info++) { + if (pmu_data) + reg_data = pmu_data->bcm590xx_pmu_init_data[i]; + else + reg_data = NULL; + + /* Register the regulators */ + pmu->info[i] = info; + + pmu->desc[i].name = info->name; + pmu->desc[i].supply_name = info->vin_name; + pmu->desc[i].id = i; + pmu->desc[i].volt_table = info->volt_table; + pmu->desc[i].n_voltages = info->n_voltages; + pmu->desc[i].linear_ranges = info->linear_ranges; + pmu->desc[i].n_linear_ranges = info->n_linear_ranges; + + if (BCM590XX_REG_IS_LDO(i)) { + pmu->desc[i].ops = &bcm590xx_ops_ldo; + pmu->desc[i].vsel_mask = BCM590XX_LDO_VSEL_MASK; + } else { + pmu->desc[i].ops = &bcm590xx_ops_dcdc; + pmu->desc[i].vsel_mask = BCM590XX_SR_VSEL_MASK; + } + + pmu->desc[i].vsel_reg = bcm590xx_get_vsel_register(i); + pmu->desc[i].enable_is_inverted = true; + pmu->desc[i].enable_mask = BCM590XX_REG_ENABLE; + pmu->desc[i].enable_reg = bcm590xx_get_enable_register(i); + pmu->desc[i].type = REGULATOR_VOLTAGE; + pmu->desc[i].owner = THIS_MODULE; + + config.dev = bcm590xx->dev; + config.init_data = reg_data; + config.driver_data = pmu; + config.regmap = bcm590xx->regmap; + + if (bcm590xx_reg_matches) + config.of_node = bcm590xx_reg_matches[i].of_node; + + rdev = devm_regulator_register(&pdev->dev, &pmu->desc[i], + &config); + if (IS_ERR(rdev)) { + dev_err(bcm590xx->dev, + "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + + pmu->rdev[i] = rdev; + } + + return 0; +} + +static struct platform_driver bcm590xx_regulator_driver = { + .driver = { + .name = "bcm590xx-vregs", + .owner = THIS_MODULE, + }, + .probe = bcm590xx_probe, +}; +module_platform_driver(bcm590xx_regulator_driver); + +MODULE_AUTHOR("Matt Porter "); +MODULE_DESCRIPTION("BCM590xx voltage regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bcm590xx-regulator"); diff --git a/drivers/regulator/hi6421-regulator.c b/drivers/regulator/hi6421-regulator.c new file mode 100644 index 00000000000000..222ecd1d2a402a --- /dev/null +++ b/drivers/regulator/hi6421-regulator.c @@ -0,0 +1,559 @@ +/* + * Device driver for regulators in Hi6421 IC + * + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2011 Hisilicon. + * + * Guodong Xu + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct hi6421_regulator_register_info { + u32 ctrl_reg; + u32 enable_mask; + u32 eco_mode_mask; + u32 vset_reg; + u32 vset_mask; +}; + +struct hi6421_regulator { + const char *name; + struct hi6421_regulator_register_info register_info; + struct timeval last_off_time; + u32 off_on_delay; + u32 eco_uA; + struct regulator_desc rdesc; + int (*dt_parse)(struct hi6421_regulator *, struct platform_device *); +}; + +static inline struct hi6421_pmic *rdev_to_pmic(struct regulator_dev *dev) +{ + /* regulator_dev parent to-> + * hi6421 regulator platform device_dev parent to-> + * hi6421 pmic platform device_dev + */ + return dev_get_drvdata(rdev_get_dev(dev)->parent->parent); +} + +/* helper function to ensure when it returns it is at least 'delay_us' + * microseconds after 'since'. + */ +static void ensured_time_after(struct timeval since, u32 delay_us) +{ + struct timeval now; + u64 elapsed_ns64, delay_ns64; + u32 actual_us32; + + delay_ns64 = delay_us * NSEC_PER_USEC; + do_gettimeofday(&now); + elapsed_ns64 = timeval_to_ns(&now) - timeval_to_ns(&since); + if (delay_ns64 > elapsed_ns64) { + actual_us32 = ((u32)(delay_ns64 - elapsed_ns64) / + NSEC_PER_USEC); + if (actual_us32 >= 1000) { + mdelay(actual_us32 / 1000); + udelay(actual_us32 % 1000); + } else if (actual_us32 > 0) { + udelay(actual_us32); + } + } + return; +} + +static int hi6421_regulator_is_enabled(struct regulator_dev *dev) +{ + u32 reg_val; + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + + reg_val = hi6421_pmic_read(pmic, sreg->register_info.ctrl_reg); + + return ((reg_val & sreg->register_info.enable_mask) != 0); +} + +static int hi6421_regulator_enable(struct regulator_dev *dev) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + + /* keep a distance of off_on_delay from last time disabled */ + ensured_time_after(sreg->last_off_time, sreg->off_on_delay); + + /* cannot enable more than one regulator at one time */ + mutex_lock(&pmic->enable_mutex); + ensured_time_after(pmic->last_enabled, HI6421_REGS_ENA_PROTECT_TIME); + + /* set enable register */ + hi6421_pmic_rmw(pmic, sreg->register_info.ctrl_reg, + sreg->register_info.enable_mask, + sreg->register_info.enable_mask); + + do_gettimeofday(&pmic->last_enabled); + mutex_unlock(&pmic->enable_mutex); + + return 0; +} + +static int hi6421_regulator_disable(struct regulator_dev *dev) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + + /* set enable register to 0 */ + hi6421_pmic_rmw(pmic, sreg->register_info.ctrl_reg, + sreg->register_info.enable_mask, 0); + + do_gettimeofday(&sreg->last_off_time); + + return 0; +} + +static int hi6421_regulator_get_voltage(struct regulator_dev *dev) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 reg_val, selector; + + /* get voltage selector */ + reg_val = hi6421_pmic_read(pmic, sreg->register_info.vset_reg); + selector = (reg_val & sreg->register_info.vset_mask) >> + (ffs(sreg->register_info.vset_mask) - 1); + + return sreg->rdesc.ops->list_voltage(dev, selector); +} + +static int hi6421_regulator_ldo_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 vsel; + int ret = 0; + + for (vsel = 0; vsel < sreg->rdesc.n_voltages; vsel++) { + int uV = sreg->rdesc.volt_table[vsel]; + /* Break at the first in-range value */ + if (min_uV <= uV && uV <= max_uV) + break; + } + + /* unlikely to happen. sanity test done by regulator core */ + if (unlikely(vsel == sreg->rdesc.n_voltages)) + return -EINVAL; + + *selector = vsel; + /* set voltage selector */ + hi6421_pmic_rmw(pmic, sreg->register_info.vset_reg, + sreg->register_info.vset_mask, + vsel << (ffs(sreg->register_info.vset_mask) - 1)); + + return ret; +} + +static int hi6421_regulator_buck012_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 vsel; + int ret = 0; + + vsel = DIV_ROUND_UP((max_uV - sreg->rdesc.min_uV), + sreg->rdesc.uV_step); + + *selector = vsel; + /* set voltage selector */ + hi6421_pmic_rmw(pmic, sreg->register_info.vset_reg, + sreg->register_info.vset_mask, + vsel << (ffs(sreg->register_info.vset_mask) - 1)); + + return ret; +} + +static unsigned int hi6421_regulator_get_mode(struct regulator_dev *dev) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 reg_val; + + reg_val = hi6421_pmic_read(pmic, sreg->register_info.ctrl_reg); + if (reg_val & sreg->register_info.eco_mode_mask) + return REGULATOR_MODE_IDLE; + else + return REGULATOR_MODE_NORMAL; +} + +static int hi6421_regulator_set_mode(struct regulator_dev *dev, + unsigned int mode) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 eco_mode; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + eco_mode = HI6421_ECO_MODE_DISABLE; + break; + case REGULATOR_MODE_IDLE: + eco_mode = HI6421_ECO_MODE_ENABLE; + break; + default: + return -EINVAL; + } + + /* set mode */ + hi6421_pmic_rmw(pmic, sreg->register_info.ctrl_reg, + sreg->register_info.eco_mode_mask, + eco_mode << (ffs(sreg->register_info.eco_mode_mask) - 1)); + + return 0; +} + + +unsigned int hi6421_regulator_get_optimum_mode(struct regulator_dev *dev, + int input_uV, int output_uV, int load_uA) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + + if ((load_uA == 0) || (load_uA > sreg->eco_uA)) + return REGULATOR_MODE_NORMAL; + else + return REGULATOR_MODE_IDLE; +} + +static int hi6421_dt_parse_common(struct hi6421_regulator *sreg, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regulator_desc *rdesc = &sreg->rdesc; + unsigned int register_info[3]; + int ret = 0; + + /* parse .register_info.ctrl_reg */ + ret = of_property_read_u32_array(np, "hisilicon,hi6421-ctrl", + register_info, 3); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-ctrl property set\n"); + goto dt_parse_common_end; + } + sreg->register_info.ctrl_reg = register_info[0]; + sreg->register_info.enable_mask = register_info[1]; + sreg->register_info.eco_mode_mask = register_info[2]; + + /* parse .register_info.vset_reg */ + ret = of_property_read_u32_array(np, "hisilicon,hi6421-vset", + register_info, 2); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-vset property set\n"); + goto dt_parse_common_end; + } + sreg->register_info.vset_reg = register_info[0]; + sreg->register_info.vset_mask = register_info[1]; + + /* parse .off-on-delay */ + ret = of_property_read_u32(np, "hisilicon,hi6421-off-on-delay-us", + &sreg->off_on_delay); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-off-on-delay-us property set\n"); + goto dt_parse_common_end; + } + + /* parse .enable_time */ + ret = of_property_read_u32(np, "hisilicon,hi6421-enable-time-us", + &rdesc->enable_time); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-enable-time-us property set\n"); + goto dt_parse_common_end; + } + + /* parse .eco_uA */ + ret = of_property_read_u32(np, "hisilicon,hi6421-eco-microamp", + &sreg->eco_uA); + if (ret) { + sreg->eco_uA = 0; + ret = 0; + } + +dt_parse_common_end: + return ret; +} + +static int hi6421_dt_parse_ldo(struct hi6421_regulator *sreg, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regulator_desc *rdesc = &sreg->rdesc; + unsigned int *v_table; + int ret = 0; + + /* parse .n_voltages, and .volt_table */ + ret = of_property_read_u32(np, "hisilicon,hi6421-n-voltages", + &rdesc->n_voltages); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-n-voltages property set\n"); + goto dt_parse_ldo_end; + } + + /* alloc space for .volt_table */ + v_table = devm_kzalloc(dev, sizeof(unsigned int) * rdesc->n_voltages, + GFP_KERNEL); + if (unlikely(!v_table)) { + ret = -ENOMEM; + dev_err(dev, "no memory for .volt_table\n"); + goto dt_parse_ldo_end; + } + + ret = of_property_read_u32_array(np, "hisilicon,hi6421-vset-table", + v_table, rdesc->n_voltages); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-vset-table property set\n"); + goto dt_parse_ldo_end; + } + rdesc->volt_table = v_table; + + /* parse hi6421 regulator's dt common part */ + ret = hi6421_dt_parse_common(sreg, pdev); + if (ret) { + dev_err(dev, "failure in hi6421_dt_parse_common\n"); + goto dt_parse_ldo_end; + } + +dt_parse_ldo_end: + return ret; +} + +static int hi6421_dt_parse_buck012(struct hi6421_regulator *sreg, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regulator_desc *rdesc = &sreg->rdesc; + int ret = 0; + + /* parse .n_voltages, and .uV_step */ + ret = of_property_read_u32(np, "hisilicon,hi6421-n-voltages", + &rdesc->n_voltages); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-n-voltages property set\n"); + goto dt_parse_buck012_end; + } + ret = of_property_read_u32(np, "hisilicon,hi6421-uv-step", + &rdesc->uV_step); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-uv-step property set\n"); + goto dt_parse_buck012_end; + } + + /* parse hi6421 regulator's dt common part */ + ret = hi6421_dt_parse_common(sreg, pdev); + if (ret) { + dev_err(dev, "failure in hi6421_dt_parse_common\n"); + goto dt_parse_buck012_end; + } + +dt_parse_buck012_end: + return ret; +} + +static struct regulator_ops hi6421_ldo_rops = { + .is_enabled = hi6421_regulator_is_enabled, + .enable = hi6421_regulator_enable, + .disable = hi6421_regulator_disable, + .list_voltage = regulator_list_voltage_table, + .get_voltage = hi6421_regulator_get_voltage, + .set_voltage = hi6421_regulator_ldo_set_voltage, + .get_mode = hi6421_regulator_get_mode, + .set_mode = hi6421_regulator_set_mode, + .get_optimum_mode = hi6421_regulator_get_optimum_mode, +}; + +static struct regulator_ops hi6421_buck012_rops = { + .is_enabled = hi6421_regulator_is_enabled, + .enable = hi6421_regulator_enable, + .disable = hi6421_regulator_disable, + .list_voltage = regulator_list_voltage_linear, + .get_voltage = hi6421_regulator_get_voltage, + .set_voltage = hi6421_regulator_buck012_set_voltage, + .get_mode = hi6421_regulator_get_mode, + .set_mode = hi6421_regulator_set_mode, + .get_optimum_mode = hi6421_regulator_get_optimum_mode, +}; + +static struct regulator_ops hi6421_buck345_rops = { + .is_enabled = hi6421_regulator_is_enabled, + .enable = hi6421_regulator_enable, + .disable = hi6421_regulator_disable, + .list_voltage = regulator_list_voltage_table, + .get_voltage = hi6421_regulator_get_voltage, + .set_voltage = hi6421_regulator_ldo_set_voltage, + .get_mode = hi6421_regulator_get_mode, + .set_mode = hi6421_regulator_set_mode, + .get_optimum_mode = hi6421_regulator_get_optimum_mode, +}; + +static const struct hi6421_regulator hi6421_regulator_ldo = { + .rdesc = { + .ops = &hi6421_ldo_rops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .dt_parse = hi6421_dt_parse_ldo, +}; + +static const struct hi6421_regulator hi6421_regulator_buck012 = { + .rdesc = { + .ops = &hi6421_buck012_rops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .dt_parse = hi6421_dt_parse_buck012, +}; + +static const struct hi6421_regulator hi6421_regulator_buck345 = { + .rdesc = { + .ops = &hi6421_buck345_rops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .dt_parse = hi6421_dt_parse_ldo, +}; + +static struct of_device_id of_hi6421_regulator_match_tbl[] = { + { + .compatible = "hisilicon,hi6421-ldo", + .data = &hi6421_regulator_ldo, + }, + { + .compatible = "hisilicon,hi6421-buck012", + .data = &hi6421_regulator_buck012, + }, + { + .compatible = "hisilicon,hi6421-buck345", + .data = &hi6421_regulator_buck345, + }, + { /* end */ } +}; + +static int hi6421_regulator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regulator_desc *rdesc; + struct regulator_dev *rdev; + struct hi6421_regulator *sreg = NULL; + struct regulator_init_data *initdata; + struct regulation_constraints *c; + struct regulator_config config = { }; + const struct of_device_id *match; + const struct hi6421_regulator *template = NULL; + int ret = 0; + + /* to check which type of regulator this is */ + match = of_match_device(of_hi6421_regulator_match_tbl, &pdev->dev); + if (match) + template = match->data; + else + return -EINVAL; + + initdata = of_get_regulator_init_data(dev, np); + + /* hi6421 regulator supports two modes */ + c = &initdata->constraints; + c->valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE; + c->valid_ops_mask |= (REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_DRMS); + c->input_uV = c->min_uV; + + sreg = devm_kzalloc(dev, sizeof(*sreg), GFP_KERNEL); + if (sreg == NULL) + return -ENOMEM; + memcpy(sreg, template, sizeof(*sreg)); + + sreg->name = initdata->constraints.name; + rdesc = &sreg->rdesc; + rdesc->name = sreg->name; + rdesc->min_uV = initdata->constraints.min_uV; + if (of_get_property(np, "ldo-supply", NULL)) { + rdesc->supply_name = "ldo"; + } + + /* to parse device tree data for regulator specific */ + ret = sreg->dt_parse(sreg, pdev); + if (ret) { + dev_err(dev, "device tree parameter parse error!\n"); + goto hi6421_probe_end; + } + + config.dev = &pdev->dev; + config.init_data = initdata; + config.driver_data = sreg; + config.of_node = pdev->dev.of_node; + + /* register regulator */ + rdev = regulator_register(rdesc, &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register %s\n", + rdesc->name); + ret = PTR_ERR(rdev); + goto hi6421_probe_end; + } + + platform_set_drvdata(pdev, rdev); + +hi6421_probe_end: + return ret; +} + +static int hi6421_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + + return 0; +} + +static struct platform_driver hi6421_regulator_driver = { + .driver = { + .name = "hi6421_regulator", + .owner = THIS_MODULE, + .of_match_table = of_hi6421_regulator_match_tbl, + }, + .probe = hi6421_regulator_probe, + .remove = hi6421_regulator_remove, +}; +module_platform_driver(hi6421_regulator_driver); + +MODULE_AUTHOR("Guodong Xu "); +MODULE_DESCRIPTION("Hi6421 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/max77xxx-regulator.c b/drivers/regulator/max77xxx-regulator.c new file mode 100644 index 00000000000000..e59ae02a786e5f --- /dev/null +++ b/drivers/regulator/max77xxx-regulator.c @@ -0,0 +1,1078 @@ +/* + * max77xxx.c - Regulator driver for the Maxim 77686/802 + * + * Copyright (C) 2013 Google, Inc + * + * Copyright (C) 2012 Samsung Electronics + * Chiwoong Byun + * Jonghwa Lee + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8997.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Default ramp delay in case it is not manually set */ +#define MAX77XXX_RAMP_DELAY 100000 /* uV/us */ + +#define MAX77XXX_OPMODE_SHIFT_LDO 6 +#define MAX77XXX_OPMODE_BUCK234_SHIFT 4 +#define MAX77XXX_OPMODE_MASK 0x3 + +#define MAX77XXX_VSEL_MASK 0x3F +#define MAX77XXX_DVS_VSEL_MASK 0xFF + +#define MAX77XXX_RAMP_RATE_MASK_2BIT 0xC0 +#define MAX77XXX_RAMP_RATE_SHIFT_2BIT 6 +#define MAX77XXX_RAMP_RATE_MASK_4BIT 0xF0 +#define MAX77XXX_RAMP_RATE_SHIFT_4BIT 4 + +/* + * This is the number of regulators that we support, across all PMICs supported + * by this driver. + */ +#define MAX77XXX_MAX_REGULATORS MAX77XXX_REG_MAX + +#define MAX77XXX_CLOCK_OPMODE_MASK 0x1 +#define MAX77XXX_CLOCK_EN32KHZ_AP_SHIFT 0x0 +#define MAX77XXX_CLOCK_EN32KHZ_CP_SHIFT 0x1 +#define MAX77XXX_CLOCK_P32KHZ_SHIFT 0x2 +#define MAX77XXX_CLOCK_LOW_JITTER_SHIFT 0x3 + +/* Voltage ramp thresholds for MAX77686 in mV/uS */ +static const unsigned int ramp_table_77686[] = { + 13750, + 27500, + 55000, + 100000, +}; + +/* MAX77802 has two register formats: 2-bit and 4-bit */ +static const unsigned int ramp_table_77802_2bit[] = { + 12500, + 25000, + 50000, + 100000, +}; + +static unsigned int ramp_table_77802_4bit[] = { + 1000, 2000, 3030, 4000, + 5000, 5880, 7140, 8330, + 9090, 10000, 11110, 12500, + 16670, 25000, 50000, 100000, +}; + +struct max77xxx_regulator_prv { + int num_regulators; + enum max77xxx_types type; + struct regulator_dev *rdev[MAX77XXX_MAX_REGULATORS]; + unsigned int opmode[MAX77XXX_MAX_REGULATORS]; +}; + +static int max77xxx_get_opmode_shift(int id) +{ + if (id >= MAX77XXX_LDO1 && id <= MAX77XXX_LDO35) + return MAX77XXX_OPMODE_SHIFT_LDO; + else if (id == MAX77XXX_BUCK1 || (id >= MAX77XXX_BUCK5 && + id <= MAX77XXX_BUCK10)) + return 0; + else if (id >= MAX77XXX_BUCK2 && id <= MAX77XXX_BUCK4) + return MAX77XXX_OPMODE_BUCK234_SHIFT; + else if (id == MAX77XXX_EN32KHZ_AP) + return MAX77XXX_CLOCK_EN32KHZ_AP_SHIFT; + else if (id == MAX77XXX_EN32KHZ_CP) + return MAX77XXX_CLOCK_EN32KHZ_CP_SHIFT; + else if (id == MAX77XXX_P32KHZ) + return MAX77XXX_CLOCK_P32KHZ_SHIFT; + else + return -EINVAL; +} + +/* + * Some BUCKS supports Normal[ON/OFF] mode during suspend + * + * 686: BUCK 1, 2-4 + * 802: BUCK 1, 6, 2-4, 5, 7-10 (all) + * + * On 686 Bucks 2/3/4 also have another mode (0x02) that will force to low + * power. On 802 the other mode (0x02) will make pwrreq switch between normal + * and low power. + */ +static int max77xxx_buck_set_suspend_disable(struct regulator_dev *rdev) +{ + unsigned int val = MAX77XXX_OPMODE_STANDBY; + struct max77xxx_regulator_prv *max77xxx = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + int shift = max77xxx_get_opmode_shift(id); + + max77xxx->opmode[id] = val; + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val << shift); +} + +/* + * Some LDOs support [LPM/Normal]ON mode during suspend state. This function + * does not deal with BUCKs. + * + * Used on 686 for LDOs 3-5, 9, 13, 17-26 + */ +static int max77686_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct max77xxx_regulator_prv *max77xxx = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + unsigned int val; + int shift = max77xxx_get_opmode_shift(id); + + /* BUCKs don't support this feature */ + if (id >= MAX77XXX_BUCK1) + return 0; + + switch (mode) { + case REGULATOR_MODE_NORMAL: /* ON in Normal Mode */ + val = MAX77XXX_OPMODE_NORMAL; + break; + case REGULATOR_MODE_IDLE: /* ON in LP Mode */ + if (id != MAX77XXX_LDO5) { + val = MAX77XXX_OPMODE_LP; + break; + } + /* no break */ + default: + pr_warn("%s: regulator_suspend_mode : 0x%x not supported\n", + rdev->desc->name, mode); + return -EINVAL; + } + + max77xxx->opmode[id] = val; + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val << shift); +} + +/* + * Some LDOs supports LPM-ON/OFF/Normal-ON mode during suspend state + * + * 686: LDOs 10-12, 14, 16, 2, 6-8, 15 + * 802: LDOs 3-7, 9-14, 18-26, 28, 29, 32-34, 1, 2, 8, 15, 17, 27, 30, 35 (all) + * + * For 802 LDOs 1, 20, 21, and 3, mode 1 is not supported + */ +static int max77xxx_ldo_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct max77xxx_regulator_prv *max77xxx = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + unsigned int val; + int shift = max77xxx_get_opmode_shift(id); + + switch (mode) { + case REGULATOR_MODE_IDLE: /* ON in LP Mode */ + val = MAX77XXX_OPMODE_LP; + break; + case REGULATOR_MODE_NORMAL: /* ON in Normal Mode */ + val = MAX77XXX_OPMODE_NORMAL; + break; + case REGULATOR_MODE_STANDBY: /* switch off */ + if (id != MAX77XXX_LDO1 && id != MAX77XXX_LDO20 && + id != MAX77XXX_LDO21 && id != MAX77XXX_LDO3) { + val = MAX77XXX_OPMODE_STANDBY; + break; + } + /* no break */ + default: + pr_warn("%s: regulator_suspend_mode : 0x%x not supported\n", + rdev->desc->name, mode); + return -EINVAL; + } + + max77xxx->opmode[rdev_get_id(rdev)] = val; + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val << shift); +} + +static int max77xxx_enable(struct regulator_dev *rdev) +{ + struct max77xxx_regulator_prv *max77xxx = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + int shift = max77xxx_get_opmode_shift(id); + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, + max77xxx->opmode[id] << shift); +} + +static int max77xxx_find_ramp_value(struct regulator_dev *rdev, + const unsigned int limits[], int size, + unsigned int ramp_delay) +{ + int i; + + for (i = 0; i < size; i++) { + if (ramp_delay <= limits[i]) + return i; + } + + /* Use maximum value for no ramp control */ + pr_warn("%s: ramp_delay: %d not supported, setting 100000\n", + rdev->desc->name, ramp_delay); + return size - 1; +} + +static int max77686_set_ramp_delay(struct regulator_dev *rdev, + int ramp_delay) +{ + unsigned int ramp_value; + + ramp_value = max77xxx_find_ramp_value(rdev, ramp_table_77686, + ARRAY_SIZE(ramp_table_77686), ramp_delay); + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + MAX77XXX_RAMP_RATE_MASK_2BIT, + ramp_value << MAX77XXX_RAMP_RATE_SHIFT_2BIT); +} + +/* Used for BUCKs 2-4 */ +static int max77802_set_ramp_delay_2bit(struct regulator_dev *rdev, + int ramp_delay) +{ + int id = rdev_get_id(rdev); + unsigned int ramp_value; + + if (id > MAX77XXX_BUCK4) { + pr_warn("%s: regulator_suspend_mode: ramp delay not supported\n", + rdev->desc->name); + return -EINVAL; + } + ramp_value = max77xxx_find_ramp_value(rdev, ramp_table_77802_2bit, + ARRAY_SIZE(ramp_table_77802_2bit), ramp_delay); + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + MAX77XXX_RAMP_RATE_MASK_2BIT, + ramp_value << MAX77XXX_RAMP_RATE_SHIFT_2BIT); +} + +/* For BUCK1, 6 */ +static int max77802_set_ramp_delay_4bit(struct regulator_dev *rdev, + int ramp_delay) +{ + unsigned int ramp_value; + + ramp_value = max77xxx_find_ramp_value(rdev, ramp_table_77802_4bit, + ARRAY_SIZE(ramp_table_77802_4bit), ramp_delay); + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + MAX77XXX_RAMP_RATE_MASK_4BIT, + ramp_value << MAX77XXX_RAMP_RATE_SHIFT_4BIT); +} + +/* Used for LDOs 3-5, 9, 13, 17-26, 1, BUCKs 5-9 */ +static struct regulator_ops max77686_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77xxx_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_mode = max77686_set_suspend_mode, +}; + +/* + * 686: LDOs 10-12, 14, 16, 2, 6-8, 15 + * 802: LDOs 3-7, 9-14, 18-26, 28, 29, 32-34, 1, 2, 8, 15, 17, 27, 30, 35 (all) + */ +static struct regulator_ops max77xxx_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77xxx_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_mode = max77xxx_ldo_set_suspend_mode, +}; + +/* 686: BUCK1 */ +static struct regulator_ops max77686_buck1_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77xxx_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_disable = max77xxx_buck_set_suspend_disable, +}; + +/* 686: BUCK 2-4 */ +static struct regulator_ops max77686_buck_dvs_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77xxx_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = max77686_set_ramp_delay, + .set_suspend_disable = max77xxx_buck_set_suspend_disable, +}; + +static struct regulator_ops max77xxx_fixedvolt_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = max77xxx_enable, + .disable = regulator_disable_regmap, +}; + +/* LDOs 3-5, 9, 13, 17-26 */ +#define regulator_77686_desc_ldo(num) { \ + .name = "LDO"#num, \ + .id = MAX77XXX_LDO##num, \ + .ops = &max77686_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 800000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = MAX77XXX_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77XXX_OPMODE_MASK \ + << MAX77XXX_OPMODE_SHIFT_LDO, \ +} + +/* LDOs 10-12, 14, 16 */ +#define regulator_77686_desc_lpm_ldo(num) { \ + .name = "LDO"#num, \ + .id = MAX77XXX_LDO##num, \ + .ops = &max77xxx_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 800000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = MAX77XXX_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77XXX_OPMODE_MASK \ + << MAX77XXX_OPMODE_SHIFT_LDO, \ +} + +/* LDOs 2, 6-8, 15 */ +#define regulator_77686_desc_ldo_low(num) { \ + .name = "LDO"#num, \ + .id = MAX77XXX_LDO##num, \ + .ops = &max77xxx_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 800000, \ + .uV_step = 25000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = MAX77XXX_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77XXX_OPMODE_MASK \ + << MAX77XXX_OPMODE_SHIFT_LDO, \ +} + +/* LDO1 */ +#define regulator_77686_desc_ldo1_low(num) { \ + .name = "LDO"#num, \ + .id = MAX77XXX_LDO##num, \ + .ops = &max77686_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 800000, \ + .uV_step = 25000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = MAX77XXX_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77XXX_OPMODE_MASK \ + << MAX77XXX_OPMODE_SHIFT_LDO, \ +} + +/* BUCK 5-9 */ +#define regulator_77686_desc_buck(num) { \ + .name = "BUCK"#num, \ + .id = MAX77XXX_BUCK##num, \ + .ops = &max77686_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 750000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = MAX77XXX_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_BUCK5OUT + (num - 5) * 2, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77686_REG_BUCK5CTRL + (num - 5) * 2, \ + .enable_mask = MAX77XXX_OPMODE_MASK, \ +} + +/* BUCK1 */ +#define regulator_77686_desc_buck1(num) { \ + .name = "BUCK"#num, \ + .id = MAX77XXX_BUCK##num, \ + .ops = &max77686_buck1_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 750000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = MAX77XXX_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_BUCK1OUT, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77686_REG_BUCK1CTRL, \ + .enable_mask = MAX77XXX_OPMODE_MASK, \ +} + +/* BUCK 2-4 */ +#define regulator_77686_desc_buck_dvs(num) { \ + .name = "BUCK"#num, \ + .id = MAX77XXX_BUCK##num, \ + .ops = &max77686_buck_dvs_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 600000, \ + .uV_step = 12500, \ + .ramp_delay = 27500, \ + .n_voltages = MAX77XXX_DVS_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_BUCK2DVS1 + (num - 2) * 10, \ + .vsel_mask = MAX77XXX_DVS_VSEL_MASK, \ + .enable_reg = MAX77686_REG_BUCK2CTRL1 + (num - 2) * 10, \ + .enable_mask = MAX77XXX_OPMODE_MASK \ + << MAX77XXX_OPMODE_BUCK234_SHIFT, \ +} + +static struct regulator_desc regulators_77686[] = { + regulator_77686_desc_ldo1_low(1), + regulator_77686_desc_ldo_low(2), + regulator_77686_desc_ldo(3), + regulator_77686_desc_ldo(4), + regulator_77686_desc_ldo(5), + regulator_77686_desc_ldo_low(6), + regulator_77686_desc_ldo_low(7), + regulator_77686_desc_ldo_low(8), + regulator_77686_desc_ldo(9), + regulator_77686_desc_lpm_ldo(10), + regulator_77686_desc_lpm_ldo(11), + regulator_77686_desc_lpm_ldo(12), + regulator_77686_desc_ldo(13), + regulator_77686_desc_lpm_ldo(14), + regulator_77686_desc_ldo_low(15), + regulator_77686_desc_lpm_ldo(16), + regulator_77686_desc_ldo(17), + regulator_77686_desc_ldo(18), + regulator_77686_desc_ldo(19), + regulator_77686_desc_ldo(20), + regulator_77686_desc_ldo(21), + regulator_77686_desc_ldo(22), + regulator_77686_desc_ldo(23), + regulator_77686_desc_ldo(24), + regulator_77686_desc_ldo(25), + regulator_77686_desc_ldo(26), + regulator_77686_desc_buck1(1), + regulator_77686_desc_buck_dvs(2), + regulator_77686_desc_buck_dvs(3), + regulator_77686_desc_buck_dvs(4), + regulator_77686_desc_buck(5), + regulator_77686_desc_buck(6), + regulator_77686_desc_buck(7), + regulator_77686_desc_buck(8), + regulator_77686_desc_buck(9), + { + .name = "EN32KHZ_AP", + .id = MAX77XXX_EN32KHZ_AP, + .ops = &max77xxx_fixedvolt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = MAX77686_REG_32KHZ, + .enable_mask = MAX77XXX_CLOCK_OPMODE_MASK + << MAX77XXX_CLOCK_EN32KHZ_AP_SHIFT, + }, { + .name = "EN32KHZ_CP", + .id = MAX77XXX_EN32KHZ_CP, + .ops = &max77xxx_fixedvolt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = MAX77686_REG_32KHZ, + .enable_mask = MAX77XXX_CLOCK_OPMODE_MASK + << MAX77XXX_CLOCK_EN32KHZ_CP_SHIFT, + }, { + .name = "P32KHZ", + .id = MAX77XXX_P32KHZ, + .ops = &max77xxx_fixedvolt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = MAX77686_REG_32KHZ, + .enable_mask = MAX77XXX_CLOCK_OPMODE_MASK + << MAX77XXX_CLOCK_P32KHZ_SHIFT, + }, +}; + +/* BUCKS 1, 6 */ +static struct regulator_ops max77802_buck_16_dvs_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77xxx_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = max77802_set_ramp_delay_4bit, + .set_suspend_disable = max77xxx_buck_set_suspend_disable, +}; + +/* BUCKs 2-4, 5, 7-10 */ +static struct regulator_ops max77802_buck_dvs_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77xxx_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = max77802_set_ramp_delay_2bit, + .set_suspend_disable = max77xxx_buck_set_suspend_disable, +}; + +/* LDOs 3-7, 9-14, 18-26, 28, 29, 32-34 */ +#define regulator_77802_desc_p_ldo(num) { \ + .name = "LDO"#num, \ + .id = MAX77XXX_LDO##num, \ + .ops = &max77xxx_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 800000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = 1 << 6, \ + .vsel_reg = MAX77802_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77802_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77XXX_OPMODE_MASK << MAX77XXX_OPMODE_SHIFT_LDO, \ +} + +/* LDOs 1, 2, 8, 15, 17, 27, 30, 35 */ +#define regulator_77802_desc_n_ldo(num) { \ + .name = "LDO"#num, \ + .id = MAX77XXX_LDO##num, \ + .ops = &max77xxx_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 800000, \ + .uV_step = 25000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = 1 << 6, \ + .vsel_reg = MAX77802_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77802_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77XXX_OPMODE_MASK << MAX77XXX_OPMODE_SHIFT_LDO, \ +} + +/* BUCKs 1, 6 */ +#define regulator_77802_desc_16_buck(num) { \ + .name = "BUCK"#num, \ + .id = MAX77XXX_BUCK##num, \ + .ops = &max77802_buck_16_dvs_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 612500, \ + .uV_step = 6250, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = 1 << 8, \ + .vsel_reg = MAX77802_REG_BUCK ## num ## DVS1, \ + .vsel_mask = MAX77XXX_DVS_VSEL_MASK, \ + .enable_reg = MAX77802_REG_BUCK ## num ## CTRL, \ + .enable_mask = MAX77XXX_OPMODE_MASK, \ +} + +/* BUCKS 2-4 */ +#define regulator_77802_desc_234_buck(num) { \ + .name = "BUCK"#num, \ + .id = MAX77XXX_BUCK##num, \ + .ops = &max77802_buck_dvs_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 600000, \ + .uV_step = 6250, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = 0x91, \ + .vsel_reg = MAX77802_REG_BUCK ## num ## DVS1, \ + .vsel_mask = MAX77XXX_DVS_VSEL_MASK, \ + .enable_reg = MAX77802_REG_BUCK ## num ## CTRL1, \ + .enable_mask = MAX77XXX_OPMODE_MASK << \ + MAX77XXX_OPMODE_BUCK234_SHIFT, \ +} + +/* BUCK 5 */ +#define regulator_77802_desc_buck5(num) { \ + .name = "BUCK"#num, \ + .id = MAX77XXX_BUCK##num, \ + .ops = &max77802_buck_dvs_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 750000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = 1 << 6, \ + .vsel_reg = MAX77802_REG_BUCK5OUT, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77802_REG_BUCK5CTRL, \ + .enable_mask = MAX77XXX_OPMODE_MASK, \ +} + +/* BUCKs 7-10 */ +#define regulator_77802_desc_buck7_10(num) { \ + .name = "BUCK"#num, \ + .id = MAX77XXX_BUCK##num, \ + .ops = &max77802_buck_dvs_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 750000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77XXX_RAMP_DELAY, \ + .n_voltages = 1 << 6, \ + .vsel_reg = MAX77802_REG_BUCK7OUT + (num - 7) * 3, \ + .vsel_mask = MAX77XXX_VSEL_MASK, \ + .enable_reg = MAX77802_REG_BUCK7CTRL + (num - 7) * 3, \ + .enable_mask = MAX77XXX_OPMODE_MASK, \ +} + +static struct regulator_desc regulators_77802[] = { + regulator_77802_desc_n_ldo(1), + regulator_77802_desc_n_ldo(2), + regulator_77802_desc_p_ldo(3), + regulator_77802_desc_p_ldo(4), + regulator_77802_desc_p_ldo(5), + regulator_77802_desc_p_ldo(6), + regulator_77802_desc_p_ldo(7), + regulator_77802_desc_n_ldo(8), + regulator_77802_desc_p_ldo(9), + regulator_77802_desc_p_ldo(10), + regulator_77802_desc_p_ldo(11), + regulator_77802_desc_p_ldo(12), + regulator_77802_desc_p_ldo(13), + regulator_77802_desc_p_ldo(14), + regulator_77802_desc_n_ldo(15), + regulator_77802_desc_n_ldo(17), + regulator_77802_desc_p_ldo(18), + regulator_77802_desc_p_ldo(19), + regulator_77802_desc_p_ldo(20), + regulator_77802_desc_p_ldo(21), + regulator_77802_desc_p_ldo(23), + regulator_77802_desc_p_ldo(24), + regulator_77802_desc_p_ldo(25), + regulator_77802_desc_p_ldo(26), + regulator_77802_desc_n_ldo(27), + regulator_77802_desc_p_ldo(28), + regulator_77802_desc_p_ldo(29), + regulator_77802_desc_n_ldo(30), + regulator_77802_desc_p_ldo(32), + regulator_77802_desc_p_ldo(33), + regulator_77802_desc_p_ldo(34), + regulator_77802_desc_n_ldo(35), + regulator_77802_desc_16_buck(1), + regulator_77802_desc_234_buck(2), + regulator_77802_desc_234_buck(3), + regulator_77802_desc_234_buck(4), + regulator_77802_desc_buck5(5), + regulator_77802_desc_16_buck(6), + regulator_77802_desc_buck7_10(7), + regulator_77802_desc_buck7_10(8), + regulator_77802_desc_buck7_10(9), + regulator_77802_desc_buck7_10(10), + { + .name = "EN32KHZ_AP", + .id = MAX77XXX_EN32KHZ_AP, + .ops = &max77xxx_fixedvolt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = MAX77802_REG_32KHZ, + .enable_mask = 1 << 0, + }, { + .name = "EN32KHZ_CP", + .id = MAX77XXX_EN32KHZ_CP, + .ops = &max77xxx_fixedvolt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = MAX77802_REG_32KHZ, + .enable_mask = 1 << 1, + }, +}; + +static struct max77xxx_info { + int num_regulators; + struct regulator_desc *regulators; +} max77xxx_info[TYPE_COUNT] = { + { + .num_regulators = ARRAY_SIZE(regulators_77686), + .regulators = regulators_77686, + }, { + .num_regulators = ARRAY_SIZE(regulators_77802), + .regulators = regulators_77802 + }, +}; + +#ifdef CONFIG_OF +static int max77xxx_pmic_dt_parse_pdata(struct platform_device *pdev, + struct max77xxx_platform_data *pdata, + struct max77xxx_info *info) +{ + struct max77xxx_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct device_node *pmic_np, *regulators_np; + struct max77xxx_regulator_data *rdata; + struct of_regulator_match rmatch; + enum max77xxx_types type; + unsigned int i; + + type = pdev->id_entry->driver_data; + pmic_np = iodev->dev->of_node; + regulators_np = of_find_node_by_name(pmic_np, "voltage-regulators"); + if (!regulators_np) { + dev_err(&pdev->dev, "could not find regulators sub-node\n"); + return -EINVAL; + } + + pdata->num_regulators = info->num_regulators; + rdata = devm_kzalloc(&pdev->dev, sizeof(*rdata) * + pdata->num_regulators, GFP_KERNEL); + if (!rdata) { + dev_err(&pdev->dev, + "could not allocate memory for regulator data\n"); + return -ENOMEM; + } + + for (i = 0; i < pdata->num_regulators; i++) { + rmatch.name = info->regulators[i].name; + rmatch.init_data = NULL; + rmatch.of_node = NULL; + if (of_regulator_match(&pdev->dev, regulators_np, &rmatch, + 1) != 1) { + dev_warn(&pdev->dev, "No matching regulator for '%s'\n", + rmatch.name); + continue; + } + rdata[i].initdata = rmatch.init_data; + rdata[i].of_node = rmatch.of_node; + rdata[i].id = info->regulators[i].id; + if (of_property_read_u32(rdata[i].of_node, "regulator-op-mode", + &rdata[i].opmode)) { + dev_warn(iodev->dev, "no regulator-op-mode property property at %s\n", + rmatch.name); + rdata[i].opmode = MAX77XXX_OPMODE_NORMAL; + } + } + + pdata->regulators = rdata; + + return 0; +} +#else +static int max77xxx_pmic_dt_parse_pdata(struct platform_device *pdev, + struct max77xxx_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ + +/** + * max77xxx_setup_gpios - init DVS-related GPIOs + * + * This function claims / initalizations GPIOs related to DVS if they are + * defined. This may have the effect of switching voltages if the + * pdata->buck_default_idx does not match the boot time state of pins. + */ +static int max77xxx_setup_gpios(struct device *dev, + struct max77xxx_platform_data *pdata) +{ + int buck_default_idx = pdata->buck_default_idx; + int ret; + int i; + + /* Set all SELB high to avoid glitching while DVS is changing */ + for (i = 0; i < ARRAY_SIZE(pdata->buck_gpio_selb); i++) { + int gpio = pdata->buck_gpio_selb[i]; + + /* OK if some GPIOs aren't defined */ + if (!gpio_is_valid(gpio)) + continue; + + /* If a GPIO is valid, we'd better be able to claim it */ + ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_HIGH, + "max77xxx selb"); + if (ret) { + dev_err(dev, "can't claim gpio[%d]: %d\n", i, ret); + return ret; + } + } + + /* Set our initial setting */ + for (i = 0; i < ARRAY_SIZE(pdata->buck_gpio_dvs); i++) { + int gpio = pdata->buck_gpio_dvs[i]; + + /* OK if some GPIOs aren't defined */ + if (!gpio_is_valid(gpio)) + continue; + + /* If a GPIO is valid, we'd better be able to claim it */ + ret = devm_gpio_request(dev, gpio, "max77xxx dvs"); + if (ret) { + dev_err(dev, "can't claim gpio[%d]: %d\n", i, ret); + return ret; + } + gpio_direction_output(gpio, (buck_default_idx >> i) & 1); + } + + /* Now set SELB low to take effect */ + for (i = 0; i < ARRAY_SIZE(pdata->buck_gpio_selb); i++) { + int gpio = pdata->buck_gpio_selb[i]; + + if (gpio_is_valid(gpio)) + gpio_set_value(gpio, 0); + } + + return 0; +} + +/** + * max77xxx_read_gpios - read the current state of the dvs GPIOs + * + * We call this function at bootup to detect what slot the firmware was + * using for the DVS GPIOs. That way we can properly preserve the firmware's + * voltage settings + */ +static int max77xxx_read_gpios(struct max77xxx_platform_data *pdata) +{ + int buck_default_idx = pdata->buck_default_idx; + int result = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(pdata->buck_gpio_dvs); i++) { + int gpio = pdata->buck_gpio_dvs[i]; + + /* OK if some GPIOs aren't defined; we'll use default */ + if (!gpio_is_valid(gpio)) { + result |= buck_default_idx & (1 << i); + continue; + } + + if (gpio_get_value_cansleep(gpio)) + result |= 1 << i; + } + + return result; +} + +static inline bool max77xxx_is_dvs_buck(int id, int type) +{ + /* On 77686 bucks 2-4 are DVS; on 77802 bucks 1-4, 6 are */ + return (id >= MAX77XXX_BUCK2 && id <= MAX77XXX_BUCK4) || + (type == TYPE_MAX77802 && (id == MAX77XXX_BUCK1 || + id == MAX77XXX_BUCK6)); +} + +static void max77xxx_copy_reg(struct device *dev, struct regmap *regmap, + int from_reg, int to_reg) +{ + int val; + int ret; + + if (from_reg == to_reg) + return; + + ret = regmap_read(regmap, from_reg, &val); + if (!ret) + ret = regmap_write(regmap, to_reg, val); + + if (ret) + dev_warn(dev, "Copy err %d => %d (%d)\n", + from_reg, to_reg, ret); +} + +static int max77xxx_pmic_probe(struct platform_device *pdev) +{ + struct max77xxx_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max77xxx_platform_data *pdata = dev_get_platdata(iodev->dev); + struct max77xxx_regulator_prv *max77xxx; + int i, ret = 0; + struct regulator_config config = { }; + struct max77xxx_info *info; + unsigned int reg; + int buck_default_idx; + int buck_old_idx; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + /* This is allocated by the MFD driver */ + if (!pdata) { + dev_err(&pdev->dev, "no platform data found for regulator\n"); + return -ENODEV; + } + + max77xxx = devm_kzalloc(&pdev->dev, + sizeof(struct max77xxx_regulator_prv), + GFP_KERNEL); + if (!max77xxx) + return -ENOMEM; + max77xxx->type = pdev->id_entry->driver_data; + if (max77xxx->type < 0 || max77xxx->type >= TYPE_COUNT) { + dev_err(&pdev->dev, "unknown MAX77xxx type\n"); + return -EINVAL; + } + info = &max77xxx_info[max77xxx->type]; + max77xxx->num_regulators = info->num_regulators; + + if (iodev->dev->of_node) { + ret = max77xxx_pmic_dt_parse_pdata(pdev, pdata, info); + if (ret) + return ret; + } + + if (pdata->num_regulators != info->num_regulators) { + dev_err(&pdev->dev, + "Invalid initial data for regulator's initialiation: expected %d, pdata/dt provided %d\n", + info->num_regulators, pdata->num_regulators); + return -EINVAL; + } + + config.dev = &pdev->dev; + config.regmap = iodev->regmap; + config.driver_data = max77xxx; + platform_set_drvdata(pdev, max77xxx); + + buck_default_idx = pdata->buck_default_idx; + buck_old_idx = max77xxx_read_gpios(pdata); + + for (i = 0; i < max77xxx->num_regulators; i++) { + int id = pdata->regulators[i].id; + + config.init_data = pdata->regulators[i].initdata; + config.of_node = pdata->regulators[i].of_node; + + if (config.of_node) + max77xxx->opmode[id] = pdata->regulators[i].opmode; + else + max77xxx->opmode[id] = MAX77XXX_OPMODE_NORMAL; + + if (max77xxx_is_dvs_buck(id, max77xxx->type)) { + /* Try to copy over data so we keep firmware settings */ + reg = info->regulators[i].vsel_reg; + max77xxx_copy_reg(&pdev->dev, iodev->regmap, + reg + buck_old_idx, + reg + buck_default_idx); + info->regulators[i].vsel_reg += buck_default_idx; + } + + max77xxx->rdev[i] = regulator_register(&info->regulators[i], + &config); + if (IS_ERR(max77xxx->rdev[i])) { + ret = PTR_ERR(max77xxx->rdev[i]); + dev_err(&pdev->dev, + "regulator init failed for %d\n", i); + max77xxx->rdev[i] = NULL; + goto err; + } + } + + /* Enable low-jitter mode on the 32khz clocks. */ + if (max77xxx->type == TYPE_MAX77686) + reg = MAX77686_REG_32KHZ; + else + reg = MAX77802_REG_32KHZ; + ret = regmap_update_bits(iodev->regmap, reg, + 1 << MAX77XXX_CLOCK_LOW_JITTER_SHIFT, + 1 << MAX77XXX_CLOCK_LOW_JITTER_SHIFT); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable low-jitter mode\n"); + goto err; + } + + ret = max77xxx_setup_gpios(&pdev->dev, pdata); + if (ret) + goto err; + + return 0; +err: + while (--i >= 0) + regulator_unregister(max77xxx->rdev[i]); + return ret; +} + +static int max77xxx_pmic_remove(struct platform_device *pdev) +{ + struct max77xxx_regulator_prv *max77xxx = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < max77xxx->num_regulators; i++) + regulator_unregister(max77xxx->rdev[i]); + + return 0; +} + +static const struct platform_device_id max77xxx_pmic_id[] = { + {"max77686-pmic", TYPE_MAX77686}, + {"max77802-pmic", TYPE_MAX77802}, + { }, +}; +MODULE_DEVICE_TABLE(platform, max77xxx_pmic_id); + +static struct platform_driver max77xxx_pmic_driver = { + .driver = { + .name = "max77xxx-pmic", + .owner = THIS_MODULE, + }, + .probe = max77xxx_pmic_probe, + .remove = max77xxx_pmic_remove, + .id_table = max77xxx_pmic_id, +}; + +static int __init max77xxx_pmic_init(void) +{ + return platform_driver_register(&max77xxx_pmic_driver); +} +subsys_initcall(max77xxx_pmic_init); + +static void __exit max77xxx_pmic_cleanup(void) +{ + platform_driver_unregister(&max77xxx_pmic_driver); +} +module_exit(max77xxx_pmic_cleanup); + +MODULE_DESCRIPTION("MAXIM 77xxx Regulator Driver"); +MODULE_AUTHOR("Simon Glass "); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index db933decc39ce3..e80a25776f6bd6 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -229,6 +229,17 @@ config RTC_DRV_LP8788 help Say Y to enable support for the LP8788 RTC/ALARM driver. +config RTC_DRV_HI6421 + tristate "Hisilicon Hi6421 RTC" + depends on MFD_HI6421_PMIC + help + If you say yes here you get support for Hisilicon Hi6421 PMIC + RTC functions. If an interrupt is associated with the device, + the alarm functionality is supported. + + This driver can also be built as a module. If so, the module + will be called rtc-hi6421. + config RTC_DRV_MAX6900 tristate "Maxim MAX6900" help @@ -288,6 +299,36 @@ config RTC_DRV_MAX77686 This driver can also be built as a module. If so, the module will be called rtc-max77686. +config MAX77686_RTC_WTSR + bool "MAX77686_RTC_WTSR" + depends on RTC_DRV_MAX77686 + help + MAX77686 rtc can supports WTSR(Watchdog Timeout and Software Reset). + WTSR event would keep the PMIC on regardless of PWRHOLD signal. + When PWRHOLD signal is falling, it turns on WTSR timer rather executes + reset imidiately. After timer expiring, it determine to reset the PMIC + depends on PWRHOLD state. + +config MAX77686_RTC_SMPL + bool "MAX77686_RTC_SMPL" + depends on RTC_DRV_MAX77686 + help + MAX77686 rtc can supports SMPL(Sudden Momentary Power Loss). + SMPL function can be used to intiate a power up sequence after momentarily + loosing contact to the battery pack. If battery goes under UVLO(Under + Lock Out) it starts SMPL timer. After timer exipiring, it checks battery + whether its state recovers from UVLO or not to make power supplies keep on. + +config RTC_DRV_MAX77XXX + tristate "Maxim MAX77XXX" + depends on MFD_MAX77XXX + help + If you say yes here you will get support for the + RTC of Maxim MAX77XXX PMIC. + + This driver can also be built as a module. If so, the module + will be called rtc-max77xxx. + config RTC_DRV_RS5C372 tristate "Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index b427bf7dd20d93..dafb1dc341a37b 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_RTC_DRV_EM3027) += rtc-em3027.o obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o +obj-$(CONFIG_RTC_DRV_HI6421) += rtc-hi6421.o obj-$(CONFIG_RTC_DRV_HID_SENSOR_TIME) += rtc-hid-sensor-time.o obj-$(CONFIG_RTC_DRV_HYM8563) += rtc-hym8563.o obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o @@ -78,6 +79,7 @@ obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o obj-$(CONFIG_RTC_DRV_MAX8997) += rtc-max8997.o obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o obj-$(CONFIG_RTC_DRV_MAX77686) += rtc-max77686.o +obj-$(CONFIG_RTC_DRV_MAX77XXX) += rtc-max77xxx.o obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o diff --git a/drivers/rtc/rtc-hi6421.c b/drivers/rtc/rtc-hi6421.c new file mode 100644 index 00000000000000..50ec3911861b38 --- /dev/null +++ b/drivers/rtc/rtc-hi6421.c @@ -0,0 +1,259 @@ +/* + * Hisilicon Hi6421 RTC driver + * + * Copyright (C) 2013 Hisilicon Ltd. + * Copyright (C) 2013 Linaro Ltd. + * + * Author: Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include +#include +#include + +#define REG_IRQ1 0x01 +#define REG_IRQM1 0x04 +#define REG_RTCDR0 0x58 +#define REG_RTCDR1 0x59 +#define REG_RTCDR2 0x5a +#define REG_RTCDR3 0x5b +#define REG_RTCMR0 0x5c +#define REG_RTCMR1 0x5d +#define REG_RTCMR2 0x5e +#define REG_RTCMR3 0x5f +#define REG_RTCLR0 0x60 +#define REG_RTCLR1 0x61 +#define REG_RTCLR2 0x62 +#define REG_RTCLR3 0x63 +#define REG_RTCCTRL 0x64 +#define REG_SOFT_RST 0x86 + +#define ALARM_ON (1 << HI6421_IRQ_ALARM) + +struct hi6421_rtc_info { + struct rtc_device *rtc; + struct hi6421_pmic *pmic; + int irq; +}; + +static irqreturn_t hi6421_rtc_handler(int irq, void *data) +{ + struct hi6421_rtc_info *info = (struct hi6421_rtc_info *)data; + + /* clear alarm status */ + hi6421_pmic_rmw(info->pmic, REG_IRQ1, ALARM_ON, ALARM_ON); + rtc_update_irq(info->rtc, 1, RTC_AF); + return IRQ_HANDLED; +} + +/* read 4 8-bit registers & covert it into a 32-bit data */ +static unsigned int hi6421_read_bulk(struct hi6421_pmic *pmic, + unsigned int addr) +{ + unsigned int data, sum = 0; + int i; + + for (i = 0; i < 4; i++) { + data = hi6421_pmic_read(pmic, addr + i); + sum |= (data & 0xff) << (i * 8); + } + return sum; +} + +/* write a 32-bit data into 4 8-bit registers */ +static void hi6421_write_bulk(struct hi6421_pmic *pmic, unsigned int addr, + unsigned int data) +{ + unsigned int value; + int i; + + for (i = 0; i < 4; i++) { + value = (data >> (i * 8)) & 0xff; + hi6421_pmic_write(pmic, addr + i, value); + } +} + +static int hi6421_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct hi6421_rtc_info *info = dev_get_drvdata(dev); + unsigned long ticks; + + ticks = hi6421_read_bulk(info->pmic, REG_RTCDR0); + rtc_time_to_tm(ticks, tm); + return 0; +} + +static int hi6421_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct hi6421_rtc_info *info = dev_get_drvdata(dev); + unsigned long ticks; + + if ((tm->tm_year < 70) || (tm->tm_year > 138)) { + dev_dbg(dev, "Set time %d out of range. " + "Please set time between 1970 to 2038.\n", + 1900 + tm->tm_year); + return -EINVAL; + } + rtc_tm_to_time(tm, &ticks); + hi6421_write_bulk(info->pmic, REG_RTCLR0, ticks); + return 0; +} + +static int hi6421_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct hi6421_rtc_info *info = dev_get_drvdata(dev); + unsigned long ticks, data; + + ticks = hi6421_read_bulk(info->pmic, REG_RTCMR0); + rtc_time_to_tm(ticks, &alrm->time); + + data = hi6421_pmic_read(info->pmic, REG_IRQ1); + alrm->pending = (data & ALARM_ON) ? 1 : 0; + + data = hi6421_pmic_read(info->pmic, REG_IRQM1); + alrm->enabled = (data & ALARM_ON) ? 0 : 1; + return 0; +} + +/* + * Calculate the next alarm time given the requested alarm time mask + * and the current time. + */ +static void rtc_next_alarm_time(struct rtc_time *next, struct rtc_time *now, + struct rtc_time *alrm) +{ + unsigned long next_time; + unsigned long now_time; + + next->tm_year = now->tm_year; + next->tm_mon = now->tm_mon; + next->tm_mday = now->tm_mday; + next->tm_hour = alrm->tm_hour; + next->tm_min = alrm->tm_min; + next->tm_sec = alrm->tm_sec; + + rtc_tm_to_time(now, &now_time); + rtc_tm_to_time(next, &next_time); + + if (next_time < now_time) { + /* Advance one day */ + next_time += 60 * 60 * 24; + rtc_time_to_tm(next_time, next); + } +} + +static int hi6421_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct hi6421_rtc_info *info = dev_get_drvdata(dev); + struct rtc_time now_tm, alarm_tm; + unsigned long ticks; + + /* load 32-bit read-only counter */ + ticks = hi6421_read_bulk(info->pmic, REG_RTCDR0); + rtc_time_to_tm(ticks, &now_tm); + dev_dbg(dev, "%s, now time : %lu\n", __func__, ticks); + rtc_next_alarm_time(&alarm_tm, &now_tm, &alrm->time); + + /* get new ticks for alarm in 24 hours */ + rtc_tm_to_time(&alarm_tm, &ticks); + dev_dbg(dev, "%s, alarm time: %lu\n", __func__, ticks); + if (alrm->enabled) + hi6421_write_bulk(info->pmic, REG_RTCMR0, ticks); + return 0; +} + +static int hi6421_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct hi6421_rtc_info *info = dev_get_drvdata(dev); + unsigned int data = 0; + + if (!enabled) + data = ALARM_ON; + hi6421_pmic_rmw(info->pmic, REG_IRQM1, ALARM_ON, data); + return 0; +} + +static const struct rtc_class_ops hi6421_rtc_ops = { + .read_time = hi6421_rtc_read_time, + .set_time = hi6421_rtc_set_time, + .read_alarm = hi6421_rtc_read_alarm, + .set_alarm = hi6421_rtc_set_alarm, + .alarm_irq_enable = hi6421_rtc_alarm_irq_enable, +}; + +static int hi6421_rtc_probe(struct platform_device *pdev) +{ + struct hi6421_rtc_info *info; + int ret; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) + return -ENOENT; + + info->pmic = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, info); + + /* enable RTC device */ + hi6421_pmic_write(info->pmic, REG_RTCCTRL, 1); + + info->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, + &hi6421_rtc_ops, THIS_MODULE); + if (IS_ERR(info->rtc)) + return PTR_ERR(info->rtc); + + ret = devm_request_irq(&pdev->dev, info->irq, hi6421_rtc_handler, + IRQF_DISABLED, "alarm", info); + if (ret < 0) + return ret; + + return 0; +} + +static int hi6421_rtc_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct of_device_id hi6421_rtc_of_match[] = { + { .compatible = "hisilicon,hi6421-rtc" }, + { } +}; +MODULE_DEVICE_TABLE(of, hi6421_rtc_of_match); + +static struct platform_driver hi6421_rtc_driver = { + .driver = { + .name = "hi6421-rtc", + .of_match_table = of_match_ptr(hi6421_rtc_of_match), + }, + .probe = hi6421_rtc_probe, + .remove = hi6421_rtc_remove, +}; +module_platform_driver(hi6421_rtc_driver); + +MODULE_LICENSE("GPLv2"); +MODULE_AUTHOR("Haojian Zhuang +#include +#include +#include +#include +#include +#include +#include +#include + +/* RTC Control Register */ +#define BCD_EN_SHIFT 0 +#define BCD_EN_MASK (1 << BCD_EN_SHIFT) +#define MODEL24_SHIFT 1 +#define MODEL24_MASK (1 << MODEL24_SHIFT) +/* RTC Update Register1 */ +#define RTC_UDR_SHIFT 0 +#define RTC_UDR_MASK (1 << RTC_UDR_SHIFT) +#define RTC_RBUDR_SHIFT 4 +#define RTC_RBUDR_MASK (1 << RTC_RBUDR_SHIFT) +/* WTSR and SMPL Register */ +#define WTSRT_SHIFT 0 +#define SMPLT_SHIFT 2 +#define WTSR_EN_SHIFT 6 +#define SMPL_EN_SHIFT 7 +#define WTSRT_MASK (3 << WTSRT_SHIFT) +#define SMPLT_MASK (3 << SMPLT_SHIFT) +#define WTSR_EN_MASK (1 << WTSR_EN_SHIFT) +#define SMPL_EN_MASK (1 << SMPL_EN_SHIFT) +/* RTC Hour register */ +#define HOUR_PM_SHIFT 6 +#define HOUR_PM_MASK (1 << HOUR_PM_SHIFT) +/* RTC Alarm Enable */ +#define ALARM_ENABLE_SHIFT 7 +#define ALARM_ENABLE_MASK (1 << ALARM_ENABLE_SHIFT) + +/* For the RTCAE1 register, we write this value to enable the alarm */ +#define ALARM_ENABLE_VALUE 0x77 + +#define MAX77686_RTC_UPDATE_DELAY_US 16000 +#define MAX77802_RTC_UPDATE_DELAY_US 200 +#undef MAX77XXX_RTC_WTSR_SMPL + +enum { + RTC_SEC = 0, + RTC_MIN, + RTC_HOUR, + RTC_WEEKDAY, + RTC_MONTH, + RTC_YEAR, + RTC_DATE, + RTC_NR_TIME +}; + +enum { + /* + * If this flag is set, the PMIC has a separate alarm enable + * register instead of using one bit out of each of the + * hour/min/sec/etc. registers. + * + * This also means that we get a full 8-bits for the year, so can + * report up to 199 years instead of 99. + */ + RTC_FLAG_HAS_ALARM_REG = 1 << 0, +}; + +struct max77xxx_rtc_info { + struct device *dev; + struct max77xxx_dev *max77xxx; + struct i2c_client *rtc; + struct rtc_device *rtc_dev; + struct mutex lock; + + struct regmap *regmap; + const u8 *reg; /* Indirection from MAX77XXX_RTC_... to reg num */ + + int virq; + int rtc_24hr_mode; + int flags; +}; + +enum MAX77XXX_RTC_OP { + MAX77XXX_RTC_WRITE, + MAX77XXX_RTC_READ, +}; + +static inline int max77xxx_rtc_calculate_wday(u8 shifted) +{ + int counter = -1; + while (shifted) { + shifted >>= 1; + counter++; + } + return counter; +} + +static void max77xxx_rtc_data_to_tm(struct max77xxx_rtc_info *info, u8 *data, + struct rtc_time *tm, int rtc_24hr_mode) +{ + int mask = info->flags & RTC_FLAG_HAS_ALARM_REG ? 0xff : 0x7f; + + tm->tm_sec = data[RTC_SEC] & mask; + tm->tm_min = data[RTC_MIN] & mask; + if (rtc_24hr_mode) + tm->tm_hour = data[RTC_HOUR] & 0x1f; + else { + tm->tm_hour = data[RTC_HOUR] & 0x0f; + if (data[RTC_HOUR] & HOUR_PM_MASK) + tm->tm_hour += 12; + } + + tm->tm_wday = max77xxx_rtc_calculate_wday(data[RTC_WEEKDAY] & mask); + tm->tm_mday = data[RTC_DATE] & 0x1f; + tm->tm_mon = (data[RTC_MONTH] & 0x0f) - 1; + + /* + * TODO(sjg@chromium.org): Needs testing: possibly we should not + * add 100 here for the 802 since it can count up to 199. With this + * implementation we use the extra range to extend up to 2199. + */ + tm->tm_year = (data[RTC_YEAR] & mask) + 100; + tm->tm_yday = 0; + tm->tm_isdst = 0; +} + +static int max77xxx_rtc_tm_to_data(struct max77xxx_rtc_info *info, + struct rtc_time *tm, u8 *data) +{ + data[RTC_SEC] = tm->tm_sec; + data[RTC_MIN] = tm->tm_min; + data[RTC_HOUR] = tm->tm_hour; + data[RTC_WEEKDAY] = 1 << tm->tm_wday; + data[RTC_DATE] = tm->tm_mday; + data[RTC_MONTH] = tm->tm_mon + 1; + data[RTC_YEAR] = tm->tm_year >= 100 ? (tm->tm_year - 100) : 0; + + if (tm->tm_year < 100) { + pr_warn("%s: MAX77XXX RTC cannot handle the year %d. Assume it's 2000.\n", + __func__, 1900 + tm->tm_year); + return -EINVAL; + } + + return 0; +} + +static int max77xxx_rtc_update(struct max77xxx_rtc_info *info, + enum MAX77XXX_RTC_OP op) +{ + int ret; + unsigned int data; + + if (op == MAX77XXX_RTC_WRITE) + data = 1 << RTC_UDR_SHIFT; + else + data = 1 << RTC_RBUDR_SHIFT; + + ret = regmap_update_bits(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_RTC_UPDATE0], data, data); + if (ret < 0) + dev_err(info->dev, "%s: fail to write update reg(ret=%d, data=0x%x)\n", + __func__, ret, data); + else { + /* Minimum delay required before RTC update. */ + int delay = info->max77xxx->type == TYPE_MAX77686 ? + MAX77686_RTC_UPDATE_DELAY_US : + MAX77802_RTC_UPDATE_DELAY_US; + + usleep_range(delay, delay * 2); + } + + return ret; +} + +static int max77xxx_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct max77xxx_rtc_info *info = dev_get_drvdata(dev); + u8 data[RTC_NR_TIME]; + int ret; + + mutex_lock(&info->lock); + + ret = max77xxx_rtc_update(info, MAX77XXX_RTC_READ); + if (ret < 0) + goto out; + + ret = regmap_bulk_read(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_RTC_SEC], data, RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to read time reg(%d)\n", __func__, + ret); + goto out; + } + + max77xxx_rtc_data_to_tm(info, data, tm, info->rtc_24hr_mode); + + ret = rtc_valid_tm(tm); + +out: + mutex_unlock(&info->lock); + return ret; +} + +static int max77xxx_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct max77xxx_rtc_info *info = dev_get_drvdata(dev); + u8 data[RTC_NR_TIME]; + int ret; + + ret = max77xxx_rtc_tm_to_data(info, tm, data); + if (ret < 0) + return ret; + + mutex_lock(&info->lock); + + ret = regmap_bulk_write(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_RTC_SEC], data, + RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to write time reg(%d)\n", + __func__, ret); + goto out; + } + + ret = max77xxx_rtc_update(info, MAX77XXX_RTC_WRITE); + +out: + mutex_unlock(&info->lock); + return ret; +} + +static int max77xxx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max77xxx_rtc_info *info = dev_get_drvdata(dev); + u8 data[RTC_NR_TIME]; + unsigned int val; + int i, ret; + + mutex_lock(&info->lock); + + ret = max77xxx_rtc_update(info, MAX77XXX_RTC_READ); + if (ret < 0) + goto out; + + ret = regmap_bulk_read(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_ALARM1_SEC], data, + RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s:%d fail to read alarm reg(%d)\n", + __func__, __LINE__, ret); + goto out; + } + + max77xxx_rtc_data_to_tm(info, data, &alrm->time, info->rtc_24hr_mode); + + alrm->enabled = 0; + if (info->flags & RTC_FLAG_HAS_ALARM_REG) { + ret = regmap_read(info->max77xxx->regmap, + info->reg[MAX77XXX_RTC_AE1], &val); + if (ret < 0) { + dev_err(info->dev, "%s:%d fail to read alarm enable(%d)\n", + __func__, __LINE__, ret); + goto out; + } + if (val) + alrm->enabled = 1; + } else { + for (i = 0; i < RTC_NR_TIME; i++) { + if (data[i] & ALARM_ENABLE_MASK) { + alrm->enabled = 1; + break; + } + } + } + + alrm->pending = 0; + ret = regmap_read(info->max77xxx->regmap, MAX77XXX_REG_STATUS2, &val); + if (ret < 0) { + dev_err(info->dev, "%s:%d fail to read status1 reg(%d)\n", + __func__, __LINE__, ret); + goto out; + } + + if (val & (1 << 2)) /* RTCA1 */ + alrm->pending = 1; + +out: + mutex_unlock(&info->lock); + return 0; +} + +static int max77xxx_rtc_stop_alarm(struct max77xxx_rtc_info *info) +{ + u8 data[RTC_NR_TIME]; + int ret, i; + + if (!mutex_is_locked(&info->lock)) + dev_warn(info->dev, "%s: should have mutex locked\n", + __func__); + + /* + * This matches the code in MAX77686 but it doesn't seem like + * it should be necessary to update when all we are doing is + * updating alarm registers. The buffered values should already + * be correct. + */ + ret = max77xxx_rtc_update(info, MAX77XXX_RTC_READ); + if (ret < 0) + goto out; + + if (info->flags & RTC_FLAG_HAS_ALARM_REG) { + ret = regmap_write(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_RTC_AE1], 0); + if (ret < 0) { + dev_err(info->dev, "%s: fail to write alarm reg(%d)\n", + __func__, ret); + goto out; + } + } else { + ret = regmap_bulk_read(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_ALARM1_SEC], data, + RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to read alarm reg(%d)\n", + __func__, ret); + goto out; + } + + for (i = 0; i < RTC_NR_TIME; i++) + data[i] &= ~ALARM_ENABLE_MASK; + + ret = regmap_bulk_write(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_ALARM1_SEC], data, + RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to write alarm reg(%d)\n", + __func__, ret); + goto out; + } + } + + ret = max77xxx_rtc_update(info, MAX77XXX_RTC_WRITE); +out: + return ret; +} + +static int max77xxx_rtc_start_alarm(struct max77xxx_rtc_info *info) +{ + u8 data[RTC_NR_TIME]; + int ret; + + if (!mutex_is_locked(&info->lock)) + dev_warn(info->dev, "%s: should have mutex locked\n", + __func__); + + ret = max77xxx_rtc_update(info, MAX77XXX_RTC_READ); + if (ret < 0) + goto out; + + if (info->flags & RTC_FLAG_HAS_ALARM_REG) { + ret = regmap_write(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_RTC_AE1], + ALARM_ENABLE_VALUE); + } else { + int i; + + ret = regmap_bulk_read(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_ALARM1_SEC], data, + RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to read alarm reg(%d)\n", + __func__, ret); + goto out; + } + + for (i = 0; i < RTC_NR_TIME; i++) + data[i] |= 1 << ALARM_ENABLE_SHIFT; + data[RTC_WEEKDAY] &= ~ALARM_ENABLE_MASK; + + ret = regmap_bulk_write(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_ALARM1_SEC], data, + RTC_NR_TIME); + } + if (ret < 0) { + dev_err(info->dev, "%s: fail to write alarm reg(%d)\n", + __func__, ret); + goto out; + } + + ret = max77xxx_rtc_update(info, MAX77XXX_RTC_WRITE); +out: + return ret; +} + +static int max77xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max77xxx_rtc_info *info = dev_get_drvdata(dev); + u8 data[RTC_NR_TIME]; + int ret; + + /* + * As noted by dianders@chromium.org: + * + * This code is very inefficient, especially for 77686. We read all + * the registers, clear the high bit, write them all back, wait for + * an update, then set all new values (with the high bit cleared) + * and wait for an update. ...and if the alarm was enabled then we + * do it all a 3rd time. Whoa! + * + * Ideally this whole function could just have a single + * max77xxx_rtc_update() call. + */ + ret = max77xxx_rtc_tm_to_data(info, &alrm->time, data); + if (ret < 0) + return ret; + + mutex_lock(&info->lock); + + ret = max77xxx_rtc_stop_alarm(info); + if (ret < 0) + goto out; + + ret = regmap_bulk_write(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_ALARM1_SEC], data, + RTC_NR_TIME); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to write alarm reg(%d)\n", + __func__, ret); + goto out; + } + + ret = max77xxx_rtc_update(info, MAX77XXX_RTC_WRITE); + if (ret < 0) + goto out; + + if (alrm->enabled) + ret = max77xxx_rtc_start_alarm(info); +out: + mutex_unlock(&info->lock); + return ret; +} + +static int max77xxx_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct max77xxx_rtc_info *info = dev_get_drvdata(dev); + int ret; + + mutex_lock(&info->lock); + if (enabled) + ret = max77xxx_rtc_start_alarm(info); + else + ret = max77xxx_rtc_stop_alarm(info); + mutex_unlock(&info->lock); + + return ret; +} + +static irqreturn_t max77xxx_rtc_alarm_irq(int irq, void *data) +{ + struct max77xxx_rtc_info *info = data; + + dev_info(info->dev, "%s:irq(%d)\n", __func__, irq); + + rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF); + + return IRQ_HANDLED; +} + +static const struct rtc_class_ops max77xxx_rtc_ops = { + .read_time = max77xxx_rtc_read_time, + .set_time = max77xxx_rtc_set_time, + .read_alarm = max77xxx_rtc_read_alarm, + .set_alarm = max77xxx_rtc_set_alarm, + .alarm_irq_enable = max77xxx_rtc_alarm_irq_enable, +}; + +#ifdef MAX77XXX_RTC_WTSR_SMPL +static void max77xxx_rtc_enable_wtsr(struct max77xxx_rtc_info *info, + bool enable) +{ + int ret; + unsigned int val, mask; + + if (enable) + val = (1 << WTSR_EN_SHIFT) | (3 << WTSRT_SHIFT); + else + val = 0; + + mask = WTSR_EN_MASK | WTSRT_MASK; + + dev_info(info->dev, "%s: %s WTSR\n", __func__, + enable ? "enable" : "disable"); + + ret = regmap_update_bits(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_WTSR_SMPL_CNTL], mask, val); + if (ret < 0) { + dev_err(info->dev, "%s: fail to update WTSR reg(%d)\n", + __func__, ret); + return; + } + + max77xxx_rtc_update(info, MAX77XXX_RTC_WRITE); +} + +static void max77xxx_rtc_enable_smpl(struct max77xxx_rtc_info *info, + bool enable) +{ + int ret; + unsigned int val, mask; + + if (enable) + val = (1 << SMPL_EN_SHIFT) | (0 << SMPLT_SHIFT); + else + val = 0; + + mask = SMPL_EN_MASK | SMPLT_MASK; + + dev_info(info->dev, "%s: %s SMPL\n", __func__, + enable ? "enable" : "disable"); + + ret = regmap_update_bits(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_WTSR_SMPL_CNTL], mask, + val); + if (ret < 0) { + dev_err(info->dev, "%s: fail to update SMPL reg(%d)\n", + __func__, ret); + return; + } + + max77xxx_rtc_update(info, MAX77XXX_RTC_WRITE); + + val = 0; + regmap_read(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_WTSR_SMPL_CNTL], &val); + pr_info("%s: WTSR_SMPL(0x%02x)\n", __func__, val); +} +#endif /* MAX77XXX_RTC_WTSR_SMPL */ + +static int max77xxx_rtc_init_reg(struct max77xxx_rtc_info *info) +{ + u8 data[2]; + int ret; + + max77xxx_rtc_update(info, MAX77XXX_RTC_READ); + + /* Set RTC control register : Binary mode, 24hour mdoe */ + data[0] = (1 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT); + data[1] = (0 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT); + + info->rtc_24hr_mode = 1; + + ret = regmap_bulk_write(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_RTC_CONTROLM], data, 2); + if (ret < 0) { + dev_err(info->dev, "%s: fail to write controlm reg(%d)\n", + __func__, ret); + return ret; + } + + max77xxx_rtc_update(info, MAX77XXX_RTC_WRITE); + + /* Mask control register */ + max77xxx_rtc_update(info, MAX77XXX_RTC_READ); + + ret = regmap_update_bits(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_RTC_CONTROLM], 0x0, 0x3); + if (ret < 0) { + dev_err(info->dev, "%s: fail to mask CONTROLM reg(%d)\n", + __func__, ret); + return ret; + } + + ret = max77xxx_rtc_update(info, MAX77XXX_RTC_WRITE); + + return ret; +} + +/* + * Sadly these two chips don't quite share register numbers. The elements + * of these arrays must be in the same order, starting with the register + * for MAX77XXX_RTC_CONTROLM in the first element. + */ +static const u8 max77686_map[MAX77XXX_REG_COUNT] = { + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0x1a, 0x1b, MAX77XXX_REG_NONE, MAX77XXX_REG_NONE, +}; + +static const u8 max77802_map[MAX77XXX_REG_COUNT] = { + 0xc2, 0xc3, 0xc4, MAX77XXX_REG_NONE, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xcf, 0xd0, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, + 0xdc, 0xdd, 0xce, 0xd6, +}; + +static bool max77686_rtc_is_accessible_reg(struct device *dev, + unsigned int reg) +{ + return (reg >= MAX77686_RTC_INT && reg < MAX77686_RTC_END); +} + +static bool max77802_rtc_is_accessible_reg(struct device *dev, + unsigned int reg) +{ + return (reg >= MAX77802_RTC_INT && reg < MAX77802_RTC_END); +} + +static bool max77xxx_rtc_is_precious_reg(enum max77xxx_types type, + unsigned int reg) +{ + const u8 *regs; + + if (type == TYPE_MAX77686) + regs = max77686_map; + else + regs = max77802_map; + return ((type == TYPE_MAX77686 && reg == MAX77686_RTC_INT) || + (type == TYPE_MAX77802 && reg == MAX77802_RTC_INT) || + reg == regs[MAX77XXX_RTC_UPDATE0] || + reg == regs[MAX77XXX_RTC_UPDATE1]); +} + +static bool max77686_rtc_is_precious_reg(struct device *dev, + unsigned int reg) +{ + return max77xxx_rtc_is_precious_reg(TYPE_MAX77686, reg); +} + +static bool max77802_rtc_is_precious_reg(struct device *dev, + unsigned int reg) +{ + return max77xxx_rtc_is_precious_reg(TYPE_MAX77802, reg); +} + +static bool max77xxx_rtc_is_volatile_reg(enum max77xxx_types type, + unsigned int reg) +{ + const u8 *regs; + + if (type == TYPE_MAX77686) + regs = max77686_map; + else + regs = max77802_map; + return (max77xxx_rtc_is_precious_reg(type, reg) || + reg == regs[MAX77XXX_RTC_SEC] || + reg == regs[MAX77XXX_RTC_MIN] || + reg == regs[MAX77XXX_RTC_HOUR] || + reg == regs[MAX77XXX_RTC_WEEKDAY] || + reg == regs[MAX77XXX_RTC_MONTH] || + reg == regs[MAX77XXX_RTC_YEAR] || + reg == regs[MAX77XXX_RTC_DATE]); +} + +static bool max77686_rtc_is_volatile_reg(struct device *dev, + unsigned int reg) +{ + return max77xxx_rtc_is_volatile_reg(TYPE_MAX77686, reg); +} + +static bool max77802_rtc_is_volatile_reg(struct device *dev, + unsigned int reg) +{ + return max77xxx_rtc_is_volatile_reg(TYPE_MAX77802, reg); +} + +static const struct regmap_config max77686_rtc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = max77686_rtc_is_accessible_reg, + .readable_reg = max77686_rtc_is_accessible_reg, + .precious_reg = max77686_rtc_is_precious_reg, + .volatile_reg = max77686_rtc_is_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config max77802_rtc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = max77802_rtc_is_accessible_reg, + .readable_reg = max77802_rtc_is_accessible_reg, + .precious_reg = max77802_rtc_is_precious_reg, + .volatile_reg = max77802_rtc_is_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static int max77xxx_rtc_probe(struct platform_device *pdev) +{ + struct max77xxx_dev *max77xxx = dev_get_drvdata(pdev->dev.parent); + struct max77xxx_rtc_info *info; + int ret, virq; + const struct regmap_config *config; + + dev_info(&pdev->dev, "%s\n", __func__); + + info = devm_kzalloc(&pdev->dev, sizeof(struct max77xxx_rtc_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + mutex_init(&info->lock); + info->dev = &pdev->dev; + info->max77xxx = max77xxx; + info->rtc = max77xxx->rtc; + if (!info->rtc) + info->rtc = max77xxx->i2c; + if (max77xxx->type == TYPE_MAX77802) { + info->reg = max77802_map; + config = &max77802_rtc_regmap_config; + } else { + info->reg = max77686_map; + config = &max77686_rtc_regmap_config; + } + info->max77xxx->rtc_regmap = devm_regmap_init_i2c(info->rtc, config); + if (IS_ERR(info->max77xxx->rtc_regmap)) { + ret = PTR_ERR(info->max77xxx->rtc_regmap); + dev_err(&pdev->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + platform_set_drvdata(pdev, info); + if (max77xxx->type == TYPE_MAX77802) + info->flags |= RTC_FLAG_HAS_ALARM_REG; + + ret = max77xxx_rtc_init_reg(info); + + if (ret < 0) { + dev_err(&pdev->dev, "Failed to initialize RTC reg:%d\n", ret); + return ret; + } + +#ifdef MAX77XXX_RTC_WTSR_SMPL + max77xxx_rtc_enable_wtsr(info, true); + max77xxx_rtc_enable_smpl(info, true); +#endif + + device_init_wakeup(&pdev->dev, 1); + + info->rtc_dev = rtc_device_register("max77xxx-rtc", &pdev->dev, + &max77xxx_rtc_ops, THIS_MODULE); + + if (IS_ERR(info->rtc_dev)) { + dev_info(&pdev->dev, "%s: fail\n", __func__); + + ret = PTR_ERR(info->rtc_dev); + dev_err(&pdev->dev, "Failed to register RTC device: %d\n", + ret); + if (ret == 0) + ret = -EINVAL; + return ret; + } + virq = irq_create_mapping(max77xxx->irq_domain, MAX77XXX_RTCIRQ_RTCA1); + if (!virq) + goto err_irq; + info->virq = virq; + + ret = request_threaded_irq(virq, NULL, max77xxx_rtc_alarm_irq, 0, + "rtc-alarm1", info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request alarm1 IRQ: %d: %d\n", + info->virq, ret); + goto err_irq; + } + + return 0; + +err_irq: + rtc_device_unregister(info->rtc_dev); + return ret; +} + +static int max77xxx_rtc_remove(struct platform_device *pdev) +{ + struct max77xxx_rtc_info *info = platform_get_drvdata(pdev); + + free_irq(info->virq, info); + rtc_device_unregister(info->rtc_dev); + + return 0; +} + +static void max77xxx_rtc_shutdown(struct platform_device *pdev) +{ +#ifdef MAX77XXX_RTC_WTSR_SMPL + struct max77xxx_rtc_info *info = platform_get_drvdata(pdev); + int i; + u8 val = 0; + + for (i = 0; i < 3; i++) { + max77xxx_rtc_enable_wtsr(info, false); + regmap_read(info->max77xxx->rtc_regmap, + info->reg[MAX77XXX_WTSR_SMPL_CNTL], &val); + pr_info("%s: WTSR_SMPL reg(0x%02x)\n", __func__, val); + if (val & WTSR_EN_MASK) + pr_emerg("%s: fail to disable WTSR\n", __func__); + else { + pr_info("%s: success to disable WTSR\n", __func__); + break; + } + } + + /* Disable SMPL when power off */ + max77xxx_rtc_enable_smpl(info, false); +#endif /* MAX77XXX_RTC_WTSR_SMPL */ +} + +#ifdef CONFIG_PM_SLEEP +static int max77xxx_rtc_suspend(struct device *dev) +{ + if (device_may_wakeup(dev)) { + struct max77xxx_rtc_info *info = dev_get_drvdata(dev); + + return enable_irq_wake(info->virq); + } + + return 0; +} + +static int max77xxx_rtc_resume(struct device *dev) +{ + if (device_may_wakeup(dev)) { + struct max77xxx_rtc_info *info = dev_get_drvdata(dev); + + return disable_irq_wake(info->virq); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(max77xxx_rtc_pm_ops, + max77xxx_rtc_suspend, max77xxx_rtc_resume); + +static const struct platform_device_id rtc_id[] = { + { "max77686-rtc", TYPE_MAX77686 }, + { "max77802-rtc", TYPE_MAX77802 }, + {}, +}; + +static struct platform_driver max77xxx_rtc_driver = { + .driver = { + .name = "max77xxx-rtc", + .owner = THIS_MODULE, + .pm = &max77xxx_rtc_pm_ops, + }, + .probe = max77xxx_rtc_probe, + .remove = max77xxx_rtc_remove, + .shutdown = max77xxx_rtc_shutdown, + .id_table = rtc_id, +}; + +static int __init max77xxx_rtc_init(void) +{ + return platform_driver_register(&max77xxx_rtc_driver); +} +module_init(max77xxx_rtc_init); + +static void __exit max77xxx_rtc_exit(void) +{ + platform_driver_unregister(&max77xxx_rtc_driver); +} +module_exit(max77xxx_rtc_exit); + +MODULE_DESCRIPTION("Maxim MAX77XXX RTC driver"); +MODULE_AUTHOR("Simon Glass "); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/android/Kconfig b/drivers/staging/android/Kconfig index b91c758883bf41..511883e1789b4e 100644 --- a/drivers/staging/android/Kconfig +++ b/drivers/staging/android/Kconfig @@ -20,6 +20,14 @@ config ANDROID_BINDER_IPC Android process, using Binder to identify, invoke and pass arguments between said processes. +config ANDROID_BINDER_IPC_32BIT + bool + default y + depends on !64BIT && ANDROID_BINDER_IPC + ---help--- + Enable to support an old 32-bit Android user-space. Breaks the new + Android user-space. + config ASHMEM bool "Enable the Anonymous Shared Memory Subsystem" default n @@ -64,6 +72,15 @@ config ANDROID_LOW_MEMORY_KILLER ---help--- Registers processes to be killed when memory is low +config ANDROID_LOW_MEMORY_KILLER_AUTODETECT_OOM_ADJ_VALUES + bool "Android Low Memory Killer: detect oom_adj values" + depends on ANDROID_LOW_MEMORY_KILLER + default y + ---help--- + Detect oom_adj values written to + /sys/module/lowmemorykiller/parameters/adj and convert them + to oom_score_adj values. + config ANDROID_INTF_ALARM_DEV bool "Android alarm driver" depends on RTC_CLASS diff --git a/drivers/staging/android/TODO b/drivers/staging/android/TODO deleted file mode 100644 index b15fb0d6b15258..00000000000000 --- a/drivers/staging/android/TODO +++ /dev/null @@ -1,10 +0,0 @@ -TODO: - - checkpatch.pl cleanups - - sparse fixes - - rename files to be not so "generic" - - make sure things build as modules properly - - add proper arch dependencies as needed - - audit userspace interfaces to make sure they are sane - -Please send patches to Greg Kroah-Hartman and Cc: -Brian Swetland diff --git a/drivers/staging/android/android_alarm.h b/drivers/staging/android/android_alarm.h index 4fd32f337f9cbb..495b20cf3bf67e 100644 --- a/drivers/staging/android/android_alarm.h +++ b/drivers/staging/android/android_alarm.h @@ -16,50 +16,10 @@ #ifndef _LINUX_ANDROID_ALARM_H #define _LINUX_ANDROID_ALARM_H -#include -#include #include +#include -enum android_alarm_type { - /* return code bit numbers or set alarm arg */ - ANDROID_ALARM_RTC_WAKEUP, - ANDROID_ALARM_RTC, - ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, - ANDROID_ALARM_ELAPSED_REALTIME, - ANDROID_ALARM_SYSTEMTIME, - - ANDROID_ALARM_TYPE_COUNT, - - /* return code bit numbers */ - /* ANDROID_ALARM_TIME_CHANGE = 16 */ -}; - -enum android_alarm_return_flags { - ANDROID_ALARM_RTC_WAKEUP_MASK = 1U << ANDROID_ALARM_RTC_WAKEUP, - ANDROID_ALARM_RTC_MASK = 1U << ANDROID_ALARM_RTC, - ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK = - 1U << ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, - ANDROID_ALARM_ELAPSED_REALTIME_MASK = - 1U << ANDROID_ALARM_ELAPSED_REALTIME, - ANDROID_ALARM_SYSTEMTIME_MASK = 1U << ANDROID_ALARM_SYSTEMTIME, - ANDROID_ALARM_TIME_CHANGE_MASK = 1U << 16 -}; - -/* Disable alarm */ -#define ANDROID_ALARM_CLEAR(type) _IO('a', 0 | ((type) << 4)) - -/* Ack last alarm and wait for next */ -#define ANDROID_ALARM_WAIT _IO('a', 1) - -#define ALARM_IOW(c, type, size) _IOW('a', (c) | ((type) << 4), size) -/* Set alarm */ -#define ANDROID_ALARM_SET(type) ALARM_IOW(2, type, struct timespec) -#define ANDROID_ALARM_SET_AND_WAIT(type) ALARM_IOW(3, type, struct timespec) -#define ANDROID_ALARM_GET_TIME(type) ALARM_IOW(4, type, struct timespec) -#define ANDROID_ALARM_SET_RTC _IOW('a', 5, struct timespec) -#define ANDROID_ALARM_BASE_CMD(cmd) (cmd & ~(_IOC(0, 0, 0xf0, 0))) -#define ANDROID_ALARM_IOCTL_TO_TYPE(cmd) (_IOC_NR(cmd) >> 4) - +#include "uapi/android_alarm.h" #ifdef CONFIG_COMPAT #define ANDROID_ALARM_SET_COMPAT(type) ALARM_IOW(2, type, \ diff --git a/drivers/staging/android/ashmem.c b/drivers/staging/android/ashmem.c index 713a9722678746..5a26e7c5ab5ebc 100644 --- a/drivers/staging/android/ashmem.c +++ b/drivers/staging/android/ashmem.c @@ -396,22 +396,14 @@ static int ashmem_mmap(struct file *file, struct vm_area_struct *vma) } get_file(asma->file); - /* - * XXX - Reworked to use shmem_zero_setup() instead of - * shmem_set_file while we're in staging. -jstultz - */ - if (vma->vm_flags & VM_SHARED) { - ret = shmem_zero_setup(vma); - if (ret) { - fput(asma->file); - goto out; - } + if (vma->vm_flags & VM_SHARED) + shmem_set_file(vma, asma->file); + else { + if (vma->vm_file) + fput(vma->vm_file); + vma->vm_file = asma->file; } - if (vma->vm_file) - fput(vma->vm_file); - vma->vm_file = asma->file; - out: mutex_unlock(&ashmem_mutex); return ret; diff --git a/drivers/staging/android/ashmem.h b/drivers/staging/android/ashmem.h index 8dc0f0d3adf310..5abcfd7aa706c3 100644 --- a/drivers/staging/android/ashmem.h +++ b/drivers/staging/android/ashmem.h @@ -16,35 +16,7 @@ #include #include -#define ASHMEM_NAME_LEN 256 - -#define ASHMEM_NAME_DEF "dev/ashmem" - -/* Return values from ASHMEM_PIN: Was the mapping purged while unpinned? */ -#define ASHMEM_NOT_PURGED 0 -#define ASHMEM_WAS_PURGED 1 - -/* Return values from ASHMEM_GET_PIN_STATUS: Is the mapping pinned? */ -#define ASHMEM_IS_UNPINNED 0 -#define ASHMEM_IS_PINNED 1 - -struct ashmem_pin { - __u32 offset; /* offset into region, in bytes, page-aligned */ - __u32 len; /* length forward from offset, in bytes, page-aligned */ -}; - -#define __ASHMEMIOC 0x77 - -#define ASHMEM_SET_NAME _IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN]) -#define ASHMEM_GET_NAME _IOR(__ASHMEMIOC, 2, char[ASHMEM_NAME_LEN]) -#define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t) -#define ASHMEM_GET_SIZE _IO(__ASHMEMIOC, 4) -#define ASHMEM_SET_PROT_MASK _IOW(__ASHMEMIOC, 5, unsigned long) -#define ASHMEM_GET_PROT_MASK _IO(__ASHMEMIOC, 6) -#define ASHMEM_PIN _IOW(__ASHMEMIOC, 7, struct ashmem_pin) -#define ASHMEM_UNPIN _IOW(__ASHMEMIOC, 8, struct ashmem_pin) -#define ASHMEM_GET_PIN_STATUS _IO(__ASHMEMIOC, 9) -#define ASHMEM_PURGE_ALL_CACHES _IO(__ASHMEMIOC, 10) +#include "uapi/ashmem.h" /* support of 32bit userspace on 64bit platforms */ #ifdef CONFIG_COMPAT diff --git a/drivers/staging/android/binder.c b/drivers/staging/android/binder.c index 1432d956769c2d..f66abb0c89437d 100644 --- a/drivers/staging/android/binder.c +++ b/drivers/staging/android/binder.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "binder.h" #include "binder_trace.h" @@ -228,8 +229,8 @@ struct binder_node { int internal_strong_refs; int local_weak_refs; int local_strong_refs; - void __user *ptr; - void __user *cookie; + binder_uintptr_t ptr; + binder_uintptr_t cookie; unsigned has_strong_ref:1; unsigned pending_strong_ref:1; unsigned has_weak_ref:1; @@ -242,7 +243,7 @@ struct binder_node { struct binder_ref_death { struct binder_work work; - void __user *cookie; + binder_uintptr_t cookie; }; struct binder_ref { @@ -515,14 +516,14 @@ static void binder_insert_allocated_buffer(struct binder_proc *proc, } static struct binder_buffer *binder_buffer_lookup(struct binder_proc *proc, - void __user *user_ptr) + uintptr_t user_ptr) { struct rb_node *n = proc->allocated_buffers.rb_node; struct binder_buffer *buffer; struct binder_buffer *kern_ptr; - kern_ptr = user_ptr - proc->user_buffer_offset - - offsetof(struct binder_buffer, data); + kern_ptr = (struct binder_buffer *)(user_ptr - proc->user_buffer_offset + - offsetof(struct binder_buffer, data)); while (n) { buffer = rb_entry(n, struct binder_buffer, rb_node); @@ -856,7 +857,7 @@ static void binder_free_buf(struct binder_proc *proc, } static struct binder_node *binder_get_node(struct binder_proc *proc, - void __user *ptr) + binder_uintptr_t ptr) { struct rb_node *n = proc->nodes.rb_node; struct binder_node *node; @@ -875,8 +876,8 @@ static struct binder_node *binder_get_node(struct binder_proc *proc, } static struct binder_node *binder_new_node(struct binder_proc *proc, - void __user *ptr, - void __user *cookie) + binder_uintptr_t ptr, + binder_uintptr_t cookie) { struct rb_node **p = &proc->nodes.rb_node; struct rb_node *parent = NULL; @@ -908,9 +909,9 @@ static struct binder_node *binder_new_node(struct binder_proc *proc, INIT_LIST_HEAD(&node->work.entry); INIT_LIST_HEAD(&node->async_todo); binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "%d:%d node %d u%p c%p created\n", + "%d:%d node %d u%016llx c%016llx created\n", proc->pid, current->pid, node->debug_id, - node->ptr, node->cookie); + (u64)node->ptr, (u64)node->cookie); return node; } @@ -1226,9 +1227,9 @@ static void binder_send_failed_reply(struct binder_transaction *t, static void binder_transaction_buffer_release(struct binder_proc *proc, struct binder_buffer *buffer, - size_t *failed_at) + binder_size_t *failed_at) { - size_t *offp, *off_end; + binder_size_t *offp, *off_end; int debug_id = buffer->debug_id; binder_debug(BINDER_DEBUG_TRANSACTION, @@ -1239,7 +1240,8 @@ static void binder_transaction_buffer_release(struct binder_proc *proc, if (buffer->target_node) binder_dec_node(buffer->target_node, 1, 0); - offp = (size_t *)(buffer->data + ALIGN(buffer->data_size, sizeof(void *))); + offp = (binder_size_t *)(buffer->data + + ALIGN(buffer->data_size, sizeof(void *))); if (failed_at) off_end = failed_at; else @@ -1249,8 +1251,8 @@ static void binder_transaction_buffer_release(struct binder_proc *proc, if (*offp > buffer->data_size - sizeof(*fp) || buffer->data_size < sizeof(*fp) || !IS_ALIGNED(*offp, sizeof(u32))) { - pr_err("transaction release %d bad offset %zd, size %zd\n", - debug_id, *offp, buffer->data_size); + pr_err("transaction release %d bad offset %lld, size %zd\n", + debug_id, (u64)*offp, buffer->data_size); continue; } fp = (struct flat_binder_object *)(buffer->data + *offp); @@ -1259,13 +1261,13 @@ static void binder_transaction_buffer_release(struct binder_proc *proc, case BINDER_TYPE_WEAK_BINDER: { struct binder_node *node = binder_get_node(proc, fp->binder); if (node == NULL) { - pr_err("transaction release %d bad node %p\n", - debug_id, fp->binder); + pr_err("transaction release %d bad node %016llx\n", + debug_id, (u64)fp->binder); break; } binder_debug(BINDER_DEBUG_TRANSACTION, - " node %d u%p\n", - node->debug_id, node->ptr); + " node %d u%016llx\n", + node->debug_id, (u64)node->ptr); binder_dec_node(node, fp->type == BINDER_TYPE_BINDER, 0); } break; case BINDER_TYPE_HANDLE: @@ -1303,7 +1305,8 @@ static void binder_transaction(struct binder_proc *proc, { struct binder_transaction *t; struct binder_work *tcomplete; - size_t *offp, *off_end; + binder_size_t *offp, *off_end; + binder_size_t off_min; struct binder_proc *target_proc; struct binder_thread *target_thread = NULL; struct binder_node *target_node = NULL; @@ -1383,6 +1386,10 @@ static void binder_transaction(struct binder_proc *proc, return_error = BR_DEAD_REPLY; goto err_dead_binder; } + if (security_binder_transaction(proc->tsk, target_proc->tsk) < 0) { + return_error = BR_FAILED_REPLY; + goto err_invalid_target_handle; + } if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) { struct binder_transaction *tmp; tmp = thread->transaction_stack; @@ -1432,18 +1439,20 @@ static void binder_transaction(struct binder_proc *proc, if (reply) binder_debug(BINDER_DEBUG_TRANSACTION, - "%d:%d BC_REPLY %d -> %d:%d, data %p-%p size %zd-%zd\n", + "%d:%d BC_REPLY %d -> %d:%d, data %016llx-%016llx size %lld-%lld\n", proc->pid, thread->pid, t->debug_id, target_proc->pid, target_thread->pid, - tr->data.ptr.buffer, tr->data.ptr.offsets, - tr->data_size, tr->offsets_size); + (u64)tr->data.ptr.buffer, + (u64)tr->data.ptr.offsets, + (u64)tr->data_size, (u64)tr->offsets_size); else binder_debug(BINDER_DEBUG_TRANSACTION, - "%d:%d BC_TRANSACTION %d -> %d - node %d, data %p-%p size %zd-%zd\n", + "%d:%d BC_TRANSACTION %d -> %d - node %d, data %016llx-%016llx size %lld-%lld\n", proc->pid, thread->pid, t->debug_id, target_proc->pid, target_node->debug_id, - tr->data.ptr.buffer, tr->data.ptr.offsets, - tr->data_size, tr->offsets_size); + (u64)tr->data.ptr.buffer, + (u64)tr->data.ptr.offsets, + (u64)tr->data_size, (u64)tr->offsets_size); if (!reply && !(tr->flags & TF_ONE_WAY)) t->from = thread; @@ -1472,38 +1481,47 @@ static void binder_transaction(struct binder_proc *proc, if (target_node) binder_inc_node(target_node, 1, 0, NULL); - offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *))); + offp = (binder_size_t *)(t->buffer->data + + ALIGN(tr->data_size, sizeof(void *))); - if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) { + if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t) + tr->data.ptr.buffer, tr->data_size)) { binder_user_error("%d:%d got transaction with invalid data ptr\n", proc->pid, thread->pid); return_error = BR_FAILED_REPLY; goto err_copy_data_failed; } - if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) { + if (copy_from_user(offp, (const void __user *)(uintptr_t) + tr->data.ptr.offsets, tr->offsets_size)) { binder_user_error("%d:%d got transaction with invalid offsets ptr\n", proc->pid, thread->pid); return_error = BR_FAILED_REPLY; goto err_copy_data_failed; } - if (!IS_ALIGNED(tr->offsets_size, sizeof(size_t))) { - binder_user_error("%d:%d got transaction with invalid offsets size, %zd\n", - proc->pid, thread->pid, tr->offsets_size); + if (!IS_ALIGNED(tr->offsets_size, sizeof(binder_size_t))) { + binder_user_error("%d:%d got transaction with invalid offsets size, %lld\n", + proc->pid, thread->pid, (u64)tr->offsets_size); return_error = BR_FAILED_REPLY; goto err_bad_offset; } off_end = (void *)offp + tr->offsets_size; + off_min = 0; for (; offp < off_end; offp++) { struct flat_binder_object *fp; if (*offp > t->buffer->data_size - sizeof(*fp) || + *offp < off_min || t->buffer->data_size < sizeof(*fp) || !IS_ALIGNED(*offp, sizeof(u32))) { - binder_user_error("%d:%d got transaction with invalid offset, %zd\n", - proc->pid, thread->pid, *offp); + binder_user_error("%d:%d got transaction with invalid offset, %lld (min %lld, max %lld)\n", + proc->pid, thread->pid, (u64)*offp, + (u64)off_min, + (u64)(t->buffer->data_size - + sizeof(*fp))); return_error = BR_FAILED_REPLY; goto err_bad_offset; } fp = (struct flat_binder_object *)(t->buffer->data + *offp); + off_min = *offp + sizeof(struct flat_binder_object); switch (fp->type) { case BINDER_TYPE_BINDER: case BINDER_TYPE_WEAK_BINDER: { @@ -1519,10 +1537,14 @@ static void binder_transaction(struct binder_proc *proc, node->accept_fds = !!(fp->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS); } if (fp->cookie != node->cookie) { - binder_user_error("%d:%d sending u%p node %d, cookie mismatch %p != %p\n", + binder_user_error("%d:%d sending u%016llx node %d, cookie mismatch %016llx != %016llx\n", proc->pid, thread->pid, - fp->binder, node->debug_id, - fp->cookie, node->cookie); + (u64)fp->binder, node->debug_id, + (u64)fp->cookie, (u64)node->cookie); + goto err_binder_get_ref_for_node_failed; + } + if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) { + return_error = BR_FAILED_REPLY; goto err_binder_get_ref_for_node_failed; } ref = binder_get_ref_for_node(target_proc, node); @@ -1540,9 +1562,9 @@ static void binder_transaction(struct binder_proc *proc, trace_binder_transaction_node_to_ref(t, node, ref); binder_debug(BINDER_DEBUG_TRANSACTION, - " node %d u%p -> ref %d desc %d\n", - node->debug_id, node->ptr, ref->debug_id, - ref->desc); + " node %d u%016llx -> ref %d desc %d\n", + node->debug_id, (u64)node->ptr, + ref->debug_id, ref->desc); } break; case BINDER_TYPE_HANDLE: case BINDER_TYPE_WEAK_HANDLE: { @@ -1554,6 +1576,10 @@ static void binder_transaction(struct binder_proc *proc, return_error = BR_FAILED_REPLY; goto err_binder_get_ref_failed; } + if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) { + return_error = BR_FAILED_REPLY; + goto err_binder_get_ref_failed; + } if (ref->node->proc == target_proc) { if (fp->type == BINDER_TYPE_HANDLE) fp->type = BINDER_TYPE_BINDER; @@ -1564,9 +1590,9 @@ static void binder_transaction(struct binder_proc *proc, binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL); trace_binder_transaction_ref_to_node(t, ref); binder_debug(BINDER_DEBUG_TRANSACTION, - " ref %d desc %d -> node %d u%p\n", + " ref %d desc %d -> node %d u%016llx\n", ref->debug_id, ref->desc, ref->node->debug_id, - ref->node->ptr); + (u64)ref->node->ptr); } else { struct binder_ref *new_ref; new_ref = binder_get_ref_for_node(target_proc, ref->node); @@ -1610,6 +1636,11 @@ static void binder_transaction(struct binder_proc *proc, return_error = BR_FAILED_REPLY; goto err_fget_failed; } + if (security_binder_transfer_file(proc->tsk, target_proc->tsk, file) < 0) { + fput(file); + return_error = BR_FAILED_REPLY; + goto err_get_unused_fd_failed; + } target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC); if (target_fd < 0) { fput(file); @@ -1682,9 +1713,9 @@ static void binder_transaction(struct binder_proc *proc, err_invalid_target_handle: err_no_context_mgr_node: binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, - "%d:%d transaction failed %d, size %zd-%zd\n", + "%d:%d transaction failed %d, size %lld-%lld\n", proc->pid, thread->pid, return_error, - tr->data_size, tr->offsets_size); + (u64)tr->data_size, (u64)tr->offsets_size); { struct binder_transaction_log_entry *fe; @@ -1702,9 +1733,11 @@ static void binder_transaction(struct binder_proc *proc, static int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread, - void __user *buffer, size_t size, size_t *consumed) + binder_uintptr_t binder_buffer, size_t size, + binder_size_t *consumed) { uint32_t cmd; + void __user *buffer = (void __user *)(uintptr_t)binder_buffer; void __user *ptr = buffer + *consumed; void __user *end = buffer + size; @@ -1773,33 +1806,33 @@ static int binder_thread_write(struct binder_proc *proc, } case BC_INCREFS_DONE: case BC_ACQUIRE_DONE: { - void __user *node_ptr; - void __user *cookie; + binder_uintptr_t node_ptr; + binder_uintptr_t cookie; struct binder_node *node; - if (get_user(node_ptr, (void * __user *)ptr)) + if (get_user(node_ptr, (binder_uintptr_t __user *)ptr)) return -EFAULT; - ptr += sizeof(void *); - if (get_user(cookie, (void * __user *)ptr)) + ptr += sizeof(binder_uintptr_t); + if (get_user(cookie, (binder_uintptr_t __user *)ptr)) return -EFAULT; - ptr += sizeof(void *); + ptr += sizeof(binder_uintptr_t); node = binder_get_node(proc, node_ptr); if (node == NULL) { - binder_user_error("%d:%d %s u%p no match\n", + binder_user_error("%d:%d %s u%016llx no match\n", proc->pid, thread->pid, cmd == BC_INCREFS_DONE ? "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE", - node_ptr); + (u64)node_ptr); break; } if (cookie != node->cookie) { - binder_user_error("%d:%d %s u%p node %d cookie mismatch %p != %p\n", + binder_user_error("%d:%d %s u%016llx node %d cookie mismatch %016llx != %016llx\n", proc->pid, thread->pid, cmd == BC_INCREFS_DONE ? "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE", - node_ptr, node->debug_id, - cookie, node->cookie); + (u64)node_ptr, node->debug_id, + (u64)cookie, (u64)node->cookie); break; } if (cmd == BC_ACQUIRE_DONE) { @@ -1835,27 +1868,27 @@ static int binder_thread_write(struct binder_proc *proc, return -EINVAL; case BC_FREE_BUFFER: { - void __user *data_ptr; + binder_uintptr_t data_ptr; struct binder_buffer *buffer; - if (get_user(data_ptr, (void * __user *)ptr)) + if (get_user(data_ptr, (binder_uintptr_t __user *)ptr)) return -EFAULT; - ptr += sizeof(void *); + ptr += sizeof(binder_uintptr_t); buffer = binder_buffer_lookup(proc, data_ptr); if (buffer == NULL) { - binder_user_error("%d:%d BC_FREE_BUFFER u%p no match\n", - proc->pid, thread->pid, data_ptr); + binder_user_error("%d:%d BC_FREE_BUFFER u%016llx no match\n", + proc->pid, thread->pid, (u64)data_ptr); break; } if (!buffer->allow_user_free) { - binder_user_error("%d:%d BC_FREE_BUFFER u%p matched unreturned buffer\n", - proc->pid, thread->pid, data_ptr); + binder_user_error("%d:%d BC_FREE_BUFFER u%016llx matched unreturned buffer\n", + proc->pid, thread->pid, (u64)data_ptr); break; } binder_debug(BINDER_DEBUG_FREE_BUFFER, - "%d:%d BC_FREE_BUFFER u%p found buffer %d for %s transaction\n", - proc->pid, thread->pid, data_ptr, buffer->debug_id, + "%d:%d BC_FREE_BUFFER u%016llx found buffer %d for %s transaction\n", + proc->pid, thread->pid, (u64)data_ptr, buffer->debug_id, buffer->transaction ? "active" : "finished"); if (buffer->transaction) { @@ -1925,16 +1958,16 @@ static int binder_thread_write(struct binder_proc *proc, case BC_REQUEST_DEATH_NOTIFICATION: case BC_CLEAR_DEATH_NOTIFICATION: { uint32_t target; - void __user *cookie; + binder_uintptr_t cookie; struct binder_ref *ref; struct binder_ref_death *death; if (get_user(target, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); - if (get_user(cookie, (void __user * __user *)ptr)) + if (get_user(cookie, (binder_uintptr_t __user *)ptr)) return -EFAULT; - ptr += sizeof(void *); + ptr += sizeof(binder_uintptr_t); ref = binder_get_ref(proc, target); if (ref == NULL) { binder_user_error("%d:%d %s invalid ref %d\n", @@ -1947,12 +1980,12 @@ static int binder_thread_write(struct binder_proc *proc, } binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION, - "%d:%d %s %p ref %d desc %d s %d w %d for node %d\n", + "%d:%d %s %016llx ref %d desc %d s %d w %d for node %d\n", proc->pid, thread->pid, cmd == BC_REQUEST_DEATH_NOTIFICATION ? "BC_REQUEST_DEATH_NOTIFICATION" : "BC_CLEAR_DEATH_NOTIFICATION", - cookie, ref->debug_id, ref->desc, + (u64)cookie, ref->debug_id, ref->desc, ref->strong, ref->weak, ref->node->debug_id); if (cmd == BC_REQUEST_DEATH_NOTIFICATION) { @@ -1990,9 +2023,9 @@ static int binder_thread_write(struct binder_proc *proc, } death = ref->death; if (death->cookie != cookie) { - binder_user_error("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch %p != %p\n", + binder_user_error("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch %016llx != %016llx\n", proc->pid, thread->pid, - death->cookie, cookie); + (u64)death->cookie, (u64)cookie); break; } ref->death = NULL; @@ -2012,9 +2045,9 @@ static int binder_thread_write(struct binder_proc *proc, } break; case BC_DEAD_BINDER_DONE: { struct binder_work *w; - void __user *cookie; + binder_uintptr_t cookie; struct binder_ref_death *death = NULL; - if (get_user(cookie, (void __user * __user *)ptr)) + if (get_user(cookie, (binder_uintptr_t __user *)ptr)) return -EFAULT; ptr += sizeof(void *); @@ -2026,11 +2059,11 @@ static int binder_thread_write(struct binder_proc *proc, } } binder_debug(BINDER_DEBUG_DEAD_BINDER, - "%d:%d BC_DEAD_BINDER_DONE %p found %p\n", - proc->pid, thread->pid, cookie, death); + "%d:%d BC_DEAD_BINDER_DONE %016llx found %p\n", + proc->pid, thread->pid, (u64)cookie, death); if (death == NULL) { - binder_user_error("%d:%d BC_DEAD_BINDER_DONE %p not found\n", - proc->pid, thread->pid, cookie); + binder_user_error("%d:%d BC_DEAD_BINDER_DONE %016llx not found\n", + proc->pid, thread->pid, (u64)cookie); break; } @@ -2082,9 +2115,10 @@ static int binder_has_thread_work(struct binder_thread *thread) static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread, - void __user *buffer, size_t size, - size_t *consumed, int non_block) + binder_uintptr_t binder_buffer, size_t size, + binder_size_t *consumed, int non_block) { + void __user *buffer = (void __user *)(uintptr_t)binder_buffer; void __user *ptr = buffer + *consumed; void __user *end = buffer + size; @@ -2229,32 +2263,36 @@ static int binder_thread_read(struct binder_proc *proc, if (put_user(cmd, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); - if (put_user(node->ptr, (void * __user *)ptr)) + if (put_user(node->ptr, + (binder_uintptr_t __user *)ptr)) return -EFAULT; - ptr += sizeof(void *); - if (put_user(node->cookie, (void * __user *)ptr)) + ptr += sizeof(binder_uintptr_t); + if (put_user(node->cookie, + (binder_uintptr_t __user *)ptr)) return -EFAULT; - ptr += sizeof(void *); + ptr += sizeof(binder_uintptr_t); binder_stat_br(proc, thread, cmd); binder_debug(BINDER_DEBUG_USER_REFS, - "%d:%d %s %d u%p c%p\n", - proc->pid, thread->pid, cmd_name, node->debug_id, node->ptr, node->cookie); + "%d:%d %s %d u%016llx c%016llx\n", + proc->pid, thread->pid, cmd_name, + node->debug_id, + (u64)node->ptr, (u64)node->cookie); } else { list_del_init(&w->entry); if (!weak && !strong) { binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "%d:%d node %d u%p c%p deleted\n", + "%d:%d node %d u%016llx c%016llx deleted\n", proc->pid, thread->pid, node->debug_id, - node->ptr, node->cookie); + (u64)node->ptr, (u64)node->cookie); rb_erase(&node->rb_node, &proc->nodes); kfree(node); binder_stats_deleted(BINDER_STAT_NODE); } else { binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "%d:%d node %d u%p c%p state unchanged\n", - proc->pid, thread->pid, node->debug_id, node->ptr, - node->cookie); + "%d:%d node %d u%016llx c%016llx state unchanged\n", + proc->pid, thread->pid, node->debug_id, + (u64)node->ptr, (u64)node->cookie); } } } break; @@ -2272,17 +2310,18 @@ static int binder_thread_read(struct binder_proc *proc, if (put_user(cmd, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); - if (put_user(death->cookie, (void * __user *)ptr)) + if (put_user(death->cookie, + (binder_uintptr_t __user *)ptr)) return -EFAULT; - ptr += sizeof(void *); + ptr += sizeof(binder_uintptr_t); binder_stat_br(proc, thread, cmd); binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION, - "%d:%d %s %p\n", + "%d:%d %s %016llx\n", proc->pid, thread->pid, cmd == BR_DEAD_BINDER ? "BR_DEAD_BINDER" : "BR_CLEAR_DEATH_NOTIFICATION_DONE", - death->cookie); + (u64)death->cookie); if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) { list_del(&w->entry); @@ -2312,8 +2351,8 @@ static int binder_thread_read(struct binder_proc *proc, binder_set_nice(target_node->min_priority); cmd = BR_TRANSACTION; } else { - tr.target.ptr = NULL; - tr.cookie = NULL; + tr.target.ptr = 0; + tr.cookie = 0; cmd = BR_REPLY; } tr.code = t->code; @@ -2330,8 +2369,9 @@ static int binder_thread_read(struct binder_proc *proc, tr.data_size = t->buffer->data_size; tr.offsets_size = t->buffer->offsets_size; - tr.data.ptr.buffer = (void *)t->buffer->data + - proc->user_buffer_offset; + tr.data.ptr.buffer = (binder_uintptr_t)( + (uintptr_t)t->buffer->data + + proc->user_buffer_offset); tr.data.ptr.offsets = tr.data.ptr.buffer + ALIGN(t->buffer->data_size, sizeof(void *)); @@ -2346,14 +2386,14 @@ static int binder_thread_read(struct binder_proc *proc, trace_binder_transaction_received(t); binder_stat_br(proc, thread, cmd); binder_debug(BINDER_DEBUG_TRANSACTION, - "%d:%d %s %d %d:%d, cmd %d size %zd-%zd ptr %p-%p\n", + "%d:%d %s %d %d:%d, cmd %d size %zd-%zd ptr %016llx-%016llx\n", proc->pid, thread->pid, (cmd == BR_TRANSACTION) ? "BR_TRANSACTION" : "BR_REPLY", t->debug_id, t->from ? t->from->proc->pid : 0, t->from ? t->from->pid : 0, cmd, t->buffer->data_size, t->buffer->offsets_size, - tr.data.ptr.buffer, tr.data.ptr.offsets); + (u64)tr.data.ptr.buffer, (u64)tr.data.ptr.offsets); list_del(&t->work.entry); t->buffer->allow_user_free = 1; @@ -2423,8 +2463,8 @@ static void binder_release_work(struct list_head *list) death = container_of(w, struct binder_ref_death, work); binder_debug(BINDER_DEBUG_DEAD_TRANSACTION, - "undelivered death notification, %p\n", - death->cookie); + "undelivered death notification, %016llx\n", + (u64)death->cookie); kfree(death); binder_stats_deleted(BINDER_STAT_DEATH); } break; @@ -2580,12 +2620,13 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) goto err; } binder_debug(BINDER_DEBUG_READ_WRITE, - "%d:%d write %zd at %016lx, read %zd at %016lx\n", - proc->pid, thread->pid, bwr.write_size, - bwr.write_buffer, bwr.read_size, bwr.read_buffer); + "%d:%d write %lld at %016llx, read %lld at %016llx\n", + proc->pid, thread->pid, + (u64)bwr.write_size, (u64)bwr.write_buffer, + (u64)bwr.read_size, (u64)bwr.read_buffer); if (bwr.write_size > 0) { - ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed); + ret = binder_thread_write(proc, thread, bwr.write_buffer, bwr.write_size, &bwr.write_consumed); trace_binder_write_done(ret); if (ret < 0) { bwr.read_consumed = 0; @@ -2595,7 +2636,7 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) } } if (bwr.read_size > 0) { - ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK); + ret = binder_thread_read(proc, thread, bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK); trace_binder_read_done(ret); if (!list_empty(&proc->todo)) wake_up_interruptible(&proc->wait); @@ -2606,9 +2647,10 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) } } binder_debug(BINDER_DEBUG_READ_WRITE, - "%d:%d wrote %zd of %zd, read return %zd of %zd\n", - proc->pid, thread->pid, bwr.write_consumed, bwr.write_size, - bwr.read_consumed, bwr.read_size); + "%d:%d wrote %lld of %lld, read return %lld of %lld\n", + proc->pid, thread->pid, + (u64)bwr.write_consumed, (u64)bwr.write_size, + (u64)bwr.read_consumed, (u64)bwr.read_size); if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { ret = -EFAULT; goto err; @@ -2627,6 +2669,9 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ret = -EBUSY; goto err; } + ret = security_binder_set_context_mgr(proc->tsk); + if (ret < 0) + goto err; if (uid_valid(binder_context_mgr_uid)) { if (!uid_eq(binder_context_mgr_uid, current->cred->euid)) { pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n", @@ -2637,7 +2682,7 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) } } else binder_context_mgr_uid = current->cred->euid; - binder_context_mgr_node = binder_new_node(proc, NULL, NULL); + binder_context_mgr_node = binder_new_node(proc, 0, 0); if (binder_context_mgr_node == NULL) { ret = -ENOMEM; goto err; @@ -3132,8 +3177,9 @@ static void print_binder_work(struct seq_file *m, const char *prefix, break; case BINDER_WORK_NODE: node = container_of(w, struct binder_node, work); - seq_printf(m, "%snode work %d: u%p c%p\n", - prefix, node->debug_id, node->ptr, node->cookie); + seq_printf(m, "%snode work %d: u%016llx c%016llx\n", + prefix, node->debug_id, + (u64)node->ptr, (u64)node->cookie); break; case BINDER_WORK_DEAD_BINDER: seq_printf(m, "%shas dead binder\n", prefix); @@ -3193,8 +3239,8 @@ static void print_binder_node(struct seq_file *m, struct binder_node *node) hlist_for_each_entry(ref, &node->refs, node_entry) count++; - seq_printf(m, " node %d: u%p c%p hs %d hw %d ls %d lw %d is %d iw %d", - node->debug_id, node->ptr, node->cookie, + seq_printf(m, " node %d: u%016llx c%016llx hs %d hw %d ls %d lw %d is %d iw %d", + node->debug_id, (u64)node->ptr, (u64)node->cookie, node->has_strong_ref, node->has_weak_ref, node->local_strong_refs, node->local_weak_refs, node->internal_strong_refs, count); @@ -3496,6 +3542,7 @@ static const struct file_operations binder_fops = { .owner = THIS_MODULE, .poll = binder_poll, .unlocked_ioctl = binder_ioctl, + .compat_ioctl = binder_ioctl, .mmap = binder_mmap, .open = binder_open, .flush = binder_flush, diff --git a/drivers/staging/android/binder.h b/drivers/staging/android/binder.h index cbe34516806717..eb0834656dfe2f 100644 --- a/drivers/staging/android/binder.h +++ b/drivers/staging/android/binder.h @@ -20,311 +20,11 @@ #ifndef _LINUX_BINDER_H #define _LINUX_BINDER_H -#include +#ifdef CONFIG_ANDROID_BINDER_IPC_32BIT +#define BINDER_IPC_32BIT 1 +#endif -#define B_PACK_CHARS(c1, c2, c3, c4) \ - ((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4)) -#define B_TYPE_LARGE 0x85 - -enum { - BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE), - BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE), - BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE), - BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE), - BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE), -}; - -enum { - FLAT_BINDER_FLAG_PRIORITY_MASK = 0xff, - FLAT_BINDER_FLAG_ACCEPTS_FDS = 0x100, -}; - -/* - * This is the flattened representation of a Binder object for transfer - * between processes. The 'offsets' supplied as part of a binder transaction - * contains offsets into the data where these structures occur. The Binder - * driver takes care of re-writing the structure type and data as it moves - * between processes. - */ -struct flat_binder_object { - /* 8 bytes for large_flat_header. */ - __u32 type; - __u32 flags; - - /* 8 bytes of data. */ - union { - void __user *binder; /* local object */ - __u32 handle; /* remote object */ - }; - - /* extra data associated with local object */ - void __user *cookie; -}; - -/* - * On 64-bit platforms where user code may run in 32-bits the driver must - * translate the buffer (and local binder) addresses appropriately. - */ - -struct binder_write_read { - size_t write_size; /* bytes to write */ - size_t write_consumed; /* bytes consumed by driver */ - unsigned long write_buffer; - size_t read_size; /* bytes to read */ - size_t read_consumed; /* bytes consumed by driver */ - unsigned long read_buffer; -}; - -/* Use with BINDER_VERSION, driver fills in fields. */ -struct binder_version { - /* driver protocol version -- increment with incompatible change */ - __s32 protocol_version; -}; - -/* This is the current protocol version. */ -#define BINDER_CURRENT_PROTOCOL_VERSION 7 - -#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read) -#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, __s64) -#define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32) -#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, __s32) -#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, __s32) -#define BINDER_THREAD_EXIT _IOW('b', 8, __s32) -#define BINDER_VERSION _IOWR('b', 9, struct binder_version) - -/* - * NOTE: Two special error codes you should check for when calling - * in to the driver are: - * - * EINTR -- The operation has been interupted. This should be - * handled by retrying the ioctl() until a different error code - * is returned. - * - * ECONNREFUSED -- The driver is no longer accepting operations - * from your process. That is, the process is being destroyed. - * You should handle this by exiting from your process. Note - * that once this error code is returned, all further calls to - * the driver from any thread will return this same code. - */ - -enum transaction_flags { - TF_ONE_WAY = 0x01, /* this is a one-way call: async, no return */ - TF_ROOT_OBJECT = 0x04, /* contents are the component's root object */ - TF_STATUS_CODE = 0x08, /* contents are a 32-bit status code */ - TF_ACCEPT_FDS = 0x10, /* allow replies with file descriptors */ -}; - -struct binder_transaction_data { - /* The first two are only used for bcTRANSACTION and brTRANSACTION, - * identifying the target and contents of the transaction. - */ - union { - __u32 handle; /* target descriptor of command transaction */ - void *ptr; /* target descriptor of return transaction */ - } target; - void *cookie; /* target object cookie */ - __u32 code; /* transaction command */ - - /* General information about the transaction. */ - __u32 flags; - pid_t sender_pid; - uid_t sender_euid; - size_t data_size; /* number of bytes of data */ - size_t offsets_size; /* number of bytes of offsets */ - - /* If this transaction is inline, the data immediately - * follows here; otherwise, it ends with a pointer to - * the data buffer. - */ - union { - struct { - /* transaction data */ - const void __user *buffer; - /* offsets from buffer to flat_binder_object structs */ - const void __user *offsets; - } ptr; - __u8 buf[8]; - } data; -}; - -struct binder_ptr_cookie { - void *ptr; - void *cookie; -}; - -struct binder_pri_desc { - __s32 priority; - __u32 desc; -}; - -struct binder_pri_ptr_cookie { - __s32 priority; - void *ptr; - void *cookie; -}; - -enum binder_driver_return_protocol { - BR_ERROR = _IOR('r', 0, __s32), - /* - * int: error code - */ - - BR_OK = _IO('r', 1), - /* No parameters! */ - - BR_TRANSACTION = _IOR('r', 2, struct binder_transaction_data), - BR_REPLY = _IOR('r', 3, struct binder_transaction_data), - /* - * binder_transaction_data: the received command. - */ - - BR_ACQUIRE_RESULT = _IOR('r', 4, __s32), - /* - * not currently supported - * int: 0 if the last bcATTEMPT_ACQUIRE was not successful. - * Else the remote object has acquired a primary reference. - */ - - BR_DEAD_REPLY = _IO('r', 5), - /* - * The target of the last transaction (either a bcTRANSACTION or - * a bcATTEMPT_ACQUIRE) is no longer with us. No parameters. - */ - - BR_TRANSACTION_COMPLETE = _IO('r', 6), - /* - * No parameters... always refers to the last transaction requested - * (including replies). Note that this will be sent even for - * asynchronous transactions. - */ - - BR_INCREFS = _IOR('r', 7, struct binder_ptr_cookie), - BR_ACQUIRE = _IOR('r', 8, struct binder_ptr_cookie), - BR_RELEASE = _IOR('r', 9, struct binder_ptr_cookie), - BR_DECREFS = _IOR('r', 10, struct binder_ptr_cookie), - /* - * void *: ptr to binder - * void *: cookie for binder - */ - - BR_ATTEMPT_ACQUIRE = _IOR('r', 11, struct binder_pri_ptr_cookie), - /* - * not currently supported - * int: priority - * void *: ptr to binder - * void *: cookie for binder - */ - - BR_NOOP = _IO('r', 12), - /* - * No parameters. Do nothing and examine the next command. It exists - * primarily so that we can replace it with a BR_SPAWN_LOOPER command. - */ - - BR_SPAWN_LOOPER = _IO('r', 13), - /* - * No parameters. The driver has determined that a process has no - * threads waiting to service incoming transactions. When a process - * receives this command, it must spawn a new service thread and - * register it via bcENTER_LOOPER. - */ - - BR_FINISHED = _IO('r', 14), - /* - * not currently supported - * stop threadpool thread - */ - - BR_DEAD_BINDER = _IOR('r', 15, void *), - /* - * void *: cookie - */ - BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, void *), - /* - * void *: cookie - */ - - BR_FAILED_REPLY = _IO('r', 17), - /* - * The the last transaction (either a bcTRANSACTION or - * a bcATTEMPT_ACQUIRE) failed (e.g. out of memory). No parameters. - */ -}; - -enum binder_driver_command_protocol { - BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data), - BC_REPLY = _IOW('c', 1, struct binder_transaction_data), - /* - * binder_transaction_data: the sent command. - */ - - BC_ACQUIRE_RESULT = _IOW('c', 2, __s32), - /* - * not currently supported - * int: 0 if the last BR_ATTEMPT_ACQUIRE was not successful. - * Else you have acquired a primary reference on the object. - */ - - BC_FREE_BUFFER = _IOW('c', 3, void *), - /* - * void *: ptr to transaction data received on a read - */ - - BC_INCREFS = _IOW('c', 4, __u32), - BC_ACQUIRE = _IOW('c', 5, __u32), - BC_RELEASE = _IOW('c', 6, __u32), - BC_DECREFS = _IOW('c', 7, __u32), - /* - * int: descriptor - */ - - BC_INCREFS_DONE = _IOW('c', 8, struct binder_ptr_cookie), - BC_ACQUIRE_DONE = _IOW('c', 9, struct binder_ptr_cookie), - /* - * void *: ptr to binder - * void *: cookie for binder - */ - - BC_ATTEMPT_ACQUIRE = _IOW('c', 10, struct binder_pri_desc), - /* - * not currently supported - * int: priority - * int: descriptor - */ - - BC_REGISTER_LOOPER = _IO('c', 11), - /* - * No parameters. - * Register a spawned looper thread with the device. - */ - - BC_ENTER_LOOPER = _IO('c', 12), - BC_EXIT_LOOPER = _IO('c', 13), - /* - * No parameters. - * These two commands are sent as an application-level thread - * enters and exits the binder loop, respectively. They are - * used so the binder can have an accurate count of the number - * of looping threads it has available. - */ - - BC_REQUEST_DEATH_NOTIFICATION = _IOW('c', 14, struct binder_ptr_cookie), - /* - * void *: ptr to binder - * void *: cookie - */ - - BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15, struct binder_ptr_cookie), - /* - * void *: ptr to binder - * void *: cookie - */ - - BC_DEAD_BINDER_DONE = _IOW('c', 16, void *), - /* - * void *: cookie - */ -}; +#include "uapi/binder.h" #endif /* _LINUX_BINDER_H */ diff --git a/drivers/staging/android/binder_trace.h b/drivers/staging/android/binder_trace.h index 82a567c2af6797..02add80b62a0f7 100644 --- a/drivers/staging/android/binder_trace.h +++ b/drivers/staging/android/binder_trace.h @@ -152,19 +152,20 @@ TRACE_EVENT(binder_transaction_node_to_ref, TP_STRUCT__entry( __field(int, debug_id) __field(int, node_debug_id) - __field(void __user *, node_ptr) + __field(binder_uintptr_t, node_ptr) __field(int, ref_debug_id) __field(uint32_t, ref_desc) ), TP_fast_assign( __entry->debug_id = t->debug_id; __entry->node_debug_id = node->debug_id; - __entry->node_ptr = node->ptr; + __entry->node_ptr = (void __user *)node->ptr; __entry->ref_debug_id = ref->debug_id; __entry->ref_desc = ref->desc; ), - TP_printk("transaction=%d node=%d src_ptr=0x%p ==> dest_ref=%d dest_desc=%d", - __entry->debug_id, __entry->node_debug_id, __entry->node_ptr, + TP_printk("transaction=%d node=%d src_ptr=0x%016llx ==> dest_ref=%d dest_desc=%d", + __entry->debug_id, __entry->node_debug_id, + (u64)__entry->node_ptr, __entry->ref_debug_id, __entry->ref_desc) ); @@ -177,18 +178,19 @@ TRACE_EVENT(binder_transaction_ref_to_node, __field(int, ref_debug_id) __field(uint32_t, ref_desc) __field(int, node_debug_id) - __field(void __user *, node_ptr) + __field(binder_uintptr_t, node_ptr) ), TP_fast_assign( __entry->debug_id = t->debug_id; __entry->ref_debug_id = ref->debug_id; __entry->ref_desc = ref->desc; __entry->node_debug_id = ref->node->debug_id; - __entry->node_ptr = ref->node->ptr; + __entry->node_ptr = (void __user *)ref->node->ptr; ), - TP_printk("transaction=%d node=%d src_ref=%d src_desc=%d ==> dest_ptr=0x%p", + TP_printk("transaction=%d node=%d src_ref=%d src_desc=%d ==> dest_ptr=0x%016llx", __entry->debug_id, __entry->node_debug_id, - __entry->ref_debug_id, __entry->ref_desc, __entry->node_ptr) + __entry->ref_debug_id, __entry->ref_desc, + (u64)__entry->node_ptr) ); TRACE_EVENT(binder_transaction_ref_to_ref, diff --git a/drivers/staging/android/ion/ion.c b/drivers/staging/android/ion/ion.c index 574066ff73f80d..1ecbf317f3fc0f 100644 --- a/drivers/staging/android/ion/ion.c +++ b/drivers/staging/android/ion/ion.c @@ -59,6 +59,8 @@ struct ion_device { unsigned long arg); struct rb_root clients; struct dentry *debug_root; + struct dentry *heaps_debug_root; + struct dentry *clients_debug_root; }; /** @@ -69,6 +71,8 @@ struct ion_device { * @idr: an idr space for allocating handle ids * @lock: lock protecting the tree of handles * @name: used for debugging + * @display_name: used for debugging (unique version of @name) + * @display_serial: used for debugging (to make display_name unique) * @task: used for debugging * * A client represents a list of buffers this client may access. @@ -82,6 +86,8 @@ struct ion_client { struct idr idr; struct mutex lock; const char *name; + char *display_name; + int display_serial; struct task_struct *task; pid_t pid; struct dentry *debug_root; @@ -708,6 +714,21 @@ static const struct file_operations debug_client_fops = { .release = single_release, }; +static int ion_get_client_serial(const struct rb_root *root, + const unsigned char *name) +{ + int serial = -1; + struct rb_node *node; + for (node = rb_first(root); node; node = rb_next(node)) { + struct ion_client *client = rb_entry(node, struct ion_client, + node); + if (strcmp(client->name, name)) + continue; + serial = max(serial, client->display_serial); + } + return serial + 1; +} + struct ion_client *ion_client_create(struct ion_device *dev, const char *name) { @@ -716,9 +737,13 @@ struct ion_client *ion_client_create(struct ion_device *dev, struct rb_node **p; struct rb_node *parent = NULL; struct ion_client *entry; - char debug_name[64]; pid_t pid; + if (!name) { + pr_err("%s: Name cannot be null\n", __func__); + return ERR_PTR(-EINVAL); + } + get_task_struct(current->group_leader); task_lock(current->group_leader); pid = task_pid_nr(current->group_leader); @@ -733,21 +758,27 @@ struct ion_client *ion_client_create(struct ion_device *dev, task_unlock(current->group_leader); client = kzalloc(sizeof(struct ion_client), GFP_KERNEL); - if (!client) { - if (task) - put_task_struct(current->group_leader); - return ERR_PTR(-ENOMEM); - } + if (!client) + goto err_put_task_struct; client->dev = dev; client->handles = RB_ROOT; idr_init(&client->idr); mutex_init(&client->lock); - client->name = name; client->task = task; client->pid = pid; + client->name = kstrdup(name, GFP_KERNEL); + if (!client->name) + goto err_free_client; down_write(&dev->lock); + client->display_serial = ion_get_client_serial(&dev->clients, name); + client->display_name = kasprintf( + GFP_KERNEL, "%s-%d", name, client->display_serial); + if (!client->display_name) { + up_write(&dev->lock); + goto err_free_client_name; + } p = &dev->clients.rb_node; while (*p) { parent = *p; @@ -761,13 +792,28 @@ struct ion_client *ion_client_create(struct ion_device *dev, rb_link_node(&client->node, parent, p); rb_insert_color(&client->node, &dev->clients); - snprintf(debug_name, 64, "%u", client->pid); - client->debug_root = debugfs_create_file(debug_name, 0664, - dev->debug_root, client, - &debug_client_fops); + client->debug_root = debugfs_create_file(client->display_name, 0664, + dev->clients_debug_root, + client, &debug_client_fops); + if (!client->debug_root) { + char buf[256], *path; + path = dentry_path(dev->clients_debug_root, buf, 256); + pr_err("Failed to create client debugfs at %s/%s\n", + path, client->display_name); + } + up_write(&dev->lock); return client; + +err_free_client_name: + kfree(client->name); +err_free_client: + kfree(client); +err_put_task_struct: + if (task) + put_task_struct(current->group_leader); + return ERR_PTR(-ENOMEM); } EXPORT_SYMBOL(ion_client_create); @@ -792,6 +838,8 @@ void ion_client_destroy(struct ion_client *client) debugfs_remove_recursive(client->debug_root); up_write(&dev->lock); + kfree(client->display_name); + kfree(client->name); kfree(client); } EXPORT_SYMBOL(ion_client_destroy); @@ -1293,9 +1341,11 @@ static int ion_open(struct inode *inode, struct file *file) struct miscdevice *miscdev = file->private_data; struct ion_device *dev = container_of(miscdev, struct ion_device, dev); struct ion_client *client; + char debug_name[64]; pr_debug("%s: %d\n", __func__, __LINE__); - client = ion_client_create(dev, "user"); + snprintf(debug_name, 64, "%u", task_pid_nr(current->group_leader)); + client = ion_client_create(dev, debug_name); if (IS_ERR(client)) return PTR_ERR(client); file->private_data = client; @@ -1443,6 +1493,8 @@ DEFINE_SIMPLE_ATTRIBUTE(debug_shrink_fops, debug_shrink_get, void ion_device_add_heap(struct ion_device *dev, struct ion_heap *heap) { + struct dentry *debug_file; + if (!heap->ops->allocate || !heap->ops->free || !heap->ops->map_dma || !heap->ops->unmap_dma) pr_err("%s: can not add heap with invalid ops struct.\n", @@ -1457,15 +1509,31 @@ void ion_device_add_heap(struct ion_device *dev, struct ion_heap *heap) the list later attempt higher id numbers first */ plist_node_init(&heap->node, -heap->id); plist_add(&heap->node, &dev->heaps); - debugfs_create_file(heap->name, 0664, dev->debug_root, heap, - &debug_heap_fops); + debug_file = debugfs_create_file(heap->name, 0664, + dev->heaps_debug_root, heap, + &debug_heap_fops); + + if (!debug_file) { + char buf[256], *path; + path = dentry_path(dev->heaps_debug_root, buf, 256); + pr_err("Failed to create heap debugfs at %s/%s\n", + path, heap->name); + } + #ifdef DEBUG_HEAP_SHRINKER if (heap->shrinker.shrink) { char debug_name[64]; snprintf(debug_name, 64, "%s_shrink", heap->name); - debugfs_create_file(debug_name, 0644, dev->debug_root, heap, - &debug_shrink_fops); + debug_file = debugfs_create_file( + debug_name, 0644, dev->heaps_debug_root, heap, + &debug_shrink_fops); + if (!debug_file) { + char buf[256], *path; + path = dentry_path(dev->heaps_debug_root, buf, 256); + pr_err("Failed to create heap shrinker debugfs at %s/%s\n", + path, debug_name); + } } #endif up_write(&dev->lock); @@ -1494,8 +1562,21 @@ struct ion_device *ion_device_create(long (*custom_ioctl) } idev->debug_root = debugfs_create_dir("ion", NULL); - if (!idev->debug_root) - pr_err("ion: failed to create debug files.\n"); + if (!idev->debug_root) { + pr_err("ion: failed to create debugfs root directory.\n"); + goto debugfs_done; + } + idev->heaps_debug_root = debugfs_create_dir("heaps", idev->debug_root); + if (!idev->heaps_debug_root) { + pr_err("ion: failed to create debugfs heaps directory.\n"); + goto debugfs_done; + } + idev->clients_debug_root = debugfs_create_dir("clients", + idev->debug_root); + if (!idev->clients_debug_root) + pr_err("ion: failed to create debugfs clients directory.\n"); + +debugfs_done: idev->custom_ioctl = custom_ioctl; idev->buffers = RB_ROOT; @@ -1509,6 +1590,7 @@ struct ion_device *ion_device_create(long (*custom_ioctl) void ion_device_destroy(struct ion_device *dev) { misc_deregister(&dev->dev); + debugfs_remove_recursive(dev->debug_root); /* XXX need to free the heaps and clients ? */ kfree(dev); } diff --git a/drivers/staging/android/ion/ion_priv.h b/drivers/staging/android/ion/ion_priv.h index fc2e4fccf69d21..ad1150d3bb1967 100644 --- a/drivers/staging/android/ion/ion_priv.h +++ b/drivers/staging/android/ion/ion_priv.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "ion.h" diff --git a/drivers/staging/android/lowmemorykiller.c b/drivers/staging/android/lowmemorykiller.c index 6f094b37f1f1dc..89fed36b4bfbf4 100644 --- a/drivers/staging/android/lowmemorykiller.c +++ b/drivers/staging/android/lowmemorykiller.c @@ -39,7 +39,6 @@ #include #include #include -#include #include static uint32_t lowmem_debug_level = 1; @@ -83,6 +82,7 @@ static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc) int tasksize; int i; short min_score_adj = OOM_SCORE_ADJ_MAX + 1; + int minfree = 0; int selected_tasksize = 0; short selected_oom_score_adj; int array_size = ARRAY_SIZE(lowmem_adj); @@ -95,8 +95,8 @@ static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc) if (lowmem_minfree_size < array_size) array_size = lowmem_minfree_size; for (i = 0; i < array_size; i++) { - if (other_free < lowmem_minfree[i] && - other_file < lowmem_minfree[i]) { + minfree = lowmem_minfree[i]; + if (other_free < minfree && other_file < minfree) { min_score_adj = lowmem_adj[i]; break; } @@ -151,13 +151,22 @@ static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc) selected = p; selected_tasksize = tasksize; selected_oom_score_adj = oom_score_adj; - lowmem_print(2, "select %d (%s), adj %hd, size %d, to kill\n", - p->pid, p->comm, oom_score_adj, tasksize); + lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n", + p->comm, p->pid, oom_score_adj, tasksize); } if (selected) { - lowmem_print(1, "send sigkill to %d (%s), adj %hd, size %d\n", - selected->pid, selected->comm, - selected_oom_score_adj, selected_tasksize); + lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \ + " to free %ldkB on behalf of '%s' (%d) because\n" \ + " cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \ + " Free memory is %ldkB above reserved\n", + selected->comm, selected->pid, + selected_oom_score_adj, + selected_tasksize * (long)(PAGE_SIZE / 1024), + current->comm, current->pid, + other_file * (long)(PAGE_SIZE / 1024), + minfree * (long)(PAGE_SIZE / 1024), + min_score_adj, + other_free * (long)(PAGE_SIZE / 1024)); lowmem_deathpending_timeout = jiffies + HZ; send_sig(SIGKILL, selected, 0); set_tsk_thread_flag(selected, TIF_MEMDIE); @@ -187,9 +196,94 @@ static void __exit lowmem_exit(void) unregister_shrinker(&lowmem_shrinker); } +#ifdef CONFIG_ANDROID_LOW_MEMORY_KILLER_AUTODETECT_OOM_ADJ_VALUES +static short lowmem_oom_adj_to_oom_score_adj(short oom_adj) +{ + if (oom_adj == OOM_ADJUST_MAX) + return OOM_SCORE_ADJ_MAX; + else + return (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE; +} + +static void lowmem_autodetect_oom_adj_values(void) +{ + int i; + short oom_adj; + short oom_score_adj; + int array_size = ARRAY_SIZE(lowmem_adj); + + if (lowmem_adj_size < array_size) + array_size = lowmem_adj_size; + + if (array_size <= 0) + return; + + oom_adj = lowmem_adj[array_size - 1]; + if (oom_adj > OOM_ADJUST_MAX) + return; + + oom_score_adj = lowmem_oom_adj_to_oom_score_adj(oom_adj); + if (oom_score_adj <= OOM_ADJUST_MAX) + return; + + lowmem_print(1, "lowmem_shrink: convert oom_adj to oom_score_adj:\n"); + for (i = 0; i < array_size; i++) { + oom_adj = lowmem_adj[i]; + oom_score_adj = lowmem_oom_adj_to_oom_score_adj(oom_adj); + lowmem_adj[i] = oom_score_adj; + lowmem_print(1, "oom_adj %d => oom_score_adj %d\n", + oom_adj, oom_score_adj); + } +} + +static int lowmem_adj_array_set(const char *val, const struct kernel_param *kp) +{ + int ret; + + ret = param_array_ops.set(val, kp); + + /* HACK: Autodetect oom_adj values in lowmem_adj array */ + lowmem_autodetect_oom_adj_values(); + + return ret; +} + +static int lowmem_adj_array_get(char *buffer, const struct kernel_param *kp) +{ + return param_array_ops.get(buffer, kp); +} + +static void lowmem_adj_array_free(void *arg) +{ + param_array_ops.free(arg); +} + +static struct kernel_param_ops lowmem_adj_array_ops = { + .set = lowmem_adj_array_set, + .get = lowmem_adj_array_get, + .free = lowmem_adj_array_free, +}; + +static const struct kparam_array __param_arr_adj = { + .max = ARRAY_SIZE(lowmem_adj), + .num = &lowmem_adj_size, + .ops = ¶m_ops_short, + .elemsize = sizeof(lowmem_adj[0]), + .elem = lowmem_adj, +}; +#endif + module_param_named(cost, lowmem_shrinker.seeks, int, S_IRUGO | S_IWUSR); +#ifdef CONFIG_ANDROID_LOW_MEMORY_KILLER_AUTODETECT_OOM_ADJ_VALUES +__module_param_call(MODULE_PARAM_PREFIX, adj, + &lowmem_adj_array_ops, + .arr = &__param_arr_adj, + S_IRUGO | S_IWUSR, -1); +__MODULE_PARM_TYPE(adj, "array of short"); +#else module_param_array_named(adj, lowmem_adj, short, &lowmem_adj_size, S_IRUGO | S_IWUSR); +#endif module_param_array_named(minfree, lowmem_minfree, uint, &lowmem_minfree_size, S_IRUGO | S_IWUSR); module_param_named(debug_level, lowmem_debug_level, uint, S_IRUGO | S_IWUSR); diff --git a/drivers/staging/android/sw_sync.h b/drivers/staging/android/sw_sync.h index 5aaf71d6974b16..1a50669ec8a945 100644 --- a/drivers/staging/android/sw_sync.h +++ b/drivers/staging/android/sw_sync.h @@ -18,10 +18,9 @@ #define _LINUX_SW_SYNC_H #include - -#ifdef __KERNEL__ - +#include #include "sync.h" +#include "uapi/sw_sync.h" struct sw_sync_timeline { struct sync_timeline obj; @@ -57,19 +56,4 @@ static inline struct sync_pt *sw_sync_pt_create(struct sw_sync_timeline *obj, } #endif /* IS_ENABLED(CONFIG_SW_SYNC) */ -#endif /* __KERNEL __ */ - -struct sw_sync_create_fence_data { - __u32 value; - char name[32]; - __s32 fence; /* fd of new fence */ -}; - -#define SW_SYNC_IOC_MAGIC 'W' - -#define SW_SYNC_IOC_CREATE_FENCE _IOWR(SW_SYNC_IOC_MAGIC, 0,\ - struct sw_sync_create_fence_data) -#define SW_SYNC_IOC_INC _IOW(SW_SYNC_IOC_MAGIC, 1, __u32) - - #endif /* _LINUX_SW_SYNC_H */ diff --git a/drivers/staging/android/sync.h b/drivers/staging/android/sync.h index 62e2255b1c1e2c..e0701de8312aa0 100644 --- a/drivers/staging/android/sync.h +++ b/drivers/staging/android/sync.h @@ -14,14 +14,14 @@ #define _LINUX_SYNC_H #include -#ifdef __KERNEL__ - #include #include #include #include #include +#include "uapi/sync.h" + struct sync_timeline; struct sync_pt; struct sync_fence; @@ -341,86 +341,4 @@ int sync_fence_cancel_async(struct sync_fence *fence, */ int sync_fence_wait(struct sync_fence *fence, long timeout); -#endif /* __KERNEL__ */ - -/** - * struct sync_merge_data - data passed to merge ioctl - * @fd2: file descriptor of second fence - * @name: name of new fence - * @fence: returns the fd of the new fence to userspace - */ -struct sync_merge_data { - __s32 fd2; /* fd of second fence */ - char name[32]; /* name of new fence */ - __s32 fence; /* fd on newly created fence */ -}; - -/** - * struct sync_pt_info - detailed sync_pt information - * @len: length of sync_pt_info including any driver_data - * @obj_name: name of parent sync_timeline - * @driver_name: name of driver implementing the parent - * @status: status of the sync_pt 0:active 1:signaled <0:error - * @timestamp_ns: timestamp of status change in nanoseconds - * @driver_data: any driver dependent data - */ -struct sync_pt_info { - __u32 len; - char obj_name[32]; - char driver_name[32]; - __s32 status; - __u64 timestamp_ns; - - __u8 driver_data[0]; -}; - -/** - * struct sync_fence_info_data - data returned from fence info ioctl - * @len: ioctl caller writes the size of the buffer its passing in. - * ioctl returns length of sync_fence_data returned to userspace - * including pt_info. - * @name: name of fence - * @status: status of fence. 1: signaled 0:active <0:error - * @pt_info: a sync_pt_info struct for every sync_pt in the fence - */ -struct sync_fence_info_data { - __u32 len; - char name[32]; - __s32 status; - - __u8 pt_info[0]; -}; - -#define SYNC_IOC_MAGIC '>' - -/** - * DOC: SYNC_IOC_WAIT - wait for a fence to signal - * - * pass timeout in milliseconds. Waits indefinitely timeout < 0. - */ -#define SYNC_IOC_WAIT _IOW(SYNC_IOC_MAGIC, 0, __s32) - -/** - * DOC: SYNC_IOC_MERGE - merge two fences - * - * Takes a struct sync_merge_data. Creates a new fence containing copies of - * the sync_pts in both the calling fd and sync_merge_data.fd2. Returns the - * new fence's fd in sync_merge_data.fence - */ -#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 1, struct sync_merge_data) - -/** - * DOC: SYNC_IOC_FENCE_INFO - get detailed information on a fence - * - * Takes a struct sync_fence_info_data with extra space allocated for pt_info. - * Caller should write the size of the buffer into len. On return, len is - * updated to reflect the total size of the sync_fence_info_data including - * pt_info. - * - * pt_info is a buffer containing sync_pt_infos for every sync_pt in the fence. - * To iterate over the sync_pt_infos, use the sync_pt_info.len field. - */ -#define SYNC_IOC_FENCE_INFO _IOWR(SYNC_IOC_MAGIC, 2,\ - struct sync_fence_info_data) - #endif /* _LINUX_SYNC_H */ diff --git a/drivers/staging/android/uapi/android_alarm.h b/drivers/staging/android/uapi/android_alarm.h new file mode 100644 index 00000000000000..aa013f6f5f3a29 --- /dev/null +++ b/drivers/staging/android/uapi/android_alarm.h @@ -0,0 +1,62 @@ +/* drivers/staging/android/uapi/android_alarm.h + * + * Copyright (C) 2006-2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef _UAPI_LINUX_ANDROID_ALARM_H +#define _UAPI_LINUX_ANDROID_ALARM_H + +#include +#include + +enum android_alarm_type { + /* return code bit numbers or set alarm arg */ + ANDROID_ALARM_RTC_WAKEUP, + ANDROID_ALARM_RTC, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + ANDROID_ALARM_ELAPSED_REALTIME, + ANDROID_ALARM_SYSTEMTIME, + + ANDROID_ALARM_TYPE_COUNT, + + /* return code bit numbers */ + /* ANDROID_ALARM_TIME_CHANGE = 16 */ +}; + +enum android_alarm_return_flags { + ANDROID_ALARM_RTC_WAKEUP_MASK = 1U << ANDROID_ALARM_RTC_WAKEUP, + ANDROID_ALARM_RTC_MASK = 1U << ANDROID_ALARM_RTC, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK = + 1U << ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + ANDROID_ALARM_ELAPSED_REALTIME_MASK = + 1U << ANDROID_ALARM_ELAPSED_REALTIME, + ANDROID_ALARM_SYSTEMTIME_MASK = 1U << ANDROID_ALARM_SYSTEMTIME, + ANDROID_ALARM_TIME_CHANGE_MASK = 1U << 16 +}; + +/* Disable alarm */ +#define ANDROID_ALARM_CLEAR(type) _IO('a', 0 | ((type) << 4)) + +/* Ack last alarm and wait for next */ +#define ANDROID_ALARM_WAIT _IO('a', 1) + +#define ALARM_IOW(c, type, size) _IOW('a', (c) | ((type) << 4), size) +/* Set alarm */ +#define ANDROID_ALARM_SET(type) ALARM_IOW(2, type, struct timespec) +#define ANDROID_ALARM_SET_AND_WAIT(type) ALARM_IOW(3, type, struct timespec) +#define ANDROID_ALARM_GET_TIME(type) ALARM_IOW(4, type, struct timespec) +#define ANDROID_ALARM_SET_RTC _IOW('a', 5, struct timespec) +#define ANDROID_ALARM_BASE_CMD(cmd) (cmd & ~(_IOC(0, 0, 0xf0, 0))) +#define ANDROID_ALARM_IOCTL_TO_TYPE(cmd) (_IOC_NR(cmd) >> 4) + +#endif diff --git a/drivers/staging/android/uapi/ashmem.h b/drivers/staging/android/uapi/ashmem.h new file mode 100644 index 00000000000000..ba4743c71d6b45 --- /dev/null +++ b/drivers/staging/android/uapi/ashmem.h @@ -0,0 +1,47 @@ +/* + * drivers/staging/android/uapi/ashmem.h + * + * Copyright 2008 Google Inc. + * Author: Robert Love + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#ifndef _UAPI_LINUX_ASHMEM_H +#define _UAPI_LINUX_ASHMEM_H + +#include + +#define ASHMEM_NAME_LEN 256 + +#define ASHMEM_NAME_DEF "dev/ashmem" + +/* Return values from ASHMEM_PIN: Was the mapping purged while unpinned? */ +#define ASHMEM_NOT_PURGED 0 +#define ASHMEM_WAS_PURGED 1 + +/* Return values from ASHMEM_GET_PIN_STATUS: Is the mapping pinned? */ +#define ASHMEM_IS_UNPINNED 0 +#define ASHMEM_IS_PINNED 1 + +struct ashmem_pin { + __u32 offset; /* offset into region, in bytes, page-aligned */ + __u32 len; /* length forward from offset, in bytes, page-aligned */ +}; + +#define __ASHMEMIOC 0x77 + +#define ASHMEM_SET_NAME _IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN]) +#define ASHMEM_GET_NAME _IOR(__ASHMEMIOC, 2, char[ASHMEM_NAME_LEN]) +#define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t) +#define ASHMEM_GET_SIZE _IO(__ASHMEMIOC, 4) +#define ASHMEM_SET_PROT_MASK _IOW(__ASHMEMIOC, 5, unsigned long) +#define ASHMEM_GET_PROT_MASK _IO(__ASHMEMIOC, 6) +#define ASHMEM_PIN _IOW(__ASHMEMIOC, 7, struct ashmem_pin) +#define ASHMEM_UNPIN _IOW(__ASHMEMIOC, 8, struct ashmem_pin) +#define ASHMEM_GET_PIN_STATUS _IO(__ASHMEMIOC, 9) +#define ASHMEM_PURGE_ALL_CACHES _IO(__ASHMEMIOC, 10) + +#endif /* _UAPI_LINUX_ASHMEM_H */ diff --git a/drivers/staging/android/uapi/binder.h b/drivers/staging/android/uapi/binder.h new file mode 100644 index 00000000000000..04c735d7274491 --- /dev/null +++ b/drivers/staging/android/uapi/binder.h @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2008 Google, Inc. + * + * Based on, but no longer compatible with, the original + * OpenBinder.org binder driver interface, which is: + * + * Copyright (c) 2005 Palmsource, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef _UAPI_LINUX_BINDER_H +#define _UAPI_LINUX_BINDER_H + +#include + +#define B_PACK_CHARS(c1, c2, c3, c4) \ + ((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4)) +#define B_TYPE_LARGE 0x85 + +enum { + BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE), + BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE), + BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE), + BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE), + BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE), +}; + +enum { + FLAT_BINDER_FLAG_PRIORITY_MASK = 0xff, + FLAT_BINDER_FLAG_ACCEPTS_FDS = 0x100, +}; + +#ifdef BINDER_IPC_32BIT +typedef __u32 binder_size_t; +typedef __u32 binder_uintptr_t; +#else +typedef __u64 binder_size_t; +typedef __u64 binder_uintptr_t; +#endif + +/* + * This is the flattened representation of a Binder object for transfer + * between processes. The 'offsets' supplied as part of a binder transaction + * contains offsets into the data where these structures occur. The Binder + * driver takes care of re-writing the structure type and data as it moves + * between processes. + */ +struct flat_binder_object { + /* 8 bytes for large_flat_header. */ + __u32 type; + __u32 flags; + + /* 8 bytes of data. */ + union { + binder_uintptr_t binder; /* local object */ + __u32 handle; /* remote object */ + }; + + /* extra data associated with local object */ + binder_uintptr_t cookie; +}; + +/* + * On 64-bit platforms where user code may run in 32-bits the driver must + * translate the buffer (and local binder) addresses appropriately. + */ + +struct binder_write_read { + binder_size_t write_size; /* bytes to write */ + binder_size_t write_consumed; /* bytes consumed by driver */ + binder_uintptr_t write_buffer; + binder_size_t read_size; /* bytes to read */ + binder_size_t read_consumed; /* bytes consumed by driver */ + binder_uintptr_t read_buffer; +}; + +/* Use with BINDER_VERSION, driver fills in fields. */ +struct binder_version { + /* driver protocol version -- increment with incompatible change */ + __s32 protocol_version; +}; + +/* This is the current protocol version. */ +#ifdef BINDER_IPC_32BIT +#define BINDER_CURRENT_PROTOCOL_VERSION 7 +#else +#define BINDER_CURRENT_PROTOCOL_VERSION 8 +#endif + +#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read) +#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, __s64) +#define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32) +#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, __s32) +#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, __s32) +#define BINDER_THREAD_EXIT _IOW('b', 8, __s32) +#define BINDER_VERSION _IOWR('b', 9, struct binder_version) + +/* + * NOTE: Two special error codes you should check for when calling + * in to the driver are: + * + * EINTR -- The operation has been interupted. This should be + * handled by retrying the ioctl() until a different error code + * is returned. + * + * ECONNREFUSED -- The driver is no longer accepting operations + * from your process. That is, the process is being destroyed. + * You should handle this by exiting from your process. Note + * that once this error code is returned, all further calls to + * the driver from any thread will return this same code. + */ + +enum transaction_flags { + TF_ONE_WAY = 0x01, /* this is a one-way call: async, no return */ + TF_ROOT_OBJECT = 0x04, /* contents are the component's root object */ + TF_STATUS_CODE = 0x08, /* contents are a 32-bit status code */ + TF_ACCEPT_FDS = 0x10, /* allow replies with file descriptors */ +}; + +struct binder_transaction_data { + /* The first two are only used for bcTRANSACTION and brTRANSACTION, + * identifying the target and contents of the transaction. + */ + union { + __u32 handle; /* target descriptor of command transaction */ + binder_uintptr_t ptr; /* target descriptor of return transaction */ + } target; + binder_uintptr_t cookie; /* target object cookie */ + __u32 code; /* transaction command */ + + /* General information about the transaction. */ + __u32 flags; + pid_t sender_pid; + uid_t sender_euid; + binder_size_t data_size; /* number of bytes of data */ + binder_size_t offsets_size; /* number of bytes of offsets */ + + /* If this transaction is inline, the data immediately + * follows here; otherwise, it ends with a pointer to + * the data buffer. + */ + union { + struct { + /* transaction data */ + binder_uintptr_t buffer; + /* offsets from buffer to flat_binder_object structs */ + binder_uintptr_t offsets; + } ptr; + __u8 buf[8]; + } data; +}; + +struct binder_ptr_cookie { + binder_uintptr_t ptr; + binder_uintptr_t cookie; +}; + +struct binder_handle_cookie { + __u32 handle; + binder_uintptr_t cookie; +} __attribute__((packed)); + +struct binder_pri_desc { + __s32 priority; + __u32 desc; +}; + +struct binder_pri_ptr_cookie { + __s32 priority; + binder_uintptr_t ptr; + binder_uintptr_t cookie; +}; + +enum binder_driver_return_protocol { + BR_ERROR = _IOR('r', 0, __s32), + /* + * int: error code + */ + + BR_OK = _IO('r', 1), + /* No parameters! */ + + BR_TRANSACTION = _IOR('r', 2, struct binder_transaction_data), + BR_REPLY = _IOR('r', 3, struct binder_transaction_data), + /* + * binder_transaction_data: the received command. + */ + + BR_ACQUIRE_RESULT = _IOR('r', 4, __s32), + /* + * not currently supported + * int: 0 if the last bcATTEMPT_ACQUIRE was not successful. + * Else the remote object has acquired a primary reference. + */ + + BR_DEAD_REPLY = _IO('r', 5), + /* + * The target of the last transaction (either a bcTRANSACTION or + * a bcATTEMPT_ACQUIRE) is no longer with us. No parameters. + */ + + BR_TRANSACTION_COMPLETE = _IO('r', 6), + /* + * No parameters... always refers to the last transaction requested + * (including replies). Note that this will be sent even for + * asynchronous transactions. + */ + + BR_INCREFS = _IOR('r', 7, struct binder_ptr_cookie), + BR_ACQUIRE = _IOR('r', 8, struct binder_ptr_cookie), + BR_RELEASE = _IOR('r', 9, struct binder_ptr_cookie), + BR_DECREFS = _IOR('r', 10, struct binder_ptr_cookie), + /* + * void *: ptr to binder + * void *: cookie for binder + */ + + BR_ATTEMPT_ACQUIRE = _IOR('r', 11, struct binder_pri_ptr_cookie), + /* + * not currently supported + * int: priority + * void *: ptr to binder + * void *: cookie for binder + */ + + BR_NOOP = _IO('r', 12), + /* + * No parameters. Do nothing and examine the next command. It exists + * primarily so that we can replace it with a BR_SPAWN_LOOPER command. + */ + + BR_SPAWN_LOOPER = _IO('r', 13), + /* + * No parameters. The driver has determined that a process has no + * threads waiting to service incoming transactions. When a process + * receives this command, it must spawn a new service thread and + * register it via bcENTER_LOOPER. + */ + + BR_FINISHED = _IO('r', 14), + /* + * not currently supported + * stop threadpool thread + */ + + BR_DEAD_BINDER = _IOR('r', 15, binder_uintptr_t), + /* + * void *: cookie + */ + BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, binder_uintptr_t), + /* + * void *: cookie + */ + + BR_FAILED_REPLY = _IO('r', 17), + /* + * The the last transaction (either a bcTRANSACTION or + * a bcATTEMPT_ACQUIRE) failed (e.g. out of memory). No parameters. + */ +}; + +enum binder_driver_command_protocol { + BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data), + BC_REPLY = _IOW('c', 1, struct binder_transaction_data), + /* + * binder_transaction_data: the sent command. + */ + + BC_ACQUIRE_RESULT = _IOW('c', 2, __s32), + /* + * not currently supported + * int: 0 if the last BR_ATTEMPT_ACQUIRE was not successful. + * Else you have acquired a primary reference on the object. + */ + + BC_FREE_BUFFER = _IOW('c', 3, binder_uintptr_t), + /* + * void *: ptr to transaction data received on a read + */ + + BC_INCREFS = _IOW('c', 4, __u32), + BC_ACQUIRE = _IOW('c', 5, __u32), + BC_RELEASE = _IOW('c', 6, __u32), + BC_DECREFS = _IOW('c', 7, __u32), + /* + * int: descriptor + */ + + BC_INCREFS_DONE = _IOW('c', 8, struct binder_ptr_cookie), + BC_ACQUIRE_DONE = _IOW('c', 9, struct binder_ptr_cookie), + /* + * void *: ptr to binder + * void *: cookie for binder + */ + + BC_ATTEMPT_ACQUIRE = _IOW('c', 10, struct binder_pri_desc), + /* + * not currently supported + * int: priority + * int: descriptor + */ + + BC_REGISTER_LOOPER = _IO('c', 11), + /* + * No parameters. + * Register a spawned looper thread with the device. + */ + + BC_ENTER_LOOPER = _IO('c', 12), + BC_EXIT_LOOPER = _IO('c', 13), + /* + * No parameters. + * These two commands are sent as an application-level thread + * enters and exits the binder loop, respectively. They are + * used so the binder can have an accurate count of the number + * of looping threads it has available. + */ + + BC_REQUEST_DEATH_NOTIFICATION = _IOW('c', 14, struct binder_handle_cookie), + /* + * int: handle + * void *: cookie + */ + + BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15, struct binder_handle_cookie), + /* + * int: handle + * void *: cookie + */ + + BC_DEAD_BINDER_DONE = _IOW('c', 16, binder_uintptr_t), + /* + * void *: cookie + */ +}; + +#endif /* _UAPI_LINUX_BINDER_H */ + diff --git a/drivers/staging/android/uapi/sw_sync.h b/drivers/staging/android/uapi/sw_sync.h new file mode 100644 index 00000000000000..9b5d4869505cb4 --- /dev/null +++ b/drivers/staging/android/uapi/sw_sync.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef _UAPI_LINUX_SW_SYNC_H +#define _UAPI_LINUX_SW_SYNC_H + +#include + +struct sw_sync_create_fence_data { + __u32 value; + char name[32]; + __s32 fence; /* fd of new fence */ +}; + +#define SW_SYNC_IOC_MAGIC 'W' + +#define SW_SYNC_IOC_CREATE_FENCE _IOWR(SW_SYNC_IOC_MAGIC, 0,\ + struct sw_sync_create_fence_data) +#define SW_SYNC_IOC_INC _IOW(SW_SYNC_IOC_MAGIC, 1, __u32) + +#endif /* _UAPI_LINUX_SW_SYNC_H */ diff --git a/drivers/staging/android/uapi/sync.h b/drivers/staging/android/uapi/sync.h new file mode 100644 index 00000000000000..e964c751f6b8f6 --- /dev/null +++ b/drivers/staging/android/uapi/sync.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2012 Google, Inc. + * + * 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. + * + */ + +#ifndef _UAPI_LINUX_SYNC_H +#define _UAPI_LINUX_SYNC_H + +#include +#include + +/** + * struct sync_merge_data - data passed to merge ioctl + * @fd2: file descriptor of second fence + * @name: name of new fence + * @fence: returns the fd of the new fence to userspace + */ +struct sync_merge_data { + __s32 fd2; /* fd of second fence */ + char name[32]; /* name of new fence */ + __s32 fence; /* fd on newly created fence */ +}; + +/** + * struct sync_pt_info - detailed sync_pt information + * @len: length of sync_pt_info including any driver_data + * @obj_name: name of parent sync_timeline + * @driver_name: name of driver implementing the parent + * @status: status of the sync_pt 0:active 1:signaled <0:error + * @timestamp_ns: timestamp of status change in nanoseconds + * @driver_data: any driver dependent data + */ +struct sync_pt_info { + __u32 len; + char obj_name[32]; + char driver_name[32]; + __s32 status; + __u64 timestamp_ns; + + __u8 driver_data[0]; +}; + +/** + * struct sync_fence_info_data - data returned from fence info ioctl + * @len: ioctl caller writes the size of the buffer its passing in. + * ioctl returns length of sync_fence_data returned to userspace + * including pt_info. + * @name: name of fence + * @status: status of fence. 1: signaled 0:active <0:error + * @pt_info: a sync_pt_info struct for every sync_pt in the fence + */ +struct sync_fence_info_data { + __u32 len; + char name[32]; + __s32 status; + + __u8 pt_info[0]; +}; + +#define SYNC_IOC_MAGIC '>' + +/** + * DOC: SYNC_IOC_WAIT - wait for a fence to signal + * + * pass timeout in milliseconds. Waits indefinitely timeout < 0. + */ +#define SYNC_IOC_WAIT _IOW(SYNC_IOC_MAGIC, 0, __s32) + +/** + * DOC: SYNC_IOC_MERGE - merge two fences + * + * Takes a struct sync_merge_data. Creates a new fence containing copies of + * the sync_pts in both the calling fd and sync_merge_data.fd2. Returns the + * new fence's fd in sync_merge_data.fence + */ +#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 1, struct sync_merge_data) + +/** + * DOC: SYNC_IOC_FENCE_INFO - get detailed information on a fence + * + * Takes a struct sync_fence_info_data with extra space allocated for pt_info. + * Caller should write the size of the buffer into len. On return, len is + * updated to reflect the total size of the sync_fence_info_data including + * pt_info. + * + * pt_info is a buffer containing sync_pt_infos for every sync_pt in the fence. + * To iterate over the sync_pt_infos, use the sync_pt_info.len field. + */ +#define SYNC_IOC_FENCE_INFO _IOWR(SYNC_IOC_MAGIC, 2,\ + struct sync_fence_info_data) + +#endif /* _UAPI_LINUX_SYNC_H */ diff --git a/drivers/switch/Kconfig b/drivers/switch/Kconfig new file mode 100644 index 00000000000000..19404b6f77781e --- /dev/null +++ b/drivers/switch/Kconfig @@ -0,0 +1,15 @@ +menuconfig SWITCH + tristate "Switch class support" + help + Say Y here to enable switch class support. This allows + monitoring switches by userspace via sysfs and uevent. + +if SWITCH + +config SWITCH_GPIO + tristate "GPIO Swith support" + depends on GPIOLIB + help + Say Y here to enable GPIO based switch support. + +endif # SWITCH diff --git a/drivers/switch/Makefile b/drivers/switch/Makefile new file mode 100644 index 00000000000000..f7606ed4a719b9 --- /dev/null +++ b/drivers/switch/Makefile @@ -0,0 +1,4 @@ +# Switch Class Driver +obj-$(CONFIG_SWITCH) += switch_class.o +obj-$(CONFIG_SWITCH_GPIO) += switch_gpio.o + diff --git a/drivers/switch/switch_class.c b/drivers/switch/switch_class.c new file mode 100644 index 00000000000000..e373b625806ef2 --- /dev/null +++ b/drivers/switch/switch_class.c @@ -0,0 +1,174 @@ +/* + * drivers/switch/switch_class.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * +*/ + +#include +#include +#include +#include +#include +#include +#include + +struct class *switch_class; +static atomic_t device_count; + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct switch_dev *sdev = (struct switch_dev *) + dev_get_drvdata(dev); + + if (sdev->print_state) { + int ret = sdev->print_state(sdev, buf); + if (ret >= 0) + return ret; + } + return sprintf(buf, "%d\n", sdev->state); +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct switch_dev *sdev = (struct switch_dev *) + dev_get_drvdata(dev); + + if (sdev->print_name) { + int ret = sdev->print_name(sdev, buf); + if (ret >= 0) + return ret; + } + return sprintf(buf, "%s\n", sdev->name); +} + +static DEVICE_ATTR(state, S_IRUGO, state_show, NULL); +static DEVICE_ATTR(name, S_IRUGO, name_show, NULL); + +void switch_set_state(struct switch_dev *sdev, int state) +{ + char name_buf[120]; + char state_buf[120]; + char *prop_buf; + char *envp[3]; + int env_offset = 0; + int length; + + if (sdev->state != state) { + sdev->state = state; + + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); + if (prop_buf) { + length = name_show(sdev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(name_buf, sizeof(name_buf), + "SWITCH_NAME=%s", prop_buf); + envp[env_offset++] = name_buf; + } + length = state_show(sdev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(state_buf, sizeof(state_buf), + "SWITCH_STATE=%s", prop_buf); + envp[env_offset++] = state_buf; + } + envp[env_offset] = NULL; + kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp); + free_page((unsigned long)prop_buf); + } else { + printk(KERN_ERR "out of memory in switch_set_state\n"); + kobject_uevent(&sdev->dev->kobj, KOBJ_CHANGE); + } + } +} +EXPORT_SYMBOL_GPL(switch_set_state); + +static int create_switch_class(void) +{ + if (!switch_class) { + switch_class = class_create(THIS_MODULE, "switch"); + if (IS_ERR(switch_class)) + return PTR_ERR(switch_class); + atomic_set(&device_count, 0); + } + + return 0; +} + +int switch_dev_register(struct switch_dev *sdev) +{ + int ret; + + if (!switch_class) { + ret = create_switch_class(); + if (ret < 0) + return ret; + } + + sdev->index = atomic_inc_return(&device_count); + sdev->dev = device_create(switch_class, NULL, + MKDEV(0, sdev->index), NULL, sdev->name); + if (IS_ERR(sdev->dev)) + return PTR_ERR(sdev->dev); + + ret = device_create_file(sdev->dev, &dev_attr_state); + if (ret < 0) + goto err_create_file_1; + ret = device_create_file(sdev->dev, &dev_attr_name); + if (ret < 0) + goto err_create_file_2; + + dev_set_drvdata(sdev->dev, sdev); + sdev->state = 0; + return 0; + +err_create_file_2: + device_remove_file(sdev->dev, &dev_attr_state); +err_create_file_1: + device_destroy(switch_class, MKDEV(0, sdev->index)); + printk(KERN_ERR "switch: Failed to register driver %s\n", sdev->name); + + return ret; +} +EXPORT_SYMBOL_GPL(switch_dev_register); + +void switch_dev_unregister(struct switch_dev *sdev) +{ + device_remove_file(sdev->dev, &dev_attr_name); + device_remove_file(sdev->dev, &dev_attr_state); + device_destroy(switch_class, MKDEV(0, sdev->index)); + dev_set_drvdata(sdev->dev, NULL); +} +EXPORT_SYMBOL_GPL(switch_dev_unregister); + +static int __init switch_class_init(void) +{ + return create_switch_class(); +} + +static void __exit switch_class_exit(void) +{ + class_destroy(switch_class); +} + +module_init(switch_class_init); +module_exit(switch_class_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("Switch class driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/switch/switch_gpio.c b/drivers/switch/switch_gpio.c new file mode 100644 index 00000000000000..621d62d20c99c1 --- /dev/null +++ b/drivers/switch/switch_gpio.c @@ -0,0 +1,172 @@ +/* + * drivers/switch/switch_gpio.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpio_switch_data { + struct switch_dev sdev; + unsigned gpio; + const char *name_on; + const char *name_off; + const char *state_on; + const char *state_off; + int irq; + struct work_struct work; +}; + +static void gpio_switch_work(struct work_struct *work) +{ + int state; + struct gpio_switch_data *data = + container_of(work, struct gpio_switch_data, work); + + state = gpio_get_value(data->gpio); + switch_set_state(&data->sdev, state); +} + +static irqreturn_t gpio_irq_handler(int irq, void *dev_id) +{ + struct gpio_switch_data *switch_data = + (struct gpio_switch_data *)dev_id; + + schedule_work(&switch_data->work); + return IRQ_HANDLED; +} + +static ssize_t switch_gpio_print_state(struct switch_dev *sdev, char *buf) +{ + struct gpio_switch_data *switch_data = + container_of(sdev, struct gpio_switch_data, sdev); + const char *state; + if (switch_get_state(sdev)) + state = switch_data->state_on; + else + state = switch_data->state_off; + + if (state) + return sprintf(buf, "%s\n", state); + return -1; +} + +static int gpio_switch_probe(struct platform_device *pdev) +{ + struct gpio_switch_platform_data *pdata = pdev->dev.platform_data; + struct gpio_switch_data *switch_data; + int ret = 0; + + if (!pdata) + return -EBUSY; + + switch_data = kzalloc(sizeof(struct gpio_switch_data), GFP_KERNEL); + if (!switch_data) + return -ENOMEM; + + switch_data->sdev.name = pdata->name; + switch_data->gpio = pdata->gpio; + switch_data->name_on = pdata->name_on; + switch_data->name_off = pdata->name_off; + switch_data->state_on = pdata->state_on; + switch_data->state_off = pdata->state_off; + switch_data->sdev.print_state = switch_gpio_print_state; + + ret = switch_dev_register(&switch_data->sdev); + if (ret < 0) + goto err_switch_dev_register; + + ret = gpio_request(switch_data->gpio, pdev->name); + if (ret < 0) + goto err_request_gpio; + + ret = gpio_direction_input(switch_data->gpio); + if (ret < 0) + goto err_set_gpio_input; + + INIT_WORK(&switch_data->work, gpio_switch_work); + + switch_data->irq = gpio_to_irq(switch_data->gpio); + if (switch_data->irq < 0) { + ret = switch_data->irq; + goto err_detect_irq_num_failed; + } + + ret = request_irq(switch_data->irq, gpio_irq_handler, + IRQF_TRIGGER_LOW, pdev->name, switch_data); + if (ret < 0) + goto err_request_irq; + + /* Perform initial detection */ + gpio_switch_work(&switch_data->work); + + return 0; + +err_request_irq: +err_detect_irq_num_failed: +err_set_gpio_input: + gpio_free(switch_data->gpio); +err_request_gpio: + switch_dev_unregister(&switch_data->sdev); +err_switch_dev_register: + kfree(switch_data); + + return ret; +} + +static int gpio_switch_remove(struct platform_device *pdev) +{ + struct gpio_switch_data *switch_data = platform_get_drvdata(pdev); + + cancel_work_sync(&switch_data->work); + gpio_free(switch_data->gpio); + switch_dev_unregister(&switch_data->sdev); + kfree(switch_data); + + return 0; +} + +static struct platform_driver gpio_switch_driver = { + .probe = gpio_switch_probe, + .remove = gpio_switch_remove, + .driver = { + .name = "switch-gpio", + .owner = THIS_MODULE, + }, +}; + +static int __init gpio_switch_init(void) +{ + return platform_driver_register(&gpio_switch_driver); +} + +static void __exit gpio_switch_exit(void) +{ + platform_driver_unregister(&gpio_switch_driver); +} + +module_init(gpio_switch_init); +module_exit(gpio_switch_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("GPIO Switch driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 0d96a510389f41..42e1e2b4e2b61e 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -41,12 +41,13 @@ * @id: identifier of the one instance of the TMU controller. * @pdata: pointer to the tmu platform/configuration data * @base: base address of the single instance of the TMU controller. - * @base_common: base address of the common registers of the TMU controller. + * @base_second: base address of the common registers of the TMU controller. * @irq: irq number of the TMU controller. * @soc: id of the SOC type. * @irq_work: pointer to the irq work structure. * @lock: lock to implement synchronization. * @clk: pointer to the clock structure. + * @clk_sec: pointer to the clock structure for accessing the base_second. * @temp_error1: fused value of the first point trim. * @temp_error2: fused value of the second point trim. * @regulator: pointer to the TMU regulator structure. @@ -56,12 +57,12 @@ struct exynos_tmu_data { int id; struct exynos_tmu_platform_data *pdata; void __iomem *base; - void __iomem *base_common; + void __iomem *base_second; int irq; enum soc_type soc; struct work_struct irq_work; struct mutex lock; - struct clk *clk; + struct clk *clk, *clk_sec; u8 temp_error1, temp_error2; struct regulator *regulator; struct thermal_sensor_conf *reg_conf; @@ -152,6 +153,8 @@ static int exynos_tmu_initialize(struct platform_device *pdev) mutex_lock(&data->lock); clk_enable(data->clk); + if (!IS_ERR(data->clk_sec)) + clk_enable(data->clk_sec); if (TMU_SUPPORTS(pdata, READY_STATUS)) { status = readb(data->base + reg->tmu_status); @@ -162,7 +165,7 @@ static int exynos_tmu_initialize(struct platform_device *pdev) } if (TMU_SUPPORTS(pdata, TRIM_RELOAD)) - __raw_writel(1, data->base + reg->triminfo_ctrl); + writel_relaxed(1, data->base + reg->triminfo_ctrl); if (pdata->cal_mode == HW_MODE) goto skip_calib_data; @@ -186,7 +189,12 @@ static int exynos_tmu_initialize(struct platform_device *pdev) EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data); } } else { - trim_info = readl(data->base + reg->triminfo_data); + /* On exynos5420 the triminfo register is in the shared space */ + if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) + trim_info = readl(data->base_second + + reg->triminfo_data); + else + trim_info = readl(data->base + reg->triminfo_data); } data->temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; data->temp_error2 = ((trim_info >> reg->triminfo_85_shift) & @@ -238,7 +246,7 @@ static int exynos_tmu_initialize(struct platform_device *pdev) writeb(pdata->trigger_levels[i], data->base + reg->threshold_th0 + i * sizeof(reg->threshold_th0)); - writel(reg->inten_rise_mask, data->base + reg->tmu_intclear); + writel(reg->intclr_rise_mask, data->base + reg->tmu_intclear); } else { /* Write temperature code for rising and falling threshold */ for (i = 0; @@ -265,8 +273,8 @@ static int exynos_tmu_initialize(struct platform_device *pdev) writel(falling_threshold, data->base + reg->threshold_th1); - writel((reg->inten_rise_mask << reg->inten_rise_shift) | - (reg->inten_fall_mask << reg->inten_fall_shift), + writel((reg->intclr_rise_mask << reg->intclr_rise_shift) | + (reg->intclr_fall_mask << reg->intclr_fall_shift), data->base + reg->tmu_intclear); /* if last threshold limit is also present */ @@ -298,10 +306,12 @@ static int exynos_tmu_initialize(struct platform_device *pdev) } /*Clear the PMIN in the common TMU register*/ if (reg->tmu_pmin && !data->id) - writel(0, data->base_common + reg->tmu_pmin); + writel(0, data->base_second + reg->tmu_pmin); out: clk_disable(data->clk); mutex_unlock(&data->lock); + if (!IS_ERR(data->clk_sec)) + clk_disable(data->clk_sec); return ret; } @@ -453,12 +463,16 @@ static void exynos_tmu_work(struct work_struct *work) const struct exynos_tmu_registers *reg = pdata->registers; unsigned int val_irq, val_type; + if (!IS_ERR(data->clk_sec)) + clk_enable(data->clk_sec); /* Find which sensor generated this interrupt */ if (reg->tmu_irqstatus) { - val_type = readl(data->base_common + reg->tmu_irqstatus); + val_type = readl(data->base_second + reg->tmu_irqstatus); if (!((val_type >> data->id) & 0x1)) goto out; } + if (!IS_ERR(data->clk_sec)) + clk_disable(data->clk_sec); exynos_report_trigger(data->reg_conf); mutex_lock(&data->lock); @@ -498,6 +512,14 @@ static const struct of_device_id exynos_tmu_match[] = { .compatible = "samsung,exynos5250-tmu", .data = (void *)EXYNOS5250_TMU_DRV_DATA, }, + { + .compatible = "samsung,exynos5420-tmu", + .data = (void *)EXYNOS5420_TMU_DRV_DATA, + }, + { + .compatible = "samsung,exynos5420-tmu-ext-triminfo", + .data = (void *)EXYNOS5420_TMU_DRV_DATA, + }, { .compatible = "samsung,exynos5440-tmu", .data = (void *)EXYNOS5440_TMU_DRV_DATA, @@ -580,7 +602,7 @@ static int exynos_map_dt_data(struct platform_device *pdev) * Check if the TMU shares some registers and then try to map the * memory of common registers. */ - if (!TMU_SUPPORTS(pdata, SHARED_MEMORY)) + if (!TMU_SUPPORTS(pdata, ADDRESS_MULTIPLE)) return 0; if (of_address_to_resource(pdev->dev.of_node, 1, &res)) { @@ -588,9 +610,9 @@ static int exynos_map_dt_data(struct platform_device *pdev) return -ENODEV; } - data->base_common = devm_ioremap(&pdev->dev, res.start, + data->base_second = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); - if (!data->base_common) { + if (!data->base_second) { dev_err(&pdev->dev, "Failed to ioremap memory\n"); return -ENOMEM; } @@ -629,13 +651,30 @@ static int exynos_tmu_probe(struct platform_device *pdev) return PTR_ERR(data->clk); } + data->clk_sec = devm_clk_get(&pdev->dev, "tmu_triminfo_apbif"); + if (IS_ERR(data->clk_sec)) { + if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) { + dev_err(&pdev->dev, "Failed to get triminfo clock\n"); + return PTR_ERR(data->clk_sec); + } + } else { + ret = clk_prepare(data->clk_sec); + if (ret) { + dev_err(&pdev->dev, "Failed to get clock\n"); + return ret; + } + } + ret = clk_prepare(data->clk); - if (ret) - return ret; + if (ret) { + dev_err(&pdev->dev, "Failed to get clock\n"); + goto err_clk_sec; + } if (pdata->type == SOC_ARCH_EXYNOS4210 || pdata->type == SOC_ARCH_EXYNOS4412 || pdata->type == SOC_ARCH_EXYNOS5250 || + pdata->type == SOC_ARCH_EXYNOS5420_TRIMINFO || pdata->type == SOC_ARCH_EXYNOS5440) data->soc = pdata->type; else { @@ -704,6 +743,9 @@ static int exynos_tmu_probe(struct platform_device *pdev) return 0; err_clk: clk_unprepare(data->clk); +err_clk_sec: + if (!IS_ERR(data->clk_sec)) + clk_unprepare(data->clk_sec); return ret; } @@ -716,6 +758,8 @@ static int exynos_tmu_remove(struct platform_device *pdev) exynos_unregister_thermal(data->reg_conf); clk_unprepare(data->clk); + if (!IS_ERR(data->clk_sec)) + clk_unprepare(data->clk_sec); if (!IS_ERR(data->regulator)) regulator_disable(data->regulator); diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h index 3fb65547e64c9d..60cce2855fa81b 100644 --- a/drivers/thermal/samsung/exynos_tmu.h +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -43,6 +43,7 @@ enum soc_type { SOC_ARCH_EXYNOS4210 = 1, SOC_ARCH_EXYNOS4412, SOC_ARCH_EXYNOS5250, + SOC_ARCH_EXYNOS5420_TRIMINFO, SOC_ARCH_EXYNOS5440, }; @@ -60,7 +61,7 @@ enum soc_type { * state(active/idle) can be checked. * TMU_SUPPORT_EMUL_TIME - This features allows to set next temp emulation * sample time. - * TMU_SUPPORT_SHARED_MEMORY - This feature tells that the different TMU + * TMU_SUPPORT_ADDRESS_MULTIPLE - This feature tells that the different TMU * sensors shares some common registers. * TMU_SUPPORT - macro to compare the above features with the supplied. */ @@ -70,7 +71,7 @@ enum soc_type { #define TMU_SUPPORT_FALLING_TRIP BIT(3) #define TMU_SUPPORT_READY_STATUS BIT(4) #define TMU_SUPPORT_EMUL_TIME BIT(5) -#define TMU_SUPPORT_SHARED_MEMORY BIT(6) +#define TMU_SUPPORT_ADDRESS_MULTIPLE BIT(6) #define TMU_SUPPORTS(a, b) (a->features & TMU_SUPPORT_ ## b) @@ -122,10 +123,6 @@ enum soc_type { * @threshold_th3_l0_shift: shift bits of level0 threshold temperature. * @tmu_inten: register containing the different threshold interrupt enable bits. - * @inten_rise_shift: shift bits of all rising interrupt bits. - * @inten_rise_mask: mask bits of all rising interrupt bits. - * @inten_fall_shift: shift bits of all rising interrupt bits. - * @inten_fall_mask: mask bits of all rising interrupt bits. * @inten_rise0_shift: shift bits of rising 0 interrupt bits. * @inten_rise1_shift: shift bits of rising 1 interrupt bits. * @inten_rise2_shift: shift bits of rising 2 interrupt bits. @@ -136,6 +133,10 @@ enum soc_type { * @inten_fall3_shift: shift bits of falling 3 interrupt bits. * @tmu_intstat: Register containing the interrupt status values. * @tmu_intclear: Register for clearing the raised interrupt status. + * @intclr_fall_shift: shift bits for interrupt clear fall 0 + * @intclr_rise_shift: shift bits of all rising interrupt bits. + * @intclr_rise_mask: mask bits of all rising interrupt bits. + * @intclr_fall_mask: mask bits of all rising interrupt bits. * @emul_con: TMU emulation controller register. * @emul_temp_shift: shift bits of emulation temperature. * @emul_time_shift: shift bits of emulation time. @@ -191,10 +192,6 @@ struct exynos_tmu_registers { u32 threshold_th3_l0_shift; u32 tmu_inten; - u32 inten_rise_shift; - u32 inten_rise_mask; - u32 inten_fall_shift; - u32 inten_fall_mask; u32 inten_rise0_shift; u32 inten_rise1_shift; u32 inten_rise2_shift; @@ -207,6 +204,10 @@ struct exynos_tmu_registers { u32 tmu_intstat; u32 tmu_intclear; + u32 intclr_fall_shift; + u32 intclr_rise_shift; + u32 intclr_fall_mask; + u32 intclr_rise_mask; u32 emul_con; u32 emul_temp_shift; diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index 476b768c633e93..e597f61a15f975 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -40,13 +40,13 @@ static const struct exynos_tmu_registers exynos4210_tmu_registers = { .threshold_temp = EXYNOS4210_TMU_REG_THRESHOLD_TEMP, .threshold_th0 = EXYNOS4210_TMU_REG_TRIG_LEVEL0, .tmu_inten = EXYNOS_TMU_REG_INTEN, - .inten_rise_mask = EXYNOS4210_TMU_TRIG_LEVEL_MASK, .inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, .inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, .inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, .inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, .tmu_intstat = EXYNOS_TMU_REG_INTSTAT, .tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, + .intclr_rise_mask = EXYNOS4210_TMU_TRIG_LEVEL_MASK, }; struct exynos_tmu_init_data const exynos4210_default_tmu_data = { @@ -112,10 +112,6 @@ static const struct exynos_tmu_registers exynos4412_tmu_registers = { .threshold_th0 = EXYNOS_THD_TEMP_RISE, .threshold_th1 = EXYNOS_THD_TEMP_FALL, .tmu_inten = EXYNOS_TMU_REG_INTEN, - .inten_rise_mask = EXYNOS_TMU_RISE_INT_MASK, - .inten_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, - .inten_fall_mask = EXYNOS_TMU_FALL_INT_MASK, - .inten_fall_shift = EXYNOS_TMU_FALL_INT_SHIFT, .inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, .inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, .inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, @@ -123,6 +119,10 @@ static const struct exynos_tmu_registers exynos4412_tmu_registers = { .inten_fall0_shift = EXYNOS_TMU_INTEN_FALL0_SHIFT, .tmu_intstat = EXYNOS_TMU_REG_INTSTAT, .tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, + .intclr_fall_shift = EXYNOS_TMU_CLEAR_FALL_INT_SHIFT, + .intclr_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, + .intclr_rise_mask = EXYNOS_TMU_RISE_INT_MASK, + .intclr_fall_mask = EXYNOS_TMU_FALL_INT_MASK, .emul_con = EXYNOS_EMUL_CON, .emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, .emul_time_shift = EXYNOS_EMUL_TIME_SHIFT, @@ -194,6 +194,105 @@ struct exynos_tmu_init_data const exynos5250_default_tmu_data = { }; #endif +#if defined(CONFIG_SOC_EXYNOS5420) +static const struct exynos_tmu_registers exynos5420_tmu_registers = { + .triminfo_data = EXYNOS_TMU_REG_TRIMINFO, + .triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, + .triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, + .tmu_ctrl = EXYNOS_TMU_REG_CONTROL, + .buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, + .buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, + .therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT, + .therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK, + .therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT, + .buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, + .buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, + .core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, + .tmu_status = EXYNOS_TMU_REG_STATUS, + .tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP, + .threshold_th0 = EXYNOS_THD_TEMP_RISE, + .threshold_th1 = EXYNOS_THD_TEMP_FALL, + .tmu_inten = EXYNOS_TMU_REG_INTEN, + .inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, + .inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, + .inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, + /* INTEN_RISE3 Not availble in exynos5420 */ + .inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, + .inten_fall0_shift = EXYNOS_TMU_INTEN_FALL0_SHIFT, + .tmu_intstat = EXYNOS_TMU_REG_INTSTAT, + .tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, + .intclr_fall_shift = EXYNOS5420_TMU_CLEAR_FALL_INT_SHIFT, + .intclr_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, + .intclr_rise_mask = EXYNOS_TMU_RISE_INT_MASK, + .intclr_fall_mask = EXYNOS_TMU_FALL_INT_MASK, + .emul_con = EXYNOS_EMUL_CON, + .emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, + .emul_time_shift = EXYNOS_EMUL_TIME_SHIFT, + .emul_time_mask = EXYNOS_EMUL_TIME_MASK, +}; + +#define __EXYNOS5420_TMU_DATA \ + .threshold_falling = 10, \ + .trigger_levels[0] = 85, \ + .trigger_levels[1] = 103, \ + .trigger_levels[2] = 110, \ + .trigger_levels[3] = 120, \ + .trigger_enable[0] = true, \ + .trigger_enable[1] = true, \ + .trigger_enable[2] = true, \ + .trigger_enable[3] = false, \ + .trigger_type[0] = THROTTLE_ACTIVE, \ + .trigger_type[1] = THROTTLE_ACTIVE, \ + .trigger_type[2] = SW_TRIP, \ + .trigger_type[3] = HW_TRIP, \ + .max_trigger_level = 4, \ + .gain = 8, \ + .reference_voltage = 16, \ + .noise_cancel_mode = 4, \ + .cal_type = TYPE_ONE_POINT_TRIMMING, \ + .efuse_value = 55, \ + .min_efuse_value = 40, \ + .max_efuse_value = 100, \ + .first_point_trim = 25, \ + .second_point_trim = 85, \ + .default_temp_offset = 50, \ + .freq_tab[0] = { \ + .freq_clip_max = 800 * 1000, \ + .temp_level = 85, \ + }, \ + .freq_tab[1] = { \ + .freq_clip_max = 200 * 1000, \ + .temp_level = 103, \ + }, \ + .freq_tab_count = 2, \ + .registers = &exynos5420_tmu_registers, \ + +#define EXYNOS5420_TMU_DATA \ + __EXYNOS5420_TMU_DATA \ + .type = SOC_ARCH_EXYNOS5250, \ + .features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ + TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ + TMU_SUPPORT_EMUL_TIME) + +#define EXYNOS5420_TMU_DATA_SHARED \ + __EXYNOS5420_TMU_DATA \ + .type = SOC_ARCH_EXYNOS5420_TRIMINFO, \ + .features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ + TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ + TMU_SUPPORT_EMUL_TIME | TMU_SUPPORT_ADDRESS_MULTIPLE) + +struct exynos_tmu_init_data const exynos5420_default_tmu_data = { + .tmu_data = { + { EXYNOS5420_TMU_DATA }, + { EXYNOS5420_TMU_DATA }, + { EXYNOS5420_TMU_DATA_SHARED }, + { EXYNOS5420_TMU_DATA_SHARED }, + { EXYNOS5420_TMU_DATA_SHARED }, + }, + .tmu_count = 5, +}; +#endif + #if defined(CONFIG_SOC_EXYNOS5440) static const struct exynos_tmu_registers exynos5440_tmu_registers = { .triminfo_data = EXYNOS5440_TMU_S0_7_TRIM, @@ -217,10 +316,6 @@ static const struct exynos_tmu_registers exynos5440_tmu_registers = { .threshold_th2 = EXYNOS5440_TMU_S0_7_TH2, .threshold_th3_l0_shift = EXYNOS5440_TMU_TH_RISE4_SHIFT, .tmu_inten = EXYNOS5440_TMU_S0_7_IRQEN, - .inten_rise_mask = EXYNOS5440_TMU_RISE_INT_MASK, - .inten_rise_shift = EXYNOS5440_TMU_RISE_INT_SHIFT, - .inten_fall_mask = EXYNOS5440_TMU_FALL_INT_MASK, - .inten_fall_shift = EXYNOS5440_TMU_FALL_INT_SHIFT, .inten_rise0_shift = EXYNOS5440_TMU_INTEN_RISE0_SHIFT, .inten_rise1_shift = EXYNOS5440_TMU_INTEN_RISE1_SHIFT, .inten_rise2_shift = EXYNOS5440_TMU_INTEN_RISE2_SHIFT, @@ -228,6 +323,10 @@ static const struct exynos_tmu_registers exynos5440_tmu_registers = { .inten_fall0_shift = EXYNOS5440_TMU_INTEN_FALL0_SHIFT, .tmu_intstat = EXYNOS5440_TMU_S0_7_IRQ, .tmu_intclear = EXYNOS5440_TMU_S0_7_IRQ, + .intclr_fall_shift = EXYNOS5440_TMU_CLEAR_FALL_INT_SHIFT, + .intclr_rise_shift = EXYNOS5440_TMU_RISE_INT_SHIFT, + .intclr_rise_mask = EXYNOS5440_TMU_RISE_INT_MASK, + .intclr_fall_mask = EXYNOS5440_TMU_FALL_INT_MASK, .tmu_irqstatus = EXYNOS5440_TMU_IRQ_STATUS, .emul_con = EXYNOS5440_TMU_S0_7_DEBUG, .emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, @@ -255,7 +354,7 @@ static const struct exynos_tmu_registers exynos5440_tmu_registers = { .type = SOC_ARCH_EXYNOS5440, \ .registers = &exynos5440_tmu_registers, \ .features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_FALLING_TRIP | \ - TMU_SUPPORT_MULTI_INST | TMU_SUPPORT_SHARED_MEMORY), + TMU_SUPPORT_MULTI_INST | TMU_SUPPORT_ADDRESS_MULTIPLE), struct exynos_tmu_init_data const exynos5440_default_tmu_data = { .tmu_data = { diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h index a1ea19d9e0a6e6..41f06dc708492f 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.h +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -69,9 +69,11 @@ #define EXYNOS_TMU_RISE_INT_MASK 0x111 #define EXYNOS_TMU_RISE_INT_SHIFT 0 #define EXYNOS_TMU_FALL_INT_MASK 0x111 -#define EXYNOS_TMU_FALL_INT_SHIFT 12 #define EXYNOS_TMU_CLEAR_RISE_INT 0x111 #define EXYNOS_TMU_CLEAR_FALL_INT (0x111 << 12) +#define EXYNOS_TMU_CLEAR_FALL_INT_SHIFT 12 +#define EXYNOS5420_TMU_CLEAR_FALL_INT_SHIFT 16 +#define EXYNOS5440_TMU_CLEAR_FALL_INT_SHIFT 4 #define EXYNOS_TMU_TRIP_MODE_SHIFT 13 #define EXYNOS_TMU_TRIP_MODE_MASK 0x7 #define EXYNOS_TMU_THERM_TRIP_EN_SHIFT 12 @@ -119,7 +121,6 @@ #define EXYNOS5440_TMU_RISE_INT_MASK 0xf #define EXYNOS5440_TMU_RISE_INT_SHIFT 0 #define EXYNOS5440_TMU_FALL_INT_MASK 0xf -#define EXYNOS5440_TMU_FALL_INT_SHIFT 4 #define EXYNOS5440_TMU_INTEN_RISE0_SHIFT 0 #define EXYNOS5440_TMU_INTEN_RISE1_SHIFT 1 #define EXYNOS5440_TMU_INTEN_RISE2_SHIFT 2 @@ -156,6 +157,13 @@ extern struct exynos_tmu_init_data const exynos5250_default_tmu_data; #define EXYNOS5250_TMU_DRV_DATA (NULL) #endif +#if defined(CONFIG_SOC_EXYNOS5420) +extern struct exynos_tmu_init_data const exynos5420_default_tmu_data; +#define EXYNOS5420_TMU_DRV_DATA (&exynos5420_default_tmu_data) +#else +#define EXYNOS5420_TMU_DRV_DATA (NULL) +#endif + #if defined(CONFIG_SOC_EXYNOS5440) extern struct exynos_tmu_init_data const exynos5440_default_tmu_data; #define EXYNOS5440_TMU_DRV_DATA (&exynos5440_default_tmu_data) diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index 9cd706df3b3351..f7ed6a5eb8d8b6 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -59,6 +59,41 @@ #define S3C24XX_SERIAL_MAJOR 204 #define S3C24XX_SERIAL_MINOR 64 +#ifndef CONFIG_CPU_BIG_ENDIAN /* little endian */ +static inline void __hw_set_bit(int nr, volatile unsigned long *addr) +{ + __set_bit(nr, addr); +} + +static inline void __hw_clear_bit(int nr, volatile unsigned long *addr) +{ + __clear_bit(nr, addr); +} +#else +static inline void __hw_set_bit(int nr, volatile unsigned long *addr) +{ + unsigned long mask = BIT_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); + unsigned long val = le32_to_cpu(*p); + + val |= mask; + + *p = cpu_to_le32(val); +} + +static inline void __hw_clear_bit(int nr, volatile unsigned long *addr) +{ + unsigned long mask = BIT_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); + + unsigned long val = le32_to_cpu(*p); + + val &= ~mask; + + *p = cpu_to_le32(val); +} +#endif + /* macros to change one thing to another */ #define tx_enabled(port) ((port)->unused[0]) @@ -138,7 +173,7 @@ static void s3c24xx_serial_stop_tx(struct uart_port *port) if (tx_enabled(port)) { if (s3c24xx_serial_has_interrupt_mask(port)) - __set_bit(S3C64XX_UINTM_TXD, + __hw_set_bit(S3C64XX_UINTM_TXD, portaddrl(port, S3C64XX_UINTM)); else disable_irq_nosync(ourport->tx_irq); @@ -157,7 +192,7 @@ static void s3c24xx_serial_start_tx(struct uart_port *port) s3c24xx_serial_rx_disable(port); if (s3c24xx_serial_has_interrupt_mask(port)) - __clear_bit(S3C64XX_UINTM_TXD, + __hw_clear_bit(S3C64XX_UINTM_TXD, portaddrl(port, S3C64XX_UINTM)); else enable_irq(ourport->tx_irq); @@ -172,7 +207,7 @@ static void s3c24xx_serial_stop_rx(struct uart_port *port) if (rx_enabled(port)) { dbg("s3c24xx_serial_stop_rx: port=%p\n", port); if (s3c24xx_serial_has_interrupt_mask(port)) - __set_bit(S3C64XX_UINTM_RXD, + __hw_set_bit(S3C64XX_UINTM_RXD, portaddrl(port, S3C64XX_UINTM)); else disable_irq_nosync(ourport->rx_irq); @@ -533,11 +568,33 @@ static int s3c64xx_serial_startup(struct uart_port *port) ourport->tx_claimed = 1; /* Enable Rx Interrupt */ - __clear_bit(S3C64XX_UINTM_RXD, portaddrl(port, S3C64XX_UINTM)); + __hw_clear_bit(S3C64XX_UINTM_RXD, portaddrl(port, S3C64XX_UINTM)); dbg("s3c64xx_serial_startup ok\n"); return ret; } +static void s3c64xx_serial_shutdown(struct uart_port *port) +{ + struct s3c24xx_uart_port *ourport = to_ourport(port); + + if (ourport->tx_claimed) { + free_irq(port->irq, ourport); + tx_enabled(port) = 0; + ourport->tx_claimed = 0; + } + + if (ourport->rx_claimed) { + ourport->rx_claimed = 0; + rx_enabled(port) = 0; + } + + /* Clear pending interrupts and mask all interrupts */ + if (s3c24xx_serial_has_interrupt_mask(port)) { + wr_regl(port, S3C64XX_UINTP, 0xf); + wr_regl(port, S3C64XX_UINTM, 0xf); + } +} + /* power power management control */ static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level, @@ -1030,6 +1087,9 @@ static void s3c24xx_serial_resetport(struct uart_port *port, wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH); wr_regl(port, S3C2410_UFCON, cfg->ufcon); + wr_regl(port, S3C64XX_UINTM, 0xf); + wr_regl(port, S3C64XX_UINTP, 0xf); + /* some delay is required after fifo reset */ udelay(1); } @@ -1142,8 +1202,10 @@ static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport, port->dev = &platdev->dev; /* Startup sequence is different for s3c64xx and higher SoC's */ - if (s3c24xx_serial_has_interrupt_mask(port)) + if (s3c24xx_serial_has_interrupt_mask(port)) { s3c24xx_serial_ops.startup = s3c64xx_serial_startup; + s3c24xx_serial_ops.shutdown = s3c64xx_serial_shutdown; + } port->uartclk = 1; diff --git a/drivers/tty/serial/samsung.h b/drivers/tty/serial/samsung.h index 8827e5424cef7d..ceb11f73c777b9 100644 --- a/drivers/tty/serial/samsung.h +++ b/drivers/tty/serial/samsung.h @@ -72,10 +72,10 @@ struct s3c24xx_uart_port { ((unsigned long *)(unsigned long)((port)->membase + (reg))) #define rd_regb(port, reg) (__raw_readb(portaddr(port, reg))) -#define rd_regl(port, reg) (__raw_readl(portaddr(port, reg))) +#define rd_regl(port, reg) (readl_relaxed(portaddr(port, reg))) #define wr_regb(port, reg, val) __raw_writeb(val, portaddr(port, reg)) -#define wr_regl(port, reg, val) __raw_writel(val, portaddr(port, reg)) +#define wr_regl(port, reg, val) writel_relaxed(val, portaddr(port, reg)) #if defined(CONFIG_SERIAL_SAMSUNG_DEBUG) && \ defined(CONFIG_DEBUG_LL) && \ diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index ece2049bd27016..7f3d7a3564aca9 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -89,6 +89,9 @@ static void __uart_start(struct tty_struct *tty) struct uart_state *state = tty->driver_data; struct uart_port *port = state->uart_port; + if (port->ops->wake_peer) + port->ops->wake_peer(port); + if (!uart_circ_empty(&state->xmit) && state->xmit.buf && !tty->stopped && !tty->hw_stopped) port->ops->start_tx(port); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 64ea21971be23f..8b0dc994c0d63a 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -824,11 +824,8 @@ static unsigned hub_power_on(struct usb_hub *hub, bool do_delay) dev_dbg(hub->intfdev, "trying to enable port power on " "non-switchable hub\n"); for (port1 = 1; port1 <= hub->hdev->maxchild; port1++) - if (hub->ports[port1 - 1]->power_is_on) - set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); - else - usb_clear_port_feature(hub->hdev, port1, - USB_PORT_FEAT_POWER); + usb_hub_set_port_power(hub->hdev, hub, port1, + hub->ports[port1 - 1]->power_is_on); /* Wait at least 100 msec for power to become stable */ delay = max(pgood_delay, (unsigned) 100); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 8154165aa60142..d8e4660faf478d 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -1019,6 +1019,26 @@ config USB_G_PRINTER For more information, see Documentation/usb/gadget_printer.txt which includes sample code for accessing the device file. +config USB_G_ANDROID + boolean "Android Composite Gadget" + select USB_F_ACM + select USB_LIBCOMPOSITE + select USB_U_SERIAL + select USB_F_MASS_STORAGE + select SND_PCM + help + The Android Composite Gadget supports multiple USB + functions: adb, acm, mass storage, mtp, accessory + and rndis. + Each function can be configured and enabled/disabled + dynamically from userspace through a sysfs interface. + +config USB_ANDROID_RNDIS_DWORD_ALIGNED + boolean "Use double word aligned" + depends on USB_G_ANDROID + help + Provides dword aligned for DMA controller. + if TTY config USB_CDC_COMPOSITE diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 5f150bc1b4bc92..98af79e679e832 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -84,6 +84,7 @@ g_nokia-y := nokia.o g_webcam-y := webcam.o g_ncm-y := ncm.o g_acm_ms-y := acm_ms.o +g_android-y := android.o g_tcm_usb_gadget-y := tcm_usb_gadget.o obj-$(CONFIG_USB_ZERO) += g_zero.o @@ -103,4 +104,5 @@ obj-$(CONFIG_USB_G_NOKIA) += g_nokia.o obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o obj-$(CONFIG_USB_G_NCM) += g_ncm.o obj-$(CONFIG_USB_G_ACM_MS) += g_acm_ms.o +obj-$(CONFIG_USB_G_ANDROID) += g_android.o obj-$(CONFIG_USB_GADGET_TARGET) += tcm_usb_gadget.o diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c new file mode 100644 index 00000000000000..91f0efdabc8a02 --- /dev/null +++ b/drivers/usb/gadget/android.c @@ -0,0 +1,1561 @@ +/* + * Gadget Driver for Android + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * Benoit Goby + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "gadget_chips.h" + +#include "f_fs.c" +#include "f_audio_source.c" +#include "f_mtp.c" +#include "f_accessory.c" +#define USB_ETH_RNDIS y +#include "f_rndis.c" +#include "rndis.c" +#include "u_ether.c" + +USB_ETHERNET_MODULE_PARAMETERS(); + +MODULE_AUTHOR("Mike Lockwood"); +MODULE_DESCRIPTION("Android Composite USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +static const char longname[] = "Gadget Android"; + +/* Default vendor and product IDs, overridden by userspace */ +#define VENDOR_ID 0x18D1 +#define PRODUCT_ID 0x0001 + +struct android_usb_function { + char *name; + void *config; + + struct device *dev; + char *dev_name; + struct device_attribute **attributes; + + /* for android_dev.enabled_functions */ + struct list_head enabled_list; + + /* Optional: initialization during gadget bind */ + int (*init)(struct android_usb_function *, struct usb_composite_dev *); + /* Optional: cleanup during gadget unbind */ + void (*cleanup)(struct android_usb_function *); + /* Optional: called when the function is added the list of + * enabled functions */ + void (*enable)(struct android_usb_function *); + /* Optional: called when it is removed */ + void (*disable)(struct android_usb_function *); + + int (*bind_config)(struct android_usb_function *, + struct usb_configuration *); + + /* Optional: called when the configuration is removed */ + void (*unbind_config)(struct android_usb_function *, + struct usb_configuration *); + /* Optional: handle ctrl requests before the device is configured */ + int (*ctrlrequest)(struct android_usb_function *, + struct usb_composite_dev *, + const struct usb_ctrlrequest *); +}; + +struct android_dev { + struct android_usb_function **functions; + struct list_head enabled_functions; + struct usb_composite_dev *cdev; + struct device *dev; + + bool enabled; + int disable_depth; + struct mutex mutex; + bool connected; + bool sw_connected; + struct work_struct work; + char ffs_aliases[256]; +}; + +static struct class *android_class; +static struct android_dev *_android_dev; +static int android_bind_config(struct usb_configuration *c); +static void android_unbind_config(struct usb_configuration *c); + +/* string IDs are assigned dynamically */ +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +static char manufacturer_string[256]; +static char product_string[256]; +static char serial_string[256]; + +/* String Table */ +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer_string, + [STRING_PRODUCT_IDX].s = product_string, + [STRING_SERIAL_IDX].s = serial_string, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof(device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = __constant_cpu_to_le16(VENDOR_ID), + .idProduct = __constant_cpu_to_le16(PRODUCT_ID), + .bcdDevice = __constant_cpu_to_le16(0xffff), + .bNumConfigurations = 1, +}; + +static struct usb_configuration android_config_driver = { + .label = "android", + .unbind = android_unbind_config, + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .MaxPower = 500, /* 500ma */ +}; + +static void android_work(struct work_struct *data) +{ + struct android_dev *dev = container_of(data, struct android_dev, work); + struct usb_composite_dev *cdev = dev->cdev; + char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL }; + char *connected[2] = { "USB_STATE=CONNECTED", NULL }; + char *configured[2] = { "USB_STATE=CONFIGURED", NULL }; + char **uevent_envp = NULL; + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + uevent_envp = configured; + else if (dev->connected != dev->sw_connected) + uevent_envp = dev->connected ? connected : disconnected; + dev->sw_connected = dev->connected; + spin_unlock_irqrestore(&cdev->lock, flags); + + if (uevent_envp) { + kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp); + pr_info("%s: sent uevent %s\n", __func__, uevent_envp[0]); + } else { + pr_info("%s: did not send uevent (%d %d %p)\n", __func__, + dev->connected, dev->sw_connected, cdev->config); + } +} + +static void android_enable(struct android_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + + if (WARN_ON(!dev->disable_depth)) + return; + + if (--dev->disable_depth == 0) { + usb_add_config(cdev, &android_config_driver, + android_bind_config); + usb_gadget_connect(cdev->gadget); + } +} + +static void android_disable(struct android_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + + if (dev->disable_depth++ == 0) { + usb_gadget_disconnect(cdev->gadget); + /* Cancel pending control requests */ + usb_ep_dequeue(cdev->gadget->ep0, cdev->req); + usb_remove_config(cdev, &android_config_driver); + } +} + +/*-------------------------------------------------------------------------*/ +/* Supported functions initialization */ + +struct functionfs_config { + bool opened; + bool enabled; + struct ffs_data *data; +}; + +static int ffs_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct functionfs_config), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + + return functionfs_init(); +} + +static void ffs_function_cleanup(struct android_usb_function *f) +{ + functionfs_cleanup(); + kfree(f->config); +} + +static void ffs_function_enable(struct android_usb_function *f) +{ + struct android_dev *dev = _android_dev; + struct functionfs_config *config = f->config; + + config->enabled = true; + + /* Disable the gadget until the function is ready */ + if (!config->opened) + android_disable(dev); +} + +static void ffs_function_disable(struct android_usb_function *f) +{ + struct android_dev *dev = _android_dev; + struct functionfs_config *config = f->config; + + config->enabled = false; + + /* Balance the disable that was called in closed_callback */ + if (!config->opened) + android_enable(dev); +} + +static int ffs_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct functionfs_config *config = f->config; + return functionfs_bind_config(c->cdev, c, config->data); +} + +static ssize_t +ffs_aliases_show(struct device *pdev, struct device_attribute *attr, char *buf) +{ + struct android_dev *dev = _android_dev; + int ret; + + mutex_lock(&dev->mutex); + ret = sprintf(buf, "%s\n", dev->ffs_aliases); + mutex_unlock(&dev->mutex); + + return ret; +} + +static ssize_t +ffs_aliases_store(struct device *pdev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct android_dev *dev = _android_dev; + char buff[256]; + + mutex_lock(&dev->mutex); + + if (dev->enabled) { + mutex_unlock(&dev->mutex); + return -EBUSY; + } + + strlcpy(buff, buf, sizeof(buff)); + strlcpy(dev->ffs_aliases, strim(buff), sizeof(dev->ffs_aliases)); + + mutex_unlock(&dev->mutex); + + return size; +} + +static DEVICE_ATTR(aliases, S_IRUGO | S_IWUSR, ffs_aliases_show, + ffs_aliases_store); +static struct device_attribute *ffs_function_attributes[] = { + &dev_attr_aliases, + NULL +}; + +static struct android_usb_function ffs_function = { + .name = "ffs", + .init = ffs_function_init, + .enable = ffs_function_enable, + .disable = ffs_function_disable, + .cleanup = ffs_function_cleanup, + .bind_config = ffs_function_bind_config, + .attributes = ffs_function_attributes, +}; + +static int functionfs_ready_callback(struct ffs_data *ffs) +{ + struct android_dev *dev = _android_dev; + struct functionfs_config *config = ffs_function.config; + int ret = 0; + + mutex_lock(&dev->mutex); + + ret = functionfs_bind(ffs, dev->cdev); + if (ret) + goto err; + + config->data = ffs; + config->opened = true; + + if (config->enabled) + android_enable(dev); + +err: + mutex_unlock(&dev->mutex); + return ret; +} + +static void functionfs_closed_callback(struct ffs_data *ffs) +{ + struct android_dev *dev = _android_dev; + struct functionfs_config *config = ffs_function.config; + + mutex_lock(&dev->mutex); + + if (config->enabled) + android_disable(dev); + + config->opened = false; + config->data = NULL; + + functionfs_unbind(ffs); + + mutex_unlock(&dev->mutex); +} + +static void *functionfs_acquire_dev_callback(const char *dev_name) +{ + return 0; +} + +static void functionfs_release_dev_callback(struct ffs_data *ffs_data) +{ +} + +#define MAX_ACM_INSTANCES 4 +struct acm_function_config { + int instances; + int instances_on; + struct usb_function *f_acm[MAX_ACM_INSTANCES]; + struct usb_function_instance *f_acm_inst[MAX_ACM_INSTANCES]; +}; + +static int +acm_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + int i; + int ret; + struct acm_function_config *config; + + config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + f->config = config; + + for (i = 0; i < MAX_ACM_INSTANCES; i++) { + config->f_acm_inst[i] = usb_get_function_instance("acm"); + if (IS_ERR(config->f_acm_inst[i])) { + ret = PTR_ERR(config->f_acm_inst[i]); + goto err_usb_get_function_instance; + } + config->f_acm[i] = usb_get_function(config->f_acm_inst[i]); + if (IS_ERR(config->f_acm[i])) { + ret = PTR_ERR(config->f_acm[i]); + goto err_usb_get_function; + } + } + return 0; +err_usb_get_function_instance: + while (i-- > 0) { + usb_put_function(config->f_acm[i]); +err_usb_get_function: + usb_put_function_instance(config->f_acm_inst[i]); + } + return ret; +} + +static void acm_function_cleanup(struct android_usb_function *f) +{ + int i; + struct acm_function_config *config = f->config; + + for (i = 0; i < MAX_ACM_INSTANCES; i++) { + usb_put_function(config->f_acm[i]); + usb_put_function_instance(config->f_acm_inst[i]); + } + kfree(f->config); + f->config = NULL; +} + +static int +acm_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int i; + int ret = 0; + struct acm_function_config *config = f->config; + + config->instances_on = config->instances; + for (i = 0; i < config->instances_on; i++) { + ret = usb_add_function(c, config->f_acm[i]); + if (ret) { + pr_err("Could not bind acm%u config\n", i); + goto err_usb_add_function; + } + } + + return 0; + +err_usb_add_function: + while (i-- > 0) + usb_remove_function(c, config->f_acm[i]); + return ret; +} + +static void acm_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int i; + struct acm_function_config *config = f->config; + + for (i = 0; i < config->instances_on; i++) + usb_remove_function(c, config->f_acm[i]); +} + +static ssize_t acm_instances_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + return sprintf(buf, "%d\n", config->instances); +} + +static ssize_t acm_instances_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + int value; + + sscanf(buf, "%d", &value); + if (value > MAX_ACM_INSTANCES) + value = MAX_ACM_INSTANCES; + config->instances = value; + return size; +} + +static DEVICE_ATTR(instances, S_IRUGO | S_IWUSR, acm_instances_show, + acm_instances_store); +static struct device_attribute *acm_function_attributes[] = { + &dev_attr_instances, + NULL +}; + +static struct android_usb_function acm_function = { + .name = "acm", + .init = acm_function_init, + .cleanup = acm_function_cleanup, + .bind_config = acm_function_bind_config, + .unbind_config = acm_function_unbind_config, + .attributes = acm_function_attributes, +}; + + +static int +mtp_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return mtp_setup(); +} + +static void mtp_function_cleanup(struct android_usb_function *f) +{ + mtp_cleanup(); +} + +static int +mtp_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return mtp_bind_config(c, false); +} + +static int +ptp_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + /* nothing to do - initialization is handled by mtp_function_init */ + return 0; +} + +static void ptp_function_cleanup(struct android_usb_function *f) +{ + /* nothing to do - cleanup is handled by mtp_function_cleanup */ +} + +static int +ptp_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return mtp_bind_config(c, true); +} + +static int mtp_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return mtp_ctrlrequest(cdev, c); +} + +static struct android_usb_function mtp_function = { + .name = "mtp", + .init = mtp_function_init, + .cleanup = mtp_function_cleanup, + .bind_config = mtp_function_bind_config, + .ctrlrequest = mtp_function_ctrlrequest, +}; + +/* PTP function is same as MTP with slightly different interface descriptor */ +static struct android_usb_function ptp_function = { + .name = "ptp", + .init = ptp_function_init, + .cleanup = ptp_function_cleanup, + .bind_config = ptp_function_bind_config, +}; + + +struct rndis_function_config { + u8 ethaddr[ETH_ALEN]; + u32 vendorID; + char manufacturer[256]; + /* "Wireless" RNDIS; auto-detected by Windows */ + bool wceis; + struct eth_dev *dev; +}; + +static int +rndis_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct rndis_function_config), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + return 0; +} + +static void rndis_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); + f->config = NULL; +} + +static int +rndis_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int ret; + struct eth_dev *dev; + struct rndis_function_config *rndis = f->config; + + if (!rndis) { + pr_err("%s: rndis_pdata\n", __func__); + return -1; + } + + pr_info("%s MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", __func__, + rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2], + rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]); + + dev = gether_setup_name(c->cdev->gadget,dev_addr, host_addr, rndis->ethaddr, qmult, "rndis"); + if (IS_ERR(dev)) { + ret = PTR_ERR(dev); + pr_err("%s: gether_setup failed\n", __func__); + return ret; + } + rndis->dev = dev; + + if (rndis->wceis) { + /* "Wireless" RNDIS; auto-detected by Windows */ + rndis_iad_descriptor.bFunctionClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_iad_descriptor.bFunctionSubClass = 0x01; + rndis_iad_descriptor.bFunctionProtocol = 0x03; + rndis_control_intf.bInterfaceClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_control_intf.bInterfaceSubClass = 0x01; + rndis_control_intf.bInterfaceProtocol = 0x03; + } + + return rndis_bind_config_vendor(c, rndis->ethaddr, rndis->vendorID, + rndis->manufacturer, rndis->dev); +} + +static void rndis_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct rndis_function_config *rndis = f->config; + gether_cleanup(rndis->dev); +} + +static ssize_t rndis_manufacturer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%s\n", config->manufacturer); +} + +static ssize_t rndis_manufacturer_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + + if (size >= sizeof(config->manufacturer)) + return -EINVAL; + if (sscanf(buf, "%s", config->manufacturer) == 1) + return size; + return -1; +} + +static DEVICE_ATTR(manufacturer, S_IRUGO | S_IWUSR, rndis_manufacturer_show, + rndis_manufacturer_store); + +static ssize_t rndis_wceis_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%d\n", config->wceis); +} + +static ssize_t rndis_wceis_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%d", &value) == 1) { + config->wceis = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(wceis, S_IRUGO | S_IWUSR, rndis_wceis_show, + rndis_wceis_store); + +static ssize_t rndis_ethaddr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *rndis = f->config; + return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2], + rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]); +} + +static ssize_t rndis_ethaddr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *rndis = f->config; + + if (sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + (int *)&rndis->ethaddr[0], (int *)&rndis->ethaddr[1], + (int *)&rndis->ethaddr[2], (int *)&rndis->ethaddr[3], + (int *)&rndis->ethaddr[4], (int *)&rndis->ethaddr[5]) == 6) + return size; + return -EINVAL; +} + +static DEVICE_ATTR(ethaddr, S_IRUGO | S_IWUSR, rndis_ethaddr_show, + rndis_ethaddr_store); + +static ssize_t rndis_vendorID_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%04x\n", config->vendorID); +} + +static ssize_t rndis_vendorID_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%04x", &value) == 1) { + config->vendorID = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(vendorID, S_IRUGO | S_IWUSR, rndis_vendorID_show, + rndis_vendorID_store); + +static struct device_attribute *rndis_function_attributes[] = { + &dev_attr_manufacturer, + &dev_attr_wceis, + &dev_attr_ethaddr, + &dev_attr_vendorID, + NULL +}; + +static struct android_usb_function rndis_function = { + .name = "rndis", + .init = rndis_function_init, + .cleanup = rndis_function_cleanup, + .bind_config = rndis_function_bind_config, + .unbind_config = rndis_function_unbind_config, + .attributes = rndis_function_attributes, +}; + + +#define MAX_MS_INSTANCES 1 +struct mass_storage_function_config { + int instances; + int instances_on; + struct usb_function *f_ms[MAX_MS_INSTANCES]; + struct usb_function_instance *f_ms_inst[MAX_MS_INSTANCES]; +}; + +static int mass_storage_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct mass_storage_function_config *config; + int i; + int ret; + + config = kzalloc(sizeof(struct mass_storage_function_config), + GFP_KERNEL); + if (!config) + return -ENOMEM; + f->config = config; + + for (i = 0; i < MAX_MS_INSTANCES; i++) { + config->f_ms_inst[i] = usb_get_function_instance("mass_storage"); + if (IS_ERR(config->f_ms_inst[i])) { + ret = PTR_ERR(config->f_ms_inst[i]); + goto err_usb_get_function_instance; + } + config->f_ms[i] = usb_get_function(config->f_ms_inst[i]); + if (IS_ERR(config->f_ms[i])) { + ret = PTR_ERR(config->f_ms[i]); + goto err_usb_get_function; + } + } + return 0; +err_usb_get_function_instance: + while (i-- > 0) { + usb_put_function(config->f_ms[i]); +err_usb_get_function: + usb_put_function_instance(config->f_ms_inst[i]); + } + return ret; +} + +static void mass_storage_function_cleanup(struct android_usb_function *f) +{ + struct mass_storage_function_config *config = f->config; + int i; + + for (i = 0; i < MAX_MS_INSTANCES; i++) { + usb_put_function(config->f_ms[i]); + usb_put_function_instance(config->f_ms_inst[i]); + } + kfree(f->config); + f->config = NULL; +} + +static int mass_storage_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct mass_storage_function_config *config = f->config; + int ret = 0; + int i; + + config->instances_on = config->instances; + for (i = 0; i < config->instances_on; i++) { + ret = usb_add_function(c, config->f_ms[i]); + if (ret) { + pr_err("Could not bind ms%u config\n", i); + goto err_usb_add_function; + } + } + + return 0; + +err_usb_add_function: + while (i-- > 0) + usb_remove_function(c, config->f_ms[i]); + return ret; +} + +static void mass_storage_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int i; + struct mass_storage_function_config *config = f->config; + + for (i = 0; i < config->instances_on; i++) + usb_remove_function(c, config->f_ms[i]); +} + +static ssize_t mass_storage_inquiry_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + return sprintf(buf, "%d\n", config->instances); +} + +static ssize_t mass_storage_inquiry_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + int value; + + sscanf(buf, "%d", &value); + if (value > MAX_MS_INSTANCES) + value = MAX_MS_INSTANCES; + config->instances = value; + return size; +} + +static DEVICE_ATTR(inquiry_string, S_IRUGO | S_IWUSR, + mass_storage_inquiry_show, + mass_storage_inquiry_store); + +static struct device_attribute *mass_storage_function_attributes[] = { + &dev_attr_inquiry_string, + NULL +}; + +static struct android_usb_function mass_storage_function = { + .name = "mass_storage", + .init = mass_storage_function_init, + .cleanup = mass_storage_function_cleanup, + .bind_config = mass_storage_function_bind_config, + .attributes = mass_storage_function_attributes, +}; + + +static int accessory_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return acc_setup(); +} + +static void accessory_function_cleanup(struct android_usb_function *f) +{ + acc_cleanup(); +} + +static int accessory_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return acc_bind_config(c); +} + +static int accessory_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return acc_ctrlrequest(cdev, c); +} + +static struct android_usb_function accessory_function = { + .name = "accessory", + .init = accessory_function_init, + .cleanup = accessory_function_cleanup, + .bind_config = accessory_function_bind_config, + .ctrlrequest = accessory_function_ctrlrequest, +}; + +static int audio_source_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct audio_source_config *config; + + config = kzalloc(sizeof(struct audio_source_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + config->card = -1; + config->device = -1; + f->config = config; + return 0; +} + +static void audio_source_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); +} + +static int audio_source_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + return audio_source_bind_config(c, config); +} + +static void audio_source_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + config->card = -1; + config->device = -1; +} + +static ssize_t audio_source_pcm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct audio_source_config *config = f->config; + + /* print PCM card and device numbers */ + return sprintf(buf, "%d %d\n", config->card, config->device); +} + +static DEVICE_ATTR(pcm, S_IRUGO, audio_source_pcm_show, NULL); + +static struct device_attribute *audio_source_function_attributes[] = { + &dev_attr_pcm, + NULL +}; + +static struct android_usb_function audio_source_function = { + .name = "audio_source", + .init = audio_source_function_init, + .cleanup = audio_source_function_cleanup, + .bind_config = audio_source_function_bind_config, + .unbind_config = audio_source_function_unbind_config, + .attributes = audio_source_function_attributes, +}; + +static struct android_usb_function *supported_functions[] = { + &ffs_function, + &acm_function, + &mtp_function, + &ptp_function, + &rndis_function, + &mass_storage_function, + &accessory_function, + &audio_source_function, + NULL +}; + + +static int android_init_functions(struct android_usb_function **functions, + struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct android_usb_function *f; + struct device_attribute **attrs; + struct device_attribute *attr; + int err; + int index = 0; + + for (; (f = *functions++); index++) { + f->dev_name = kasprintf(GFP_KERNEL, "f_%s", f->name); + f->dev = device_create(android_class, dev->dev, + MKDEV(0, index), f, f->dev_name); + if (IS_ERR(f->dev)) { + pr_err("%s: Failed to create dev %s", __func__, + f->dev_name); + err = PTR_ERR(f->dev); + goto err_create; + } + + if (f->init) { + err = f->init(f, cdev); + if (err) { + pr_err("%s: Failed to init %s", __func__, + f->name); + goto err_out; + } + } + + attrs = f->attributes; + if (attrs) { + while ((attr = *attrs++) && !err) + err = device_create_file(f->dev, attr); + } + if (err) { + pr_err("%s: Failed to create function %s attributes", + __func__, f->name); + goto err_out; + } + } + return 0; + +err_out: + device_destroy(android_class, f->dev->devt); +err_create: + kfree(f->dev_name); + return err; +} + +static void android_cleanup_functions(struct android_usb_function **functions) +{ + struct android_usb_function *f; + + while (*functions) { + f = *functions++; + + if (f->dev) { + device_destroy(android_class, f->dev->devt); + kfree(f->dev_name); + } + + if (f->cleanup) + f->cleanup(f); + } +} + +static int +android_bind_enabled_functions(struct android_dev *dev, + struct usb_configuration *c) +{ + struct android_usb_function *f; + int ret; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + ret = f->bind_config(f, c); + if (ret) { + pr_err("%s: %s failed", __func__, f->name); + return ret; + } + } + return 0; +} + +static void +android_unbind_enabled_functions(struct android_dev *dev, + struct usb_configuration *c) +{ + struct android_usb_function *f; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->unbind_config) + f->unbind_config(f, c); + } +} + +static int android_enable_function(struct android_dev *dev, char *name) +{ + struct android_usb_function **functions = dev->functions; + struct android_usb_function *f; + while ((f = *functions++)) { + if (!strcmp(name, f->name)) { + list_add_tail(&f->enabled_list, + &dev->enabled_functions); + return 0; + } + } + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ +/* /sys/class/android_usb/android%d/ interface */ + +static ssize_t +functions_show(struct device *pdev, struct device_attribute *attr, char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct android_usb_function *f; + char *buff = buf; + + mutex_lock(&dev->mutex); + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) + buff += sprintf(buff, "%s,", f->name); + + mutex_unlock(&dev->mutex); + + if (buff != buf) + *(buff-1) = '\n'; + return buff - buf; +} + +static ssize_t +functions_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + char *name; + char buf[256], *b; + char aliases[256], *a; + int err; + int is_ffs; + int ffs_enabled = 0; + + mutex_lock(&dev->mutex); + + if (dev->enabled) { + mutex_unlock(&dev->mutex); + return -EBUSY; + } + + INIT_LIST_HEAD(&dev->enabled_functions); + + strlcpy(buf, buff, sizeof(buf)); + b = strim(buf); + + while (b) { + name = strsep(&b, ","); + if (!name) + continue; + + is_ffs = 0; + strlcpy(aliases, dev->ffs_aliases, sizeof(aliases)); + a = aliases; + + while (a) { + char *alias = strsep(&a, ","); + if (alias && !strcmp(name, alias)) { + is_ffs = 1; + break; + } + } + + if (is_ffs) { + if (ffs_enabled) + continue; + err = android_enable_function(dev, "ffs"); + if (err) + pr_err("android_usb: Cannot enable ffs (%d)", + err); + else + ffs_enabled = 1; + continue; + } + + err = android_enable_function(dev, name); + if (err) + pr_err("android_usb: Cannot enable '%s' (%d)", + name, err); + } + + mutex_unlock(&dev->mutex); + + return size; +} + +static ssize_t enable_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + return sprintf(buf, "%d\n", dev->enabled); +} + +static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + struct android_usb_function *f; + int enabled = 0; + + + if (!cdev) + return -ENODEV; + + mutex_lock(&dev->mutex); + + sscanf(buff, "%d", &enabled); + if (enabled && !dev->enabled) { + cdev->next_string_id = 0; + /* + * Update values in composite driver's copy of + * device descriptor. + */ + cdev->desc.idVendor = device_desc.idVendor; + cdev->desc.idProduct = device_desc.idProduct; + cdev->desc.bcdDevice = device_desc.bcdDevice; + cdev->desc.bDeviceClass = device_desc.bDeviceClass; + cdev->desc.bDeviceSubClass = device_desc.bDeviceSubClass; + cdev->desc.bDeviceProtocol = device_desc.bDeviceProtocol; + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->enable) + f->enable(f); + } + android_enable(dev); + dev->enabled = true; + } else if (!enabled && dev->enabled) { + android_disable(dev); + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->disable) + f->disable(f); + } + dev->enabled = false; + } else { + pr_err("android_usb: already %s\n", + dev->enabled ? "enabled" : "disabled"); + } + + mutex_unlock(&dev->mutex); + return size; +} + +static ssize_t state_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + char *state = "DISCONNECTED"; + unsigned long flags; + + if (!cdev) + goto out; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + state = "CONFIGURED"; + else if (dev->connected) + state = "CONNECTED"; + spin_unlock_irqrestore(&cdev->lock, flags); +out: + return sprintf(buf, "%s\n", state); +} + +#define DESCRIPTOR_ATTR(field, format_string) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, format_string, device_desc.field); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + int value; \ + if (sscanf(buf, format_string, &value) == 1) { \ + device_desc.field = value; \ + return size; \ + } \ + return -1; \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + +#define DESCRIPTOR_STRING_ATTR(field, buffer) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, "%s", buffer); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + if (size >= sizeof(buffer)) \ + return -EINVAL; \ + return strlcpy(buffer, buf, sizeof(buffer)); \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + + +DESCRIPTOR_ATTR(idVendor, "%04x\n") +DESCRIPTOR_ATTR(idProduct, "%04x\n") +DESCRIPTOR_ATTR(bcdDevice, "%04x\n") +DESCRIPTOR_ATTR(bDeviceClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceSubClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceProtocol, "%d\n") +DESCRIPTOR_STRING_ATTR(iManufacturer, manufacturer_string) +DESCRIPTOR_STRING_ATTR(iProduct, product_string) +DESCRIPTOR_STRING_ATTR(iSerial, serial_string) + +static DEVICE_ATTR(functions, S_IRUGO | S_IWUSR, functions_show, + functions_store); +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store); +static DEVICE_ATTR(state, S_IRUGO, state_show, NULL); + +static struct device_attribute *android_usb_attributes[] = { + &dev_attr_idVendor, + &dev_attr_idProduct, + &dev_attr_bcdDevice, + &dev_attr_bDeviceClass, + &dev_attr_bDeviceSubClass, + &dev_attr_bDeviceProtocol, + &dev_attr_iManufacturer, + &dev_attr_iProduct, + &dev_attr_iSerial, + &dev_attr_functions, + &dev_attr_enable, + &dev_attr_state, + NULL +}; + +/*-------------------------------------------------------------------------*/ +/* Composite driver */ + +static int android_bind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + int ret = 0; + + ret = android_bind_enabled_functions(dev, c); + if (ret) + return ret; + + return 0; +} + +static void android_unbind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + + android_unbind_enabled_functions(dev, c); +} + +static int android_bind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct usb_gadget *gadget = cdev->gadget; + int id, ret; + + /* + * Start disconnected. Userspace will connect the gadget once + * it is done configuring the functions. + */ + usb_gadget_disconnect(gadget); + + ret = android_init_functions(dev->functions, cdev); + if (ret) + return ret; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_MANUFACTURER_IDX].id = id; + device_desc.iManufacturer = id; + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_PRODUCT_IDX].id = id; + device_desc.iProduct = id; + + /* Default strings - should be updated by userspace */ + strncpy(manufacturer_string, "Android", sizeof(manufacturer_string)-1); + strncpy(product_string, "Android", sizeof(product_string) - 1); + strncpy(serial_string, "0123456789ABCDEF", sizeof(serial_string) - 1); + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_SERIAL_IDX].id = id; + device_desc.iSerialNumber = id; + + usb_gadget_set_selfpowered(gadget); + dev->cdev = cdev; + + return 0; +} + +static int android_usb_unbind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + + cancel_work_sync(&dev->work); + android_cleanup_functions(dev->functions); + return 0; +} + +/* HACK: android needs to override setup for accessory to work */ +static int (*composite_setup_func)(struct usb_gadget *gadget, const struct usb_ctrlrequest *c); + +static int +android_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *c) +{ + struct android_dev *dev = _android_dev; + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_request *req = cdev->req; + struct android_usb_function *f; + int value = -EOPNOTSUPP; + unsigned long flags; + + req->zero = 0; + req->length = 0; + gadget->ep0->driver_data = cdev; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->ctrlrequest) { + value = f->ctrlrequest(f, cdev, c); + if (value >= 0) + break; + } + } + + /* Special case the accessory function. + * It needs to handle control requests before it is enabled. + */ + if (value < 0) + value = acc_ctrlrequest(cdev, c); + + if (value < 0) + value = composite_setup_func(gadget, c); + + spin_lock_irqsave(&cdev->lock, flags); + if (!dev->connected) { + dev->connected = 1; + schedule_work(&dev->work); + } else if (c->bRequest == USB_REQ_SET_CONFIGURATION && + cdev->config) { + schedule_work(&dev->work); + } + spin_unlock_irqrestore(&cdev->lock, flags); + + return value; +} + +static void android_disconnect(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + + /* accessory HID support can be active while the + accessory function is not actually enabled, + so we need to inform it when we are disconnected. + */ + acc_disconnect(); + + dev->connected = 0; + schedule_work(&dev->work); +} + +static struct usb_composite_driver android_usb_driver = { + .name = "android_usb", + .dev = &device_desc, + .strings = dev_strings, + .bind = android_bind, + .unbind = android_usb_unbind, + .disconnect = android_disconnect, + .max_speed = USB_SPEED_HIGH, +}; + +static int android_create_device(struct android_dev *dev) +{ + struct device_attribute **attrs = android_usb_attributes; + struct device_attribute *attr; + int err; + + dev->dev = device_create(android_class, NULL, + MKDEV(0, 0), NULL, "android0"); + if (IS_ERR(dev->dev)) + return PTR_ERR(dev->dev); + + dev_set_drvdata(dev->dev, dev); + + while ((attr = *attrs++)) { + err = device_create_file(dev->dev, attr); + if (err) { + device_destroy(android_class, dev->dev->devt); + return err; + } + } + return 0; +} + + +static int __init init(void) +{ + struct android_dev *dev; + int err; + + android_class = class_create(THIS_MODULE, "android_usb"); + if (IS_ERR(android_class)) + return PTR_ERR(android_class); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + err = -ENOMEM; + goto err_dev; + } + + dev->disable_depth = 1; + dev->functions = supported_functions; + INIT_LIST_HEAD(&dev->enabled_functions); + INIT_WORK(&dev->work, android_work); + mutex_init(&dev->mutex); + + err = android_create_device(dev); + if (err) { + pr_err("%s: failed to create android device %d", __func__, err); + goto err_create; + } + + _android_dev = dev; + + err = usb_composite_probe(&android_usb_driver); + if (err) { + pr_err("%s: failed to probe driver %d", __func__, err); + goto err_probe; + } + + /* HACK: exchange composite's setup with ours */ + composite_setup_func = android_usb_driver.gadget_driver.setup; + android_usb_driver.gadget_driver.setup = android_setup; + + return 0; + +err_probe: + device_destroy(android_class, dev->dev->devt); +err_create: + kfree(dev); +err_dev: + class_destroy(android_class); + return err; +} +late_initcall(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&android_usb_driver); + class_destroy(android_class); + kfree(_android_dev); + _android_dev = NULL; +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index d742bed7a5fa60..e9169497a4c98c 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -812,7 +812,7 @@ int usb_add_config(struct usb_composite_dev *cdev, } EXPORT_SYMBOL_GPL(usb_add_config); -static void remove_config(struct usb_composite_dev *cdev, +static void unbind_config(struct usb_composite_dev *cdev, struct usb_configuration *config) { while (!list_empty(&config->functions)) { @@ -827,7 +827,6 @@ static void remove_config(struct usb_composite_dev *cdev, /* may free memory for "f" */ } } - list_del(&config->list); if (config->unbind) { DBG(cdev, "unbind config '%s'/%p\n", config->label, config); config->unbind(config); @@ -854,9 +853,11 @@ void usb_remove_config(struct usb_composite_dev *cdev, if (cdev->config == config) reset_config(cdev); + list_del(&config->list); + spin_unlock_irqrestore(&cdev->lock, flags); - remove_config(cdev, config); + unbind_config(cdev, config); } /*-------------------------------------------------------------------------*/ @@ -1537,7 +1538,8 @@ static void __composite_unbind(struct usb_gadget *gadget, bool unbind_driver) struct usb_configuration *c; c = list_first_entry(&cdev->configs, struct usb_configuration, list); - remove_config(cdev, c); + list_del(&c->list); + unbind_config(cdev, c); } if (cdev->driver->unbind && unbind_driver) cdev->driver->unbind(cdev); diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c new file mode 100644 index 00000000000000..53e50b5e86128a --- /dev/null +++ b/drivers/usb/gadget/f_accessory.c @@ -0,0 +1,1204 @@ +/* + * Gadget Function Driver for Android USB accessories + * + * Copyright (C) 2011 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define BULK_BUFFER_SIZE 16384 +#define ACC_STRING_SIZE 256 + +#define PROTOCOL_VERSION 2 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 + +struct acc_hid_dev { + struct list_head list; + struct hid_device *hid; + struct acc_dev *dev; + /* accessory defined ID */ + int id; + /* HID report descriptor */ + u8 *report_desc; + /* length of HID report descriptor */ + int report_desc_len; + /* number of bytes of report_desc we have received so far */ + int report_desc_offset; +}; + +struct acc_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + /* set to 1 when we connect */ + int online:1; + /* Set to 1 when we disconnect. + * Not cleared until our file is closed. + */ + int disconnected:1; + + /* strings sent by the host */ + char manufacturer[ACC_STRING_SIZE]; + char model[ACC_STRING_SIZE]; + char description[ACC_STRING_SIZE]; + char version[ACC_STRING_SIZE]; + char uri[ACC_STRING_SIZE]; + char serial[ACC_STRING_SIZE]; + + /* for acc_complete_set_string */ + int string_index; + + /* set to 1 if we have a pending start request */ + int start_requested; + + int audio_mode; + + /* synchronize access to our device file */ + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + + /* delayed work for handling ACCESSORY_START */ + struct delayed_work start_work; + + /* worker for registering and unregistering hid devices */ + struct work_struct hid_work; + + /* list of active HID devices */ + struct list_head hid_list; + + /* list of new HID devices to register */ + struct list_head new_hid_list; + + /* list of dead HID devices to unregister */ + struct list_head dead_hid_list; +}; + +static struct usb_interface_descriptor acc_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_endpoint_descriptor acc_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor acc_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_fullspeed_in_desc, + (struct usb_descriptor_header *) &acc_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_highspeed_in_desc, + (struct usb_descriptor_header *) &acc_highspeed_out_desc, + NULL, +}; + +static struct usb_string acc_string_defs[] = { + [INTERFACE_STRING_INDEX].s = "Android Accessory Interface", + { }, /* end of list */ +}; + +static struct usb_gadget_strings acc_string_table = { + .language = 0x0409, /* en-US */ + .strings = acc_string_defs, +}; + +static struct usb_gadget_strings *acc_strings[] = { + &acc_string_table, + NULL, +}; + +/* temporary variable used between acc_open() and acc_gadget_bind() */ +static struct acc_dev *_acc_dev; + +static inline struct acc_dev *func_to_dev(struct usb_function *f) +{ + return container_of(f, struct acc_dev, function); +} + +static struct usb_request *acc_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void acc_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +/* add a request to the tail of a list */ +static void req_put(struct acc_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *req_get(struct acc_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void acc_set_disconnected(struct acc_dev *dev) +{ + dev->online = 0; + dev->disconnected = 1; +} + +static void acc_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = _acc_dev; + + if (req->status == -ESHUTDOWN) { + pr_debug("acc_complete_in set disconnected"); + acc_set_disconnected(dev); + } + + req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void acc_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = _acc_dev; + + dev->rx_done = 1; + if (req->status == -ESHUTDOWN) { + pr_debug("acc_complete_out set disconnected"); + acc_set_disconnected(dev); + } + + wake_up(&dev->read_wq); +} + +static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = ep->driver_data; + char *string_dest = NULL; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_set_string, err %d\n", req->status); + return; + } + + switch (dev->string_index) { + case ACCESSORY_STRING_MANUFACTURER: + string_dest = dev->manufacturer; + break; + case ACCESSORY_STRING_MODEL: + string_dest = dev->model; + break; + case ACCESSORY_STRING_DESCRIPTION: + string_dest = dev->description; + break; + case ACCESSORY_STRING_VERSION: + string_dest = dev->version; + break; + case ACCESSORY_STRING_URI: + string_dest = dev->uri; + break; + case ACCESSORY_STRING_SERIAL: + string_dest = dev->serial; + break; + } + if (string_dest) { + unsigned long flags; + + if (length >= ACC_STRING_SIZE) + length = ACC_STRING_SIZE - 1; + + spin_lock_irqsave(&dev->lock, flags); + memcpy(string_dest, req->buf, length); + /* ensure zero termination */ + string_dest[length] = 0; + spin_unlock_irqrestore(&dev->lock, flags); + } else { + pr_err("unknown accessory string index %d\n", + dev->string_index); + } +} + +static void acc_complete_set_hid_report_desc(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + struct acc_dev *dev = hid->dev; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_set_hid_report_desc, err %d\n", + req->status); + return; + } + + memcpy(hid->report_desc + hid->report_desc_offset, req->buf, length); + hid->report_desc_offset += length; + if (hid->report_desc_offset == hid->report_desc_len) { + /* After we have received the entire report descriptor + * we schedule work to initialize the HID device + */ + schedule_work(&dev->hid_work); + } +} + +static void acc_complete_send_hid_event(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_send_hid_event, err %d\n", req->status); + return; + } + + hid_report_raw_event(hid->hid, HID_INPUT_REPORT, req->buf, length, 1); +} + +static int acc_hid_parse(struct hid_device *hid) +{ + struct acc_hid_dev *hdev = hid->driver_data; + + hid_parse_report(hid, hdev->report_desc, hdev->report_desc_len); + return 0; +} + +static int acc_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_stop(struct hid_device *hid) +{ +} + +static int acc_hid_open(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_close(struct hid_device *hid) +{ +} + +static struct hid_ll_driver acc_hid_ll_driver = { + .parse = acc_hid_parse, + .start = acc_hid_start, + .stop = acc_hid_stop, + .open = acc_hid_open, + .close = acc_hid_close, +}; + +static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev, + int id, int desc_len) +{ + struct acc_hid_dev *hdev; + + hdev = kzalloc(sizeof(*hdev), GFP_ATOMIC); + if (!hdev) + return NULL; + hdev->report_desc = kzalloc(desc_len, GFP_ATOMIC); + if (!hdev->report_desc) { + kfree(hdev); + return NULL; + } + hdev->dev = dev; + hdev->id = id; + hdev->report_desc_len = desc_len; + + return hdev; +} + +static struct acc_hid_dev *acc_hid_get(struct list_head *list, int id) +{ + struct acc_hid_dev *hid; + + list_for_each_entry(hid, list, list) { + if (hid->id == id) + return hid; + } + return NULL; +} + +static int acc_register_hid(struct acc_dev *dev, int id, int desc_length) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + /* report descriptor length must be > 0 */ + if (desc_length <= 0) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + /* replace HID if one already exists with this ID */ + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (hid) + list_move(&hid->list, &dev->dead_hid_list); + + hid = acc_hid_new(dev, id, desc_length); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -ENOMEM; + } + + list_add(&hid->list, &dev->new_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + /* schedule work to register the HID device */ + schedule_work(&dev->hid_work); + return 0; +} + +static int acc_unregister_hid(struct acc_dev *dev, int id) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -EINVAL; + } + + list_move(&hid->list, &dev->dead_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); + return 0; +} + +static int create_bulk_endpoints(struct acc_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_in, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_in; + req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_out, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_out; + dev->rx_req[i] = req; + } + + return 0; + +fail: + pr_err("acc_bind() could not allocate requests\n"); + while ((req = req_get(dev, &dev->tx_idle))) + acc_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + acc_request_free(dev->rx_req[i], dev->ep_out); + return -1; +} + +static ssize_t acc_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req; + ssize_t r = count; + unsigned xfer; + int ret = 0; + + pr_debug("acc_read(%zu)\n", count); + + if (dev->disconnected) { + pr_debug("acc_read disconnected"); + return -ENODEV; + } + + if (count > BULK_BUFFER_SIZE) + count = BULK_BUFFER_SIZE; + + /* we will block until we're online */ + pr_debug("acc_read: waiting for online\n"); + ret = wait_event_interruptible(dev->read_wq, dev->online); + if (ret < 0) { + r = ret; + goto done; + } + + if (dev->rx_done) { + // last req cancelled. try to get it. + req = dev->rx_req[0]; + goto copy_data; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + ret = usb_ep_dequeue(dev->ep_out, req); + if (ret != 0) { + // cancel failed. There can be a data already received. + // it will be retrieved in the next read. + pr_debug("acc_read: cancelling failed %d", ret); + } + goto done; + } + +copy_data: + dev->rx_done = 0; + if (dev->online) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %u\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + pr_debug("acc_read returning %zd\n", r); + return r; +} + +static ssize_t acc_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req = 0; + ssize_t r = count; + unsigned xfer; + int ret; + + pr_debug("acc_write(%zu)\n", count); + + if (!dev->online || dev->disconnected) { + pr_debug("acc_write disconnected or not online"); + return -ENODEV; + } + + while (count > 0) { + if (!dev->online) { + pr_debug("acc_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = req_get(dev, &dev->tx_idle)) || !dev->online)); + if (!req) { + r = ret; + break; + } + + if (count > BULK_BUFFER_SIZE) + xfer = BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + pr_debug("acc_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + req_put(dev, &dev->tx_idle, req); + + pr_debug("acc_write returning %zd\n", r); + return r; +} + +static long acc_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct acc_dev *dev = fp->private_data; + char *src = NULL; + int ret; + + switch (code) { + case ACCESSORY_GET_STRING_MANUFACTURER: + src = dev->manufacturer; + break; + case ACCESSORY_GET_STRING_MODEL: + src = dev->model; + break; + case ACCESSORY_GET_STRING_DESCRIPTION: + src = dev->description; + break; + case ACCESSORY_GET_STRING_VERSION: + src = dev->version; + break; + case ACCESSORY_GET_STRING_URI: + src = dev->uri; + break; + case ACCESSORY_GET_STRING_SERIAL: + src = dev->serial; + break; + case ACCESSORY_IS_START_REQUESTED: + return dev->start_requested; + case ACCESSORY_GET_AUDIO_MODE: + return dev->audio_mode; + } + if (!src) + return -EINVAL; + + ret = strlen(src) + 1; + if (copy_to_user((void __user *)value, src, ret)) + ret = -EFAULT; + return ret; +} + +static int acc_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "acc_open\n"); + if (atomic_xchg(&_acc_dev->open_excl, 1)) + return -EBUSY; + + _acc_dev->disconnected = 0; + fp->private_data = _acc_dev; + return 0; +} + +static int acc_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "acc_release\n"); + + WARN_ON(!atomic_xchg(&_acc_dev->open_excl, 0)); + _acc_dev->disconnected = 0; + return 0; +} + +/* file operations for /dev/usb_accessory */ +static const struct file_operations acc_fops = { + .owner = THIS_MODULE, + .read = acc_read, + .write = acc_write, + .unlocked_ioctl = acc_ioctl, + .open = acc_open, + .release = acc_release, +}; + +static int acc_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) + return ret; + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + +static struct miscdevice acc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "usb_accessory", + .fops = &acc_fops, +}; + +static const struct hid_device_id acc_hid_table[] = { + { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) }, + { } +}; + +static struct hid_driver acc_hid_driver = { + .name = "USB accessory", + .id_table = acc_hid_table, + .probe = acc_hid_probe, +}; + +static int acc_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct acc_dev *dev = _acc_dev; + int value = -EOPNOTSUPP; + struct acc_hid_dev *hid; + int offset; + u8 b_requestType = ctrl->bRequestType; + u8 b_request = ctrl->bRequest; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + unsigned long flags; + +/* + printk(KERN_INFO "acc_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + b_requestType, b_request, + w_value, w_index, w_length); +*/ + + if (b_requestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) { + if (b_request == ACCESSORY_START) { + dev->start_requested = 1; + schedule_delayed_work( + &dev->start_work, msecs_to_jiffies(10)); + value = 0; + } else if (b_request == ACCESSORY_SEND_STRING) { + dev->string_index = w_index; + cdev->gadget->ep0->driver_data = dev; + cdev->req->complete = acc_complete_set_string; + value = w_length; + } else if (b_request == ACCESSORY_SET_AUDIO_MODE && + w_index == 0 && w_length == 0) { + dev->audio_mode = w_value; + value = 0; + } else if (b_request == ACCESSORY_REGISTER_HID) { + value = acc_register_hid(dev, w_value, w_index); + } else if (b_request == ACCESSORY_UNREGISTER_HID) { + value = acc_unregister_hid(dev, w_value); + } else if (b_request == ACCESSORY_SET_HID_REPORT_DESC) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->new_hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + offset = w_index; + if (offset != hid->report_desc_offset + || offset + w_length > hid->report_desc_len) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_set_hid_report_desc; + value = w_length; + } else if (b_request == ACCESSORY_SEND_HID_EVENT) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_send_hid_event; + value = w_length; + } + } else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) { + if (b_request == ACCESSORY_GET_PROTOCOL) { + *((u16 *)cdev->req->buf) = PROTOCOL_VERSION; + value = sizeof(u16); + + /* clear any string left over from a previous session */ + memset(dev->manufacturer, 0, sizeof(dev->manufacturer)); + memset(dev->model, 0, sizeof(dev->model)); + memset(dev->description, 0, sizeof(dev->description)); + memset(dev->version, 0, sizeof(dev->version)); + memset(dev->uri, 0, sizeof(dev->uri)); + memset(dev->serial, 0, sizeof(dev->serial)); + dev->start_requested = 0; + dev->audio_mode = 0; + } + } + + if (value >= 0) { + cdev->req->zero = 0; + cdev->req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "%s setup response queue error\n", + __func__); + } + +err: + if (value == -EOPNOTSUPP) + VDBG(cdev, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + return value; +} + +static int +acc_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct acc_dev *dev = func_to_dev(f); + int id; + int ret; + + DBG(cdev, "acc_function_bind dev: %p\n", dev); + + ret = hid_register_driver(&acc_hid_driver); + if (ret) + return ret; + + dev->start_requested = 0; + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + acc_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = create_bulk_endpoints(dev, &acc_fullspeed_in_desc, + &acc_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + acc_highspeed_in_desc.bEndpointAddress = + acc_fullspeed_in_desc.bEndpointAddress; + acc_highspeed_out_desc.bEndpointAddress = + acc_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +kill_all_hid_devices(struct acc_dev *dev) +{ + struct acc_hid_dev *hid; + struct list_head *entry, *temp; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_for_each_safe(entry, temp, &dev->hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); +} + +static void +acc_hid_unbind(struct acc_dev *dev) +{ + hid_unregister_driver(&acc_hid_driver); + kill_all_hid_devices(dev); +} + +static void +acc_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_request *req; + int i; + + while ((req = req_get(dev, &dev->tx_idle))) + acc_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + acc_request_free(dev->rx_req[i], dev->ep_out); + + acc_hid_unbind(dev); +} + +static void acc_start_work(struct work_struct *data) +{ + char *envp[2] = { "ACCESSORY=START", NULL }; + kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp); +} + +static int acc_hid_init(struct acc_hid_dev *hdev) +{ + struct hid_device *hid; + int ret; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + hid->ll_driver = &acc_hid_ll_driver; + hid->dev.parent = acc_device.this_device; + + hid->bus = BUS_USB; + hid->vendor = HID_ANY_ID; + hid->product = HID_ANY_ID; + hid->driver_data = hdev; + ret = hid_add_device(hid); + if (ret) { + pr_err("can't add hid device: %d\n", ret); + hid_destroy_device(hid); + return ret; + } + + hdev->hid = hid; + return 0; +} + +static void acc_hid_delete(struct acc_hid_dev *hid) +{ + kfree(hid->report_desc); + kfree(hid); +} + +static void acc_hid_work(struct work_struct *data) +{ + struct acc_dev *dev = _acc_dev; + struct list_head *entry, *temp; + struct acc_hid_dev *hid; + struct list_head new_list, dead_list; + unsigned long flags; + + INIT_LIST_HEAD(&new_list); + + spin_lock_irqsave(&dev->lock, flags); + + /* copy hids that are ready for initialization to new_list */ + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (hid->report_desc_offset == hid->report_desc_len) + list_move(&hid->list, &new_list); + } + + if (list_empty(&dev->dead_hid_list)) { + INIT_LIST_HEAD(&dead_list); + } else { + /* move all of dev->dead_hid_list to dead_list */ + dead_list.prev = dev->dead_hid_list.prev; + dead_list.next = dev->dead_hid_list.next; + dead_list.next->prev = &dead_list; + dead_list.prev->next = &dead_list; + INIT_LIST_HEAD(&dev->dead_hid_list); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + /* register new HID devices */ + list_for_each_safe(entry, temp, &new_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (acc_hid_init(hid)) { + pr_err("can't add HID device %p\n", hid); + acc_hid_delete(hid); + } else { + spin_lock_irqsave(&dev->lock, flags); + list_move(&hid->list, &dev->hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + } + } + + /* remove dead HID devices */ + list_for_each_safe(entry, temp, &dead_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + if (hid->hid) + hid_destroy_device(hid->hid); + acc_hid_delete(hid); + } +} + +static int acc_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "acc_function_set_alt intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void acc_function_disable(struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "acc_function_disable\n"); + acc_set_disconnected(dev); + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int acc_bind_config(struct usb_configuration *c) +{ + struct acc_dev *dev = _acc_dev; + int ret; + + printk(KERN_INFO "acc_bind_config\n"); + + /* allocate a string ID for our interface */ + if (acc_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + acc_string_defs[INTERFACE_STRING_INDEX].id = ret; + acc_interface_desc.iInterface = ret; + } + + dev->cdev = c->cdev; + dev->function.name = "accessory"; + dev->function.strings = acc_strings, + dev->function.fs_descriptors = fs_acc_descs; + dev->function.hs_descriptors = hs_acc_descs; + dev->function.bind = acc_function_bind; + dev->function.unbind = acc_function_unbind; + dev->function.set_alt = acc_function_set_alt; + dev->function.disable = acc_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int acc_setup(void) +{ + struct acc_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + atomic_set(&dev->open_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->hid_list); + INIT_LIST_HEAD(&dev->new_hid_list); + INIT_LIST_HEAD(&dev->dead_hid_list); + INIT_DELAYED_WORK(&dev->start_work, acc_start_work); + INIT_WORK(&dev->hid_work, acc_hid_work); + + /* _acc_dev must be set before calling usb_gadget_register_driver */ + _acc_dev = dev; + + ret = misc_register(&acc_device); + if (ret) + goto err; + + return 0; + +err: + kfree(dev); + pr_err("USB accessory gadget driver failed to initialize\n"); + return ret; +} + +static void acc_disconnect(void) +{ + /* unregister all HID devices if USB is disconnected */ + kill_all_hid_devices(_acc_dev); +} + +static void acc_cleanup(void) +{ + misc_deregister(&acc_device); + kfree(_acc_dev); + _acc_dev = NULL; +} diff --git a/drivers/usb/gadget/f_audio_source.c b/drivers/usb/gadget/f_audio_source.c new file mode 100644 index 00000000000000..56dcf217cfe564 --- /dev/null +++ b/drivers/usb/gadget/f_audio_source.c @@ -0,0 +1,828 @@ +/* + * Gadget Function Driver for USB audio source device + * + * Copyright (C) 2012 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include + +#define SAMPLE_RATE 44100 +#define FRAMES_PER_MSEC (SAMPLE_RATE / 1000) + +#define IN_EP_MAX_PACKET_SIZE 384 + +/* Number of requests to allocate */ +#define IN_EP_REQ_COUNT 4 + +#define AUDIO_AC_INTERFACE 0 +#define AUDIO_AS_INTERFACE 1 +#define AUDIO_NUM_INTERFACES 2 + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); + +#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(AUDIO_NUM_INTERFACES) +/* 1 input terminal, 1 output terminal and 1 feature unit */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \ + + UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE \ + + UAC_DT_FEATURE_UNIT_SIZE(0)) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor_2 ac_header_desc = { + .bLength = UAC_DT_AC_HEADER_LENGTH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_HEADER, + .bcdADC = __constant_cpu_to_le16(0x0100), + .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bInCollection = AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + [0] = AUDIO_AC_INTERFACE, + [1] = AUDIO_AS_INTERFACE, + } +}; + +#define INPUT_TERMINAL_ID 1 +static struct uac_input_terminal_descriptor input_terminal_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = INPUT_TERMINAL_ID, + .wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID 2 +static struct uac_feature_unit_descriptor_0 feature_unit_desc = { + .bLength = UAC_DT_FEATURE_UNIT_SIZE(0), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FEATURE_UNIT, + .bUnitID = FEATURE_UNIT_ID, + .bSourceID = INPUT_TERMINAL_ID, + .bControlSize = 2, +}; + +#define OUTPUT_TERMINAL_ID 3 +static struct uac1_output_terminal_descriptor output_terminal_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = OUTPUT_TERMINAL_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = FEATURE_UNIT_ID, + .bSourceID = FEATURE_UNIT_ID, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2 Class-Specific AS Interface Descriptor */ +static struct uac1_as_header_descriptor as_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO IN Endpoint Descriptor for highspeed */ +static struct usb_endpoint_descriptor hs_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_SYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE), + .bInterval = 4, /* poll 1 per millisecond */ +}; + +/* Standard ISO IN Endpoint Descriptor for highspeed */ +static struct usb_endpoint_descriptor fs_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_SYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE), + .bInterval = 1, /* poll 1 per millisecond */ +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_in_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = __constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *hs_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&hs_as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&fs_as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +static struct snd_pcm_hardware audio_hw_info = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = SAMPLE_RATE, + .rate_max = SAMPLE_RATE, + + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 512 * 1024, + .periods_min = 2, + .periods_max = 1024, +}; + +/*-------------------------------------------------------------------------*/ + +struct audio_source_config { + int card; + int device; +}; + +struct audio_dev { + struct usb_function func; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + + struct list_head idle_reqs; + struct usb_ep *in_ep; + + spinlock_t lock; + + /* beginning, end and current position in our buffer */ + void *buffer_start; + void *buffer_end; + void *buffer_pos; + + /* byte size of a "period" */ + unsigned int period; + /* bytes sent since last call to snd_pcm_period_elapsed */ + unsigned int period_offset; + /* time we started playing */ + ktime_t start_time; + /* number of frames sent since start_time */ + s64 frames_sent; +}; + +static inline struct audio_dev *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct audio_dev, func); +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_request *audio_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + req->length = buffer_size; + return req; +} + +static void audio_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static void audio_req_put(struct audio_dev *audio, struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + list_add_tail(&req->list, &audio->idle_reqs); + spin_unlock_irqrestore(&audio->lock, flags); +} + +static struct usb_request *audio_req_get(struct audio_dev *audio) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&audio->lock, flags); + if (list_empty(&audio->idle_reqs)) { + req = 0; + } else { + req = list_first_entry(&audio->idle_reqs, struct usb_request, + list); + list_del(&req->list); + } + spin_unlock_irqrestore(&audio->lock, flags); + return req; +} + +/* send the appropriate number of packets to match our bitrate */ +static void audio_send(struct audio_dev *audio) +{ + struct snd_pcm_runtime *runtime; + struct usb_request *req; + int length, length1, length2, ret; + s64 msecs; + s64 frames; + ktime_t now; + + /* audio->substream will be null if we have been closed */ + if (!audio->substream) + return; + /* audio->buffer_pos will be null if we have been stopped */ + if (!audio->buffer_pos) + return; + + runtime = audio->substream->runtime; + + /* compute number of frames to send */ + now = ktime_get(); + msecs = ktime_to_ns(now) - ktime_to_ns(audio->start_time); + do_div(msecs, 1000000); + frames = msecs * SAMPLE_RATE; + do_div(frames, 1000); + + /* Readjust our frames_sent if we fall too far behind. + * If we get too far behind it is better to drop some frames than + * to keep sending data too fast in an attempt to catch up. + */ + if (frames - audio->frames_sent > 10 * FRAMES_PER_MSEC) + audio->frames_sent = frames - FRAMES_PER_MSEC; + + frames -= audio->frames_sent; + + /* We need to send something to keep the pipeline going */ + if (frames <= 0) + frames = FRAMES_PER_MSEC; + + while (frames > 0) { + req = audio_req_get(audio); + if (!req) + break; + + length = frames_to_bytes(runtime, frames); + if (length > IN_EP_MAX_PACKET_SIZE) + length = IN_EP_MAX_PACKET_SIZE; + + if (audio->buffer_pos + length > audio->buffer_end) + length1 = audio->buffer_end - audio->buffer_pos; + else + length1 = length; + memcpy(req->buf, audio->buffer_pos, length1); + if (length1 < length) { + /* Wrap around and copy remaining length + * at beginning of buffer. + */ + length2 = length - length1; + memcpy(req->buf + length1, audio->buffer_start, + length2); + audio->buffer_pos = audio->buffer_start + length2; + } else { + audio->buffer_pos += length1; + if (audio->buffer_pos >= audio->buffer_end) + audio->buffer_pos = audio->buffer_start; + } + + req->length = length; + ret = usb_ep_queue(audio->in_ep, req, GFP_ATOMIC); + if (ret < 0) { + pr_err("usb_ep_queue failed ret: %d\n", ret); + audio_req_put(audio, req); + break; + } + + frames -= bytes_to_frames(runtime, length); + audio->frames_sent += bytes_to_frames(runtime, length); + } +} + +static void audio_control_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* nothing to do here */ +} + +static void audio_data_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct audio_dev *audio = req->context; + + pr_debug("audio_data_complete req->status %d req->actual %d\n", + req->status, req->actual); + + audio_req_put(audio, req); + + if (!audio->buffer_start || req->status) + return; + + audio->period_offset += req->actual; + if (audio->period_offset >= audio->period) { + snd_pcm_period_elapsed(audio->substream); + audio->period_offset = 0; + } + audio_send(audio); +} + +static int audio_set_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + int value = -EOPNOTSUPP; + u16 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_SET_CUR: + case UAC_SET_MIN: + case UAC_SET_MAX: + case UAC_SET_RES: + value = len; + break; + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 *buf = cdev->req->buf; + + pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + if (w_value == UAC_EP_CS_ATTR_SAMPLE_RATE << 8) { + switch (ctrl->bRequest) { + case UAC_GET_CUR: + case UAC_GET_MIN: + case UAC_GET_MAX: + case UAC_GET_RES: + /* return our sample rate */ + buf[0] = (u8)SAMPLE_RATE; + buf[1] = (u8)(SAMPLE_RATE >> 8); + buf[2] = (u8)(SAMPLE_RATE >> 16); + value = 3; + break; + default: + break; + } + } + + return value; +} + +static int +audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything; interface + * activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_set_endpoint_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_get_endpoint_req(f, ctrl); + break; + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + pr_debug("audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + req->complete = audio_control_complete; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + pr_err("audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct audio_dev *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + pr_debug("audio_set_alt intf %d, alt %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, audio->in_ep); + if (ret) + return ret; + + usb_ep_enable(audio->in_ep); + return 0; +} + +static void audio_disable(struct usb_function *f) +{ + struct audio_dev *audio = func_to_audio(f); + + pr_debug("audio_disable\n"); + usb_ep_disable(audio->in_ep); +} + +/*-------------------------------------------------------------------------*/ + +static void audio_build_desc(struct audio_dev *audio) +{ + u8 *sam_freq; + int rate; + + /* Set channel numbers */ + input_terminal_desc.bNrChannels = 2; + as_type_i_desc.bNrChannels = 2; + + /* Set sample rates */ + rate = SAMPLE_RATE; + sam_freq = as_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); +} + +/* audio function driver setup/binding */ +static int +audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct audio_dev *audio = func_to_audio(f); + int status; + struct usb_ep *ep; + struct usb_request *req; + int i; + + audio_build_desc(audio); + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_interface_alt_0_desc.bInterfaceNumber = status; + as_interface_alt_1_desc.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate our endpoint */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_ep_desc); + if (!ep) + goto fail; + audio->in_ep = ep; + ep->driver_data = audio; /* claim */ + + if (gadget_is_dualspeed(c->cdev->gadget)) + hs_as_in_ep_desc.bEndpointAddress = + fs_as_in_ep_desc.bEndpointAddress; + + f->fs_descriptors = fs_audio_desc; + f->hs_descriptors = hs_audio_desc; + + for (i = 0, status = 0; i < IN_EP_REQ_COUNT && status == 0; i++) { + req = audio_request_new(ep, IN_EP_MAX_PACKET_SIZE); + if (req) { + req->context = audio; + req->complete = audio_data_complete; + audio_req_put(audio, req); + } else + status = -ENOMEM; + } + +fail: + return status; +} + +static void +audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct audio_dev *audio = func_to_audio(f); + struct usb_request *req; + + while ((req = audio_req_get(audio))) + audio_request_free(req, audio->in_ep); + + snd_card_free_when_closed(audio->card); + audio->card = NULL; + audio->pcm = NULL; + audio->substream = NULL; + audio->in_ep = NULL; +} + +static void audio_pcm_playback_start(struct audio_dev *audio) +{ + audio->start_time = ktime_get(); + audio->frames_sent = 0; + audio_send(audio); +} + +static void audio_pcm_playback_stop(struct audio_dev *audio) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + audio->buffer_start = 0; + audio->buffer_end = 0; + audio->buffer_pos = 0; + spin_unlock_irqrestore(&audio->lock, flags); +} + +static int audio_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = substream->private_data; + + runtime->private_data = audio; + runtime->hw = audio_hw_info; + snd_pcm_limit_hw_rates(runtime); + runtime->hw.channels_max = 2; + + audio->substream = substream; + return 0; +} + +static int audio_pcm_close(struct snd_pcm_substream *substream) +{ + struct audio_dev *audio = substream->private_data; + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + audio->substream = NULL; + spin_unlock_irqrestore(&audio->lock, flags); + + return 0; +} + +static int audio_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + + if (rate != SAMPLE_RATE) + return -EINVAL; + if (channels != 2) + return -EINVAL; + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(params)); +} + +static int audio_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int audio_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = runtime->private_data; + + audio->period = snd_pcm_lib_period_bytes(substream); + audio->period_offset = 0; + audio->buffer_start = runtime->dma_area; + audio->buffer_end = audio->buffer_start + + snd_pcm_lib_buffer_bytes(substream); + audio->buffer_pos = audio->buffer_start; + + return 0; +} + +static snd_pcm_uframes_t audio_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = runtime->private_data; + ssize_t bytes = audio->buffer_pos - audio->buffer_start; + + /* return offset of next frame to fill in our buffer */ + return bytes_to_frames(runtime, bytes); +} + +static int audio_pcm_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct audio_dev *audio = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + audio_pcm_playback_start(audio); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + audio_pcm_playback_stop(audio); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static struct audio_dev _audio_dev = { + .func = { + .name = "audio_source", + .bind = audio_bind, + .unbind = audio_unbind, + .set_alt = audio_set_alt, + .setup = audio_setup, + .disable = audio_disable, + }, + .lock = __SPIN_LOCK_UNLOCKED(_audio_dev.lock), + .idle_reqs = LIST_HEAD_INIT(_audio_dev.idle_reqs), +}; + +static struct snd_pcm_ops audio_playback_ops = { + .open = audio_pcm_open, + .close = audio_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = audio_pcm_hw_params, + .hw_free = audio_pcm_hw_free, + .prepare = audio_pcm_prepare, + .trigger = audio_pcm_playback_trigger, + .pointer = audio_pcm_pointer, +}; + +int audio_source_bind_config(struct usb_configuration *c, + struct audio_source_config *config) +{ + struct audio_dev *audio; + struct snd_card *card; + struct snd_pcm *pcm; + int err; + + config->card = -1; + config->device = -1; + + audio = &_audio_dev; + + err = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (err) + return err; + + snd_card_set_dev(card, &c->cdev->gadget->dev); + + err = snd_pcm_new(card, "USB audio source", 0, 1, 0, &pcm); + if (err) + goto pcm_fail; + pcm->private_data = audio; + pcm->info_flags = 0; + audio->pcm = pcm; + + strlcpy(pcm->name, "USB gadget audio", sizeof(pcm->name)); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &audio_playback_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + NULL, 0, 64 * 1024); + + strlcpy(card->driver, "audio_source", sizeof(card->driver)); + strlcpy(card->shortname, card->driver, sizeof(card->shortname)); + strlcpy(card->longname, "USB accessory audio source", + sizeof(card->longname)); + + err = snd_card_register(card); + if (err) + goto register_fail; + + err = usb_add_function(c, &audio->func); + if (err) + goto add_fail; + + config->card = pcm->card->number; + config->device = pcm->device; + audio->card = card; + return 0; + +add_fail: +register_fail: +pcm_fail: + snd_card_free(audio->card); + return err; +} diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 2b433439407662..ebbae4667b1110 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -98,12 +98,15 @@ static struct ffs_function *ffs_func_from_usb(struct usb_function *f) return container_of(f, struct ffs_function, function); } +static void ffs_func_free(struct ffs_function *func); static void ffs_func_eps_disable(struct ffs_function *func); static int __must_check ffs_func_eps_enable(struct ffs_function *func); static int ffs_func_bind(struct usb_configuration *, struct usb_function *); +static void old_ffs_func_unbind(struct usb_configuration *, + struct usb_function *); static int ffs_func_set_alt(struct usb_function *, unsigned, unsigned); static void ffs_func_disable(struct usb_function *); static int ffs_func_setup(struct usb_function *, @@ -1302,6 +1305,73 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) } +static int functionfs_bind_config(struct usb_composite_dev *cdev, + struct usb_configuration *c, + struct ffs_data *ffs) +{ + struct ffs_function *func; + int ret; + + ENTER(); + + func = kzalloc(sizeof *func, GFP_KERNEL); + if (unlikely(!func)) + return -ENOMEM; + + func->function.name = "Function FS Gadget"; + func->function.strings = ffs->stringtabs; + + func->function.bind = ffs_func_bind; + func->function.unbind = old_ffs_func_unbind; + func->function.set_alt = ffs_func_set_alt; + func->function.disable = ffs_func_disable; + func->function.setup = ffs_func_setup; + func->function.suspend = ffs_func_suspend; + func->function.resume = ffs_func_resume; + + func->conf = c; + func->gadget = cdev->gadget; + func->ffs = ffs; + ffs_data_get(ffs); + + ret = usb_add_function(c, &func->function); + if (unlikely(ret)) + ffs_func_free(func); + + return ret; +} + +static void ffs_func_free(struct ffs_function *func) +{ + struct ffs_ep *ep = func->eps; + unsigned count = func->ffs->eps_count; + unsigned long flags; + + ENTER(); + + /* cleanup after autoconfig */ + spin_lock_irqsave(&func->ffs->eps_lock, flags); + do { + if (ep->ep && ep->req) + usb_ep_free_request(ep->ep, ep->req); + ep->req = NULL; + ++ep; + } while (--count); + spin_unlock_irqrestore(&func->ffs->eps_lock, flags); + + ffs_data_put(func->ffs); + + kfree(func->eps); + /* + * eps and interfaces_nums are allocated in the same chunk so + * only one free is required. Descriptors are also allocated + * in the same chunk. + */ + + kfree(func); +} + + static void ffs_func_eps_disable(struct ffs_function *func) { struct ffs_ep *ep = func->eps; @@ -1334,7 +1404,12 @@ static int ffs_func_eps_enable(struct ffs_function *func) spin_lock_irqsave(&func->ffs->eps_lock, flags); do { struct usb_endpoint_descriptor *ds; - ds = ep->descs[ep->descs[1] ? 1 : 0]; + int desc_idx = ffs->gadget->speed == USB_SPEED_HIGH ? 1 : 0; + ds = ep->descs[desc_idx]; + if (!ds) { + ret = -EINVAL; + break; + } ep->ep->driver_data = ep; ep->ep->desc = ds; @@ -2128,6 +2203,26 @@ static int ffs_func_bind(struct usb_configuration *c, /* Other USB function hooks *************************************************/ + +static void old_ffs_func_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + + ENTER(); + + if (ffs->func == func) { + ffs_func_eps_disable(func); + ffs->func = NULL; + } + + ffs_event_add(ffs, FUNCTIONFS_UNBIND); + + ffs_func_free(func); +} + + static int ffs_func_set_alt(struct usb_function *f, unsigned interface, unsigned alt) { diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c new file mode 100644 index 00000000000000..620aeaaf2d7259 --- /dev/null +++ b/drivers/usb/gadget/f_mtp.c @@ -0,0 +1,1287 @@ +/* + * Gadget Function Driver for MTP + * + * Copyright (C) 2010 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MTP_BULK_BUFFER_SIZE 16384 +#define INTR_BUFFER_SIZE 28 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* values for mtp_dev.state */ +#define STATE_OFFLINE 0 /* initial state, disconnected */ +#define STATE_READY 1 /* ready for userspace calls */ +#define STATE_BUSY 2 /* processing userspace calls */ +#define STATE_CANCELED 3 /* transaction canceled by host */ +#define STATE_ERROR 4 /* error from completion routine */ + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 +#define INTR_REQ_MAX 5 + +/* ID for Microsoft MTP OS String */ +#define MTP_OS_STRING_ID 0xEE + +/* MTP class reqeusts */ +#define MTP_REQ_CANCEL 0x64 +#define MTP_REQ_GET_EXT_EVENT_DATA 0x65 +#define MTP_REQ_RESET 0x66 +#define MTP_REQ_GET_DEVICE_STATUS 0x67 + +/* constants for device status */ +#define MTP_RESPONSE_OK 0x2001 +#define MTP_RESPONSE_DEVICE_BUSY 0x2019 + +static const char mtp_shortname[] = "mtp_usb"; + +struct mtp_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + struct usb_ep *ep_intr; + + int state; + + /* synchronize access to our device file */ + atomic_t open_excl; + /* to enforce only one ioctl at a time */ + atomic_t ioctl_excl; + + struct list_head tx_idle; + struct list_head intr_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + wait_queue_head_t intr_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + + /* for processing MTP_SEND_FILE, MTP_RECEIVE_FILE and + * MTP_SEND_FILE_WITH_HEADER ioctls on a work queue + */ + struct workqueue_struct *wq; + struct work_struct send_file_work; + struct work_struct receive_file_work; + struct file *xfer_file; + loff_t xfer_file_offset; + int64_t xfer_file_length; + unsigned xfer_send_header; + uint16_t xfer_command; + uint32_t xfer_transaction_id; + int xfer_result; +}; + +static struct usb_interface_descriptor mtp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_interface_descriptor ptp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor mtp_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_intr_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(INTR_BUFFER_SIZE), + .bInterval = 6, +}; + +static struct usb_descriptor_header *fs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_string mtp_string_defs[] = { + /* Naming interface "MTP" so libmtp will recognize us */ + [INTERFACE_STRING_INDEX].s = "MTP", + { }, /* end of list */ +}; + +static struct usb_gadget_strings mtp_string_table = { + .language = 0x0409, /* en-US */ + .strings = mtp_string_defs, +}; + +static struct usb_gadget_strings *mtp_strings[] = { + &mtp_string_table, + NULL, +}; + +/* Microsoft MTP OS String */ +static u8 mtp_os_string[] = { + 18, /* sizeof(mtp_os_string) */ + USB_DT_STRING, + /* Signature field: "MSFT100" */ + 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, + /* vendor code */ + 1, + /* padding */ + 0 +}; + +/* Microsoft Extended Configuration Descriptor Header Section */ +struct mtp_ext_config_desc_header { + __le32 dwLength; + __u16 bcdVersion; + __le16 wIndex; + __u8 bCount; + __u8 reserved[7]; +}; + +/* Microsoft Extended Configuration Descriptor Function Section */ +struct mtp_ext_config_desc_function { + __u8 bFirstInterfaceNumber; + __u8 bInterfaceCount; + __u8 compatibleID[8]; + __u8 subCompatibleID[8]; + __u8 reserved[6]; +}; + +/* MTP Extended Configuration Descriptor */ +struct { + struct mtp_ext_config_desc_header header; + struct mtp_ext_config_desc_function function; +} mtp_ext_config_desc = { + .header = { + .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)), + .bcdVersion = __constant_cpu_to_le16(0x0100), + .wIndex = __constant_cpu_to_le16(4), + .bCount = __constant_cpu_to_le16(1), + }, + .function = { + .bFirstInterfaceNumber = 0, + .bInterfaceCount = 1, + .compatibleID = { 'M', 'T', 'P' }, + }, +}; + +struct mtp_device_status { + __le16 wLength; + __le16 wCode; +}; + +struct mtp_data_header { + /* length of packet, including this header */ + __le32 length; + /* container type (2 for data packet) */ + __le16 type; + /* MTP command code */ + __le16 command; + /* MTP transaction ID */ + __le32 transaction_id; +}; + +/* temporary variable used between mtp_open() and mtp_gadget_bind() */ +static struct mtp_dev *_mtp_dev; + +static inline struct mtp_dev *func_to_mtp(struct usb_function *f) +{ + return container_of(f, struct mtp_dev, function); +} + +static struct usb_request *mtp_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void mtp_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int mtp_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void mtp_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void mtp_req_put(struct mtp_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request +*mtp_req_get(struct mtp_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void mtp_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->state = STATE_ERROR; + + wake_up(&dev->read_wq); +} + +static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->intr_idle, req); + + wake_up(&dev->intr_wq); +} + +static int mtp_create_bulk_endpoints(struct mtp_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc, + struct usb_endpoint_descriptor *intr_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, intr_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_intr = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_in, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_in; + mtp_req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_out, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_out; + dev->rx_req[i] = req; + } + for (i = 0; i < INTR_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_intr; + mtp_req_put(dev, &dev->intr_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "mtp_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t mtp_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + ssize_t r = count; + unsigned xfer; + int ret = 0; + + DBG(cdev, "mtp_read(%zu)\n", count); + + if (count > MTP_BULK_BUFFER_SIZE) + return -EINVAL; + + /* we will block until we're online */ + DBG(cdev, "mtp_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + dev->state != STATE_OFFLINE); + if (ret < 0) { + r = ret; + goto done; + } + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + DBG(cdev, "rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (dev->state == STATE_BUSY) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + DBG(cdev, "rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_read returning %zd\n", r); + return r; +} + +static ssize_t mtp_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + ssize_t r = count; + unsigned xfer; + int sendZLP = 0; + int ret; + + DBG(cdev, "mtp_write(%zu)\n", count); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + return -ENODEV; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) + sendZLP = 1; + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + if (dev->state != STATE_BUSY) { + DBG(cdev, "mtp_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY)); + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + if (xfer && copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "mtp_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_write returning %zd\n", r); + return r; +} + +/* read from a local file and write to USB */ +static void send_file_work(struct work_struct *data) +{ + struct mtp_dev *dev = container_of(data, struct mtp_dev, + send_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + struct mtp_data_header *header; + struct file *filp; + loff_t offset; + int64_t count; + int xfer, ret, hdr_size; + int r = 0; + int sendZLP = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "send_file_work(%lld %lld)\n", offset, count); + + if (dev->xfer_send_header) { + hdr_size = sizeof(struct mtp_data_header); + count += hdr_size; + } else { + hdr_size = 0; + } + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) + sendZLP = 1; + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + break; + } + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + + if (hdr_size) { + /* prepend MTP data header */ + header = (struct mtp_data_header *)req->buf; + header->length = __cpu_to_le32(count); + header->type = __cpu_to_le16(2); /* data packet */ + header->command = __cpu_to_le16(dev->xfer_command); + header->transaction_id = + __cpu_to_le32(dev->xfer_transaction_id); + } + + ret = vfs_read(filp, req->buf + hdr_size, xfer - hdr_size, + &offset); + if (ret < 0) { + r = ret; + break; + } + xfer = ret + hdr_size; + hdr_size = 0; + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "send_file_work: xfer error %d\n", ret); + dev->state = STATE_ERROR; + r = -EIO; + break; + } + + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + DBG(cdev, "send_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +/* read from USB and write to a local file */ +static void receive_file_work(struct work_struct *data) +{ + struct mtp_dev *dev = container_of(data, struct mtp_dev, + receive_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *read_req = NULL, *write_req = NULL; + struct file *filp; + loff_t offset; + int64_t count; + int ret, cur_buf = 0; + int r = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "receive_file_work(%lld)\n", count); + + while (count > 0 || write_req) { + if (count > 0) { + /* queue a request */ + read_req = dev->rx_req[cur_buf]; + cur_buf = (cur_buf + 1) % RX_REQ_MAX; + + read_req->length = (count > MTP_BULK_BUFFER_SIZE + ? MTP_BULK_BUFFER_SIZE : count); + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + } + + if (write_req) { + DBG(cdev, "rx %p %d\n", write_req, write_req->actual); + ret = vfs_write(filp, write_req->buf, write_req->actual, + &offset); + DBG(cdev, "vfs_write %d\n", ret); + if (ret != write_req->actual) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + write_req = NULL; + } + + if (read_req) { + /* wait for our last read to complete */ + ret = wait_event_interruptible(dev->read_wq, + dev->rx_done || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + if (!dev->rx_done) + usb_ep_dequeue(dev->ep_out, read_req); + break; + } + /* if xfer_file_length is 0xFFFFFFFF, then we read until + * we get a zero length packet + */ + if (count != 0xFFFFFFFF) + count -= read_req->actual; + if (read_req->actual < read_req->length) { + /* + * short packet is used to signal EOF for + * sizes > 4 gig + */ + DBG(cdev, "got short packet\n"); + count = 0; + } + + write_req = read_req; + read_req = NULL; + } + } + + DBG(cdev, "receive_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event) +{ + struct usb_request *req = NULL; + int ret; + int length = event->length; + + DBG(dev->cdev, "mtp_send_event(%zu)\n", event->length); + + if (length < 0 || length > INTR_BUFFER_SIZE) + return -EINVAL; + if (dev->state == STATE_OFFLINE) + return -ENODEV; + + ret = wait_event_interruptible_timeout(dev->intr_wq, + (req = mtp_req_get(dev, &dev->intr_idle)), + msecs_to_jiffies(1000)); + if (!req) + return -ETIME; + + if (copy_from_user(req->buf, (void __user *)event->data, length)) { + mtp_req_put(dev, &dev->intr_idle, req); + return -EFAULT; + } + req->length = length; + ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL); + if (ret) + mtp_req_put(dev, &dev->intr_idle, req); + + return ret; +} + +static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct mtp_dev *dev = fp->private_data; + struct file *filp = NULL; + int ret = -EINVAL; + + if (mtp_lock(&dev->ioctl_excl)) + return -EBUSY; + + switch (code) { + case MTP_SEND_FILE: + case MTP_RECEIVE_FILE: + case MTP_SEND_FILE_WITH_HEADER: + { + struct mtp_file_range mfr; + struct work_struct *work; + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + ret = -ECANCELED; + goto out; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + ret = -ENODEV; + goto out; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) { + ret = -EFAULT; + goto fail; + } + /* hold a reference to the file while we are working with it */ + filp = fget(mfr.fd); + if (!filp) { + ret = -EBADF; + goto fail; + } + + /* write the parameters */ + dev->xfer_file = filp; + dev->xfer_file_offset = mfr.offset; + dev->xfer_file_length = mfr.length; + smp_wmb(); + + if (code == MTP_SEND_FILE_WITH_HEADER) { + work = &dev->send_file_work; + dev->xfer_send_header = 1; + dev->xfer_command = mfr.command; + dev->xfer_transaction_id = mfr.transaction_id; + } else if (code == MTP_SEND_FILE) { + work = &dev->send_file_work; + dev->xfer_send_header = 0; + } else { + work = &dev->receive_file_work; + } + + /* We do the file transfer on a work queue so it will run + * in kernel context, which is necessary for vfs_read and + * vfs_write to use our buffers in the kernel address space. + */ + queue_work(dev->wq, work); + /* wait for operation to complete */ + flush_workqueue(dev->wq); + fput(filp); + + /* read the result */ + smp_rmb(); + ret = dev->xfer_result; + break; + } + case MTP_SEND_EVENT: + { + struct mtp_event event; + /* return here so we don't change dev->state below, + * which would interfere with bulk transfer state. + */ + if (copy_from_user(&event, (void __user *)value, sizeof(event))) + ret = -EFAULT; + else + ret = mtp_send_event(dev, &event); + goto out; + } + } + +fail: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + ret = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); +out: + mtp_unlock(&dev->ioctl_excl); + DBG(dev->cdev, "ioctl returning %d\n", ret); + return ret; +} + +static int mtp_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_open\n"); + if (mtp_lock(&_mtp_dev->open_excl)) + return -EBUSY; + + /* clear any error condition */ + if (_mtp_dev->state != STATE_OFFLINE) + _mtp_dev->state = STATE_READY; + + fp->private_data = _mtp_dev; + return 0; +} + +static int mtp_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_release\n"); + + mtp_unlock(&_mtp_dev->open_excl); + return 0; +} + +/* file operations for /dev/mtp_usb */ +static const struct file_operations mtp_fops = { + .owner = THIS_MODULE, + .read = mtp_read, + .write = mtp_write, + .unlocked_ioctl = mtp_ioctl, + .open = mtp_open, + .release = mtp_release, +}; + +static struct miscdevice mtp_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = mtp_shortname, + .fops = &mtp_fops, +}; + +static int mtp_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct mtp_dev *dev = _mtp_dev; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + unsigned long flags; + + VDBG(cdev, "mtp_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* Handle MTP OS string */ + if (ctrl->bRequestType == + (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) + && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR + && (w_value >> 8) == USB_DT_STRING + && (w_value & 0xFF) == MTP_OS_STRING_ID) { + value = (w_length < sizeof(mtp_os_string) + ? w_length : sizeof(mtp_os_string)); + memcpy(cdev->req->buf, mtp_os_string, value); + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + /* Handle MTP OS descriptor */ + DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == 1 + && (ctrl->bRequestType & USB_DIR_IN) + && (w_index == 4 || w_index == 5)) { + value = (w_length < sizeof(mtp_ext_config_desc) ? + w_length : sizeof(mtp_ext_config_desc)); + memcpy(cdev->req->buf, &mtp_ext_config_desc, value); + } + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + DBG(cdev, "class request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0 + && w_value == 0) { + DBG(cdev, "MTP_REQ_CANCEL\n"); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state == STATE_BUSY) { + dev->state = STATE_CANCELED; + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* We need to queue a request to read the remaining + * bytes, but we don't actually need to look at + * the contents. + */ + value = w_length; + } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS + && w_index == 0 && w_value == 0) { + struct mtp_device_status *status = cdev->req->buf; + status->wLength = + __constant_cpu_to_le16(sizeof(*status)); + + DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n"); + spin_lock_irqsave(&dev->lock, flags); + /* device status is "busy" until we report + * the cancelation to userspace + */ + if (dev->state == STATE_CANCELED) + status->wCode = + __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY); + else + status->wCode = + __cpu_to_le16(MTP_RESPONSE_OK); + spin_unlock_irqrestore(&dev->lock, flags); + value = sizeof(*status); + } + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + int rc; + cdev->req->zero = value < w_length; + cdev->req->length = value; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + ERROR(cdev, "%s: response queue error\n", __func__); + } + return value; +} + +static int +mtp_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct mtp_dev *dev = func_to_mtp(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "mtp_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + mtp_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = mtp_create_bulk_endpoints(dev, &mtp_fullspeed_in_desc, + &mtp_fullspeed_out_desc, &mtp_intr_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + mtp_highspeed_in_desc.bEndpointAddress = + mtp_fullspeed_in_desc.bEndpointAddress; + mtp_highspeed_out_desc.bEndpointAddress = + mtp_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +mtp_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_request *req; + int i; + + while ((req = mtp_req_get(dev, &dev->tx_idle))) + mtp_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + mtp_request_free(dev->rx_req[i], dev->ep_out); + while ((req = mtp_req_get(dev, &dev->intr_idle))) + mtp_request_free(req, dev->ep_intr); + dev->state = STATE_OFFLINE; +} + +static int mtp_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_intr); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_intr); + if (ret) { + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_in); + return ret; + } + dev->state = STATE_READY; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void mtp_function_disable(struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "mtp_function_disable\n"); + dev->state = STATE_OFFLINE; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_intr); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int mtp_bind_config(struct usb_configuration *c, bool ptp_config) +{ + struct mtp_dev *dev = _mtp_dev; + int ret = 0; + + printk(KERN_INFO "mtp_bind_config\n"); + + /* allocate a string ID for our interface */ + if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + mtp_string_defs[INTERFACE_STRING_INDEX].id = ret; + mtp_interface_desc.iInterface = ret; + } + + dev->cdev = c->cdev; + dev->function.name = "mtp"; + dev->function.strings = mtp_strings; + if (ptp_config) { + dev->function.fs_descriptors = fs_ptp_descs; + dev->function.hs_descriptors = hs_ptp_descs; + } else { + dev->function.fs_descriptors = fs_mtp_descs; + dev->function.hs_descriptors = hs_mtp_descs; + } + dev->function.bind = mtp_function_bind; + dev->function.unbind = mtp_function_unbind; + dev->function.set_alt = mtp_function_set_alt; + dev->function.disable = mtp_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int mtp_setup(void) +{ + struct mtp_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + init_waitqueue_head(&dev->intr_wq); + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->ioctl_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->intr_idle); + + dev->wq = create_singlethread_workqueue("f_mtp"); + if (!dev->wq) { + ret = -ENOMEM; + goto err1; + } + INIT_WORK(&dev->send_file_work, send_file_work); + INIT_WORK(&dev->receive_file_work, receive_file_work); + + _mtp_dev = dev; + + ret = misc_register(&mtp_device); + if (ret) + goto err2; + + return 0; + +err2: + destroy_workqueue(dev->wq); +err1: + _mtp_dev = NULL; + kfree(dev); + printk(KERN_ERR "mtp gadget driver failed to initialize\n"); + return ret; +} + +static void mtp_cleanup(void) +{ + struct mtp_dev *dev = _mtp_dev; + + if (!dev) + return; + + misc_deregister(&mtp_device); + destroy_workqueue(dev->wq); + _mtp_dev = NULL; + kfree(dev); +} diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index c11761ce511302..a3a220890b58d1 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -822,6 +822,62 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) return status; } +static void +rndis_old_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rndis *rndis = func_to_rndis(f); + + rndis_deregister(rndis->config); + + usb_free_all_descriptors(f); + + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); + + kfree(rndis); +} + +int +rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer, struct eth_dev *dev) +{ + struct f_rndis *rndis; + int status; + + /* allocate and initialize one new instance */ + status = -ENOMEM; + rndis = kzalloc(sizeof *rndis, GFP_KERNEL); + if (!rndis) + goto fail; + + memcpy(rndis->ethaddr, ethaddr, ETH_ALEN); + rndis->vendorID = vendorID; + rndis->manufacturer = manufacturer; + + rndis->port.ioport = dev; + /* RNDIS activates when the host changes this filter */ + rndis->port.cdc_filter = 0; + + /* RNDIS has special (and complex) framing */ + rndis->port.header_len = sizeof(struct rndis_packet_msg_type); + rndis->port.wrap = rndis_add_header; + rndis->port.unwrap = rndis_rm_hdr; + + rndis->port.func.name = "rndis"; + /* descriptors are per-instance copies */ + rndis->port.func.bind = rndis_bind; + rndis->port.func.unbind = rndis_old_unbind; + rndis->port.func.set_alt = rndis_set_alt; + rndis->port.func.setup = rndis_setup; + rndis->port.func.disable = rndis_disable; + + status = usb_add_function(c, &rndis->port.func); + if (status) + kfree(rndis); +fail: + return status; +} + void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net) { struct f_rndis_opts *opts; diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c index b369292d4b90cd..72068082e83c2b 100644 --- a/drivers/usb/gadget/u_serial.c +++ b/drivers/usb/gadget/u_serial.c @@ -1126,6 +1126,7 @@ int gserial_alloc_line(unsigned char *line_num) tty_dev = tty_port_register_device(&ports[port_num].port->port, gs_tty_driver, port_num, NULL); + if (IS_ERR(tty_dev)) { struct gs_port *port; pr_err("%s: failed to register tty for port %d, err %ld\n", diff --git a/drivers/usb/gadget/udc-core.c b/drivers/usb/gadget/udc-core.c index 27768a7d986a7f..ce42e45e99ab81 100644 --- a/drivers/usb/gadget/udc-core.c +++ b/drivers/usb/gadget/udc-core.c @@ -351,7 +351,15 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri driver->unbind(udc->gadget); goto err1; } - usb_gadget_connect(udc->gadget); + /* + * HACK: The Android gadget driver disconnects the gadget + * on bind and expects the gadget to stay disconnected until + * it calls usb_gadget_connect when userspace is ready. Remove + * the call to usb_gadget_connect bellow to avoid enabling the + * pullup before userspace is ready. + * + * usb_gadget_connect(udc->gadget); + */ kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); return 0; diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index a9707da7da0bff..9549bb5922f977 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -158,6 +158,13 @@ config USB_EHCI_HCD_SPEAR Enables support for the on-chip EHCI controller on ST SPEAr chips. +config USB_EHCI_HCD_SYNOPSYS + tristate "Support for Synopsys Host-AHB USB 2.0 controller" + depends on USB_EHCI_HCD + ---help--- + Enable support for onchip USB controllers based on DesignWare USB 2.0 + Host-AHB Controller IP from Synopsys. + config USB_EHCI_HCD_AT91 tristate "Support for Atmel on-chip EHCI USB controller" depends on USB_EHCI_HCD && ARCH_AT91 diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 7530468c9a4fed..b6de8c7d47364f 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_USB_EHCI_MXC) += ehci-mxc.o obj-$(CONFIG_USB_EHCI_HCD_OMAP) += ehci-omap.o obj-$(CONFIG_USB_EHCI_HCD_ORION) += ehci-orion.o obj-$(CONFIG_USB_EHCI_HCD_SPEAR) += ehci-spear.o +obj-$(CONFIG_USB_EHCI_HCD_SYNOPSYS) += ehci-h20ahb.o obj-$(CONFIG_USB_EHCI_EXYNOS) += ehci-exynos.o obj-$(CONFIG_USB_EHCI_HCD_AT91) += ehci-atmel.o obj-$(CONFIG_USB_EHCI_MSM) += ehci-msm.o diff --git a/drivers/usb/host/ehci-h20ahb.c b/drivers/usb/host/ehci-h20ahb.c new file mode 100644 index 00000000000000..3ee3c7aa6e5b5a --- /dev/null +++ b/drivers/usb/host/ehci-h20ahb.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2007-2013 Texas Instruments, Inc. + * Author: Vikram Pandita + * Author: Anand Gadiyar + * Author: Keshava Munegowda + * Author: Roger Quadros + * + * Copyright (C) 2009 Nokia Corporation + * Contact: Felipe Balbi + * + * Based on ehci-omap.c - driver for USBHOST on OMAP3/4 processors + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define H20AHB_HS_USB_PORTS 1 + +/* EHCI Synopsys-specific Register Set */ +#define EHCI_INSNREG04 (0xA0) +#define EHCI_INSNREG04_DISABLE_UNSUSPEND (1 << 5) +#define EHCI_INSNREG05_ULPI (0xA4) +#define EHCI_INSNREG05_ULPI_CONTROL_SHIFT 31 +#define EHCI_INSNREG05_ULPI_PORTSEL_SHIFT 24 +#define EHCI_INSNREG05_ULPI_OPSEL_SHIFT 22 +#define EHCI_INSNREG05_ULPI_REGADD_SHIFT 16 +#define EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT 8 +#define EHCI_INSNREG05_ULPI_WRDATA_SHIFT 0 + +#define DRIVER_DESC "H20AHB-EHCI Host Controller driver" + +static const char hcd_name[] = "ehci-h20ahb"; + +/*-------------------------------------------------------------------------*/ + +struct h20ahb_hcd { + struct usb_phy *phy[H20AHB_HS_USB_PORTS]; /* one PHY for each port */ + int nports; +}; + +static inline void ehci_write(void __iomem *base, u32 reg, u32 val) +{ + __raw_writel(val, base + reg); +} + +static inline u32 ehci_read(void __iomem *base, u32 reg) +{ + return __raw_readl(base + reg); +} + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +static struct hc_driver __read_mostly ehci_h20ahb_hc_driver; + +static const struct ehci_driver_overrides ehci_h20ahb_overrides __initdata = { + .extra_priv_size = sizeof(struct h20ahb_hcd), +}; + +static int ehci_h20ahb_phy_read(struct usb_phy *x, u32 reg) +{ + u32 val = (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT) | + (1 << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) | + (3 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) | + (reg << EHCI_INSNREG05_ULPI_REGADD_SHIFT); + ehci_write(x->io_priv, 0, val); + while ((val = ehci_read(x->io_priv, 0)) & + (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT)); + return val & 0xff; +} + +static int ehci_h20ahb_phy_write(struct usb_phy *x, u32 val, u32 reg) +{ + u32 v = (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT) | + (1 << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) | + (2 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) | + (reg << EHCI_INSNREG05_ULPI_REGADD_SHIFT) | + (val & 0xff); + ehci_write(x->io_priv, 0, v); + while ((v = ehci_read(x->io_priv, 0)) & + (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT)); + return 0; +} + +static struct usb_phy_io_ops ehci_h20ahb_phy_io_ops = { + .read = ehci_h20ahb_phy_read, + .write = ehci_h20ahb_phy_write, +}; + + +/** + * ehci_hcd_h20ahb_probe - initialize Synopsis-based HCDs + * + * Allocates basic resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + */ +static int ehci_hcd_h20ahb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct usb_hcd *hcd; + void __iomem *regs; + int ret; + int irq; + int i; + struct h20ahb_hcd *h20ahb; + + if (usb_disabled()) + return -ENODEV; + + /* if (!dev->parent) { + dev_err(dev, "Missing parent device\n"); + return -ENODEV; + }*/ + + /* For DT boot, get platform data from parent. i.e. usbhshost */ + /*if (dev->of_node) { + pdata = dev_get_platdata(dev->parent); + dev->platform_data = pdata; + } + + if (!pdata) { + dev_err(dev, "Missing platform data\n"); + return -ENODEV; + }*/ + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "EHCI irq failed\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + /* + * Right now device-tree probed devices don't get dma_mask set. + * Since shared usb code relies on it, set it here for now. + * Once we have dma capability bindings this can go away. + */ + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + ret = -ENODEV; + hcd = usb_create_hcd(&ehci_h20ahb_hc_driver, dev, + dev_name(dev)); + if (!hcd) { + dev_err(dev, "Failed to create HCD\n"); + return -ENOMEM; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = regs; + hcd_to_ehci(hcd)->caps = regs; + + h20ahb = (struct h20ahb_hcd *)hcd_to_ehci(hcd)->priv; + h20ahb->nports = 1; + + platform_set_drvdata(pdev, hcd); + + /* get the PHY devices if needed */ + for (i = 0 ; i < h20ahb->nports ; i++) { + struct usb_phy *phy; + + /* get the PHY device */ +#if 0 + if (dev->of_node) + phy = devm_usb_get_phy_by_phandle(dev, "phys", i); + else + phy = devm_usb_get_phy_dev(dev, i); +#endif + phy = otg_ulpi_create(&ehci_h20ahb_phy_io_ops, 0); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + dev_err(dev, "Can't get PHY device for port %d: %d\n", + i, ret); + goto err_phy; + } + phy->dev = dev; + usb_add_phy_dev(phy); + + h20ahb->phy[i] = phy; + phy->io_priv = hcd->regs + EHCI_INSNREG05_ULPI; + +#if 0 + usb_phy_init(h20ahb->phy[i]); + /* bring PHY out of suspend */ + usb_phy_set_suspend(h20ahb->phy[i], 0); +#endif + } + + /* make the first port's phy the one used by hcd as well */ + hcd->phy = h20ahb->phy[0]; + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + /* + * An undocumented "feature" in the H20AHB EHCI controller, + * causes suspended ports to be taken out of suspend when + * the USBCMD.Run/Stop bit is cleared (for example when + * we do ehci_bus_suspend). + * This breaks suspend-resume if the root-hub is allowed + * to suspend. Writing 1 to this undocumented register bit + * disables this feature and restores normal behavior. + */ + ehci_write(regs, EHCI_INSNREG04, + EHCI_INSNREG04_DISABLE_UNSUSPEND); + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) { + dev_err(dev, "failed to add hcd with err %d\n", ret); + goto err_pm_runtime; + } + device_wakeup_enable(hcd->self.controller); + + /* + * Bring PHYs out of reset for non PHY modes. + * Even though HSIC mode is a PHY-less mode, the reset + * line exists between the chips and can be modelled + * as a PHY device for reset control. + */ + for (i = 0; i < h20ahb->nports; i++) { + usb_phy_init(h20ahb->phy[i]); + /* bring PHY out of suspend */ + usb_phy_set_suspend(h20ahb->phy[i], 0); + } + + return 0; + +err_pm_runtime: + pm_runtime_put_sync(dev); + +err_phy: + for (i = 0; i < h20ahb->nports; i++) { + if (h20ahb->phy[i]) + usb_phy_shutdown(h20ahb->phy[i]); + } + + usb_put_hcd(hcd); + + return ret; +} + + +/** + * ehci_hcd_h20ahb_remove - shutdown processing for EHCI HCDs + * @pdev: USB Host Controller being removed + * + * Reverses the effect of usb_ehci_hcd_h20ahb_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + */ +static int ehci_hcd_h20ahb_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct h20ahb_hcd *h20ahb = (struct h20ahb_hcd *)hcd_to_ehci(hcd)->priv; + int i; + + usb_remove_hcd(hcd); + + for (i = 0; i < h20ahb->nports; i++) { + if (h20ahb->phy[i]) + usb_phy_shutdown(h20ahb->phy[i]); + } + + usb_put_hcd(hcd); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return 0; +} + +static const struct of_device_id h20ahb_ehci_dt_ids[] = { + { .compatible = "snps,ehci-h20ahb" }, + { } +}; + +MODULE_DEVICE_TABLE(of, h20ahb_ehci_dt_ids); + +static struct platform_driver ehci_hcd_h20ahb_driver = { + .probe = ehci_hcd_h20ahb_probe, + .remove = ehci_hcd_h20ahb_remove, + .shutdown = usb_hcd_platform_shutdown, + /*.suspend = ehci_hcd_h20ahb_suspend, */ + /*.resume = ehci_hcd_h20ahb_resume, */ + .driver = { + .name = hcd_name, + .of_match_table = h20ahb_ehci_dt_ids, + } +}; + +/*-------------------------------------------------------------------------*/ + +static int __init ehci_h20ahb_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ehci_init_driver(&ehci_h20ahb_hc_driver, &ehci_h20ahb_overrides); + return platform_driver_register(&ehci_hcd_h20ahb_driver); +} +module_init(ehci_h20ahb_init); + +static void __exit ehci_h20ahb_cleanup(void) +{ + platform_driver_unregister(&ehci_hcd_h20ahb_driver); +} +module_exit(ehci_h20ahb_cleanup); + +MODULE_ALIAS("platform:ehci-h20ahb"); +MODULE_AUTHOR("Liviu Dudau "); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 81cda09b47e312..d1a48e49c0e8c6 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -590,11 +590,16 @@ static int ehci_run (struct usb_hcd *hcd) */ hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params); if (HCC_64BIT_ADDR(hcc_params)) { - ehci_writel(ehci, 0, &ehci->regs->segment); -#if 0 -// this is deeply broken on almost all architectures +#if CONFIG_ARM64 + ehci_writel(ehci, ehci->periodic_dma >> 32, &ehci->regs->segment); + /* + * this is deeply broken on almost all architectures + * but arm64 can use it so enable it + */ if (!dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64))) ehci_info(ehci, "enabled 64bit DMA\n"); +#else + ehci_writel(ehci, 0, &ehci->regs->segment); #endif } diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index a24720beb39dfd..f5870aa626efea 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -73,12 +73,12 @@ struct omap_hcd { static inline void ehci_write(void __iomem *base, u32 reg, u32 val) { - __raw_writel(val, base + reg); + writel_relaxed(val, base + reg); } static inline u32 ehci_read(void __iomem *base, u32 reg) { - return __raw_readl(base + reg); + return readl_relaxed(base + reg); } /* configure so an HC device and id are always provided */ diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c index a31641e18d198f..baaa132ec2dc5b 100644 --- a/drivers/usb/misc/usb3503.c +++ b/drivers/usb/misc/usb3503.c @@ -57,6 +57,7 @@ struct usb3503 { enum usb3503_mode mode; struct regmap *regmap; struct device *dev; + bool refclk_is_secondary; u8 port_off_mask; int gpio_intn; int gpio_reset; @@ -207,6 +208,7 @@ static int usb3503_probe(struct usb3503 *hub) return -EPROBE_DEFER; of_property_read_u32(np, "initial-mode", &mode); hub->mode = mode; + hub->refclk_is_secondary = of_property_read_bool(np, "secondary-refclk"); } if (hub->port_off_mask && !hub->regmap) @@ -214,7 +216,8 @@ static int usb3503_probe(struct usb3503 *hub) if (gpio_is_valid(hub->gpio_intn)) { err = devm_gpio_request_one(dev, hub->gpio_intn, - GPIOF_OUT_INIT_HIGH, "usb3503 intn"); + (hub->refclk_is_secondary ? GPIOF_OUT_INIT_LOW : + GPIOF_OUT_INIT_HIGH), "usb3503 intn"); if (err) { dev_err(dev, "unable to request GPIO %d as connect pin (%d)\n", @@ -339,7 +342,7 @@ static int __init usb3503_init(void) return 0; } -module_init(usb3503_init); +late_initcall(usb3503_init); static void __exit usb3503_exit(void) { diff --git a/drivers/usb/musb/musb_io.h b/drivers/usb/musb/musb_io.h index eebeed78edd606..76f4d2afe52849 100644 --- a/drivers/usb/musb/musb_io.h +++ b/drivers/usb/musb/musb_io.h @@ -42,17 +42,17 @@ /* NOTE: these offsets are all in bytes */ static inline u16 musb_readw(const void __iomem *addr, unsigned offset) - { return __raw_readw(addr + offset); } + { return readw_relaxed(addr + offset); } static inline u32 musb_readl(const void __iomem *addr, unsigned offset) - { return __raw_readl(addr + offset); } + { return readl_relaxed(addr + offset); } static inline void musb_writew(void __iomem *addr, unsigned offset, u16 data) - { __raw_writew(data, addr + offset); } + { writew_relaxed(data, addr + offset); } static inline void musb_writel(void __iomem *addr, unsigned offset, u32 data) - { __raw_writel(data, addr + offset); } + { writel_relaxed(data, addr + offset); } #if defined(CONFIG_USB_MUSB_TUSB6010) || defined (CONFIG_USB_MUSB_TUSB6010_MODULE) @@ -65,7 +65,7 @@ static inline u8 musb_readb(const void __iomem *addr, unsigned offset) u16 tmp; u8 val; - tmp = __raw_readw(addr + (offset & ~1)); + tmp = readw_relaxed(addr + (offset & ~1)); if (offset & 1) val = (tmp >> 8); else @@ -78,22 +78,22 @@ static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data) { u16 tmp; - tmp = __raw_readw(addr + (offset & ~1)); + tmp = readw_relaxed(addr + (offset & ~1)); if (offset & 1) tmp = (data << 8) | (tmp & 0xff); else tmp = (tmp & 0xff00) | data; - __raw_writew(tmp, addr + (offset & ~1)); + writew_relaxed(tmp, addr + (offset & ~1)); } #else static inline u8 musb_readb(const void __iomem *addr, unsigned offset) - { return __raw_readb(addr + offset); } + { return readb_relaxed(addr + offset); } static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data) - { __raw_writeb(data, addr + offset); } + { writeb_relaxed(data, addr + offset); } #endif /* CONFIG_USB_MUSB_TUSB6010 */ diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 7d1451d5bbea96..8a5d535aacfaf1 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -15,6 +15,14 @@ config USB_OTG_FSM Implements OTG Final State Machine as specified in On-The-Go and Embedded Host Supplement to the USB Revision 2.0 Specification. +config USB_OTG_WAKELOCK + bool "Hold a wakelock when USB connected" + depends on WAKELOCK + select USB_OTG_UTILS + help + Select this to automatically hold a wakelock when USB is + connected, preventing suspend. + # # USB Transceiver Drivers # @@ -253,7 +261,7 @@ config USB_RCAR_GEN2_PHY config USB_ULPI bool "Generic ULPI Transceiver Driver" - depends on ARM + depends on ARM || ARM64 help Enable this to support ULPI connected USB OTG transceivers which are likely found on embedded boards. diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index be58adae34960d..145b6a09f1b68f 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_USB_PHY) += phy.o obj-$(CONFIG_OF) += of.o obj-$(CONFIG_USB_OTG_FSM) += phy-fsm-usb.o +obj-$(CONFIG_USB_OTG_WAKELOCK) += otg-wakelock.o # transceiver drivers, keep the list sorted diff --git a/drivers/usb/phy/otg-wakelock.c b/drivers/usb/phy/otg-wakelock.c new file mode 100644 index 00000000000000..479376bfa484bd --- /dev/null +++ b/drivers/usb/phy/otg-wakelock.c @@ -0,0 +1,173 @@ +/* + * otg-wakelock.c + * + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEMPORARY_HOLD_TIME 2000 + +static bool enabled = true; +static struct usb_phy *otgwl_xceiv; +static struct notifier_block otgwl_nb; + +/* + * otgwl_spinlock is held while the VBUS lock is grabbed or dropped and the + * held field is updated to match. + */ + +static DEFINE_SPINLOCK(otgwl_spinlock); + +/* + * Only one lock, but since these 3 fields are associated with each other... + */ + +struct otgwl_lock { + char name[40]; + struct wake_lock wakelock; + bool held; +}; + +/* + * VBUS present lock. Also used as a timed lock on charger + * connect/disconnect and USB host disconnect, to allow the system + * to react to the change in power. + */ + +static struct otgwl_lock vbus_lock; + +static void otgwl_hold(struct otgwl_lock *lock) +{ + if (!lock->held) { + wake_lock(&lock->wakelock); + lock->held = true; + } +} + +static void otgwl_temporary_hold(struct otgwl_lock *lock) +{ + wake_lock_timeout(&lock->wakelock, + msecs_to_jiffies(TEMPORARY_HOLD_TIME)); + lock->held = false; +} + +static void otgwl_drop(struct otgwl_lock *lock) +{ + if (lock->held) { + wake_unlock(&lock->wakelock); + lock->held = false; + } +} + +static void otgwl_handle_event(unsigned long event) +{ + unsigned long irqflags; + + spin_lock_irqsave(&otgwl_spinlock, irqflags); + + if (!enabled) { + otgwl_drop(&vbus_lock); + spin_unlock_irqrestore(&otgwl_spinlock, irqflags); + return; + } + + switch (event) { + case USB_EVENT_VBUS: + case USB_EVENT_ENUMERATED: + otgwl_hold(&vbus_lock); + break; + + case USB_EVENT_NONE: + case USB_EVENT_ID: + case USB_EVENT_CHARGER: + otgwl_temporary_hold(&vbus_lock); + break; + + default: + break; + } + + spin_unlock_irqrestore(&otgwl_spinlock, irqflags); +} + +static int otgwl_otg_notifications(struct notifier_block *nb, + unsigned long event, void *unused) +{ + otgwl_handle_event(event); + return NOTIFY_OK; +} + +static int set_enabled(const char *val, const struct kernel_param *kp) +{ + int rv = param_set_bool(val, kp); + + if (rv) + return rv; + + if (otgwl_xceiv) + otgwl_handle_event(otgwl_xceiv->last_event); + + return 0; +} + +static struct kernel_param_ops enabled_param_ops = { + .set = set_enabled, + .get = param_get_bool, +}; + +module_param_cb(enabled, &enabled_param_ops, &enabled, 0644); +MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present"); + +static int __init otg_wakelock_init(void) +{ + int ret; + struct usb_phy *phy; + + phy = usb_get_phy(USB_PHY_TYPE_USB2); + + if (IS_ERR(phy)) { + pr_err("%s: No USB transceiver found\n", __func__); + return PTR_ERR(phy); + } + otgwl_xceiv = phy; + + snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s", + dev_name(otgwl_xceiv->dev)); + wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND, + vbus_lock.name); + + otgwl_nb.notifier_call = otgwl_otg_notifications; + ret = usb_register_notifier(otgwl_xceiv, &otgwl_nb); + + if (ret) { + pr_err("%s: usb_register_notifier on transceiver %s" + " failed\n", __func__, + dev_name(otgwl_xceiv->dev)); + otgwl_xceiv = NULL; + wake_lock_destroy(&vbus_lock.wakelock); + return ret; + } + + otgwl_handle_event(otgwl_xceiv->last_event); + return ret; +} + +late_initcall(otg_wakelock_init); diff --git a/drivers/usb/phy/phy-samsung-usb.c b/drivers/usb/phy/phy-samsung-usb.c index ac025ca08425ee..75314620498bf3 100644 --- a/drivers/usb/phy/phy-samsung-usb.c +++ b/drivers/usb/phy/phy-samsung-usb.c @@ -34,9 +34,15 @@ int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy) { struct device_node *usbphy_sys; + struct device_node *node = sphy->dev->of_node; + + /* Get the Channel number in case of multiple PHY controllers */ + sphy->channel = of_alias_get_id(node, "usb3phy"); + if (sphy->channel < 0) + dev_info(sphy->dev, "Not a multi controller PHY\n"); /* Getting node for system controller interface for usb-phy */ - usbphy_sys = of_get_child_by_name(sphy->dev->of_node, "usbphy-sys"); + usbphy_sys = of_get_child_by_name(node, "usbphy-sys"); if (!usbphy_sys) { dev_err(sphy->dev, "No sys-controller interface for usb-phy\n"); return -ENODEV; @@ -85,21 +91,26 @@ void samsung_usbphy_set_isolation_4210(struct samsung_usbphy *sphy, bool on) } if (sphy->phy_type == USB_PHY_TYPE_DEVICE) { - reg = sphy->pmuregs + sphy->drv_data->devphy_reg_offset; + if (sphy->channel == 1) + reg = sphy->pmuregs + + sphy->drv_data->dev1_phy_reg_offset; + else + reg = sphy->pmuregs + + sphy->drv_data->dev0_phy_reg_offset; en_mask = sphy->drv_data->devphy_en_mask; } else if (sphy->phy_type == USB_PHY_TYPE_HOST) { reg = sphy->pmuregs + sphy->drv_data->hostphy_reg_offset; en_mask = sphy->drv_data->hostphy_en_mask; } - reg_val = readl(reg); - - if (on) - reg_val &= ~en_mask; - else - reg_val |= en_mask; - - writel(reg_val, reg); + if (reg) { + reg_val = readl(reg); + if (on) + reg_val &= ~en_mask; + else + reg_val |= en_mask; + writel(reg_val, reg); + } if (sphy->drv_data->cpu_type == TYPE_EXYNOS4X12) { writel(reg_val, sphy->pmuregs + EXYNOS4X12_PHY_HSIC_CTRL0); @@ -217,15 +228,27 @@ int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy) struct clk *ref_clk; unsigned long rate; int refclk_freq; + int cpu_type = sphy->drv_data->cpu_type; /* * In exynos5250 USB host and device PHY use * external crystal clock XXTI */ - if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) + switch (cpu_type) { + case TYPE_EXYNOS5420: + /* fall through */ + case TYPE_EXYNOS5250: ref_clk = clk_get(sphy->dev, "ext_xtal"); - else + break; + case TYPE_EXYNOS4210: + /* fall through */ + case TYPE_S3C64XX: ref_clk = clk_get(sphy->dev, "xusbxti"); + break; + default: + break; + } + if (IS_ERR(ref_clk)) { dev_err(sphy->dev, "Failed to get reference clock\n"); return PTR_ERR(ref_clk); diff --git a/drivers/usb/phy/phy-samsung-usb.h b/drivers/usb/phy/phy-samsung-usb.h index 68771bfd18253d..8d191b874ff469 100644 --- a/drivers/usb/phy/phy-samsung-usb.h +++ b/drivers/usb/phy/phy-samsung-usb.h @@ -244,6 +244,8 @@ #endif #define EXYNOS_USBHOST_PHY_CTRL_OFFSET (0x4) +#define EXYNOS5420_USBDEV1_PHY_CTRL_OFFSET (0x4) +#define EXYNOS5420_USBHOST_PHY_CTRL_OFFSET (0x8) #define S3C64XX_USBPHY_ENABLE (0x1 << 16) #define EXYNOS_USBPHY_ENABLE (0x1 << 0) #define EXYNOS_USB20PHY_CFG_HOST_LINK (0x1 << 0) @@ -253,6 +255,7 @@ enum samsung_cpu_type { TYPE_EXYNOS4210, TYPE_EXYNOS4X12, TYPE_EXYNOS5250, + TYPE_EXYNOS5420, }; struct samsung_usbphy; @@ -262,8 +265,11 @@ struct samsung_usbphy; * @cpu_type: machine identifier * @devphy_en_mask: device phy enable mask for PHY CONTROL register * @hostphy_en_mask: host phy enable mask for PHY CONTROL register - * @devphy_reg_offset: offset to DEVICE PHY CONTROL register from + * @dev0_phy_reg_offset: offset to DEVICE_0 PHY CONTROL register from * mapped address of system controller. + * @dev1_phy_reg_offset: offset to DEVICE_1 PHY CONTROL register from + * mapped address of system controller (valid in case + * of multiple device type PHY controllers). * @hostphy_reg_offset: offset to HOST PHY CONTROL register from * mapped address of system controller. * @@ -279,7 +285,8 @@ struct samsung_usbphy_drvdata { int cpu_type; int devphy_en_mask; int hostphy_en_mask; - u32 devphy_reg_offset; + u32 dev0_phy_reg_offset; + u32 dev1_phy_reg_offset; u32 hostphy_reg_offset; int (*rate_to_clksel)(struct samsung_usbphy *, unsigned long); void (*set_isolation)(struct samsung_usbphy *, bool); @@ -302,6 +309,7 @@ struct samsung_usbphy_drvdata { * #DEVICE * @phy_usage: usage count for phy * @lock: lock for phy operations + * @channel: Channel number of controller in multi controller scenerio. */ struct samsung_usbphy { struct usb_phy phy; @@ -316,6 +324,7 @@ struct samsung_usbphy { enum samsung_usb_phy_type phy_type; atomic_t phy_usage; spinlock_t lock; + int channel; }; #define phy_to_sphy(x) container_of((x), struct samsung_usbphy, phy) diff --git a/drivers/usb/phy/phy-samsung-usb2.c b/drivers/usb/phy/phy-samsung-usb2.c index b3ba86627b7295..219e8f6586b309 100644 --- a/drivers/usb/phy/phy-samsung-usb2.c +++ b/drivers/usb/phy/phy-samsung-usb2.c @@ -382,7 +382,8 @@ static int samsung_usb2phy_probe(struct platform_device *pdev) drv_data = samsung_usbphy_get_driver_data(pdev); - if (drv_data->cpu_type == TYPE_EXYNOS5250) + if (drv_data->cpu_type == TYPE_EXYNOS5250 || + drv_data->cpu_type == TYPE_EXYNOS5420) clk = devm_clk_get(dev, "usbhost"); else clk = devm_clk_get(dev, "otg"); @@ -483,6 +484,16 @@ static struct samsung_usbphy_drvdata usb2phy_exynos5 = { .phy_disable = samsung_exynos5_usb2phy_disable, }; +static struct samsung_usbphy_drvdata usb2phy_exynos5420 = { + .cpu_type = TYPE_EXYNOS5420, + .hostphy_en_mask = EXYNOS_USBPHY_ENABLE, + .hostphy_reg_offset = EXYNOS5420_USBHOST_PHY_CTRL_OFFSET, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_exynos5_usb2phy_enable, + .phy_disable = samsung_exynos5_usb2phy_disable, +}; + #ifdef CONFIG_OF static const struct of_device_id samsung_usbphy_dt_match[] = { { @@ -497,6 +508,9 @@ static const struct of_device_id samsung_usbphy_dt_match[] = { }, { .compatible = "samsung,exynos5250-usb2phy", .data = &usb2phy_exynos5 + }, { + .compatible = "samsung,exynos5420-usb2phy", + .data = &usb2phy_exynos5420 }, {}, }; @@ -516,6 +530,9 @@ static struct platform_device_id samsung_usbphy_driver_ids[] = { }, { .name = "exynos5250-usb2phy", .driver_data = (unsigned long)&usb2phy_exynos5, + }, { + .name = "exynos5420-usb2phy", + .driver_data = (unsigned long)&usb2phy_exynos5420, }, {}, }; diff --git a/drivers/usb/phy/phy-samsung-usb3.c b/drivers/usb/phy/phy-samsung-usb3.c index cc0819248acfc6..c263e498a62af3 100644 --- a/drivers/usb/phy/phy-samsung-usb3.c +++ b/drivers/usb/phy/phy-samsung-usb3.c @@ -310,11 +310,24 @@ static struct samsung_usbphy_drvdata usb3phy_exynos5 = { .phy_disable = samsung_exynos5_usb3phy_disable, }; +static struct samsung_usbphy_drvdata usb3phy_exynos5420 = { + .cpu_type = TYPE_EXYNOS5420, + .devphy_en_mask = EXYNOS_USBPHY_ENABLE, + .dev1_phy_reg_offset = EXYNOS5420_USBDEV1_PHY_CTRL_OFFSET, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_exynos5_usb3phy_enable, + .phy_disable = samsung_exynos5_usb3phy_disable, +}; + #ifdef CONFIG_OF static const struct of_device_id samsung_usbphy_dt_match[] = { { .compatible = "samsung,exynos5250-usb3phy", .data = &usb3phy_exynos5 + }, { + .compatible = "samsung,exynos5420-usb3phy", + .data = &usb3phy_exynos5420 }, {}, }; @@ -325,6 +338,9 @@ static struct platform_device_id samsung_usbphy_driver_ids[] = { { .name = "exynos5250-usb3phy", .driver_data = (unsigned long)&usb3phy_exynos5, + }, { + .name = "exynos5420-usb3phy", + .driver_data = (unsigned long)&usb3phy_exynos5420, }, {}, }; diff --git a/drivers/usb/phy/phy-ulpi.c b/drivers/usb/phy/phy-ulpi.c index 217339dd7a9020..15b1a1210fde56 100644 --- a/drivers/usb/phy/phy-ulpi.c +++ b/drivers/usb/phy/phy-ulpi.c @@ -47,6 +47,7 @@ struct ulpi_info { static struct ulpi_info ulpi_ids[] = { ULPI_INFO(ULPI_ID(0x04cc, 0x1504), "NXP ISP1504"), ULPI_INFO(ULPI_ID(0x0424, 0x0006), "SMSC USB331x"), + ULPI_INFO(ULPI_ID(0x0424, 0x0009), "SMSC USB334x"), }; static int ulpi_set_otg_flags(struct usb_phy *phy) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index dade5b7699bc24..99e145ee0cef98 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -39,6 +39,11 @@ config VIDEOMODE_HELPERS config HDMI bool +config VEXPRESS_DVI_CONTROL + bool "Versatile Express DVI control" + depends on FB && VEXPRESS_CONFIG + default y + menuconfig FB tristate "Support for frame buffer devices" ---help--- @@ -327,6 +332,21 @@ config FB_ARMCLCD here and read . The module will be called amba-clcd. +config FB_ARMHDLCD + tristate "ARM High Definition LCD support" + depends on FB && ARM + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This framebuffer device driver is for the ARM High Definition + Colour LCD controller. + + If you want to compile this as a module (=code which can be + inserted into and removed from the running kernel), say M + here and read . The module + will be called arm-hdlcd. + config FB_ACORN bool "Acorn VIDC support" depends on (FB = y) && ARM && ARCH_ACORN @@ -2490,6 +2510,7 @@ source "drivers/video/omap2/Kconfig" source "drivers/video/exynos/Kconfig" source "drivers/video/mmp/Kconfig" source "drivers/video/backlight/Kconfig" +source "drivers/video/adf/Kconfig" if VT source "drivers/video/console/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index ae17ddf49a00d3..8abfb4db9b71ed 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -12,6 +12,7 @@ fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ modedb.o fbcvt.o fb-objs := $(fb-y) +obj-$(CONFIG_ADF) += adf/ obj-$(CONFIG_VT) += console/ obj-$(CONFIG_LOGO) += logo/ obj-y += backlight/ @@ -99,6 +100,7 @@ obj-$(CONFIG_FB_ATMEL) += atmel_lcdfb.o obj-$(CONFIG_FB_PVR2) += pvr2fb.o obj-$(CONFIG_FB_VOODOO1) += sstfb.o obj-$(CONFIG_FB_ARMCLCD) += amba-clcd.o +obj-$(CONFIG_FB_ARMHDLCD) += arm-hdlcd.o obj-$(CONFIG_FB_GOLDFISH) += goldfishfb.o obj-$(CONFIG_FB_68328) += 68328fb.o obj-$(CONFIG_FB_GBE) += gbefb.o @@ -178,3 +180,6 @@ obj-$(CONFIG_VIDEOMODE_HELPERS) += display_timing.o videomode.o ifeq ($(CONFIG_OF),y) obj-$(CONFIG_VIDEOMODE_HELPERS) += of_display_timing.o of_videomode.o endif + +# platform specific output drivers +obj-$(CONFIG_VEXPRESS_DVI_CONTROL) += vexpress-dvi.o diff --git a/drivers/video/adf/Kconfig b/drivers/video/adf/Kconfig new file mode 100644 index 00000000000000..33858b73d8bb99 --- /dev/null +++ b/drivers/video/adf/Kconfig @@ -0,0 +1,14 @@ +menuconfig ADF + depends on SYNC + depends on DMA_SHARED_BUFFER + tristate "Atomic Display Framework" + +menuconfig ADF_FBDEV + depends on ADF + depends on FB + tristate "Helper for implementing the fbdev API in ADF drivers" + +menuconfig ADF_MEMBLOCK + depends on ADF + depends on HAVE_MEMBLOCK + tristate "Helper for using memblocks as buffers in ADF drivers" diff --git a/drivers/video/adf/Makefile b/drivers/video/adf/Makefile new file mode 100644 index 00000000000000..78d0915122f41d --- /dev/null +++ b/drivers/video/adf/Makefile @@ -0,0 +1,15 @@ +ccflags-y := -Idrivers/staging/android + +CFLAGS_adf.o := -I$(src) + +obj-$(CONFIG_ADF) += adf.o \ + adf_client.o \ + adf_fops.o \ + adf_format.o \ + adf_sysfs.o + +obj-$(CONFIG_COMPAT) += adf_fops32.o + +obj-$(CONFIG_ADF_FBDEV) += adf_fbdev.o + +obj-$(CONFIG_ADF_MEMBLOCK) += adf_memblock.o diff --git a/drivers/video/adf/adf.c b/drivers/video/adf/adf.c new file mode 100644 index 00000000000000..2d20024fd2519f --- /dev/null +++ b/drivers/video/adf/adf.c @@ -0,0 +1,1167 @@ +/* + * Copyright (C) 2013 Google, Inc. + * adf_modeinfo_{set_name,set_vrefresh} modified from + * drivers/gpu/drm/drm_modes.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include