Merge branch 'android-exynos-3.4' into android-exynos-manta-3.4
diff --git a/Documentation/prctl/seccomp_filter.txt b/Documentation/prctl/seccomp_filter.txt
new file mode 100644
index 0000000..597c3c5
--- /dev/null
+++ b/Documentation/prctl/seccomp_filter.txt
@@ -0,0 +1,163 @@
+ SECure COMPuting with filters
+ =============================
+
+Introduction
+------------
+
+A large number of system calls are exposed to every userland process
+with many of them going unused for the entire lifetime of the process.
+As system calls change and mature, bugs are found and eradicated. A
+certain subset of userland applications benefit by having a reduced set
+of available system calls. The resulting set reduces the total kernel
+surface exposed to the application. System call filtering is meant for
+use with those applications.
+
+Seccomp filtering provides a means for a process to specify a filter for
+incoming system calls. The filter is expressed as a Berkeley Packet
+Filter (BPF) program, as with socket filters, except that the data
+operated on is related to the system call being made: system call
+number and the system call arguments. This allows for expressive
+filtering of system calls using a filter program language with a long
+history of being exposed to userland and a straightforward data set.
+
+Additionally, BPF makes it impossible for users of seccomp to fall prey
+to time-of-check-time-of-use (TOCTOU) attacks that are common in system
+call interposition frameworks. BPF programs may not dereference
+pointers which constrains all filters to solely evaluating the system
+call arguments directly.
+
+What it isn't
+-------------
+
+System call filtering isn't a sandbox. It provides a clearly defined
+mechanism for minimizing the exposed kernel surface. It is meant to be
+a tool for sandbox developers to use. Beyond that, policy for logical
+behavior and information flow should be managed with a combination of
+other system hardening techniques and, potentially, an LSM of your
+choosing. Expressive, dynamic filters provide further options down this
+path (avoiding pathological sizes or selecting which of the multiplexed
+system calls in socketcall() is allowed, for instance) which could be
+construed, incorrectly, as a more complete sandboxing solution.
+
+Usage
+-----
+
+An additional seccomp mode is added and is enabled using the same
+prctl(2) call as the strict seccomp. If the architecture has
+CONFIG_HAVE_ARCH_SECCOMP_FILTER, then filters may be added as below:
+
+PR_SET_SECCOMP:
+ Now takes an additional argument which specifies a new filter
+ using a BPF program.
+ The BPF program will be executed over struct seccomp_data
+ reflecting the system call number, arguments, and other
+ metadata. The BPF program must then return one of the
+ acceptable values to inform the kernel which action should be
+ taken.
+
+ Usage:
+ prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog);
+
+ The 'prog' argument is a pointer to a struct sock_fprog which
+ will contain the filter program. If the program is invalid, the
+ call will return -1 and set errno to EINVAL.
+
+ If fork/clone and execve are allowed by @prog, any child
+ processes will be constrained to the same filters and system
+ call ABI as the parent.
+
+ Prior to use, the task must call prctl(PR_SET_NO_NEW_PRIVS, 1) or
+ run with CAP_SYS_ADMIN privileges in its namespace. If these are not
+ true, -EACCES will be returned. This requirement ensures that filter
+ programs cannot be applied to child processes with greater privileges
+ than the task that installed them.
+
+ Additionally, if prctl(2) is allowed by the attached filter,
+ additional filters may be layered on which will increase evaluation
+ time, but allow for further decreasing the attack surface during
+ execution of a process.
+
+The above call returns 0 on success and non-zero on error.
+
+Return values
+-------------
+A seccomp filter may return any of the following values. If multiple
+filters exist, the return value for the evaluation of a given system
+call will always use the highest precedent value. (For example,
+SECCOMP_RET_KILL will always take precedence.)
+
+In precedence order, they are:
+
+SECCOMP_RET_KILL:
+ Results in the task exiting immediately without executing the
+ system call. The exit status of the task (status & 0x7f) will
+ be SIGSYS, not SIGKILL.
+
+SECCOMP_RET_TRAP:
+ Results in the kernel sending a SIGSYS signal to the triggering
+ task without executing the system call. The kernel will
+ rollback the register state to just before the system call
+ entry such that a signal handler in the task will be able to
+ inspect the ucontext_t->uc_mcontext registers and emulate
+ system call success or failure upon return from the signal
+ handler.
+
+ The SECCOMP_RET_DATA portion of the return value will be passed
+ as si_errno.
+
+ SIGSYS triggered by seccomp will have a si_code of SYS_SECCOMP.
+
+SECCOMP_RET_ERRNO:
+ Results in the lower 16-bits of the return value being passed
+ to userland as the errno without executing the system call.
+
+SECCOMP_RET_TRACE:
+ When returned, this value will cause the kernel to attempt to
+ notify a ptrace()-based tracer prior to executing the system
+ call. If there is no tracer present, -ENOSYS is returned to
+ userland and the system call is not executed.
+
+ A tracer will be notified if it requests PTRACE_O_TRACESECCOMP
+ using ptrace(PTRACE_SETOPTIONS). The tracer will be notified
+ of a PTRACE_EVENT_SECCOMP and the SECCOMP_RET_DATA portion of
+ the BPF program return value will be available to the tracer
+ via PTRACE_GETEVENTMSG.
+
+SECCOMP_RET_ALLOW:
+ Results in the system call being executed.
+
+If multiple filters exist, the return value for the evaluation of a
+given system call will always use the highest precedent value.
+
+Precedence is only determined using the SECCOMP_RET_ACTION mask. When
+multiple filters return values of the same precedence, only the
+SECCOMP_RET_DATA from the most recently installed filter will be
+returned.
+
+Pitfalls
+--------
+
+The biggest pitfall to avoid during use is filtering on system call
+number without checking the architecture value. Why? On any
+architecture that supports multiple system call invocation conventions,
+the system call numbers may vary based on the specific invocation. If
+the numbers in the different calling conventions overlap, then checks in
+the filters may be abused. Always check the arch value!
+
+Example
+-------
+
+The samples/seccomp/ directory contains both an x86-specific example
+and a more generic example of a higher level macro interface for BPF
+program generation.
+
+
+
+Adding architecture support
+-----------------------
+
+See arch/Kconfig for the authoritative requirements. In general, if an
+architecture supports both ptrace_event and seccomp, it will be able to
+support seccomp filter with minor fixup: SIGSYS support and seccomp return
+value checking. Then it must just add CONFIG_HAVE_ARCH_SECCOMP_FILTER
+to its arch-specific Kconfig.
diff --git a/arch/Kconfig b/arch/Kconfig
index 684eb5a..c024b3e 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -216,4 +216,27 @@
config ARCH_WANT_OLD_COMPAT_IPC
bool
+config HAVE_ARCH_SECCOMP_FILTER
+ bool
+ help
+ An arch should select this symbol if it provides all of these things:
+ - syscall_get_arch()
+ - syscall_get_arguments()
+ - syscall_rollback()
+ - syscall_set_return_value()
+ - SIGSYS siginfo_t support
+ - secure_computing is called from a ptrace_event()-safe context
+ - secure_computing return value is checked and a return value of -1
+ results in the system call being skipped immediately.
+
+config SECCOMP_FILTER
+ def_bool y
+ depends on HAVE_ARCH_SECCOMP_FILTER && SECCOMP && NET
+ help
+ Enable tasks to build secure computing environments defined
+ in terms of Berkeley Packet Filter programs which implement
+ task-defined system call filtering polices.
+
+ See Documentation/prctl/seccomp_filter.txt for details.
+
source "kernel/gcov/Kconfig"
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 3a07bf8..4e30467 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -30,6 +30,8 @@
select HAVE_HW_BREAKPOINT if (PERF_EVENTS && (CPU_V6 || CPU_V6K || CPU_V7))
select HAVE_C_RECORDMCOUNT
select HAVE_GENERIC_HARDIRQS
+ select HAVE_SPARSE_IRQ
+ select HAVE_ARCH_SECCOMP_FILTER
select GENERIC_IRQ_SHOW
select CPU_PM if (SUSPEND || CPU_IDLE)
select GENERIC_PCI_IOMAP
@@ -1445,6 +1447,16 @@
affected by this erratum such that no streaming-write ever allocates
into the L2 cache.
+config ARM_ERRATA_798181
+ bool "ARM errata: TLBI/DSB failure on Cortex-A15"
+ depends on CPU_V7 && SMP
+ help
+ On Cortex-A15 (r0p0..r3p2) the TLBI*IS/DSB operations are not
+ adequately shooting down all use of the old entries. This
+ option enables the Linux kernel workaround for this erratum
+ which sends an IPI to the CPUs that are running the same ASID
+ as the one being invalidated.
+
endmenu
source "arch/arm/common/Kconfig"
diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug
index 037bd5a..bfd179f 100644
--- a/arch/arm/Kconfig.debug
+++ b/arch/arm/Kconfig.debug
@@ -353,4 +353,13 @@
help
Perform tests of kprobes API and instruction set simulation.
+config PID_IN_CONTEXTIDR
+ bool "Write the current PID to the CONTEXTIDR register"
+ depends on CPU_COPY_V6
+ help
+ Enabling this option causes the kernel to write the current PID to
+ the PROCID field of the CONTEXTIDR register, at the expense of some
+ additional instructions during context switch. Say Y here only if you
+ are planning to use hardware trace tools with this kernel.
+
endmenu
diff --git a/arch/arm/configs/manta_defconfig b/arch/arm/configs/manta_defconfig
new file mode 100644
index 0000000..20deb27
--- /dev/null
+++ b/arch/arm/configs/manta_defconfig
@@ -0,0 +1,466 @@
+CONFIG_EXPERIMENTAL=y
+# CONFIG_SWAP is not set
+CONFIG_SYSVIPC=y
+CONFIG_AUDIT=y
+CONFIG_CGROUPS=y
+CONFIG_CGROUP_DEBUG=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_RESOURCE_COUNTERS=y
+CONFIG_CGROUP_SCHED=y
+CONFIG_RT_GROUP_SCHED=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_PANIC_TIMEOUT=5
+CONFIG_KALLSYMS_ALL=y
+# CONFIG_AIO is not set
+CONFIG_EMBEDDED=y
+CONFIG_PERF_EVENTS=y
+CONFIG_DEBUG_PERF_USE_VMALLOC=y
+CONFIG_MODULES=y
+CONFIG_MODULE_FORCE_LOAD=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODULE_FORCE_UNLOAD=y
+# CONFIG_BLK_DEV_BSG is not set
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_EFI_PARTITION=y
+CONFIG_ARCH_EXYNOS=y
+CONFIG_S3C_LOWLEVEL_UART_PORT=2
+CONFIG_S3C_ADC=y
+CONFIG_S3C24XX_PWM=y
+CONFIG_EXYNOS_CONTENT_PATH_PROTECTION=y
+CONFIG_EXYNOS5_CORESIGHT=y
+CONFIG_EXYNOS_THERMAL=y
+CONFIG_MACH_SMDK5250=y
+CONFIG_MACH_MANTA=y
+CONFIG_ARM_TRUSTZONE=y
+CONFIG_ARM_ERRATA_798181=y
+CONFIG_FIQ_DEBUGGER=y
+CONFIG_FIQ_DEBUGGER_NO_SLEEP=y
+CONFIG_FIQ_DEBUGGER_CONSOLE=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_SMP=y
+CONFIG_NR_CPUS=2
+CONFIG_PREEMPT=y
+CONFIG_AEABI=y
+CONFIG_HIGHMEM=y
+CONFIG_COMPACTION=y
+CONFIG_SECCOMP=y
+CONFIG_ARM_FLUSH_CONSOLE_ON_RESTART=y
+CONFIG_CMDLINE="vmalloc=512M debug_core.break_on_panic=0 debug_core.break_on_exception=0 no_console_suspend s3c2410-wdt.tmr_atboot=1 s3c2410-wdt.tmr_margin=30 wire.search_count=5"
+CONFIG_CMDLINE_EXTEND=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_IDLE=y
+CONFIG_VFP=y
+CONFIG_NEON=y
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
+CONFIG_BINFMT_MISC=y
+CONFIG_PM_AUTOSLEEP=y
+CONFIG_PM_WAKELOCKS=y
+CONFIG_PM_WAKELOCKS_LIMIT=0
+# CONFIG_PM_WAKELOCKS_GC is not set
+CONFIG_PM_RUNTIME=y
+CONFIG_PM_DEBUG=y
+CONFIG_PM_OPP=y
+CONFIG_SUSPEND_TIME=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_XFRM_USER=y
+CONFIG_NET_KEY=y
+CONFIG_INET=y
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_INET_ESP=y
+# CONFIG_INET_XFRM_MODE_BEET is not set
+# CONFIG_INET_LRO is not set
+CONFIG_IPV6=y
+CONFIG_IPV6_PRIVACY=y
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_OPTIMISTIC_DAD=y
+CONFIG_INET6_AH=y
+CONFIG_INET6_ESP=y
+CONFIG_INET6_IPCOMP=y
+CONFIG_IPV6_MIP6=y
+CONFIG_IPV6_TUNNEL=y
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_NETFILTER=y
+CONFIG_NF_CONNTRACK=y
+CONFIG_NF_CONNTRACK_SECMARK=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CT_PROTO_DCCP=y
+CONFIG_NF_CT_PROTO_SCTP=y
+CONFIG_NF_CT_PROTO_UDPLITE=y
+CONFIG_NF_CONNTRACK_AMANDA=y
+CONFIG_NF_CONNTRACK_FTP=y
+CONFIG_NF_CONNTRACK_H323=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_NETFILTER_TPROXY=y
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=y
+CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=y
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y
+CONFIG_NETFILTER_XT_TARGET_LOG=y
+CONFIG_NETFILTER_XT_TARGET_MARK=y
+CONFIG_NETFILTER_XT_TARGET_NFLOG=y
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y
+CONFIG_NETFILTER_XT_TARGET_TPROXY=y
+CONFIG_NETFILTER_XT_TARGET_TRACE=y
+CONFIG_NETFILTER_XT_TARGET_SECMARK=y
+CONFIG_NETFILTER_XT_MATCH_COMMENT=y
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=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_POLICY=y
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y
+CONFIG_NETFILTER_XT_MATCH_QTAGUID=y
+CONFIG_NETFILTER_XT_MATCH_QUOTA=y
+CONFIG_NETFILTER_XT_MATCH_QUOTA2=y
+CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=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_NF_CONNTRACK_IPV4=y
+CONFIG_IP_NF_IPTABLES=y
+CONFIG_IP_NF_MATCH_AH=y
+CONFIG_IP_NF_MATCH_ECN=y
+CONFIG_IP_NF_MATCH_TTL=y
+CONFIG_IP_NF_FILTER=y
+CONFIG_IP_NF_TARGET_REJECT=y
+CONFIG_IP_NF_TARGET_REJECT_SKERR=y
+CONFIG_NF_NAT=y
+CONFIG_IP_NF_TARGET_MASQUERADE=y
+CONFIG_IP_NF_TARGET_NETMAP=y
+CONFIG_IP_NF_TARGET_REDIRECT=y
+CONFIG_IP_NF_MANGLE=y
+CONFIG_IP_NF_RAW=y
+CONFIG_IP_NF_SECURITY=y
+CONFIG_IP_NF_ARPTABLES=y
+CONFIG_IP_NF_ARPFILTER=y
+CONFIG_IP_NF_ARP_MANGLE=y
+CONFIG_NF_CONNTRACK_IPV6=y
+CONFIG_IP6_NF_IPTABLES=y
+CONFIG_IP6_NF_FILTER=y
+CONFIG_IP6_NF_TARGET_REJECT=y
+CONFIG_IP6_NF_TARGET_REJECT_SKERR=y
+CONFIG_IP6_NF_MANGLE=y
+CONFIG_IP6_NF_RAW=y
+CONFIG_PHONET=y
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_HTB=y
+CONFIG_NET_SCH_INGRESS=y
+CONFIG_NET_CLS_U32=y
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_U32=y
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_POLICE=y
+CONFIG_NET_ACT_GACT=y
+CONFIG_NET_ACT_MIRRED=y
+CONFIG_BT=y
+CONFIG_BT_RFCOMM=y
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=y
+CONFIG_BT_HIDP=y
+CONFIG_BT_HCIUART=y
+CONFIG_BT_HCIUART_H4=y
+CONFIG_CFG80211=y
+CONFIG_NL80211_TESTMODE=y
+# CONFIG_CFG80211_DEFAULT_PS is not set
+# CONFIG_CFG80211_WEXT is not set
+CONFIG_RFKILL=y
+CONFIG_NFC_DEVICES=y
+CONFIG_BCM2079X_NFC_SPI=y
+CONFIG_SYNC=y
+CONFIG_SW_SYNC=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_RAM=y
+CONFIG_BLK_DEV_RAM_SIZE=8192
+CONFIG_HAPTIC_ISA1200=y
+CONFIG_UID_STAT=y
+CONFIG_STMPE811_ADC=y
+CONFIG_AUDIENCE_ES305=y
+CONFIG_SCSI=y
+CONFIG_BLK_DEV_SD=y
+CONFIG_CHR_DEV_SG=y
+CONFIG_MD=y
+CONFIG_BLK_DEV_DM=y
+CONFIG_DM_CRYPT=y
+CONFIG_DM_UEVENT=y
+CONFIG_NETDEVICES=y
+CONFIG_TUN=y
+# CONFIG_ETHERNET is not set
+CONFIG_PPP=y
+CONFIG_PPP_BSDCOMP=y
+CONFIG_PPP_DEFLATE=y
+CONFIG_PPP_MPPE=y
+CONFIG_PPPOLAC=y
+CONFIG_PPPOPNS=y
+CONFIG_USB_USBNET=y
+CONFIG_WIFI_CONTROL_FUNC=y
+CONFIG_BCMDHD=y
+CONFIG_BCMDHD_FW_PATH="/system/vendor/firmware/fw_bcmdhd.bin"
+CONFIG_DHD_USE_SCHED_SCAN=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_INPUT_KEYRESET=y
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_INPUT_JOYSTICK=y
+CONFIG_JOYSTICK_XPAD=y
+CONFIG_JOYSTICK_XPAD_FF=y
+CONFIG_JOYSTICK_XPAD_LEDS=y
+CONFIG_INPUT_TABLET=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_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_ATMEL_MXT=y
+CONFIG_TOUCHSCREEN_EGALAX_I2C=y
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_KEYCHORD=y
+CONFIG_INPUT_UINPUT=y
+CONFIG_INPUT_GPIO=y
+# CONFIG_VT is not set
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_SAMSUNG=y
+CONFIG_SERIAL_SAMSUNG_CONSOLE=y
+CONFIG_HW_RANDOM=y
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_GPIO=y
+CONFIG_I2C_S3C2410=y
+CONFIG_SPI=y
+CONFIG_SPI_S3C64XX=y
+CONFIG_GPIO_SYSFS=y
+CONFIG_W1_MASTER_DS2482=y
+CONFIG_FUELGAUGE_DS2784=y
+CONFIG_BATTERY_ANDROID=y
+CONFIG_CHARGER_SMB347=y
+# CONFIG_HWMON is not set
+CONFIG_WATCHDOG=y
+CONFIG_S3C2410_WATCHDOG=y
+CONFIG_MFD_MAX77686=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_MAX77686=y
+CONFIG_REGULATOR_WM8994=y
+CONFIG_MEDIA_SUPPORT=y
+CONFIG_VIDEO_DEV=y
+# CONFIG_RC_CORE is not set
+# CONFIG_MEDIA_TUNER_SIMPLE is not set
+# CONFIG_MEDIA_TUNER_TDA8290 is not set
+# CONFIG_MEDIA_TUNER_TDA827X is not set
+# CONFIG_MEDIA_TUNER_TDA18271 is not set
+# CONFIG_MEDIA_TUNER_TDA9887 is not set
+# CONFIG_MEDIA_TUNER_TEA5761 is not set
+# CONFIG_MEDIA_TUNER_TEA5767 is not set
+# CONFIG_MEDIA_TUNER_MT20XX is not set
+# CONFIG_MEDIA_TUNER_MT2060 is not set
+# CONFIG_MEDIA_TUNER_MT2063 is not set
+# CONFIG_MEDIA_TUNER_MT2266 is not set
+# CONFIG_MEDIA_TUNER_MT2131 is not set
+# CONFIG_MEDIA_TUNER_QT1010 is not set
+# CONFIG_MEDIA_TUNER_XC2028 is not set
+# CONFIG_MEDIA_TUNER_XC5000 is not set
+# CONFIG_MEDIA_TUNER_XC4000 is not set
+# CONFIG_MEDIA_TUNER_MXL5005S is not set
+# CONFIG_MEDIA_TUNER_MXL5007T is not set
+# CONFIG_MEDIA_TUNER_MC44S803 is not set
+# CONFIG_MEDIA_TUNER_MAX2165 is not set
+# CONFIG_MEDIA_TUNER_TDA18218 is not set
+# CONFIG_MEDIA_TUNER_TDA18212 is not set
+CONFIG_VIDEO_S5K4E5=y
+CONFIG_VIDEO_S5K6A3=y
+CONFIG_VIDEO_EXYNOS=y
+CONFIG_VIDEO_EXYNOS_GSCALER=y
+CONFIG_VIDEO_EXYNOS_JPEG=y
+CONFIG_VIDEO_EXYNOS_FIMG2D=y
+CONFIG_VIDEO_EXYNOS_MFC=y
+CONFIG_VIDEO_EXYNOS_TV=y
+CONFIG_VIDEO_EXYNOS_HDCP=y
+CONFIG_VIDEO_EXYNOS_ROTATOR=y
+CONFIG_VIDEO_EXYNOS5_FIMC_IS2=y
+CONFIG_ION=y
+CONFIG_ION_EXYNOS=y
+CONFIG_ION_EXYNOS_CONTIGHEAP_SIZE=2048
+CONFIG_MALI_T6XX=y
+CONFIG_MALI_T6XX_DVFS=y
+CONFIG_MALI_T6XX_RT_PM=y
+CONFIG_MALI_T6XX_DEBUG_SYS=y
+CONFIG_MALI_EXPERT=y
+CONFIG_MALI_PLATFORM_FAKE=y
+CONFIG_MALI_PLATFORM_THIRDPARTY_NAME="manta"
+CONFIG_FB=y
+CONFIG_FB_S3C=y
+CONFIG_S5P_DP=y
+CONFIG_BACKLIGHT_LCD_SUPPORT=y
+CONFIG_LCD_CLASS_DEVICE=y
+CONFIG_LCD_PLATFORM=y
+CONFIG_BACKLIGHT_CLASS_DEVICE=y
+# CONFIG_BACKLIGHT_GENERIC is not set
+CONFIG_BACKLIGHT_PWM=y
+CONFIG_SOUND=y
+CONFIG_SND=y
+# CONFIG_SND_DRIVERS is not set
+# CONFIG_SND_ARM is not set
+CONFIG_SND_SOC=y
+CONFIG_SND_SOC_SAMSUNG=y
+CONFIG_SND_SOC_SAMSUNG_MANTA_WM1811=y
+CONFIG_SND_SOC_SAMSUNG_MANTA_SPDIF=y
+CONFIG_UHID=y
+CONFIG_USB_HIDDEV=y
+CONFIG_HID_A4TECH=y
+CONFIG_HID_ACRUX=y
+CONFIG_HID_APPLE=y
+CONFIG_HID_BELKIN=y
+CONFIG_HID_CHERRY=y
+CONFIG_HID_CHICONY=y
+CONFIG_HID_PRODIKEYS=y
+CONFIG_HID_CYPRESS=y
+CONFIG_HID_DRAGONRISE=y
+CONFIG_HID_ELECOM=y
+CONFIG_HID_EZKEY=y
+CONFIG_HID_HOLTEK=y
+CONFIG_HID_KEYTOUCH=y
+CONFIG_HID_KYE=y
+CONFIG_HID_UCLOGIC=y
+CONFIG_HID_WALTOP=y
+CONFIG_HID_GYRATION=y
+CONFIG_HID_TWINHAN=y
+CONFIG_HID_KENSINGTON=y
+CONFIG_HID_LCPOWER=y
+CONFIG_HID_LOGITECH=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_ROCCAT=y
+CONFIG_HID_SAITEK=y
+CONFIG_HID_SAMSUNG=y
+CONFIG_HID_SONY=y
+CONFIG_HID_SPEEDLINK=y
+CONFIG_HID_SUNPLUS=y
+CONFIG_HID_GREENASIA=y
+CONFIG_HID_SMARTJOYPLUS=y
+CONFIG_HID_TIVO=y
+CONFIG_HID_TOPSEED=y
+CONFIG_HID_THRUSTMASTER=y
+CONFIG_HID_WACOM=y
+CONFIG_HID_WIIMOTE=y
+CONFIG_HID_ZEROPLUS=y
+CONFIG_HID_ZYDACRON=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_S5P=y
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_OHCI_EXYNOS=y
+CONFIG_USB_PRINTER=y
+CONFIG_USB_STORAGE=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_S3C_OTGD=y
+CONFIG_USB_G_ANDROID=y
+CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED=y
+CONFIG_USB_OTG_WAKELOCK=y
+CONFIG_MMC=y
+CONFIG_MMC_UNSAFE_RESUME=y
+CONFIG_MMC_CLKGATE=y
+CONFIG_MMC_EMBEDDED_SDIO=y
+CONFIG_MMC_PARANOID_SD_INIT=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_S3C=y
+CONFIG_MMC_SDHCI_S3C_DMA=y
+CONFIG_MMC_DW=y
+CONFIG_MMC_DW_IDMAC=y
+CONFIG_LEDS_AS3668=y
+CONFIG_LEDS_TRIGGERS=y
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_SWITCH=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_MAX77686=y
+CONFIG_STAGING=y
+CONFIG_IIO=y
+CONFIG_IIO_BUFFER=y
+CONFIG_IIO_KFIFO_BUF=y
+CONFIG_INV_MPU_IIO=y
+CONFIG_SENSORS_BH1721=y
+CONFIG_BMP182=y
+CONFIG_ANDROID=y
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_ASHMEM=y
+CONFIG_ANDROID_LOGGER=y
+CONFIG_ANDROID_RAM_CONSOLE=y
+CONFIG_ANDROID_TIMED_GPIO=y
+CONFIG_ANDROID_LOW_MEMORY_KILLER=y
+CONFIG_ANDROID_INTF_ALARM_DEV=y
+CONFIG_EXYNOS_IOMMU=y
+CONFIG_PM_DEVFREQ=y
+CONFIG_DEVFREQ_GOV_PM_QOS=y
+CONFIG_ARM_EXYNOS5_BUS_DEVFREQ=y
+CONFIG_MOBICORE_DRIVER=y
+CONFIG_MOBICORE_API=y
+CONFIG_EXT2_FS=y
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_SECURITY=y
+# CONFIG_DNOTIFY is not set
+CONFIG_FUSE_FS=y
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+# CONFIG_NETWORK_FILESYSTEMS is not set
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_PRINTK_TIME=y
+CONFIG_LOCKUP_DETECTOR=y
+CONFIG_BOOTPARAM_HARDLOCKUP_PANIC=y
+CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=y
+CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=10
+CONFIG_SCHEDSTATS=y
+# CONFIG_DEBUG_PREEMPT is not set
+CONFIG_DEBUG_ATOMIC_SLEEP=y
+CONFIG_DEBUG_INFO=y
+CONFIG_ENABLE_DEFAULT_TRACERS=y
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_KGDB=y
+CONFIG_KGDB_KDB=y
+# CONFIG_ARM_UNWIND is not set
+CONFIG_DEBUG_USER=y
+CONFIG_DEBUG_RODATA=y
+CONFIG_DEBUG_RODATA_TEST=y
+CONFIG_SECURITY=y
+CONFIG_SECURITY_NETWORK=y
+CONFIG_LSM_MMAP_MIN_ADDR=4096
+CONFIG_SECURITY_SELINUX=y
+CONFIG_CRYPTO_SHA256=y
+CONFIG_CRYPTO_TWOFISH=y
+CONFIG_CRC_CCITT=y
diff --git a/arch/arm/include/asm/highmem.h b/arch/arm/include/asm/highmem.h
index 8c5e828..91b99ab 100644
--- a/arch/arm/include/asm/highmem.h
+++ b/arch/arm/include/asm/highmem.h
@@ -41,6 +41,13 @@
#endif
#endif
+/*
+ * Needed to be able to broadcast the TLB invalidation for kmap.
+ */
+#ifdef CONFIG_ARM_ERRATA_798181
+#undef ARCH_NEEDS_KMAP_HIGH_GET
+#endif
+
#ifdef ARCH_NEEDS_KMAP_HIGH_GET
extern void *kmap_high_get(struct page *page);
#else
diff --git a/arch/arm/include/asm/mmu.h b/arch/arm/include/asm/mmu.h
index 1496565..e3d5554 100644
--- a/arch/arm/include/asm/mmu.h
+++ b/arch/arm/include/asm/mmu.h
@@ -5,18 +5,15 @@
typedef struct {
#ifdef CONFIG_CPU_HAS_ASID
- unsigned int id;
- raw_spinlock_t id_lock;
+ atomic64_t id;
#endif
- unsigned int kvm_seq;
+ unsigned int vmalloc_seq;
} mm_context_t;
#ifdef CONFIG_CPU_HAS_ASID
-#define ASID(mm) ((mm)->context.id & 255)
-
-/* init_mm.context.id_lock should be initialized. */
-#define INIT_MM_CONTEXT(name) \
- .context.id_lock = __RAW_SPIN_LOCK_UNLOCKED(name.context.id_lock),
+#define ASID_BITS 8
+#define ASID_MASK ((~0ULL) << ASID_BITS)
+#define ASID(mm) ((mm)->context.id.counter & ~ASID_MASK)
#else
#define ASID(mm) (0)
#endif
@@ -29,7 +26,7 @@
* modified for 2.6 by Hyok S. Choi <hyok.choi@samsung.com>
*/
typedef struct {
- unsigned long end_brk;
+ unsigned long end_brk;
} mm_context_t;
#endif
diff --git a/arch/arm/include/asm/mmu_context.h b/arch/arm/include/asm/mmu_context.h
index 0306bc6..a7b85e0 100644
--- a/arch/arm/include/asm/mmu_context.h
+++ b/arch/arm/include/asm/mmu_context.h
@@ -20,88 +20,14 @@
#include <asm/proc-fns.h>
#include <asm-generic/mm_hooks.h>
-void __check_kvm_seq(struct mm_struct *mm);
+void __check_vmalloc_seq(struct mm_struct *mm);
#ifdef CONFIG_CPU_HAS_ASID
-/*
- * On ARMv6, we have the following structure in the Context ID:
- *
- * 31 7 0
- * +-------------------------+-----------+
- * | process ID | ASID |
- * +-------------------------+-----------+
- * | context ID |
- * +-------------------------------------+
- *
- * The ASID is used to tag entries in the CPU caches and TLBs.
- * The context ID is used by debuggers and trace logic, and
- * should be unique within all running processes.
- */
-#define ASID_BITS 8
-#define ASID_MASK ((~0) << ASID_BITS)
-#define ASID_FIRST_VERSION (1 << ASID_BITS)
+void check_and_switch_context(struct mm_struct *mm, struct task_struct *tsk);
+#define init_new_context(tsk,mm) ({ atomic64_set(&mm->context.id, 0); 0; })
-extern unsigned int cpu_last_asid;
-
-void __init_new_context(struct task_struct *tsk, struct mm_struct *mm);
-void __new_context(struct mm_struct *mm);
-void cpu_set_reserved_ttbr0(void);
-
-static inline void switch_new_context(struct mm_struct *mm)
-{
- unsigned long flags;
-
- __new_context(mm);
-
- local_irq_save(flags);
- cpu_switch_mm(mm->pgd, mm);
- local_irq_restore(flags);
-}
-
-static inline void check_and_switch_context(struct mm_struct *mm,
- struct task_struct *tsk)
-{
- if (unlikely(mm->context.kvm_seq != init_mm.context.kvm_seq))
- __check_kvm_seq(mm);
-
- /*
- * Required during context switch to avoid speculative page table
- * walking with the wrong TTBR.
- */
- cpu_set_reserved_ttbr0();
-
- if (!((mm->context.id ^ cpu_last_asid) >> ASID_BITS))
- /*
- * The ASID is from the current generation, just switch to the
- * new pgd. This condition is only true for calls from
- * context_switch() and interrupts are already disabled.
- */
- cpu_switch_mm(mm->pgd, mm);
- else if (irqs_disabled())
- /*
- * Defer the new ASID allocation until after the context
- * switch critical region since __new_context() cannot be
- * called with interrupts disabled (it sends IPIs).
- */
- set_ti_thread_flag(task_thread_info(tsk), TIF_SWITCH_MM);
- else
- /*
- * That is a direct call to switch_mm() or activate_mm() with
- * interrupts enabled and a new context.
- */
- switch_new_context(mm);
-}
-
-#define init_new_context(tsk,mm) (__init_new_context(tsk,mm),0)
-
-#define finish_arch_post_lock_switch \
- finish_arch_post_lock_switch
-static inline void finish_arch_post_lock_switch(void)
-{
- if (test_and_clear_thread_flag(TIF_SWITCH_MM))
- switch_new_context(current->mm);
-}
+DECLARE_PER_CPU(atomic64_t, active_asids);
#else /* !CONFIG_CPU_HAS_ASID */
@@ -110,8 +36,8 @@
static inline void check_and_switch_context(struct mm_struct *mm,
struct task_struct *tsk)
{
- if (unlikely(mm->context.kvm_seq != init_mm.context.kvm_seq))
- __check_kvm_seq(mm);
+ if (unlikely(mm->context.vmalloc_seq != init_mm.context.vmalloc_seq))
+ __check_vmalloc_seq(mm);
if (irqs_disabled())
/*
@@ -143,6 +69,7 @@
#endif /* CONFIG_CPU_HAS_ASID */
#define destroy_context(mm) do { } while(0)
+#define activate_mm(prev,next) switch_mm(prev, next, NULL)
/*
* This is called when "tsk" is about to enter lazy TLB mode.
@@ -186,6 +113,5 @@
}
#define deactivate_mm(tsk,mm) do { } while (0)
-#define activate_mm(prev,next) switch_mm(prev, next, NULL)
#endif
diff --git a/arch/arm/include/asm/syscall.h b/arch/arm/include/asm/syscall.h
new file mode 100644
index 0000000..4c123db
--- /dev/null
+++ b/arch/arm/include/asm/syscall.h
@@ -0,0 +1,80 @@
+/*
+ * Access to user system call parameters and results
+ *
+ * Copyright (C) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ *
+ * This copyrighted material is made available to anyone wishing to use,
+ * modify, copy, or redistribute it subject to the terms and conditions
+ * of the GNU General Public License v.2.
+ *
+ * See asm-generic/syscall.h for descriptions of what we must do here.
+ */
+#ifndef _ASM_ARM_SYSCALL_H
+#define _ASM_ARM_SYSCALL_H
+
+#include <linux/audit.h> /* for AUDIT_ARCH_* */
+#include <linux/elf.h> /* for ELF_EM */
+#include <linux/sched.h>
+#include <linux/thread_info.h> /* for task_thread_info */
+#include <linux/err.h>
+
+static inline int syscall_get_nr(struct task_struct *task, struct pt_regs *regs)
+{
+ return task_thread_info(task)->syscall;
+}
+
+static inline void syscall_rollback(struct task_struct *task,
+ struct pt_regs *regs)
+{
+ regs->ARM_r0 = regs->ARM_ORIG_r0;
+}
+
+static inline long syscall_get_error(struct task_struct *task,
+ struct pt_regs *regs)
+{
+ unsigned long error = regs->ARM_r0;
+ return IS_ERR_VALUE(error) ? error : 0;
+}
+
+static inline long syscall_get_return_value(struct task_struct *task,
+ struct pt_regs *regs)
+{
+ return regs->ARM_r0;
+}
+
+static inline void syscall_set_return_value(struct task_struct *task,
+ struct pt_regs *regs,
+ int error, long val)
+{
+ regs->ARM_r0 = (long) error ?: val;
+}
+
+static inline void syscall_get_arguments(struct task_struct *task,
+ struct pt_regs *regs,
+ unsigned int i, unsigned int n,
+ unsigned long *args)
+{
+ BUG_ON(i + n > 6);
+ memcpy(args, ®s->ARM_r0 + i, n * sizeof(args[0]));
+}
+
+static inline void syscall_set_arguments(struct task_struct *task,
+ struct pt_regs *regs,
+ unsigned int i, unsigned int n,
+ const unsigned long *args)
+{
+ BUG_ON(i + n > 6);
+ memcpy(®s->ARM_r0 + i, args, n * sizeof(args[0]));
+}
+
+static inline int syscall_get_arch(struct task_struct *task,
+ struct pt_regs *regs)
+{
+ /* ARM tasks don't change audit architectures on the fly. */
+#ifdef __ARMEB__
+ return AUDIT_ARCH_ARMEB;
+#else
+ return AUDIT_ARCH_ARM;
+#endif
+}
+#endif /* _ASM_ARM_SYSCALL_H */
diff --git a/arch/arm/include/asm/tlbflush.h b/arch/arm/include/asm/tlbflush.h
index 85fe61e..9e9c041 100644
--- a/arch/arm/include/asm/tlbflush.h
+++ b/arch/arm/include/asm/tlbflush.h
@@ -34,10 +34,13 @@
#define TLB_V6_D_ASID (1 << 17)
#define TLB_V6_I_ASID (1 << 18)
+#define TLB_V6_BP (1 << 19)
+
/* Unified Inner Shareable TLB operations (ARMv7 MP extensions) */
-#define TLB_V7_UIS_PAGE (1 << 19)
-#define TLB_V7_UIS_FULL (1 << 20)
-#define TLB_V7_UIS_ASID (1 << 21)
+#define TLB_V7_UIS_PAGE (1 << 20)
+#define TLB_V7_UIS_FULL (1 << 21)
+#define TLB_V7_UIS_ASID (1 << 22)
+#define TLB_V7_UIS_BP (1 << 23)
#define TLB_BARRIER (1 << 28)
#define TLB_L2CLEAN_FR (1 << 29) /* Feroceon */
@@ -65,21 +68,6 @@
#define MULTI_TLB 1
#endif
-#define v3_tlb_flags (TLB_V3_FULL | TLB_V3_PAGE)
-
-#ifdef CONFIG_CPU_TLB_V3
-# define v3_possible_flags v3_tlb_flags
-# define v3_always_flags v3_tlb_flags
-# ifdef _TLB
-# define MULTI_TLB 1
-# else
-# define _TLB v3
-# endif
-#else
-# define v3_possible_flags 0
-# define v3_always_flags (-1UL)
-#endif
-
#define v4_tlb_flags (TLB_V4_U_FULL | TLB_V4_U_PAGE)
#ifdef CONFIG_CPU_TLB_V4WT
@@ -165,7 +153,8 @@
#define v6wbi_tlb_flags (TLB_WB | TLB_DCLEAN | TLB_BARRIER | \
TLB_V6_I_FULL | TLB_V6_D_FULL | \
TLB_V6_I_PAGE | TLB_V6_D_PAGE | \
- TLB_V6_I_ASID | TLB_V6_D_ASID)
+ TLB_V6_I_ASID | TLB_V6_D_ASID | \
+ TLB_V6_BP)
#ifdef CONFIG_CPU_TLB_V6
# define v6wbi_possible_flags v6wbi_tlb_flags
@@ -181,9 +170,11 @@
#endif
#define v7wbi_tlb_flags_smp (TLB_WB | TLB_DCLEAN | TLB_BARRIER | \
- TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE | TLB_V7_UIS_ASID)
+ TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE | \
+ TLB_V7_UIS_ASID | TLB_V7_UIS_BP)
#define v7wbi_tlb_flags_up (TLB_WB | TLB_DCLEAN | TLB_BARRIER | \
- TLB_V6_U_FULL | TLB_V6_U_PAGE | TLB_V6_U_ASID)
+ TLB_V6_U_FULL | TLB_V6_U_PAGE | \
+ TLB_V6_U_ASID | TLB_V6_BP)
#ifdef CONFIG_CPU_TLB_V7
@@ -298,8 +289,7 @@
* implemented the "%?" method, but this has been discontinued due to too
* many people getting it wrong.
*/
-#define possible_tlb_flags (v3_possible_flags | \
- v4_possible_flags | \
+#define possible_tlb_flags (v4_possible_flags | \
v4wbi_possible_flags | \
fr_possible_flags | \
v4wb_possible_flags | \
@@ -307,8 +297,7 @@
v6wbi_possible_flags | \
v7wbi_possible_flags)
-#define always_tlb_flags (v3_always_flags & \
- v4_always_flags & \
+#define always_tlb_flags (v4_always_flags & \
v4wbi_always_flags & \
fr_always_flags & \
v4wb_always_flags & \
@@ -447,6 +436,35 @@
}
}
+static inline void local_flush_bp_all(void)
+{
+ const int zero = 0;
+ const unsigned int __tlb_flag = __cpu_tlb_flags;
+
+ if (tlb_flag(TLB_V7_UIS_BP))
+ asm("mcr p15, 0, %0, c7, c1, 6" : : "r" (zero));
+ else if (tlb_flag(TLB_V6_BP))
+ asm("mcr p15, 0, %0, c7, c5, 6" : : "r" (zero));
+
+ if (tlb_flag(TLB_BARRIER))
+ isb();
+}
+
+#ifdef CONFIG_ARM_ERRATA_798181
+static inline void dummy_flush_tlb_a15_erratum(void)
+{
+ /*
+ * Dummy TLBIMVAIS. Using the unmapped address 0 and ASID 0.
+ */
+ asm("mcr p15, 0, %0, c8, c3, 1" : : "r" (0));
+ dsb();
+}
+#else
+static inline void dummy_flush_tlb_a15_erratum(void)
+{
+}
+#endif
+
/*
* flush_pmd_entry
*
@@ -497,6 +515,7 @@
#define flush_tlb_kernel_page local_flush_tlb_kernel_page
#define flush_tlb_range local_flush_tlb_range
#define flush_tlb_kernel_range local_flush_tlb_kernel_range
+#define flush_bp_all local_flush_bp_all
#else
extern void flush_tlb_all(void);
extern void flush_tlb_mm(struct mm_struct *mm);
@@ -504,6 +523,7 @@
extern void flush_tlb_kernel_page(unsigned long kaddr);
extern void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end);
extern void flush_tlb_kernel_range(unsigned long start, unsigned long end);
+extern void flush_bp_all(void);
#endif
/*
diff --git a/arch/arm/kernel/asm-offsets.c b/arch/arm/kernel/asm-offsets.c
index 1429d89..ebbb537 100644
--- a/arch/arm/kernel/asm-offsets.c
+++ b/arch/arm/kernel/asm-offsets.c
@@ -105,7 +105,7 @@
BLANK();
#endif
#ifdef CONFIG_CPU_HAS_ASID
- DEFINE(MM_CONTEXT_ID, offsetof(struct mm_struct, context.id));
+ DEFINE(MM_CONTEXT_ID, offsetof(struct mm_struct, context.id.counter));
BLANK();
#endif
DEFINE(VMA_VM_MM, offsetof(struct vm_area_struct, vm_mm));
diff --git a/arch/arm/kernel/entry-common.S b/arch/arm/kernel/entry-common.S
index 54ee265..e463331 100644
--- a/arch/arm/kernel/entry-common.S
+++ b/arch/arm/kernel/entry-common.S
@@ -444,12 +444,7 @@
#ifdef CONFIG_SECCOMP
tst r10, #_TIF_SECCOMP
- beq 1f
- mov r0, scno
- bl __secure_computing
- add r0, sp, #S_R0 + S_OFF @ pointer to regs
- ldmia r0, {r0 - r3} @ have to reload r0 - r3
-1:
+ bne __sys_trace
#endif
tst r10, #_TIF_SYSCALL_WORK @ are we tracing syscalls?
diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c
index 9650c14..77da665 100644
--- a/arch/arm/kernel/ptrace.c
+++ b/arch/arm/kernel/ptrace.c
@@ -909,20 +909,22 @@
asmlinkage int syscall_trace(int why, struct pt_regs *regs, int scno)
{
unsigned long ip;
+ current_thread_info()->syscall = scno;
if (why)
audit_syscall_exit(regs);
- else
+ else {
+ if (secure_computing(scno) == -1)
+ return -1;
audit_syscall_entry(AUDIT_ARCH_ARM, scno, regs->ARM_r0,
regs->ARM_r1, regs->ARM_r2, regs->ARM_r3);
+ }
if (!test_thread_flag(TIF_SYSCALL_TRACE))
return scno;
if (!(current->ptrace & PT_PTRACED))
return scno;
- current_thread_info()->syscall = scno;
-
/*
* IP is used to denote syscall entry/exit:
* IP = 0 -> entry, =1 -> exit
diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c
index f90c962..ce17a01 100644
--- a/arch/arm/kernel/smp.c
+++ b/arch/arm/kernel/smp.c
@@ -257,6 +257,7 @@
* switch away from it before attempting any exclusive accesses.
*/
cpu_switch_mm(mm->pgd, mm);
+ local_flush_bp_all();
enter_lazy_tlb(mm, current);
local_flush_tlb_all();
diff --git a/arch/arm/kernel/smp_tlb.c b/arch/arm/kernel/smp_tlb.c
index 02c5d2c..e82e1d2 100644
--- a/arch/arm/kernel/smp_tlb.c
+++ b/arch/arm/kernel/smp_tlb.c
@@ -12,6 +12,7 @@
#include <asm/smp_plat.h>
#include <asm/tlbflush.h>
+#include <asm/mmu_context.h>
/**********************************************************************/
@@ -64,12 +65,77 @@
local_flush_tlb_kernel_range(ta->ta_start, ta->ta_end);
}
+static inline void ipi_flush_bp_all(void *ignored)
+{
+ local_flush_bp_all();
+}
+
+#ifdef CONFIG_ARM_ERRATA_798181
+static int erratum_a15_798181(void)
+{
+ unsigned int midr = read_cpuid_id();
+
+ /* Cortex-A15 r0p0..r3p2 affected */
+ if ((midr & 0xff0ffff0) != 0x410fc0f0 || midr > 0x413fc0f2)
+ return 0;
+ return 1;
+}
+#else
+static int erratum_a15_798181(void)
+{
+ return 0;
+}
+#endif
+
+static void ipi_flush_tlb_a15_erratum(void *arg)
+{
+ dmb();
+}
+
+static void broadcast_tlb_a15_erratum(void)
+{
+ if (!erratum_a15_798181())
+ return;
+
+ dummy_flush_tlb_a15_erratum();
+ smp_call_function_many(cpu_online_mask, ipi_flush_tlb_a15_erratum,
+ NULL, 1);
+}
+
+static void broadcast_tlb_mm_a15_erratum(struct mm_struct *mm)
+{
+ int cpu;
+ cpumask_t mask = { CPU_BITS_NONE };
+
+ if (!erratum_a15_798181())
+ return;
+
+ dummy_flush_tlb_a15_erratum();
+ for_each_online_cpu(cpu) {
+ if (cpu == smp_processor_id())
+ continue;
+ /*
+ * We only need to send an IPI if the other CPUs are running
+ * the same ASID as the one being invalidated. There is no
+ * need for locking around the active_asids check since the
+ * switch_mm() function has at least one dmb() (as required by
+ * this workaround) in case a context switch happens on
+ * another CPU after the condition below.
+ */
+ if (atomic64_read(&mm->context.id) ==
+ atomic64_read(&per_cpu(active_asids, cpu)))
+ cpumask_set_cpu(cpu, &mask);
+ }
+ smp_call_function_many(&mask, ipi_flush_tlb_a15_erratum, NULL, 1);
+}
+
void flush_tlb_all(void)
{
if (tlb_ops_need_broadcast())
on_each_cpu(ipi_flush_tlb_all, NULL, 1);
else
local_flush_tlb_all();
+ broadcast_tlb_a15_erratum();
}
void flush_tlb_mm(struct mm_struct *mm)
@@ -78,6 +144,7 @@
on_each_cpu_mask(mm_cpumask(mm), ipi_flush_tlb_mm, mm, 1);
else
local_flush_tlb_mm(mm);
+ broadcast_tlb_mm_a15_erratum(mm);
}
void flush_tlb_page(struct vm_area_struct *vma, unsigned long uaddr)
@@ -90,6 +157,7 @@
&ta, 1);
} else
local_flush_tlb_page(vma, uaddr);
+ broadcast_tlb_mm_a15_erratum(vma->vm_mm);
}
void flush_tlb_kernel_page(unsigned long kaddr)
@@ -100,6 +168,7 @@
on_each_cpu(ipi_flush_tlb_kernel_page, &ta, 1);
} else
local_flush_tlb_kernel_page(kaddr);
+ broadcast_tlb_a15_erratum();
}
void flush_tlb_range(struct vm_area_struct *vma,
@@ -114,6 +183,7 @@
&ta, 1);
} else
local_flush_tlb_range(vma, start, end);
+ broadcast_tlb_mm_a15_erratum(vma->vm_mm);
}
void flush_tlb_kernel_range(unsigned long start, unsigned long end)
@@ -125,5 +195,13 @@
on_each_cpu(ipi_flush_tlb_kernel_range, &ta, 1);
} else
local_flush_tlb_kernel_range(start, end);
+ broadcast_tlb_a15_erratum();
}
+void flush_bp_all(void)
+{
+ if (tlb_ops_need_broadcast())
+ on_each_cpu(ipi_flush_bp_all, NULL, 1);
+ else
+ local_flush_bp_all();
+}
diff --git a/arch/arm/kernel/suspend.c b/arch/arm/kernel/suspend.c
index 1794cc3..2246e53 100644
--- a/arch/arm/kernel/suspend.c
+++ b/arch/arm/kernel/suspend.c
@@ -53,6 +53,7 @@
ret = __cpu_suspend(arg, fn);
if (ret == 0) {
cpu_switch_mm(mm->pgd, mm);
+ local_flush_bp_all();
local_flush_tlb_all();
}
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c
index a53a5a3..fa570a8 100644
--- a/arch/arm/kernel/traps.c
+++ b/arch/arm/kernel/traps.c
@@ -508,6 +508,10 @@
struct thread_info *thread = current_thread_info();
siginfo_t info;
+ /* Emulate/fallthrough. */
+ if (no == -1)
+ return regs->ARM_r0;
+
if ((no >> 16) != (__ARM_NR_BASE>> 16))
return bad_syscall(no, regs);
diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig
index 3423296..ded07c6 100644
--- a/arch/arm/mach-exynos/Kconfig
+++ b/arch/arm/mach-exynos/Kconfig
@@ -534,6 +534,61 @@
select EXYNOS5_DEV_BTS
help
Machine support for Samsung SMDK5250
+
+config MACH_MANTA
+ bool "Manta"
+ select SOC_BUS
+ select SOC_EXYNOS5250
+ select S3C_DEV_HSMMC3
+ select S3C_DEV_I2C1
+ select S3C_DEV_I2C2
+ select S3C_DEV_I2C3
+ select S3C_DEV_I2C4
+ select S3C_DEV_I2C5
+ select S3C_DEV_I2C7
+ select S3C_DEV_RTC
+ select S3C_DEV_USB_HSOTG
+ select S5P_DEV_MFC
+ select S5P_DEV_DP
+ select S5P_DEV_FIMD1
+ select S5P_DEV_FIMG2D
+ select S5P_DEV_TV
+ select S5P_DEV_I2C_HDMIPHY
+ select S5P_DEV_USB_EHCI
+ select S3C_DEV_WDT
+ select S5P_GPIO_INT
+ select EXYNOS_DEV_DMA
+ select EXYNOS_DEV_SYSMMU
+ select EXYNOS_DEV_DWMCI
+ select EXYNOS_DEV_DMA
+ select EXYNOS_PERSISTENT_CLOCK
+ select EXYNOS_SETUP_ADC
+ select EXYNOS_SETUP_DP
+ select EXYNOS_SETUP_FIMD1
+ select EXYNOS_DEV_ROTATOR
+ select EXYNOS_DEV_TMU
+ select EXYNOS4_DEV_FIMC_IS
+ select EXYNOS4_DEV_USB_OHCI
+ select EXYNOS4_SETUP_I2C1
+ select EXYNOS4_SETUP_I2C2
+ select EXYNOS4_SETUP_I2C3
+ select EXYNOS4_SETUP_I2C4
+ select EXYNOS4_SETUP_I2C5
+ select EXYNOS4_SETUP_I2C7
+ select EXYNOS4_SETUP_MFC
+ select EXYNOS4_SETUP_USB_PHY
+ select EXYNOS4_SETUP_FIMC_IS
+ select EXYNOS5_DEV_BTS
+ select SAMSUNG_DEV_ADC
+ select SAMSUNG_DEV_BACKLIGHT
+ select SAMSUNG_DEV_PWM
+ select S3C64XX_DEV_SPI1
+ select S3C64XX_DEV_SPI2
+ select EXYNOS_SETUP_SPI
+ select USB_OTG_UTILS
+ help
+ Machine support for Manta
+
endif
comment "Flattened Device Tree based board for EXYNOS SoCs"
diff --git a/arch/arm/mach-exynos/Makefile b/arch/arm/mach-exynos/Makefile
old mode 100644
new mode 100755
index 756c49d..9c7542e6
--- a/arch/arm/mach-exynos/Makefile
+++ b/arch/arm/mach-exynos/Makefile
@@ -61,6 +61,25 @@
obj-$(CONFIG_MACH_SMDK5250) += mach-smdk5250.o
+# Manta board files
+obj-$(CONFIG_MACH_MANTA) += board-manta.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-audio.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-battery.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-pogo.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-display.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-input.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-power.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-wifi.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-media.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-camera.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-sensors.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-gps.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-jack.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-vibrator.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-nfc.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-bluetooth.o
+obj-$(CONFIG_MACH_MANTA) += board-manta-connector.o
+
# device support
obj-y += dev-uart.o
diff --git a/arch/arm/mach-exynos/board-manta-audio.c b/arch/arm/mach-exynos/board-manta-audio.c
new file mode 100644
index 0000000..a39b21c
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-audio.c
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * 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 <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/mfd/wm8994/gpio.h>
+#include <linux/mfd/wm8994/pdata.h>
+#include <linux/platform_data/es305.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/fixed.h>
+#include <linux/regulator/machine.h>
+
+#include <mach/gpio.h>
+
+#include <plat/devs.h>
+#include <plat/gpio-cfg.h>
+
+#include "board-manta.h"
+
+#define GPIO_ES305_WAKEUP EXYNOS5_GPG0(3)
+#define GPIO_ES305_RESET EXYNOS5_GPG0(4)
+#define GPIO_ES305_CLK_EN EXYNOS5_GPG0(6)
+#define GPIO_CODEC_LDO_EN EXYNOS5_GPH1(1)
+
+static struct clk *clkout;
+
+static void manta_es305_clk_enable(bool enable)
+{
+ int hw_rev = exynos5_manta_get_revision();
+
+ if (enable) {
+ if (hw_rev >= MANTA_REV_PRE_ALPHA)
+ gpio_set_value(GPIO_ES305_CLK_EN, 1);
+ else
+ clk_enable(clkout);
+ } else {
+ if (hw_rev >= MANTA_REV_PRE_ALPHA)
+ gpio_set_value(GPIO_ES305_CLK_EN, 0);
+ else
+ clk_disable(clkout);
+ }
+}
+
+static struct es305_platform_data es305_pdata = {
+ .gpio_wakeup = GPIO_ES305_WAKEUP,
+ .gpio_reset = GPIO_ES305_RESET,
+ .clk_enable = manta_es305_clk_enable,
+ .passthrough_src = 1, /* port A */
+ .passthrough_dst = 4, /* port D */
+};
+
+static struct i2c_board_info i2c_devs4[] __initdata = {
+ {
+ I2C_BOARD_INFO("audience_es305", 0x3e),
+ .platform_data = &es305_pdata,
+ },
+};
+
+static struct regulator_consumer_supply vbatt_supplies[] = {
+ REGULATOR_SUPPLY("LDO1VDD", "7-001a"),
+ REGULATOR_SUPPLY("SPKVDD1", "7-001a"),
+ REGULATOR_SUPPLY("SPKVDD2", "7-001a"),
+};
+
+static struct regulator_init_data vbatt_initdata = {
+ .constraints = {
+ .always_on = 1,
+ },
+ .num_consumer_supplies = ARRAY_SIZE(vbatt_supplies),
+ .consumer_supplies = vbatt_supplies,
+};
+
+static struct fixed_voltage_config vbatt_config = {
+ .init_data = &vbatt_initdata,
+ .microvolts = 3700000,
+ .supply_name = "VBATT",
+ .gpio = -EINVAL,
+};
+
+static struct platform_device vbatt_device = {
+ .name = "reg-fixed-voltage",
+ .id = -1,
+ .dev = {
+ .platform_data = &vbatt_config,
+ },
+};
+
+static struct regulator_consumer_supply wm1811_ldo1_supplies[] = {
+ REGULATOR_SUPPLY("AVDD1", "7-001a"),
+};
+
+static struct regulator_init_data wm1811_ldo1_initdata = {
+ .constraints = {
+ .name = "WM1811 LDO1",
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = ARRAY_SIZE(wm1811_ldo1_supplies),
+ .consumer_supplies = wm1811_ldo1_supplies,
+};
+
+static struct regulator_consumer_supply wm1811_ldo2_supplies[] = {
+ REGULATOR_SUPPLY("DCVDD", "7-001a"),
+};
+
+static struct regulator_init_data wm1811_ldo2_initdata = {
+ .constraints = {
+ .name = "WM1811 LDO2",
+ .always_on = true, /* Actually status changed by LDO1 */
+ },
+ .num_consumer_supplies = ARRAY_SIZE(wm1811_ldo2_supplies),
+ .consumer_supplies = wm1811_ldo2_supplies,
+};
+
+static struct wm8994_drc_cfg wm1811_drc_cfgs[] = {
+ {
+ .name = "Default",
+ .regs = {0x0098, 0x0845, 0x0, 0x0, 0x0 }
+ },
+ {
+ .name = "Speakers Media",
+ .regs = {0x0098, 0x0245, 0x0028, 0x00c6, 0x0 }
+ }
+};
+
+static struct wm8958_micd_rate manta_micd_rates[] = {
+ { 32768, true, 0, 1 },
+ { 32768, false, 0, 1 },
+ { 44100 * 256, true, 8, 8 },
+ { 44100 * 256, false, 8, 8 },
+};
+
+static struct wm8994_pdata wm1811_pdata = {
+ .gpio_defaults = {
+ [0] = WM8994_GP_FN_IRQ, /* GPIO1 IRQ output, CMOS mode */
+ [7] = WM8994_GPN_DIR |
+ WM8994_GP_FN_PIN_SPECIFIC, /* DACDAT3 */
+ [8] = WM8994_CONFIGURE_GPIO |
+ WM8994_GP_FN_PIN_SPECIFIC, /* ADCDAT3 */
+ [9] = WM8994_CONFIGURE_GPIO |
+ WM8994_GP_FN_PIN_SPECIFIC, /* LRCLK3 */
+ [10] = WM8994_CONFIGURE_GPIO |
+ WM8994_GP_FN_PIN_SPECIFIC, /* BCLK3 */
+ },
+
+ /* The enable is shared but assign it to LDO1 for software */
+ .ldo = {
+ {
+ .enable = GPIO_CODEC_LDO_EN,
+ .init_data = &wm1811_ldo1_initdata,
+ },
+ {
+ .init_data = &wm1811_ldo2_initdata,
+ },
+ },
+
+ .num_drc_cfgs = ARRAY_SIZE(wm1811_drc_cfgs),
+ .drc_cfgs = wm1811_drc_cfgs,
+
+ .num_micd_rates = ARRAY_SIZE(manta_micd_rates),
+ .micd_rates = manta_micd_rates,
+
+ /* Regulated mode at highest output voltage */
+ .micbias = {0x2f, 0x2f},
+ .micd_lvl_sel = 0xff,
+
+ /* Support external capacitors*/
+ .jd_ext_cap = 1,
+
+ .ldo_ena_always_driven = true,
+ .irq_base = MANTA_IRQ_BOARD_AUDIO_START,
+
+ /*
+ * Restrict the i2s clock for a maximum of 2 channels to keep
+ * the bus within spec since i2s0 is shared with the HDMI block
+ */
+ .max_channels_clocked = {2, 2, 2},
+};
+
+static struct i2c_board_info i2c_devs7[] __initdata = {
+ {
+ I2C_BOARD_INFO("wm1811", (0x34 >> 1)), /* Audio codec */
+ .platform_data = &wm1811_pdata,
+ .irq = IRQ_EINT(29),
+ },
+};
+
+static struct platform_device manta_i2s_device = {
+ .name = "manta-i2s",
+ .id = -1,
+};
+
+static struct platform_device manta_spdif_device = {
+ .name = "manta-spdif",
+ .id = -1,
+};
+
+static struct platform_device manta_spdif_dit_device = {
+ .name = "spdif-dit",
+ .id = -1,
+};
+
+static struct platform_device *manta_audio_devices[] __initdata = {
+ &vbatt_device,
+ &samsung_asoc_dma,
+ &samsung_asoc_idma,
+ &exynos5_device_srp,
+ &exynos5_device_i2s0,
+ &exynos5_device_pcm0,
+ &exynos5_device_spdif,
+ &manta_i2s_device,
+ &manta_spdif_device,
+ &manta_spdif_dit_device,
+};
+
+static void manta_audio_setup_clocks(void)
+{
+ struct clk *fout_epll, *mout_epll;
+ struct clk *sclk_audio, *sclk_spdif;
+ struct clk *xxti;
+ int ret;
+
+ fout_epll = clk_get(NULL, "fout_epll");
+ if (IS_ERR(fout_epll)) {
+ pr_err("%s:cannot get fout_epll clock\n", __func__);
+ return;
+ }
+
+ mout_epll = clk_get(NULL, "mout_epll");
+ if (IS_ERR(mout_epll)) {
+ pr_err("%s: cannot get mout_epll clock\n", __func__);
+ goto out1;
+ }
+
+ sclk_audio = clk_get(NULL, "sclk_audio");
+ if (IS_ERR(sclk_audio)) {
+ pr_err("%s: cannot get sclk_audio clock\n", __func__);
+ goto out2;
+ }
+
+ sclk_spdif = clk_get(NULL, "sclk_spdif");
+ if (IS_ERR(sclk_spdif)) {
+ pr_err("%s: cannot get sclk_spdif clock\n", __func__);
+ goto out3;
+ }
+
+ xxti = clk_get(NULL, "xxti");
+ if (IS_ERR(xxti)) {
+ pr_err("%s: cannot get xxti clock\n", __func__);
+ goto out4;
+ }
+
+ clkout = clk_get(NULL, "clkout");
+ if (IS_ERR(clkout)) {
+ pr_err("%s: cannot get clkout\n", __func__);
+ goto out5;
+ }
+
+ clk_set_parent(mout_epll, fout_epll);
+ clk_set_parent(sclk_audio, mout_epll);
+ clk_set_parent(sclk_spdif, sclk_audio);
+ clk_set_parent(clkout, xxti);
+ clk_add_alias("system_clk", "manta-i2s", "clkout", NULL);
+
+ ret = gpio_request(GPIO_ES305_CLK_EN, "ES305 clk_en");
+ if (ret < 0) {
+ pr_err("%s: error requesting ES305 clk_en gpio\n", __func__);
+ goto out6;
+ }
+ gpio_direction_output(GPIO_ES305_CLK_EN, 0);
+
+ clk_put(fout_epll);
+ clk_put(mout_epll);
+ clk_put(sclk_audio);
+ clk_put(sclk_spdif);
+ clk_put(xxti);
+
+ return;
+out6:
+ clk_put(clkout);
+out5:
+ clk_put(xxti);
+out4:
+ clk_put(sclk_spdif);
+out3:
+ clk_put(sclk_audio);
+out2:
+ clk_put(mout_epll);
+out1:
+ clk_put(fout_epll);
+}
+
+void __init exynos5_manta_audio_init(void)
+{
+ manta_audio_setup_clocks();
+
+ s5p_gpio_set_pd_cfg(GPIO_ES305_WAKEUP, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_cfg(GPIO_ES305_RESET, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_cfg(GPIO_ES305_CLK_EN, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_cfg(GPIO_CODEC_LDO_EN, S5P_GPIO_PD_PREV_STATE);
+
+ i2c_register_board_info(4, i2c_devs4, ARRAY_SIZE(i2c_devs4));
+ i2c_register_board_info(7, i2c_devs7, ARRAY_SIZE(i2c_devs7));
+
+ platform_add_devices(manta_audio_devices,
+ ARRAY_SIZE(manta_audio_devices));
+}
diff --git a/arch/arm/mach-exynos/board-manta-battery.c b/arch/arm/mach-exynos/board-manta-battery.c
new file mode 100644
index 0000000..805cdb8e
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-battery.c
@@ -0,0 +1,1089 @@
+/* linux/arch/arm/mach-exynos/board-manta-battery.c
+ *
+ * Copyright (c) 2012 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.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/debugfs.h>
+#include <linux/suspend.h>
+#include <linux/pm_wakeup.h>
+#include <linux/wakelock.h>
+#include <linux/notifier.h>
+#include <linux/usb/otg.h>
+
+#include <plat/adc.h>
+#include <plat/gpio-cfg.h>
+
+#include <linux/power/smb347-charger.h>
+#include <linux/platform_data/android_battery.h>
+#include <linux/platform_data/ds2482.h>
+
+#include "board-manta.h"
+
+#include <linux/slab.h>
+
+#define TA_ADC_LOW 700
+#define TA_ADC_HIGH 1750
+
+#define ADC_NUM_SAMPLES 5
+#define ADC_LIMIT_ERR_COUNT 5
+
+#define GPIO_USB_SEL1 EXYNOS5_GPH0(1)
+#define GPIO_TA_EN EXYNOS5_GPG1(5)
+#define GPIO_TA_INT EXYNOS5_GPX0(0)
+#define GPIO_TA_NCHG EXYNOS5_GPX0(4)
+#define GPIO_OTG_VBUS_SENSE EXYNOS5_GPX1(0)
+#define GPIO_VBUS_POGO_5V EXYNOS5_GPX1(2)
+#define GPIO_OTG_VBUS_SENSE_FAC EXYNOS5_GPB0(1)
+#define GPIO_1WIRE_SLEEP EXYNOS5_GPG0(0)
+
+enum charge_connector {
+ CHARGE_CONNECTOR_NONE,
+ CHARGE_CONNECTOR_POGO,
+ CHARGE_CONNECTOR_USB,
+ CHARGE_CONNECTOR_MAX,
+};
+
+static int manta_bat_battery_status;
+static enum manta_charge_source manta_bat_charge_source[CHARGE_CONNECTOR_MAX];
+static enum charge_connector manta_bat_charge_conn;
+static union power_supply_propval manta_bat_apsd_results;
+static int manta_bat_ta_adc;
+static bool manta_bat_dock;
+static bool manta_bat_usb_online;
+static bool manta_bat_pogo_online;
+static bool manta_bat_otg_enabled;
+static bool manta_bat_chg_enabled;
+static bool manta_bat_chg_enable_synced;
+static struct power_supply *manta_bat_smb347_mains;
+static struct power_supply *manta_bat_smb347_usb;
+static struct power_supply *manta_bat_smb347_battery;
+static struct power_supply *manta_bat_ds2784_battery;
+
+static struct android_bat_callbacks *bat_callbacks;
+
+static struct s3c_adc_client *ta_adc_client;
+
+static struct wake_lock manta_bat_chgdetect_wakelock;
+
+static struct delayed_work redetect_work;
+static struct wake_lock manta_bat_redetect_wl;
+
+static DEFINE_MUTEX(manta_bat_charger_detect_lock);
+static DEFINE_MUTEX(manta_bat_adc_lock);
+
+static bool manta_bat_suspended;
+
+static inline int manta_source_to_android(enum manta_charge_source src)
+{
+ switch (src) {
+ case MANTA_CHARGE_SOURCE_NONE:
+ return CHARGE_SOURCE_NONE;
+ case MANTA_CHARGE_SOURCE_USB:
+ case MANTA_CHARGE_SOURCE_UNKNOWN:
+ return CHARGE_SOURCE_USB;
+ case MANTA_CHARGE_SOURCE_AC_SAMSUNG:
+ case MANTA_CHARGE_SOURCE_AC_OTHER:
+ return CHARGE_SOURCE_AC;
+ default:
+ break;
+ }
+
+ return CHARGE_SOURCE_NONE;
+}
+
+static inline int manta_bat_get_ds2784(void)
+{
+ if (!manta_bat_ds2784_battery)
+ manta_bat_ds2784_battery =
+ power_supply_get_by_name("ds2784-fuelgauge");
+
+ if (!manta_bat_ds2784_battery) {
+ pr_err_once("%s: failed to get ds2784-fuelgauge power supply\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static inline int manta_bat_get_smb347_usb(void)
+{
+ if (!manta_bat_smb347_usb)
+ manta_bat_smb347_usb =
+ power_supply_get_by_name("smb347-usb");
+
+ if (!manta_bat_smb347_usb) {
+ pr_err("%s: failed to get smb347-usb power supply\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void charger_gpio_init(void)
+{
+ int ret;
+
+ s3c_gpio_cfgpin(GPIO_TA_INT, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(GPIO_TA_INT, S3C_GPIO_PULL_NONE);
+
+ s3c_gpio_cfgpin(GPIO_OTG_VBUS_SENSE, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(GPIO_OTG_VBUS_SENSE, S3C_GPIO_PULL_NONE);
+
+ s3c_gpio_cfgpin(GPIO_VBUS_POGO_5V, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(GPIO_VBUS_POGO_5V, S3C_GPIO_PULL_NONE);
+
+ s3c_gpio_cfgpin(GPIO_OTG_VBUS_SENSE_FAC, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(GPIO_OTG_VBUS_SENSE_FAC, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_pd_cfg(GPIO_OTG_VBUS_SENSE_FAC, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_pull(GPIO_OTG_VBUS_SENSE_FAC,
+ S5P_GPIO_PD_UPDOWN_DISABLE);
+
+ s3c_gpio_cfgpin(GPIO_TA_NCHG, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(GPIO_TA_NCHG, S3C_GPIO_PULL_NONE);
+
+ s3c_gpio_cfgpin(GPIO_TA_EN, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(GPIO_TA_EN, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_pd_cfg(GPIO_TA_EN, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_pull(GPIO_TA_EN, S5P_GPIO_PD_UPDOWN_DISABLE);
+
+ s3c_gpio_cfgpin(GPIO_USB_SEL1, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(GPIO_USB_SEL1, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_pd_pull(GPIO_USB_SEL1, S5P_GPIO_PD_UPDOWN_DISABLE);
+ ret = gpio_request_one(GPIO_USB_SEL1, GPIOF_OUT_INIT_HIGH, "usb_sel1");
+ if (ret)
+ pr_err("%s: cannot request gpio%d\n", __func__, GPIO_USB_SEL1);
+}
+
+static int read_ta_adc(enum charge_connector conn, int ta_check_sel)
+{
+ int adc_max = -1;
+ int adc_min = 1 << 11;
+ int adc_total = 0;
+ int i, j;
+ int ret;
+
+ mutex_lock(&manta_bat_adc_lock);
+
+ /* switch to check adc */
+ if (conn == CHARGE_CONNECTOR_USB)
+ gpio_set_value(GPIO_USB_SEL1, 0);
+ else {
+ ret = manta_pogo_charge_detect_start(ta_check_sel);
+ if (ret < 0) {
+ pr_err("%s: Failed to start pogo charger detection\n",
+ __func__);
+ goto fail_gpio;
+ }
+ }
+
+ msleep(100);
+
+ for (i = 0; i < ADC_NUM_SAMPLES; i++) {
+ ret = s3c_adc_read(ta_adc_client, 0);
+
+ if (ret == -ETIMEDOUT) {
+ for (j = 0; j < ADC_LIMIT_ERR_COUNT; j++) {
+ msleep(20);
+ ret = s3c_adc_read(ta_adc_client, 0);
+ if (ret > 0)
+ break;
+ }
+ if (j >= ADC_LIMIT_ERR_COUNT) {
+ pr_err("%s: Retry count exceeded\n", __func__);
+ goto out;
+ }
+ } else if (ret < 0) {
+ pr_err("%s: Failed read adc value : %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ if (ret > adc_max)
+ adc_max = ret;
+ if (ret < adc_min)
+ adc_min = ret;
+
+ adc_total += ret;
+ }
+
+ ret = (adc_total - adc_max - adc_min) / (ADC_NUM_SAMPLES - 2);
+
+out:
+ msleep(50);
+
+ /* switch back to normal */
+ if (conn == CHARGE_CONNECTOR_USB)
+ gpio_set_value(GPIO_USB_SEL1, 1);
+ else
+ manta_pogo_charge_detect_end();
+
+fail_gpio:
+ mutex_unlock(&manta_bat_adc_lock);
+ return ret;
+}
+
+int manta_bat_otg_enable(bool enable)
+{
+ int ret;
+ union power_supply_propval value;
+
+ if (manta_bat_get_smb347_usb())
+ return -ENODEV;
+
+ mutex_lock(&manta_bat_charger_detect_lock);
+
+ if (manta_bat_otg_enabled == enable) {
+ mutex_unlock(&manta_bat_charger_detect_lock);
+ return 0;
+ }
+
+ value.intval = enable ? 1 : 0;
+ ret = manta_bat_smb347_usb->set_property(manta_bat_smb347_usb,
+ POWER_SUPPLY_PROP_USB_OTG,
+ &value);
+ if (ret)
+ pr_err("%s: failed to set smb347-usb OTG mode\n",
+ __func__);
+ else
+ manta_bat_otg_enabled = enable;
+
+ mutex_unlock(&manta_bat_charger_detect_lock);
+ return ret;
+}
+
+static enum manta_charge_source check_samsung_charger(
+ enum charge_connector conn, bool usbin_redetect)
+{
+ int ret;
+ bool samsung_ac_detect;
+ enum manta_charge_source charge_source;
+ union power_supply_propval prop_zero = {0,};
+ union power_supply_propval prop_one = {1,};
+
+ if (conn == CHARGE_CONNECTOR_POGO) {
+ manta_bat_ta_adc = read_ta_adc(conn, 0);
+ pr_debug("%s: ta_adc conn=%d ta_check=0 val=%d\n", __func__,
+ conn, manta_bat_ta_adc);
+ samsung_ac_detect = manta_bat_ta_adc > TA_ADC_LOW &&
+ manta_bat_ta_adc < TA_ADC_HIGH;
+ charge_source = samsung_ac_detect ?
+ MANTA_CHARGE_SOURCE_AC_OTHER :
+ MANTA_CHARGE_SOURCE_USB;
+ } else {
+ if (manta_bat_get_smb347_usb())
+ return MANTA_CHARGE_SOURCE_UNKNOWN;
+ ret = manta_bat_smb347_usb->set_property(
+ manta_bat_smb347_usb,
+ POWER_SUPPLY_PROP_CHARGER_DETECTION,
+ &prop_one);
+ if (ret)
+ pr_err("%s: failed to enable smb347-usb charger detect\n",
+ __func__);
+ ret = manta_bat_smb347_usb->get_property(
+ manta_bat_smb347_usb,
+ POWER_SUPPLY_PROP_REMOTE_TYPE, &manta_bat_apsd_results);
+ pr_debug("%s: type=%d ret=%d\n", __func__,
+ manta_bat_apsd_results.intval, ret);
+ samsung_ac_detect =
+ manta_bat_apsd_results.intval ==
+ POWER_SUPPLY_TYPE_USB_DCP;
+
+ switch (manta_bat_apsd_results.intval) {
+ case POWER_SUPPLY_TYPE_USB:
+ charge_source = MANTA_CHARGE_SOURCE_USB;
+ break;
+ case POWER_SUPPLY_TYPE_UNKNOWN:
+ charge_source = MANTA_CHARGE_SOURCE_UNKNOWN;
+ break;
+ default:
+ charge_source = MANTA_CHARGE_SOURCE_AC_OTHER;
+ break;
+ }
+
+ /*
+ * Leave APSD on if unknown power source (possibly timeout)
+ * and not re-detecting USBIN. If redetecting USBIN and
+ * power source is still unknown then disable APSD and assume
+ * a slow charger.
+ */
+
+ if (usbin_redetect ||
+ manta_bat_apsd_results.intval != POWER_SUPPLY_TYPE_UNKNOWN)
+ ret = manta_bat_smb347_usb->set_property(
+ manta_bat_smb347_usb,
+ POWER_SUPPLY_PROP_CHARGER_DETECTION,
+ &prop_zero);
+ }
+
+ if (samsung_ac_detect) {
+ bool samsung_ta_detected;
+
+ manta_bat_ta_adc = read_ta_adc(conn, 1);
+ pr_debug("%s: ta_adc conn=%d ta_check=1 val=%d\n",
+ __func__, conn, manta_bat_ta_adc);
+ samsung_ta_detected =
+ manta_bat_ta_adc > TA_ADC_LOW &&
+ manta_bat_ta_adc < TA_ADC_HIGH;
+
+ if (samsung_ta_detected)
+ charge_source = MANTA_CHARGE_SOURCE_AC_SAMSUNG;
+ }
+
+ return charge_source;
+}
+
+static enum manta_charge_source
+detect_charge_source(enum charge_connector conn, bool online,
+ bool force_dock_redetect, bool usbin_redetect)
+{
+ enum manta_charge_source charge_source;
+ int ret;
+
+ manta_bat_dock = false;
+
+ if (!online) {
+ if (conn == CHARGE_CONNECTOR_POGO)
+ manta_pogo_set_vbus(online, NULL);
+ return MANTA_CHARGE_SOURCE_NONE;
+ }
+
+ charge_source = force_dock_redetect ?
+ MANTA_CHARGE_SOURCE_USB :
+ check_samsung_charger(conn, usbin_redetect);
+
+ if (conn == CHARGE_CONNECTOR_POGO &&
+ charge_source == MANTA_CHARGE_SOURCE_USB) {
+ ret = manta_pogo_set_vbus(online, &charge_source);
+ manta_bat_dock = ret >= 0;
+ }
+
+ return charge_source;
+}
+
+static int update_charging_status(bool usb_connected, bool pogo_connected,
+ bool force_dock_redetect,
+ bool usbin_redetect)
+{
+ enum charge_connector old_charge_conn;
+ int ret = 0;
+
+ if (manta_bat_usb_online != usb_connected || usbin_redetect) {
+ bool usb_conn_src_usb;
+
+ manta_bat_usb_online = usb_connected;
+
+ if (usbin_redetect)
+ manta_otg_set_usb_state(false);
+
+ manta_bat_charge_source[CHARGE_CONNECTOR_USB] =
+ detect_charge_source(CHARGE_CONNECTOR_USB,
+ usb_connected, false,
+ usbin_redetect);
+ usb_conn_src_usb =
+ manta_bat_charge_source[CHARGE_CONNECTOR_USB] ==
+ MANTA_CHARGE_SOURCE_USB;
+
+ /*
+ * If USB disconnected, cancel any pending USB charger
+ * redetect.
+ */
+
+ if (!usb_conn_src_usb) {
+ ret = cancel_delayed_work(&redetect_work);
+ if (ret)
+ wake_unlock(&manta_bat_redetect_wl);
+ }
+
+ manta_otg_set_usb_state(usb_conn_src_usb);
+
+ if (!usbin_redetect &&
+ (usb_conn_src_usb ||
+ manta_bat_charge_source[CHARGE_CONNECTOR_USB] ==
+ MANTA_CHARGE_SOURCE_UNKNOWN)) {
+ cancel_delayed_work(&redetect_work);
+ wake_lock(&manta_bat_redetect_wl);
+ schedule_delayed_work(&redetect_work,
+ msecs_to_jiffies(5000));
+ }
+
+ ret = 1;
+ }
+
+ if (manta_bat_pogo_online != pogo_connected || force_dock_redetect) {
+ manta_bat_pogo_online = pogo_connected;
+ manta_bat_charge_source[CHARGE_CONNECTOR_POGO] =
+ detect_charge_source(CHARGE_CONNECTOR_POGO,
+ pogo_connected,
+ force_dock_redetect, false);
+ ret = 1;
+ }
+
+ old_charge_conn = manta_bat_charge_conn;
+
+ if (manta_bat_charge_source[CHARGE_CONNECTOR_POGO] ==
+ MANTA_CHARGE_SOURCE_NONE &&
+ manta_bat_charge_source[CHARGE_CONNECTOR_USB] ==
+ MANTA_CHARGE_SOURCE_NONE)
+ manta_bat_charge_conn = CHARGE_CONNECTOR_NONE;
+ else
+ manta_bat_charge_conn =
+ (manta_bat_charge_source[CHARGE_CONNECTOR_POGO] >=
+ manta_bat_charge_source[CHARGE_CONNECTOR_USB]) ?
+ CHARGE_CONNECTOR_POGO : CHARGE_CONNECTOR_USB;
+
+ if (old_charge_conn != manta_bat_charge_conn)
+ ret = 1;
+
+ return ret;
+}
+
+static void manta_bat_set_charging_enable(int en)
+{
+ union power_supply_propval value;
+
+ manta_bat_chg_enabled = en;
+
+ if (!manta_bat_smb347_battery)
+ manta_bat_smb347_battery =
+ power_supply_get_by_name("smb347-battery");
+
+ if (!manta_bat_smb347_battery)
+ return;
+
+ value.intval = en ? 1 : 0;
+ manta_bat_smb347_battery->set_property(
+ manta_bat_smb347_battery, POWER_SUPPLY_PROP_CHARGE_ENABLED,
+ &value);
+ manta_bat_chg_enable_synced = true;
+}
+
+static void manta_bat_sync_charge_enable(void)
+{
+ union power_supply_propval chg_enabled = {0,};
+
+ if (!manta_bat_smb347_battery)
+ return;
+
+ manta_bat_smb347_battery->get_property(
+ manta_bat_smb347_battery,
+ POWER_SUPPLY_PROP_CHARGE_ENABLED,
+ &chg_enabled);
+
+ if (chg_enabled.intval != manta_bat_chg_enabled) {
+ if (manta_bat_chg_enable_synced) {
+ pr_info("%s: charger changed enable state to %d\n",
+ __func__, chg_enabled.intval);
+ manta_bat_chg_enabled = chg_enabled.intval;
+ }
+
+ manta_bat_set_charging_enable(manta_bat_chg_enabled);
+ }
+}
+
+static void exynos5_manta_set_priority(void)
+{
+ int ret;
+ union power_supply_propval value;
+
+ if (!manta_bat_smb347_battery)
+ manta_bat_smb347_battery =
+ power_supply_get_by_name("smb347-battery");
+
+ if (!manta_bat_smb347_battery) {
+ pr_err("%s: failed to get smb347-battery power supply\n",
+ __func__);
+ return;
+ }
+
+ /* set SMB347 INPUT SOURCE PRIORITY = DCIN or USBIN */
+ value.intval = manta_bat_charge_conn == CHARGE_CONNECTOR_USB;
+ ret = manta_bat_smb347_battery->set_property(
+ manta_bat_smb347_battery, POWER_SUPPLY_PROP_USB_INPRIORITY,
+ &value);
+ if (ret)
+ pr_err("%s: failed to set smb347 input source priority: %d\n",
+ __func__, ret);
+
+ /* enable SMB347 AICL for other TA */
+ value.intval =
+ manta_bat_charge_source[manta_bat_charge_conn] ==
+ MANTA_CHARGE_SOURCE_AC_OTHER;
+ ret = manta_bat_smb347_battery->set_property(
+ manta_bat_smb347_battery, POWER_SUPPLY_PROP_AUTO_CURRENT_LIMIT,
+ &value);
+ if (ret)
+ pr_err("%s: failed to set smb347 AICL: %d\n",
+ __func__, ret);
+}
+
+static void change_charger_status(bool force_dock_redetect,
+ bool usbin_redetect)
+{
+ int ta_int;
+ union power_supply_propval pogo_connected = {0,};
+ union power_supply_propval usb_connected = {0,};
+ union power_supply_propval smb347_status = {0,};
+ int status_change = 0;
+ int ret;
+
+ mutex_lock(&manta_bat_charger_detect_lock);
+ ta_int = gpio_get_value(GPIO_OTG_VBUS_SENSE) |
+ gpio_get_value(GPIO_VBUS_POGO_5V);
+
+ if (!manta_bat_smb347_mains || !manta_bat_smb347_usb ||
+ !manta_bat_smb347_battery) {
+ manta_bat_smb347_mains =
+ power_supply_get_by_name("smb347-mains");
+ manta_bat_get_smb347_usb();
+ manta_bat_smb347_battery =
+ power_supply_get_by_name("smb347-battery");
+
+ if (!manta_bat_smb347_mains || !manta_bat_smb347_usb ||
+ !manta_bat_smb347_battery)
+ pr_err("%s: failed to get power supplies\n", __func__);
+ }
+
+ if (!manta_bat_otg_enabled)
+ usb_connected.intval = gpio_get_value(GPIO_OTG_VBUS_SENSE);
+
+ pogo_connected.intval = gpio_get_value(GPIO_VBUS_POGO_5V);
+
+ if (manta_bat_smb347_usb &&
+ usb_connected.intval != manta_bat_usb_online) {
+ ret = manta_bat_smb347_usb->set_property(
+ manta_bat_smb347_usb, POWER_SUPPLY_PROP_ONLINE,
+ &usb_connected);
+ if (ret)
+ pr_err("%s: failed to change smb347-usb online\n",
+ __func__);
+ }
+
+ if (manta_bat_smb347_mains &&
+ pogo_connected.intval != manta_bat_pogo_online) {
+ ret = manta_bat_smb347_mains->set_property(
+ manta_bat_smb347_mains, POWER_SUPPLY_PROP_ONLINE,
+ &pogo_connected);
+ if (ret)
+ pr_err("%s: failed to change smb347-mains online\n",
+ __func__);
+ }
+
+ if (manta_bat_smb347_battery) {
+ manta_bat_smb347_battery->get_property(
+ manta_bat_smb347_battery, POWER_SUPPLY_PROP_STATUS,
+ &smb347_status);
+
+ if (smb347_status.intval != manta_bat_battery_status) {
+ if (smb347_status.intval == POWER_SUPPLY_STATUS_FULL &&
+ bat_callbacks && bat_callbacks->battery_set_full)
+ bat_callbacks->battery_set_full(
+ bat_callbacks);
+
+ manta_bat_battery_status = smb347_status.intval;
+ }
+ }
+
+ if (ta_int) {
+ status_change = update_charging_status(
+ usb_connected.intval, pogo_connected.intval,
+ force_dock_redetect, usbin_redetect);
+ manta_bat_sync_charge_enable();
+ } else {
+ status_change = update_charging_status(false, false, false,
+ false);
+ }
+
+ pr_debug("%s: ta_int(%d), charge_conn(%d), charge_source(%d)\n",
+ __func__, ta_int, manta_bat_charge_conn,
+ manta_bat_charge_source[manta_bat_charge_conn]);
+
+ if (status_change)
+ exynos5_manta_set_priority();
+
+ if (status_change && bat_callbacks &&
+ bat_callbacks->charge_source_changed)
+ bat_callbacks->charge_source_changed(
+ bat_callbacks,
+ manta_source_to_android(
+ manta_bat_charge_source[manta_bat_charge_conn]));
+ mutex_unlock(&manta_bat_charger_detect_lock);
+}
+
+void manta_force_update_pogo_charger(void)
+{
+ change_charger_status(true, false);
+}
+
+static char *exynos5_manta_supplicant[] = { "manta-board" };
+
+static struct smb347_charger_platform_data smb347_chg_pdata = {
+ .use_mains = true,
+ .use_usb = true,
+ .enable_control = SMB347_CHG_ENABLE_PIN_ACTIVE_LOW,
+ .usb_mode_pin_ctrl = false,
+ .max_charge_current = 2500000,
+ .max_charge_voltage = 4300000,
+ .disable_automatic_recharge = true,
+ .pre_charge_current = 200000,
+ .termination_current = 250000,
+ .pre_to_fast_voltage = 2600000,
+ .mains_current_limit = 2000000,
+ .usb_hc_current_limit = 1800000,
+ .irq_gpio = GPIO_TA_NCHG,
+ .disable_stat_interrupts = true,
+ .en_gpio = GPIO_TA_EN,
+ .supplied_to = exynos5_manta_supplicant,
+ .num_supplicants = ARRAY_SIZE(exynos5_manta_supplicant),
+};
+
+static void manta_bat_register_callbacks(struct android_bat_callbacks *ptr)
+{
+ bat_callbacks = ptr;
+}
+
+static void manta_bat_unregister_callbacks(void)
+{
+ bat_callbacks = NULL;
+}
+
+static int manta_bat_poll_charge_source(void)
+{
+ change_charger_status(false, false);
+ return manta_source_to_android(
+ manta_bat_charge_source[manta_bat_charge_conn]);
+}
+
+static void exynos5_manta_set_mains_current(void)
+{
+ int ret;
+ union power_supply_propval value;
+
+ if (!manta_bat_smb347_mains)
+ manta_bat_smb347_mains =
+ power_supply_get_by_name("smb347-mains");
+
+ if (!manta_bat_smb347_mains) {
+ pr_err("%s: failed to get smb347-mains power supply\n",
+ __func__);
+ return;
+ }
+
+ value.intval =
+ manta_bat_charge_source[CHARGE_CONNECTOR_POGO] ==
+ MANTA_CHARGE_SOURCE_USB ? 500000 : 2000000;
+
+ ret = manta_bat_smb347_mains->set_property(manta_bat_smb347_mains,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ &value);
+ if (ret)
+ pr_err("%s: failed to set smb347-mains current limit\n",
+ __func__);
+}
+
+static void exynos5_manta_set_usb_hc(void)
+{
+ int ret;
+ union power_supply_propval value;
+
+ if (manta_bat_get_smb347_usb())
+ return;
+
+ value.intval =
+ manta_bat_charge_source[CHARGE_CONNECTOR_USB] ==
+ MANTA_CHARGE_SOURCE_USB ? 0 : 1;
+ ret = manta_bat_smb347_usb->set_property(manta_bat_smb347_usb,
+ POWER_SUPPLY_PROP_USB_HC,
+ &value);
+ if (ret)
+ pr_err("%s: failed to set smb347-usb USB/HC mode\n",
+ __func__);
+}
+
+static void manta_bat_set_charging_current(
+ int android_charge_source)
+{
+ exynos5_manta_set_priority();
+ exynos5_manta_set_usb_hc();
+ exynos5_manta_set_mains_current();
+}
+
+static int manta_bat_get_capacity(void)
+{
+ union power_supply_propval soc;
+ int ret = -ENXIO;
+
+ if (manta_bat_get_ds2784())
+ return ret;
+ ret = manta_bat_ds2784_battery->get_property(
+ manta_bat_ds2784_battery, POWER_SUPPLY_PROP_CAPACITY,
+ &soc);
+ if (ret >= 0)
+ ret = soc.intval;
+ return ret;
+}
+
+static int manta_bat_get_temperature(int *temp_now)
+{
+ union power_supply_propval temp;
+ int ret = -ENXIO;
+
+ if (manta_bat_get_ds2784())
+ return ret;
+ ret = manta_bat_ds2784_battery->get_property(
+ manta_bat_ds2784_battery, POWER_SUPPLY_PROP_TEMP, &temp);
+ if (ret >= 0)
+ *temp_now = temp.intval;
+ return ret;
+}
+
+static int manta_bat_get_voltage_now(void)
+{
+ union power_supply_propval vcell;
+ int ret = -ENXIO;
+
+ if (manta_bat_get_ds2784())
+ return ret;
+ ret = manta_bat_ds2784_battery->get_property(
+ manta_bat_ds2784_battery, POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ &vcell);
+ if (ret >= 0)
+ ret = vcell.intval;
+ return ret;
+}
+
+static int manta_bat_get_current_now(int *i_current)
+{
+ union power_supply_propval inow;
+ int ret = -ENXIO;
+
+ if (manta_bat_get_ds2784())
+ return ret;
+ ret = manta_bat_ds2784_battery->get_property(
+ manta_bat_ds2784_battery, POWER_SUPPLY_PROP_CURRENT_NOW,
+ &inow);
+ if (ret >= 0)
+ *i_current = inow.intval;
+ return ret;
+}
+
+static irqreturn_t ta_int_intr(int irq, void *arg)
+{
+ wake_lock(&manta_bat_chgdetect_wakelock);
+ msleep(600);
+ change_charger_status(false, false);
+ wake_unlock(&manta_bat_chgdetect_wakelock);
+ return IRQ_HANDLED;
+}
+
+static struct android_bat_platform_data android_battery_pdata = {
+ .register_callbacks = manta_bat_register_callbacks,
+ .unregister_callbacks = manta_bat_unregister_callbacks,
+
+ .poll_charge_source = manta_bat_poll_charge_source,
+
+ .set_charging_current = manta_bat_set_charging_current,
+ .set_charging_enable = manta_bat_set_charging_enable,
+
+ .get_capacity = manta_bat_get_capacity,
+ .get_temperature = manta_bat_get_temperature,
+ .get_voltage_now = manta_bat_get_voltage_now,
+ .get_current_now = manta_bat_get_current_now,
+
+ .temp_high_threshold = 600, /* 60c */
+ .temp_high_recovery = 420, /* 42c */
+ .temp_low_recovery = 0, /* 0c */
+ .temp_low_threshold = -50, /* -5c */
+ .full_charging_time = 12 * 60 * 60,
+ .recharging_time = 2 * 60 * 60,
+ .recharging_voltage = 4250 * 1000,
+};
+
+static struct platform_device android_device_battery = {
+ .name = "android-battery",
+ .id = -1,
+ .dev.platform_data = &android_battery_pdata,
+};
+
+static struct platform_device *manta_battery_devices[] __initdata = {
+ &android_device_battery,
+};
+
+static char *manta_charge_source_str(enum manta_charge_source charge_source)
+{
+ switch (charge_source) {
+ case MANTA_CHARGE_SOURCE_NONE:
+ return "none";
+ case MANTA_CHARGE_SOURCE_AC_SAMSUNG:
+ return "ac-samsung";
+ case MANTA_CHARGE_SOURCE_AC_OTHER:
+ return "ac-other";
+ case MANTA_CHARGE_SOURCE_USB:
+ return "usb";
+ case MANTA_CHARGE_SOURCE_UNKNOWN:
+ return "unknown";
+ default:
+ break;
+ }
+
+ return "?";
+}
+
+
+static int manta_power_debug_dump(struct seq_file *s, void *unused)
+{
+ seq_printf(s, "ta_en=%d ta_nchg=%d ta_int=%d usbin=%d, dcin=%d st=%d\n",
+ gpio_get_value(GPIO_TA_EN),
+ gpio_get_value(GPIO_TA_NCHG),
+ gpio_get_value(GPIO_TA_INT),
+ gpio_get_value(GPIO_OTG_VBUS_SENSE),
+ gpio_get_value(GPIO_VBUS_POGO_5V),
+ manta_bat_battery_status);
+ seq_printf(s, "%susb: type=%s (apsd=%d); %spogo: type=%s%s; ta_adc=%d\n",
+ manta_bat_charge_conn == CHARGE_CONNECTOR_USB ? "*" : "",
+ manta_bat_otg_enabled ? "otg" :
+ manta_charge_source_str(
+ manta_bat_charge_source[CHARGE_CONNECTOR_USB]),
+ manta_bat_apsd_results.intval,
+ manta_bat_charge_conn == CHARGE_CONNECTOR_POGO ? "*" : "",
+ manta_charge_source_str(
+ manta_bat_charge_source[CHARGE_CONNECTOR_POGO]),
+ manta_bat_dock ? "(d)" : "", manta_bat_ta_adc);
+ return 0;
+}
+
+static int manta_power_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, manta_power_debug_dump, inode->i_private);
+}
+
+static const struct file_operations manta_power_debug_fops = {
+ .open = manta_power_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int manta_power_adc_debug_dump(struct seq_file *s, void *unused)
+{
+ seq_printf(s, "usb=%d,%d pogo=%d,%d\n",
+ read_ta_adc(CHARGE_CONNECTOR_USB, 0),
+ read_ta_adc(CHARGE_CONNECTOR_USB, 1),
+ read_ta_adc(CHARGE_CONNECTOR_POGO, 0),
+ read_ta_adc(CHARGE_CONNECTOR_POGO, 1));
+ return 0;
+}
+
+static int manta_power_adc_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, manta_power_adc_debug_dump, inode->i_private);
+}
+
+static const struct file_operations manta_power_adc_debug_fops = {
+ .open = manta_power_adc_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct ds2482_platform_data ds2483_pdata = {
+ .slpz_gpio = -1,
+};
+
+static struct i2c_board_info i2c_devs2[] __initdata = {
+ {
+ I2C_BOARD_INFO("ds2482", 0x30 >> 1),
+ .platform_data = &ds2483_pdata,
+ },
+ {
+ I2C_BOARD_INFO("smb347", 0x0c >> 1),
+ .platform_data = &smb347_chg_pdata,
+ },
+};
+
+static void redetect_work_proc(struct work_struct *work)
+{
+ change_charger_status(false, true);
+ wake_unlock(&manta_bat_redetect_wl);
+}
+
+void __init exynos5_manta_battery_init(void)
+{
+ int hw_rev = exynos5_manta_get_revision();
+
+ charger_gpio_init();
+ INIT_DELAYED_WORK(&redetect_work, redetect_work_proc);
+ wake_lock_init(&manta_bat_chgdetect_wakelock, WAKE_LOCK_SUSPEND,
+ "manta-chgdetect");
+ wake_lock_init(&manta_bat_redetect_wl, WAKE_LOCK_SUSPEND,
+ "manta-chgredetect");
+
+ platform_add_devices(manta_battery_devices,
+ ARRAY_SIZE(manta_battery_devices));
+
+ if (hw_rev >= MANTA_REV_DOGFOOD02) {
+ s3c_gpio_cfgpin(GPIO_1WIRE_SLEEP, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(GPIO_1WIRE_SLEEP, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_pd_cfg(GPIO_1WIRE_SLEEP, S5P_GPIO_PD_INPUT);
+ s5p_gpio_set_pd_pull(GPIO_1WIRE_SLEEP,
+ S5P_GPIO_PD_UPDOWN_DISABLE);
+ ds2483_pdata.slpz_gpio = GPIO_1WIRE_SLEEP;
+ }
+
+ i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));
+
+ ta_adc_client =
+ s3c_adc_register(&android_device_battery, NULL, NULL, 0);
+
+ if (IS_ERR_OR_NULL(debugfs_create_file("manta-power", S_IRUGO, NULL,
+ NULL, &manta_power_debug_fops)))
+ pr_err("failed to create manta-power debugfs entry\n");
+
+ if (IS_ERR_OR_NULL(debugfs_create_file("manta-power-adc", S_IRUGO, NULL,
+ NULL,
+ &manta_power_adc_debug_fops)))
+ pr_err("failed to create manta-power-adc debugfs entry\n");
+}
+
+static void exynos5_manta_power_changed(struct power_supply *psy)
+{
+ change_charger_status(false, false);
+}
+
+static int exynos5_manta_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ return -EINVAL;
+}
+
+static struct power_supply exynos5_manta_power_supply = {
+ .name = "manta-board",
+ .type = POWER_SUPPLY_TYPE_UNKNOWN,
+ .external_power_changed = exynos5_manta_power_changed,
+ .get_property = exynos5_manta_power_get_property,
+};
+
+static int exynos5_manta_battery_pm_event(struct notifier_block *notifier,
+ unsigned long pm_event,
+ void *unused)
+{
+ switch (pm_event) {
+ case PM_SUSPEND_PREPARE:
+ disable_irq(gpio_to_irq(GPIO_OTG_VBUS_SENSE));
+ disable_irq(gpio_to_irq(GPIO_VBUS_POGO_5V));
+ manta_bat_suspended = true;
+ break;
+
+ case PM_POST_SUSPEND:
+ if (manta_bat_suspended) {
+ enable_irq(gpio_to_irq(GPIO_OTG_VBUS_SENSE));
+ enable_irq(gpio_to_irq(GPIO_VBUS_POGO_5V));
+ manta_bat_suspended = false;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int manta_bat_usb_event(struct notifier_block *nb,
+ unsigned long event, void *unused)
+{
+ int ret;
+
+ if (event == USB_EVENT_ENUMERATED) {
+ ret = __cancel_delayed_work(&redetect_work);
+ if (ret)
+ wake_unlock(&manta_bat_redetect_wl);
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block exynos5_manta_battery_pm_notifier_block = {
+ .notifier_call = exynos5_manta_battery_pm_event,
+};
+
+static struct notifier_block manta_bat_usb_nb = {
+ .notifier_call = manta_bat_usb_event,
+};
+
+static int __init exynos5_manta_battery_late_init(void)
+{
+ int ret;
+ struct usb_phy *usb_xceiv;
+
+ ret = power_supply_register(NULL, &exynos5_manta_power_supply);
+ if (ret)
+ pr_err("%s: failed to register power supply\n", __func__);
+
+ ret = request_threaded_irq(gpio_to_irq(GPIO_OTG_VBUS_SENSE),
+ NULL, ta_int_intr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT, "usb_vbus", NULL);
+ if (ret) {
+ pr_err("%s: usb_vbus irq register failed, ret=%d\n",
+ __func__, ret);
+ } else {
+ ret = enable_irq_wake(gpio_to_irq(GPIO_OTG_VBUS_SENSE));
+ if (ret)
+ pr_warn("%s: failed to enable irq_wake for usb_vbus\n",
+ __func__);
+ }
+
+ ret = request_threaded_irq(gpio_to_irq(GPIO_VBUS_POGO_5V), NULL,
+ ta_int_intr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT, "pogo_vbus", NULL);
+ if (ret) {
+ pr_err("%s: pogo_vbus irq register failed, ret=%d\n",
+ __func__, ret);
+ } else {
+ ret = enable_irq_wake(gpio_to_irq(GPIO_VBUS_POGO_5V));
+ if (ret)
+ pr_warn("%s: failed to enable irq_wake for pogo_vbus\n",
+ __func__);
+ }
+
+ ret = register_pm_notifier(&exynos5_manta_battery_pm_notifier_block);
+ if (ret)
+ pr_warn("%s: failed to register PM notifier; ret=%d\n",
+ __func__, ret);
+
+ usb_xceiv = usb_get_transceiver();
+
+ if (!usb_xceiv) {
+ pr_err("%s: No USB transceiver found\n", __func__);
+ } else {
+ ret = usb_register_notifier(usb_xceiv, &manta_bat_usb_nb);
+
+ if (ret) {
+ pr_err("%s: usb_register_notifier on transceiver %s failed\n",
+ __func__, dev_name(usb_xceiv->dev));
+ }
+ }
+
+ /* Poll initial charger state */
+ change_charger_status(false, false);
+ return 0;
+}
+
+late_initcall(exynos5_manta_battery_late_init);
diff --git a/arch/arm/mach-exynos/board-manta-bluetooth.c b/arch/arm/mach-exynos/board-manta-bluetooth.c
new file mode 100755
index 0000000..7f8e987
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-bluetooth.c
@@ -0,0 +1,397 @@
+/*
+ * 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 <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/hrtimer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/wakelock.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <asm/mach-types.h>
+#include <mach/gpio.h>
+#include <plat/gpio-cfg.h>
+
+#include "board-manta.h"
+
+#define GPIO_BT_WAKE EXYNOS5_GPH1(3)
+#define GPIO_BT_HOST_WAKE EXYNOS5_GPX2(6)
+#define GPIO_BTREG_ON EXYNOS5_GPH0(0)
+
+#define GPIO_BT_UART_RXD EXYNOS5_GPA0(0)
+#define GPIO_BT_UART_TXD EXYNOS5_GPA0(1)
+#define GPIO_BT_UART_CTS EXYNOS5_GPA0(2)
+#define GPIO_BT_UART_RTS EXYNOS5_GPA0(3)
+
+#define BT_LPM_ENABLE
+
+static struct rfkill *bt_rfkill;
+
+static DEFINE_MUTEX(manta_bt_wlan_sync);
+
+struct bcm_bt_lpm {
+ int wake;
+ int host_wake;
+
+ struct hrtimer enter_lpm_timer;
+ ktime_t enter_lpm_delay;
+
+ struct uart_port *uport;
+
+ struct wake_lock wake_lock;
+ struct wake_lock host_wake_lock;
+} bt_lpm;
+
+struct gpio_init_data {
+ uint num;
+ uint cfg;
+ uint pull;
+ uint drv;
+};
+
+struct gpio_sleep_data {
+ uint num;
+ uint cfg;
+ uint pull;
+};
+
+static struct gpio_init_data manta_init_bt_gpios[] = {
+ /* BT_UART_RXD */
+ {GPIO_BT_UART_RXD, S3C_GPIO_SFN(2), S3C_GPIO_PULL_UP,
+ S5P_GPIO_DRVSTR_LV2},
+ /* BT_UART_TXD */
+ {GPIO_BT_UART_TXD, S3C_GPIO_SFN(2), S3C_GPIO_PULL_NONE,
+ S5P_GPIO_DRVSTR_LV2},
+ /* BT_UART_CTS */
+ {GPIO_BT_UART_CTS, S3C_GPIO_SFN(2), S3C_GPIO_PULL_NONE,
+ S5P_GPIO_DRVSTR_LV2},
+ /* BT_UART_RTS */
+ {GPIO_BT_UART_RTS, S3C_GPIO_SFN(2), S3C_GPIO_PULL_NONE,
+ S5P_GPIO_DRVSTR_LV2},
+ /* BT_HOST_WAKE */
+ {GPIO_BT_HOST_WAKE, S3C_GPIO_INPUT, S3C_GPIO_PULL_NONE,
+ S5P_GPIO_DRVSTR_LV1}
+};
+
+static struct gpio_sleep_data manta_sleep_bt_gpios[] = {
+ /* BT_UART_RXD */
+ {GPIO_BT_UART_RXD, S5P_GPIO_PD_INPUT, S5P_GPIO_PD_UP_ENABLE},
+ /* BT_UART_TXD */
+ {GPIO_BT_UART_TXD, S5P_GPIO_PD_OUTPUT0, S5P_GPIO_PD_UPDOWN_DISABLE},
+ /* BT_UART_CTS */
+ {GPIO_BT_UART_CTS, S5P_GPIO_PD_INPUT, S5P_GPIO_PD_UPDOWN_DISABLE},
+ /* BT_UART_RTS */
+ {GPIO_BT_UART_RTS, S5P_GPIO_PD_OUTPUT1, S5P_GPIO_PD_UPDOWN_DISABLE},
+ /* BTREG_ON */
+ {GPIO_BTREG_ON, S5P_GPIO_PD_PREV_STATE, S5P_GPIO_PD_UPDOWN_DISABLE},
+ /* BT_WAKE */
+ {GPIO_BT_WAKE, S5P_GPIO_PD_PREV_STATE, S5P_GPIO_PD_UPDOWN_DISABLE},
+};
+
+static struct platform_device bcm43241_bluetooth_platform_device = {
+ .name = "bcm43241_bluetooth",
+ .id = -1,
+};
+
+static struct platform_device *manta_bt_devs[] __initdata = {
+ &bcm43241_bluetooth_platform_device,
+};
+
+void __init exynos5_manta_bt_init(void)
+{
+ int i;
+ int gpio;
+
+ for (i = 0; i < ARRAY_SIZE(manta_init_bt_gpios); i++) {
+ gpio = manta_init_bt_gpios[i].num;
+ s3c_gpio_cfgpin(gpio, manta_init_bt_gpios[i].cfg);
+ s3c_gpio_setpull(gpio, manta_init_bt_gpios[i].pull);
+ s5p_gpio_set_drvstr(gpio, manta_init_bt_gpios[i].drv);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(manta_sleep_bt_gpios); i++) {
+ gpio = manta_sleep_bt_gpios[i].num;
+ s5p_gpio_set_pd_cfg(gpio, manta_sleep_bt_gpios[i].cfg);
+ s5p_gpio_set_pd_pull(gpio, manta_sleep_bt_gpios[i].pull);
+ }
+
+ platform_add_devices(manta_bt_devs, ARRAY_SIZE(manta_bt_devs));
+}
+
+void bt_wlan_lock(void)
+{
+ mutex_lock(&manta_bt_wlan_sync);
+}
+
+void bt_wlan_unlock(void)
+{
+ mutex_unlock(&manta_bt_wlan_sync);
+}
+
+static int bcm43241_bt_rfkill_set_power(void *data, bool blocked)
+{
+ /* rfkill_ops callback. Turn transmitter on when blocked is false */
+ bt_wlan_lock();
+ msleep(300);
+ if (!blocked) {
+ pr_info("[BT] Bluetooth Power On.\n");
+ gpio_set_value(GPIO_BTREG_ON, 1);
+ s3c_gpio_setpull(GPIO_BT_HOST_WAKE, S3C_GPIO_PULL_NONE);
+ } else {
+ pr_info("[BT] Bluetooth Power Off.\n");
+ gpio_set_value(GPIO_BTREG_ON, 0);
+ s3c_gpio_setpull(GPIO_BT_HOST_WAKE, S3C_GPIO_PULL_DOWN);
+ }
+ msleep(50);
+ bt_wlan_unlock();
+ return 0;
+}
+
+static const struct rfkill_ops bcm43241_bt_rfkill_ops = {
+ .set_block = bcm43241_bt_rfkill_set_power,
+};
+
+#ifdef BT_LPM_ENABLE
+static void set_wake_locked(int wake)
+{
+ if (wake == bt_lpm.wake)
+ return;
+
+ bt_lpm.wake = wake;
+
+ if (wake) {
+ wake_lock(&bt_lpm.wake_lock);
+ gpio_set_value(GPIO_BT_WAKE, wake);
+ } else {
+ gpio_set_value(GPIO_BT_WAKE, wake);
+ wake_unlock(&bt_lpm.wake_lock);
+ }
+}
+
+static enum hrtimer_restart enter_lpm(struct hrtimer *timer)
+{
+ set_wake_locked(0);
+
+ return HRTIMER_NORESTART;
+}
+
+void bcm_bt_lpm_exit_lpm_locked(struct uart_port *uport)
+{
+ bt_lpm.uport = uport;
+
+ hrtimer_try_to_cancel(&bt_lpm.enter_lpm_timer);
+ set_wake_locked(1);
+
+ hrtimer_start(&bt_lpm.enter_lpm_timer, bt_lpm.enter_lpm_delay,
+ HRTIMER_MODE_REL);
+}
+
+static void update_host_wake_locked(int host_wake)
+{
+ if (host_wake == bt_lpm.host_wake)
+ return;
+
+ bt_lpm.host_wake = host_wake;
+
+ if (host_wake) {
+ wake_lock(&bt_lpm.host_wake_lock);
+ } else {
+ /* Take a timed wakelock, so that upper layers can take it.
+ * The chipset deasserts the hostwake lock, when there is no
+ * more data to send.
+ */
+ wake_lock_timeout(&bt_lpm.host_wake_lock, HZ/2);
+ }
+}
+
+static irqreturn_t host_wake_isr(int irq, void *dev)
+{
+ int host_wake;
+
+ host_wake = gpio_get_value(GPIO_BT_HOST_WAKE);
+ irq_set_irq_type(irq, host_wake ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH);
+
+ if (!bt_lpm.uport) {
+ bt_lpm.host_wake = host_wake;
+ return IRQ_HANDLED;
+ }
+
+ update_host_wake_locked(host_wake);
+
+ return IRQ_HANDLED;
+}
+
+static int bcm_bt_lpm_init(struct platform_device *pdev)
+{
+ int irq;
+ int ret;
+
+ pr_info("[BT] bcm_bt_lpm_init\n");
+ hrtimer_init(&bt_lpm.enter_lpm_timer, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
+ bt_lpm.enter_lpm_delay = ktime_set(3, 0);
+ bt_lpm.enter_lpm_timer.function = enter_lpm;
+
+ bt_lpm.host_wake = 0;
+
+ wake_lock_init(&bt_lpm.wake_lock, WAKE_LOCK_SUSPEND,
+ "BTWakeLowPower");
+ wake_lock_init(&bt_lpm.host_wake_lock, WAKE_LOCK_SUSPEND,
+ "BTHostWakeLowPower");
+
+ irq = gpio_to_irq(GPIO_BT_HOST_WAKE);
+ ret = request_threaded_irq(irq, NULL, host_wake_isr,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "bt_host_wake", NULL);
+ if (ret) {
+ pr_err("[BT] Request host_wake irq failed.\n");
+ goto err_lpm_init;
+ }
+
+ ret = irq_set_irq_wake(irq, 1);
+ if (ret) {
+ pr_err("[BT] Set_irq_wake failed.\n");
+ free_irq(irq, NULL);
+ goto err_lpm_init;
+ }
+
+ return 0;
+
+err_lpm_init:
+ wake_lock_destroy(&bt_lpm.wake_lock);
+ wake_lock_destroy(&bt_lpm.host_wake_lock);
+ return ret;
+}
+#endif
+
+static int bcm43241_bluetooth_probe(struct platform_device *pdev)
+{
+ int rc;
+
+ rc = gpio_request(GPIO_BTREG_ON, "bcm43241_bten_gpio");
+ if (unlikely(rc)) {
+ pr_err("[BT] GPIO_BTREG_ON request failed.\n");
+ goto err_gpio_btreg_on;
+ }
+
+ rc = gpio_request(GPIO_BT_WAKE, "bcm43241_btwake_gpio");
+ if (unlikely(rc)) {
+ pr_err("[BT] GPIO_BT_WAKE request failed.\n");
+ goto err_gpio_bt_wake;
+ }
+
+ rc = gpio_request(GPIO_BT_HOST_WAKE, "bcm43241_bthostwake_gpio");
+ if (unlikely(rc)) {
+ pr_err("[BT] GPIO_BT_HOST_WAKE request failed.\n");
+ goto err_gpio_bt_host_wake;
+ }
+
+ gpio_direction_input(GPIO_BT_HOST_WAKE);
+ gpio_direction_output(GPIO_BT_WAKE, 0);
+ gpio_direction_output(GPIO_BTREG_ON, 0);
+
+ bt_rfkill = rfkill_alloc("bcm43241 Bluetooth", &pdev->dev,
+ RFKILL_TYPE_BLUETOOTH, &bcm43241_bt_rfkill_ops,
+ NULL);
+ if (unlikely(!bt_rfkill)) {
+ pr_err("[BT] bt_rfkill alloc failed.\n");
+ rc = -ENOMEM;
+ goto err_rfkill_alloc;
+ }
+
+ rfkill_init_sw_state(bt_rfkill, false);
+ rc = rfkill_register(bt_rfkill);
+ if (unlikely(rc)) {
+ pr_err("[BT] bt_rfkill register failed.\n");
+ rc = -1;
+ goto err_rfkill_register;
+ }
+ rfkill_set_sw_state(bt_rfkill, true);
+
+#ifdef BT_LPM_ENABLE
+ rc = bcm_bt_lpm_init(pdev);
+ if (rc)
+ goto err_lpm_init;
+#endif
+ return rc;
+
+#ifdef BT_LPM_ENABLE
+err_lpm_init:
+ rfkill_unregister(bt_rfkill);
+#endif
+err_rfkill_register:
+ rfkill_destroy(bt_rfkill);
+err_rfkill_alloc:
+ gpio_free(GPIO_BT_HOST_WAKE);
+err_gpio_bt_host_wake:
+ gpio_free(GPIO_BT_WAKE);
+err_gpio_bt_wake:
+ gpio_free(GPIO_BTREG_ON);
+err_gpio_btreg_on:
+ return rc;
+}
+
+static int bcm43241_bluetooth_remove(struct platform_device *pdev)
+{
+#ifdef BT_LPM_ENABLE
+ int irq;
+
+ irq = gpio_to_irq(GPIO_BT_HOST_WAKE);
+ irq_set_irq_wake(irq, 0);
+ free_irq(irq, NULL);
+ set_wake_locked(0);
+ hrtimer_try_to_cancel(&bt_lpm.enter_lpm_timer);
+ wake_lock_destroy(&bt_lpm.wake_lock);
+ wake_lock_destroy(&bt_lpm.host_wake_lock);
+#endif
+
+ rfkill_unregister(bt_rfkill);
+ rfkill_destroy(bt_rfkill);
+
+ gpio_free(GPIO_BT_HOST_WAKE);
+ gpio_free(GPIO_BT_WAKE);
+ gpio_free(GPIO_BTREG_ON);
+
+ return 0;
+}
+
+static struct platform_driver bcm43241_bluetooth_platform_driver = {
+ .probe = bcm43241_bluetooth_probe,
+ .remove = bcm43241_bluetooth_remove,
+ .driver = {
+ .name = "bcm43241_bluetooth",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init bcm43241_bluetooth_init(void)
+{
+ return platform_driver_register(&bcm43241_bluetooth_platform_driver);
+}
+
+static void __exit bcm43241_bluetooth_exit(void)
+{
+ platform_driver_unregister(&bcm43241_bluetooth_platform_driver);
+}
+
+
+module_init(bcm43241_bluetooth_init);
+module_exit(bcm43241_bluetooth_exit);
+
+MODULE_ALIAS("platform:bcm43241");
+MODULE_DESCRIPTION("bcm43241_bluetooth");
+MODULE_LICENSE("GPL");
diff --git a/arch/arm/mach-exynos/board-manta-camera.c b/arch/arm/mach-exynos/board-manta-camera.c
new file mode 100755
index 0000000..b53b17a
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-camera.c
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * 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 <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/spi/spi.h>
+
+#include <plat/s3c64xx-spi.h>
+#include <plat/cpu.h>
+#include <plat/devs.h>
+#include <plat/gpio-cfg.h>
+
+#include <mach/spi-clocks.h>
+#include <mach/gpio.h>
+#include <mach/sysmmu.h>
+
+#include <media/exynos_fimc_is.h>
+
+
+static struct exynos5_platform_fimc_is exynos5_fimc_is_data;
+
+#if defined CONFIG_VIDEO_S5K4E5
+static struct exynos5_fimc_is_sensor_info s5k4e5 = {
+ .sensor_name = "S5K4E5",
+ .sensor_id = SENSOR_NAME_S5K4E5,
+#if defined CONFIG_S5K4E5_POSITION_FRONT
+ .sensor_position = SENSOR_POSITION_FRONT,
+#elif defined CONFIG_S5K4E5_POSITION_REAR
+ .sensor_position = SENSOR_POSITION_REAR,
+#endif
+#if defined CONFIG_S5K4E5_CSI_C
+ .csi_id = CSI_ID_A,
+ .flite_id = FLITE_ID_A,
+ .i2c_channel = SENSOR_CONTROL_I2C0,
+#elif defined CONFIG_S5K4E5_CSI_D
+ .csi_id = CSI_ID_B,
+ .flite_id = FLITE_ID_B,
+ .i2c_channel = SENSOR_CONTROL_I2C1,
+#endif
+ .max_width = 2560,
+ .max_height = 1920,
+ .max_frame_rate = 30,
+
+ .mipi_lanes = 2,
+ .mipi_settle = 12,
+ .mipi_align = 24,
+ .sensor_power = {
+ .cam_core = "5m_core_1.5v",
+ .cam_io_myself = "cam_io_1.8v",
+ .cam_io_peer = "vt_cam_1.8v",
+ .cam_af = "cam_af_2.8v",
+ },
+ .sensor_gpio = {
+ .cfg[0] = { /* ISP_TXD */
+ .pin = EXYNOS5_GPE0(7),
+ .name = "GPE0",
+ .value = (3<<28),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[1] = { /* ISP_RXD */
+ .pin = EXYNOS5_GPE1(1),
+ .name = "GPE1",
+ .value = (3<<4),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[2] = { /* 5M_CAM_SDA_18V */
+ .pin = EXYNOS5_GPF0(0),
+ .name = "GPF0",
+ .value = (2<<0),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[3] = { /* 5M_CAM_SCL_18V */
+ .pin = EXYNOS5_GPF0(1),
+ .name = "GPF0",
+ .value = (2<<4),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[4] = { /* CAM_FLASH_EN */
+ .pin = EXYNOS5_GPE0(1),
+ .name = "GPE0",
+ .value = (2<<4),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[5] = { /* CAM_FLASH_SET */
+ .pin = EXYNOS5_GPE0(2),
+ .name = "GPE0",
+ .value = (2<<8),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[6] = { /* CAM_MCLK */
+ .pin = EXYNOS5_GPH0(3),
+ .name = "GPH0",
+ .value = (2<<12),
+ .act = GPIO_PULL_NONE,
+ },
+ .power = { /* CAM_IO_EN - VDDA_2.8V*/
+ .pin = EXYNOS5_GPV0(3),
+ .name = "GPV0",
+ .value = 1,
+ .act = GPIO_OUTPUT,
+ },
+ .reset_myself = { /* 5M_CAM_RESET */
+ .pin = EXYNOS5_GPE0(0),
+ .name = "GPE0",
+ .value = 0,
+ .act = GPIO_RESET,
+ },
+ .reset_peer = { /* CAM_VT_nRST */
+ .pin = EXYNOS5_GPG1(6),
+ .name = "GPG1",
+ .value = 0,
+ .act = GPIO_RESET,
+ },
+ },
+};
+#endif
+
+#if defined CONFIG_VIDEO_S5K6A3
+static struct exynos5_fimc_is_sensor_info s5k6a3 = {
+ .sensor_name = "S5K6A3",
+ .sensor_id = SENSOR_NAME_S5K6A3,
+#if defined CONFIG_S5K6A3_POSITION_FRONT
+ .sensor_position = SENSOR_POSITION_FRONT,
+#elif defined CONFIG_S5K6A3_POSITION_REAR
+ .sensor_position = SENSOR_POSITION_REAR,
+#endif
+#if defined CONFIG_S5K6A3_CSI_C
+ .csi_id = CSI_ID_A,
+ .flite_id = FLITE_ID_A,
+ .i2c_channel = SENSOR_CONTROL_I2C0,
+#elif defined CONFIG_S5K6A3_CSI_D
+ .csi_id = CSI_ID_B,
+ .flite_id = FLITE_ID_B,
+ .i2c_channel = SENSOR_CONTROL_I2C1,
+#endif
+ .max_width = 1280,
+ .max_height = 720,
+ .max_frame_rate = 30,
+
+ .mipi_lanes = 1,
+ .mipi_settle = 12,
+ .mipi_align = 24,
+ .sensor_power = {
+ .cam_core = "5m_core_1.5v",
+ .cam_io_myself = "vt_cam_1.8v",
+ .cam_io_peer = "cam_io_1.8v",
+ },
+ .sensor_gpio = {
+ .cfg[0] = { /* ISP_TXD */
+ .pin = EXYNOS5_GPE0(7),
+ .name = "GPE0",
+ .value = (3<<28),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[1] = { /* ISP_RXD */
+ .pin = EXYNOS5_GPE1(1),
+ .name = "GPE1",
+ .value = (3<<4),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[2] = { /* VT_CAM_SDA_18V */
+ .pin = EXYNOS5_GPF0(2),
+ .name = "GPF0",
+ .value = (2<<8),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[3] = { /* VT_CAM_SCL_18V */
+ .pin = EXYNOS5_GPF0(3),
+ .name = "GPF0",
+ .value = (2<<12),
+ .act = GPIO_PULL_NONE,
+ },
+ .cfg[4] = { /* VTCAM_MCLK */
+ .pin = EXYNOS5_GPG2(1),
+ .name = "GPG2",
+ .value = (2<<4),
+ .act = GPIO_PULL_NONE,
+ },
+ .power = { /* CAM_IO_EN - VDDA_2.8V*/
+ .pin = EXYNOS5_GPV0(3),
+ .name = "GPV0",
+ .value = 1,
+ .act = GPIO_OUTPUT,
+ },
+ .reset_myself = { /* CAM_VT_nRST */
+ .pin = EXYNOS5_GPG1(6),
+ .name = "GPG1",
+ .value = 0,
+ .act = GPIO_RESET,
+ },
+ .reset_peer = { /* 5M_CAM_RESET */
+ .pin = EXYNOS5_GPE0(0),
+ .name = "GPE0",
+ .value = 0,
+ .act = GPIO_RESET,
+ },
+ },
+};
+#endif
+
+static struct s3c64xx_spi_csinfo spi1_csi[] = {
+ [0] = {
+ .line = EXYNOS5_GPA2(5),
+ .set_level = gpio_set_value,
+ .fb_delay = 0x2,
+ },
+};
+
+static struct spi_board_info spi1_board_info[] __initdata = {
+ {
+ .modalias = "fimc_is_spi",
+ .platform_data = NULL,
+ .max_speed_hz = 10 * 1000 * 1000,
+ .bus_num = 1,
+ .chip_select = 0,
+ .mode = SPI_MODE_0,
+ .controller_data = &spi1_csi[0],
+ }
+};
+
+static void manta_gpio_pull_up(bool pull_up)
+{
+ if (pull_up) {
+ s3c_gpio_cfgpin(EXYNOS5_GPA2(4), S3C_GPIO_SFN(2));
+ s3c_gpio_cfgpin(EXYNOS5_GPA2(6), S3C_GPIO_SFN(2));
+ s3c_gpio_cfgpin(EXYNOS5_GPA2(7), S3C_GPIO_SFN(2));
+ pr_debug("GPIO : spi function\n");
+ } else {
+ s3c_gpio_setpull(EXYNOS5_GPA2(4), S3C_GPIO_PULL_DOWN);
+ s3c_gpio_setpull(EXYNOS5_GPA2(6), S3C_GPIO_PULL_DOWN);
+ s3c_gpio_setpull(EXYNOS5_GPA2(7), S3C_GPIO_PULL_DOWN);
+ s3c_gpio_cfgpin(EXYNOS5_GPA2(4), S3C_GPIO_SFN(0));
+ s3c_gpio_cfgpin(EXYNOS5_GPA2(6), S3C_GPIO_SFN(0));
+ s3c_gpio_cfgpin(EXYNOS5_GPA2(7), S3C_GPIO_SFN(0));
+ pr_debug("GPIO : input\n");
+ }
+}
+
+static struct platform_device *camera_devices[] __initdata = {
+ &s3c64xx_device_spi1,
+ &exynos5_device_fimc_is,
+};
+
+static void __init manta_camera_sysmmu_init(void)
+{
+ platform_set_sysmmu(&SYSMMU_PLATDEV(isp).dev,
+ &exynos5_device_fimc_is.dev);
+
+}
+
+void __init exynos5_manta_camera_init(void)
+{
+ manta_camera_sysmmu_init();
+ platform_add_devices(camera_devices, ARRAY_SIZE(camera_devices));
+
+ /* SPI */
+ exynos_spi_clock_setup(&s3c64xx_device_spi1.dev, 1);
+
+ if (!exynos_spi_cfg_cs(spi1_csi[0].line, 1)) {
+ s3c64xx_spi1_pdata.gpio_pull_up = manta_gpio_pull_up;
+ s3c64xx_spi1_set_platdata(&s3c64xx_spi1_pdata,
+ EXYNOS_SPI_SRCCLK_SCLK, ARRAY_SIZE(spi1_csi));
+
+ spi_register_board_info(spi1_board_info,
+ ARRAY_SIZE(spi1_board_info));
+ } else {
+ pr_err("%s: Error requesting gpio for SPI-CH1 CS\n", __func__);
+ }
+
+ /* FIMC-IS-MC */
+ dev_set_name(&exynos5_device_fimc_is.dev, "s5p-mipi-csis.0");
+ clk_add_alias("gscl_wrap0", FIMC_IS_MODULE_NAME, "gscl_wrap0",
+ &exynos5_device_fimc_is.dev);
+ clk_add_alias("sclk_gscl_wrap0", FIMC_IS_MODULE_NAME, "sclk_gscl_wrap0",
+ &exynos5_device_fimc_is.dev);
+
+ dev_set_name(&exynos5_device_fimc_is.dev, "s5p-mipi-csis.1");
+ clk_add_alias("gscl_wrap1", FIMC_IS_MODULE_NAME, "gscl_wrap1",
+ &exynos5_device_fimc_is.dev);
+ clk_add_alias("sclk_gscl_wrap1", FIMC_IS_MODULE_NAME, "sclk_gscl_wrap1",
+ &exynos5_device_fimc_is.dev);
+
+ dev_set_name(&exynos5_device_fimc_is.dev, "exynos-gsc.0");
+ clk_add_alias("gscl", FIMC_IS_MODULE_NAME, "gscl",
+ &exynos5_device_fimc_is.dev);
+ dev_set_name(&exynos5_device_fimc_is.dev, FIMC_IS_MODULE_NAME);
+
+#if defined CONFIG_VIDEO_S5K6A3
+ exynos5_fimc_is_data.sensor_info[s5k6a3.sensor_position] = &s5k6a3;
+#endif
+#if defined CONFIG_VIDEO_S5K4E5
+ exynos5_fimc_is_data.sensor_info[s5k4e5.sensor_position] = &s5k4e5;
+#endif
+
+ exynos5_fimc_is_set_platdata(&exynos5_fimc_is_data);
+}
+
diff --git a/arch/arm/mach-exynos/board-manta-connector.c b/arch/arm/mach-exynos/board-manta-connector.c
new file mode 100644
index 0000000..87ed671
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-connector.c
@@ -0,0 +1,353 @@
+/*
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) "manta_otg %s: " fmt, __func__
+
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/otg.h>
+
+#include <plat/gpio-cfg.h>
+#include <plat/devs.h>
+#include <plat/usb-phy.h>
+
+#include "board-manta.h"
+
+#define GPIO_USB_VBUS EXYNOS5_GPX1(0)
+#define GPIO_USB_ID EXYNOS5_GPX1(1)
+
+struct manta_otg {
+ struct usb_otg otg;
+ struct usb_phy phy;
+ struct delayed_work work;
+ struct mutex lock;
+ bool usb_connected;
+ struct usb_bus *ohci;
+
+ /* HACK: s5p phy interface requires passing a pdev pointer */
+ struct platform_device pdev;
+};
+static struct manta_otg manta_otg;
+
+
+static int manta_phy_init(struct usb_phy *phy)
+{
+ struct manta_otg *motg = container_of(phy, struct manta_otg, phy);
+
+ if (phy->last_event == USB_EVENT_VBUS)
+ return s5p_usb_phy_init(&motg->pdev, S5P_USB_PHY_DEVICE);
+ else
+ return s5p_usb_phy_init(&motg->pdev, S5P_USB_PHY_HOST);
+}
+
+static void manta_phy_shutdown(struct usb_phy *phy)
+{
+ struct manta_otg *motg = container_of(phy, struct manta_otg, phy);
+
+ if (motg->phy.state == OTG_STATE_B_PERIPHERAL)
+ s5p_usb_phy_exit(&motg->pdev, S5P_USB_PHY_DEVICE);
+ else
+ s5p_usb_phy_exit(&motg->pdev, S5P_USB_PHY_HOST);
+}
+
+static int manta_phy_set_power(struct usb_phy *phy, unsigned mA)
+{
+ if (mA > 3)
+ atomic_notifier_call_chain(&phy->notifier, USB_EVENT_ENUMERATED,
+ phy->otg->gadget);
+
+ return 0;
+}
+
+static int manta_otg_host_enable(struct manta_otg *motg)
+{
+#ifdef CONFIG_USB
+ struct usb_hcd *hcd;
+ struct usb_hcd *ohci_hcd = NULL;
+ int err;
+
+ hcd = bus_to_hcd(motg->otg.host);
+ if (motg->ohci)
+ ohci_hcd = bus_to_hcd(motg->ohci);
+
+ usb_phy_init(&motg->phy);
+
+ err = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
+ if (err) {
+ pr_err("failed to add ehci: %d\n", err);
+ goto err_ehci;
+ }
+
+ if (ohci_hcd) {
+ err = usb_add_hcd(ohci_hcd, hcd->irq, IRQF_SHARED);
+ if (err) {
+ pr_err("failed to add ohci: %d\n", err);
+ goto err_ohci;
+ }
+ }
+
+ err = otg_set_vbus(&motg->otg, true);
+ if (err) {
+ pr_err("failed to enable vbus: %d\n", err);
+ goto err_vbus;
+ }
+
+ return 0;
+
+err_vbus:
+ if (ohci_hcd)
+ usb_remove_hcd(ohci_hcd);
+
+err_ohci:
+ usb_remove_hcd(hcd);
+
+err_ehci:
+ usb_phy_shutdown(&motg->phy);
+
+ return err;
+#else
+ return 0;
+#endif
+}
+
+static void manta_otg_host_disable(struct manta_otg *motg)
+{
+#ifdef CONFIG_USB
+ struct usb_hcd *hcd;
+ struct usb_hcd *ohci_hcd = NULL;
+ int err;
+
+ hcd = bus_to_hcd(motg->otg.host);
+ if (motg->ohci)
+ ohci_hcd = bus_to_hcd(motg->ohci);
+
+ err = otg_set_vbus(&motg->otg, false);
+ if (err)
+ pr_err("failed to disable vbus: %d\n", err);
+
+ if (ohci_hcd)
+ usb_remove_hcd(ohci_hcd);
+ usb_remove_hcd(hcd);
+
+ usb_phy_shutdown(&motg->phy);
+#endif
+}
+
+static int manta_otg_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+#ifdef CONFIG_USB
+ struct manta_otg *motg = container_of(otg, struct manta_otg, otg);
+ struct usb_hcd *hcd = bus_to_hcd(host);
+
+ mutex_lock(&motg->lock);
+
+ /* HACK to support for ohci */
+ if (host && otg->host) {
+ motg->ohci = host;
+ usb_remove_hcd(hcd);
+ usb_phy_shutdown(&motg->phy);
+ goto out;
+ }
+
+ otg->host = host;
+ if (host) {
+ usb_remove_hcd(bus_to_hcd(host));
+ usb_phy_shutdown(&motg->phy);
+ } else {
+ if (otg->phy->state == OTG_STATE_A_HOST)
+ otg->phy->state = OTG_STATE_UNDEFINED;
+ }
+
+out:
+ mutex_unlock(&motg->lock);
+#endif
+ return 0;
+}
+
+static int manta_otg_set_peripheral(struct usb_otg *otg,
+ struct usb_gadget *gadget)
+{
+ struct manta_otg *motg = container_of(otg, struct manta_otg, otg);
+
+ mutex_lock(&motg->lock);
+
+ otg->gadget = gadget;
+ if (gadget) {
+ if (otg->phy->state == OTG_STATE_B_PERIPHERAL)
+ usb_gadget_vbus_connect(motg->otg.gadget);
+ } else {
+ if (otg->phy->state == OTG_STATE_B_PERIPHERAL)
+ otg->phy->state = OTG_STATE_UNDEFINED;
+ }
+
+ mutex_unlock(&motg->lock);
+
+ return 0;
+}
+
+static int manta_otg_set_vbus(struct usb_otg *otg, bool enabled)
+{
+ pr_debug("vbus %s\n", enabled ? "on" : "off");
+ return manta_bat_otg_enable(enabled);
+}
+
+static void manta_otg_work(struct work_struct *work)
+{
+ struct manta_otg *motg = container_of(work, struct manta_otg, work.work);
+ enum usb_otg_state prev_state;
+ int id, vbus, err;
+
+ mutex_lock(&motg->lock);
+
+ prev_state = motg->phy.state;
+ vbus = motg->usb_connected;
+ id = gpio_get_value(GPIO_USB_ID);
+
+ pr_debug("vbus=%d id=%d\n", vbus, id);
+
+ if (!id) {
+ if (prev_state == OTG_STATE_A_HOST)
+ goto out;
+
+ if (!motg->otg.host)
+ goto out;
+
+ motg->phy.state = OTG_STATE_A_HOST;
+ motg->phy.last_event = USB_EVENT_ID;
+ atomic_notifier_call_chain(&motg->phy.notifier,
+ USB_EVENT_ID, NULL);
+
+ err = manta_otg_host_enable(motg);
+ if (err) {
+ motg->phy.last_event = USB_EVENT_NONE;
+ motg->phy.state = OTG_STATE_B_IDLE;
+ atomic_notifier_call_chain(&motg->phy.notifier,
+ USB_EVENT_NONE, NULL);
+ goto out;
+ }
+
+ } else if (vbus) {
+ if (!motg->otg.gadget)
+ goto out;
+
+ if (prev_state == OTG_STATE_B_PERIPHERAL)
+ goto out;
+
+ motg->phy.state = OTG_STATE_B_PERIPHERAL;
+ motg->phy.last_event = USB_EVENT_VBUS;
+ atomic_notifier_call_chain(&motg->phy.notifier,
+ USB_EVENT_VBUS, NULL);
+ usb_gadget_vbus_connect(motg->otg.gadget);
+ } else {
+ if (prev_state == OTG_STATE_B_IDLE)
+ goto out;
+
+ if (prev_state == OTG_STATE_B_PERIPHERAL && motg->otg.gadget)
+ usb_gadget_vbus_disconnect(motg->otg.gadget);
+
+ if (prev_state == OTG_STATE_A_HOST && motg->otg.host)
+ manta_otg_host_disable(motg);
+
+ motg->phy.state = OTG_STATE_B_IDLE;
+ motg->phy.last_event = USB_EVENT_NONE;
+ atomic_notifier_call_chain(&motg->phy.notifier,
+ USB_EVENT_NONE, NULL);
+ }
+
+ pr_info("%s -> %s\n", otg_state_string(prev_state),
+ otg_state_string(motg->phy.state));
+
+out:
+ mutex_unlock(&motg->lock);
+}
+
+static irqreturn_t manta_otg_irq(int irq, void *data)
+{
+ struct manta_otg *motg = data;
+ queue_delayed_work(system_nrt_wq, &motg->work, msecs_to_jiffies(20));
+ return IRQ_HANDLED;
+}
+
+void manta_otg_set_usb_state(bool connected)
+{
+ struct manta_otg *motg = &manta_otg;
+ motg->usb_connected = connected;
+ queue_delayed_work(system_nrt_wq, &motg->work,
+ connected ? msecs_to_jiffies(20) : 0);
+ if (!connected)
+ flush_delayed_work(&motg->work);
+}
+
+void exynos5_manta_connector_init(void)
+{
+ struct manta_otg *motg = &manta_otg;
+ struct device *dev = &motg->pdev.dev;
+
+ INIT_DELAYED_WORK(&motg->work, manta_otg_work);
+ ATOMIC_INIT_NOTIFIER_HEAD(&motg->phy.notifier);
+ mutex_init(&motg->lock);
+
+ device_initialize(dev);
+ dev_set_name(dev, "%s", "manta_otg");
+ if (device_add(dev)) {
+ dev_err(dev, "%s: cannot reg device\n", __func__);
+ return;
+ }
+ dev_set_drvdata(dev, motg);
+
+ motg->phy.dev = dev;
+ motg->phy.label = "manta_otg";
+ motg->phy.otg = &motg->otg;
+ motg->phy.init = manta_phy_init;
+ motg->phy.shutdown = manta_phy_shutdown;
+ motg->phy.set_power = manta_phy_set_power;
+
+ motg->otg.phy = &motg->phy;
+ motg->otg.set_host = manta_otg_set_host;
+ motg->otg.set_peripheral = manta_otg_set_peripheral;
+ motg->otg.set_vbus = manta_otg_set_vbus;
+
+ usb_set_transceiver(&motg->phy);
+}
+
+static int __init manta_connector_init(void)
+{
+ struct manta_otg *motg = &manta_otg;
+ int ret;
+
+ s3c_gpio_cfgpin(GPIO_USB_ID, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(GPIO_USB_ID, S3C_GPIO_PULL_NONE);
+
+ ret = request_irq(gpio_to_irq(GPIO_USB_ID), manta_otg_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT, "usb_id", motg);
+ if (ret)
+ pr_err("request_irq ID failed: %d\n", ret);
+
+ /*
+ * HACK: delay the initial otg detection by 4 secs so that it happens
+ * after the battery driver is ready (this fixes booting with otg cable
+ * attached)
+ */
+ queue_delayed_work(system_nrt_wq, &motg->work, msecs_to_jiffies(4000));
+
+ return ret;
+}
+device_initcall(manta_connector_init);
diff --git a/arch/arm/mach-exynos/board-manta-display.c b/arch/arm/mach-exynos/board-manta-display.c
new file mode 100644
index 0000000..d98233c
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-display.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * 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 <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/fb.h>
+#include <linux/platform_device.h>
+#include <linux/pwm_backlight.h>
+
+#include <video/platform_lcd.h>
+#include <video/s5p-dp.h>
+
+#include <plat/backlight.h>
+#include <plat/clock.h>
+#include <plat/devs.h>
+#include <plat/dp.h>
+#include <plat/fb.h>
+#include <plat/gpio-cfg.h>
+#include <plat/iic.h>
+#include <plat/regs-fb-v4.h>
+#include <plat/tv-core.h>
+
+#include <mach/gpio.h>
+#include <mach/map.h>
+#include <mach/regs-clock.h>
+#include <mach/sysmmu.h>
+
+#include "common.h"
+
+#define GPIO_LCD_EN EXYNOS5_GPH1(7)
+#define GPIO_LCD_ID EXYNOS5_GPD1(4)
+#define GPIO_LCD_PWM_IN_18V EXYNOS5_GPB2(0)
+#define GPIO_LED_BL_RST EXYNOS5_GPG0(5)
+
+#define GPIO_LCDP_SCL__18V EXYNOS5_GPD0(6)
+#define GPIO_LCDP_SDA__18V EXYNOS5_GPD0(7)
+
+#define GPIO_HDMI_HPD EXYNOS5_GPX3(7)
+#define GPIO_HDMI_DCDC_EN EXYNOS5_GPA0(4)
+#define GPIO_HDMI_LS_EN EXYNOS5_GPG0(7)
+#define GPIO_APS_EN_18V EXYNOS5_GPH1(6)
+
+#define LCD_POWER_OFF_TIME_US (500 * USEC_PER_MSEC)
+
+extern phys_addr_t manta_bootloader_fb_start;
+extern phys_addr_t manta_bootloader_fb_size;
+
+static ktime_t lcd_on_time;
+
+static void manta_lcd_on(void)
+{
+ s64 us = ktime_us_delta(lcd_on_time, ktime_get_boottime());
+ if (us > LCD_POWER_OFF_TIME_US) {
+ pr_warn("lcd on sleep time too long\n");
+ us = LCD_POWER_OFF_TIME_US;
+ }
+
+ if (us > 0)
+ usleep_range(us, us);
+
+ gpio_set_value(GPIO_LCD_EN, 1);
+ usleep_range(200000, 200000);
+}
+
+static void manta_lcd_off(void)
+{
+ gpio_set_value(GPIO_LCD_EN, 0);
+
+ lcd_on_time = ktime_add_us(ktime_get_boottime(), LCD_POWER_OFF_TIME_US);
+}
+
+static void manta_backlight_on(void)
+{
+ usleep_range(97000, 97000);
+ gpio_set_value(GPIO_LED_BL_RST, 1);
+}
+
+static void manta_backlight_off(void)
+{
+ /* LED_BACKLIGHT_RESET: XCI1RGB_5 => GPG0_5 */
+ gpio_set_value(GPIO_LED_BL_RST, 0);
+}
+
+static void manta_lcd_set_power(struct plat_lcd_data *pd,
+ unsigned int power)
+{
+ if (power)
+ manta_lcd_on();
+ else
+ manta_lcd_off();
+}
+
+static struct plat_lcd_data manta_lcd_data = {
+ .set_power = manta_lcd_set_power,
+};
+
+static struct platform_device manta_lcd = {
+ .name = "platform-lcd",
+ .dev = {
+ .parent = &s5p_device_fimd1.dev,
+ .platform_data = &manta_lcd_data,
+ },
+};
+
+static void manta_fimd_gpio_setup_24bpp(void)
+{
+ u32 reg;
+
+ /* basic fimd init */
+ exynos5_fimd1_gpio_setup_24bpp();
+
+ /* Reference clcok selection for DPTX_PHY: pad_osc_clk_24M */
+ reg = __raw_readl(S3C_VA_SYS + 0x04d4);
+ reg = (reg & ~(0x1 << 0)) | (0x0 << 0);
+ __raw_writel(reg, S3C_VA_SYS + 0x04d4);
+
+ /* DPTX_PHY: XXTI */
+ reg = __raw_readl(S3C_VA_SYS + 0x04d8);
+ reg = (reg & ~(0x1 << 3)) | (0x0 << 3);
+ __raw_writel(reg, S3C_VA_SYS + 0x04d8);
+}
+
+static struct s3c_fb_pd_win manta_fb_win2 = {
+ .win_mode = {
+ .left_margin = 80,
+ .right_margin = 48,
+ .upper_margin = 37,
+ .lower_margin = 3,
+ .hsync_len = 32,
+ .vsync_len = 6,
+ .xres = 2560,
+ .yres = 1600,
+ },
+ .virtual_x = 2560,
+ .virtual_y = 1600 * 2,
+ .max_bpp = 32,
+ .default_bpp = 24,
+ .width = 218,
+ .height = 136,
+};
+
+static struct s3c_fb_platdata manta_lcd1_pdata __initdata = {
+ .win[0] = &manta_fb_win2,
+ .win[1] = &manta_fb_win2,
+ .win[2] = &manta_fb_win2,
+ .win[3] = &manta_fb_win2,
+ .win[4] = &manta_fb_win2,
+ .default_win = 0,
+ .vidcon0 = VIDCON0_VIDOUT_RGB | VIDCON0_PNRMODE_RGB,
+ .vidcon1 = 0,
+ .setup_gpio = manta_fimd_gpio_setup_24bpp,
+ .backlight_off = manta_backlight_off,
+ .lcd_off = manta_lcd_off,
+};
+
+static struct video_info manta_dp_config = {
+ .name = "WQXGA(2560x1600) LCD",
+
+ .h_sync_polarity = 0,
+ .v_sync_polarity = 0,
+ .interlaced = 0,
+
+ .color_space = COLOR_RGB,
+ .dynamic_range = VESA,
+ .ycbcr_coeff = COLOR_YCBCR601,
+ .color_depth = COLOR_8,
+
+ .link_rate = LINK_RATE_2_70GBPS,
+ .lane_count = LANE_COUNT4,
+};
+
+static struct s5p_dp_platdata manta_dp_data __initdata = {
+ .video_info = &manta_dp_config,
+ .phy_init = s5p_dp_phy_init,
+ .phy_exit = s5p_dp_phy_exit,
+ .backlight_on = manta_backlight_on,
+ .backlight_off = manta_backlight_off,
+ .lcd_on = manta_lcd_on,
+ .lcd_off = manta_lcd_off,
+};
+
+/* LCD Backlight data */
+static struct samsung_bl_gpio_info manta_bl_gpio_info = {
+ .no = GPIO_LCD_PWM_IN_18V,
+ .func = S3C_GPIO_SFN(2),
+};
+
+static struct platform_pwm_backlight_data manta_bl_data = {
+ .pwm_id = 0,
+ .pwm_period_ns = 1000000,
+ .dft_brightness = 102,
+};
+
+static struct platform_device exynos_device_md0 = {
+ .name = "exynos-mdev",
+ .id = 0,
+};
+
+static struct platform_device exynos_device_md1 = {
+ .name = "exynos-mdev",
+ .id = 1,
+};
+
+static struct platform_device exynos_device_md2 = {
+ .name = "exynos-mdev",
+ .id = 2,
+};
+
+/* HDMI */
+static void manta_hdmiphy_enable(struct platform_device *pdev, int en)
+{
+ s5p_hdmiphy_enable(pdev, en ? 1 : 0);
+}
+
+static struct s5p_hdmi_platdata hdmi_platdata __initdata = {
+ .hdmiphy_enable = manta_hdmiphy_enable,
+};
+
+static struct platform_device *manta_display_devices[] __initdata = {
+ &exynos_device_md0,
+ &exynos_device_md1,
+ &exynos_device_md2,
+
+ &s5p_device_fimd1,
+ &manta_lcd,
+ &s5p_device_dp,
+
+ &s5p_device_i2c_hdmiphy,
+ &s5p_device_mixer,
+ &s5p_device_hdmi,
+};
+
+void __init exynos5_manta_display_init(void)
+{
+ struct resource *res;
+
+ /* LCD_EN , XMMC2CDN => GPC2_2 */
+ gpio_request_one(GPIO_LCD_EN, GPIOF_OUT_INIT_LOW, "LCD_EN");
+ /* LED_BACKLIGHT_RESET: XCI1RGB_5 => GPG0_5 */
+ gpio_request_one(GPIO_LED_BL_RST, GPIOF_OUT_INIT_LOW, "LED_BL_RST");
+ s5p_gpio_set_pd_cfg(GPIO_LED_BL_RST, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_pull(GPIO_LED_BL_RST, S5P_GPIO_PD_UPDOWN_DISABLE);
+
+ gpio_request_one(GPIO_LCD_PWM_IN_18V, GPIOF_OUT_INIT_LOW, "PWM_IN_18V");
+ s5p_gpio_set_pd_cfg(GPIO_LCD_PWM_IN_18V, S5P_GPIO_PD_INPUT);
+ s5p_gpio_set_pd_pull(GPIO_LCD_PWM_IN_18V, S5P_GPIO_PD_UP_ENABLE);
+ gpio_free(GPIO_LCD_PWM_IN_18V);
+
+ gpio_request_one(GPIO_APS_EN_18V, GPIOF_OUT_INIT_LOW, "APS_EN_18V");
+ s5p_gpio_set_pd_cfg(GPIO_APS_EN_18V, S5P_GPIO_PD_INPUT);
+ s5p_gpio_set_pd_pull(GPIO_APS_EN_18V, S5P_GPIO_PD_UP_ENABLE);
+ gpio_export(GPIO_APS_EN_18V, true);
+
+ samsung_bl_set(&manta_bl_gpio_info, &manta_bl_data);
+ s5p_fimd1_set_platdata(&manta_lcd1_pdata);
+ dev_set_name(&s5p_device_fimd1.dev, "exynos5-fb.1");
+ clk_add_alias("lcd", "exynos5-fb.1", "fimd", &s5p_device_fimd1.dev);
+ s5p_dp_set_platdata(&manta_dp_data);
+
+ gpio_request_one(GPIO_HDMI_HPD, GPIOF_IN, "HDMI_HPD");
+ /* HDMI Companion DC/DC converter and HPD circuitry */
+ gpio_request_one(GPIO_HDMI_DCDC_EN, GPIOF_OUT_INIT_HIGH, "HDMI_DCDC_EN");
+ /* HDMI Companion level shifters and LDO */
+ gpio_request_one(GPIO_HDMI_LS_EN, GPIOF_OUT_INIT_HIGH, "HDMI_LS_EN");
+
+ s5p_hdmi_set_platdata(&hdmi_platdata);
+ dev_set_name(&s5p_device_hdmi.dev, "exynos5-hdmi");
+ clk_add_alias("hdmi", "s5p-hdmi", "hdmi", &s5p_device_hdmi.dev);
+ platform_set_sysmmu(&SYSMMU_PLATDEV(tv).dev, &s5p_device_mixer.dev);
+ s5p_i2c_hdmiphy_set_platdata(NULL);
+
+ platform_add_devices(manta_display_devices,
+ ARRAY_SIZE(manta_display_devices));
+
+ exynos5_fimd1_setup_clock(&s5p_device_fimd1.dev,
+ "sclk_fimd", "sclk_vpll", 268 * MHZ);
+
+ res = platform_get_resource(&s5p_device_fimd1, IORESOURCE_MEM, 1);
+ if (res) {
+ res->start = manta_bootloader_fb_start;
+ res->end = res->start + manta_bootloader_fb_size - 1;
+ pr_info("bootloader fb located at %8X-%8X\n", res->start,
+ res->end);
+ } else {
+ pr_err("failed to find bootloader fb resource\n");
+ }
+}
diff --git a/arch/arm/mach-exynos/board-manta-gps.c b/arch/arm/mach-exynos/board-manta-gps.c
new file mode 100644
index 0000000..d401709
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-gps.c
@@ -0,0 +1,68 @@
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <mach/gpio.h>
+#include <plat/gpio-cfg.h>
+
+static struct device *gps_dev;
+
+static struct class *gps_class;
+
+#define GPIO_GPS_PWR_EN EXYNOS5_GPE1(0)
+#define GPIO_GPS_nRST EXYNOS5_GPE0(6)
+#define GPIO_GPS_CTS EXYNOS5_GPD0(2)
+#define GPIO_GPS_RTS EXYNOS5_GPD0(3)
+#define GPIO_GPS_RXD EXYNOS5_GPD0(0)
+#define GPIO_GPS_TXD EXYNOS5_GPD0(1)
+
+#define GPIO_GPS_CTS_AF 2
+#define GPIO_GPS_RTS_AF 2
+#define GPIO_GPS_RXD_AF 2
+#define GPIO_GPS_TXD_AF 2
+
+void __init exynos5_manta_gps_init(void)
+{
+ int n_rst_pin = 0;
+ int n_rst_nc_pin = 0;
+ gps_class = class_create(THIS_MODULE, "gps");
+ if (IS_ERR(gps_class)) {
+ pr_err("Failed to create class(sec)!\n");
+ return;
+ }
+ BUG_ON(!gps_class);
+ gps_dev = device_create(gps_class, NULL, 0, NULL, "bcm475x");
+ BUG_ON(!gps_dev);
+ s3c_gpio_cfgpin(GPIO_GPS_RXD, S3C_GPIO_SFN(GPIO_GPS_RXD_AF));
+ s3c_gpio_setpull(GPIO_GPS_RXD, S3C_GPIO_PULL_UP);
+ s3c_gpio_cfgpin(GPIO_GPS_TXD, S3C_GPIO_SFN(GPIO_GPS_TXD_AF));
+ s3c_gpio_setpull(GPIO_GPS_TXD, S3C_GPIO_PULL_NONE);
+ s3c_gpio_cfgpin(GPIO_GPS_CTS, S3C_GPIO_SFN(GPIO_GPS_CTS_AF));
+ s3c_gpio_setpull(GPIO_GPS_CTS, S3C_GPIO_PULL_NONE);
+ s3c_gpio_cfgpin(GPIO_GPS_RTS, S3C_GPIO_SFN(GPIO_GPS_RTS_AF));
+ s3c_gpio_setpull(GPIO_GPS_RTS, S3C_GPIO_PULL_NONE);
+
+ n_rst_pin = GPIO_GPS_nRST;
+ n_rst_nc_pin = 0;
+
+ if (gpio_request(n_rst_pin, "GPS_nRST"))
+ WARN(1, "fail to request gpio (GPS_nRST)\n");
+
+ s3c_gpio_setpull(n_rst_pin, S3C_GPIO_PULL_UP);
+ s3c_gpio_cfgpin(n_rst_pin, S3C_GPIO_OUTPUT);
+ gpio_direction_output(n_rst_pin, 1);
+
+ if (gpio_request(GPIO_GPS_PWR_EN, "GPS_PWR_EN"))
+ WARN(1, "fail to request gpio (GPS_PWR_EN)\n");
+
+ s3c_gpio_setpull(GPIO_GPS_PWR_EN, S3C_GPIO_PULL_NONE);
+ s3c_gpio_cfgpin(GPIO_GPS_PWR_EN, S3C_GPIO_OUTPUT);
+ gpio_direction_output(GPIO_GPS_PWR_EN, 0);
+ gpio_export(n_rst_pin, 1);
+ gpio_export(GPIO_GPS_PWR_EN, 1);
+
+ gpio_export_link(gps_dev, "GPS_nRST", n_rst_pin);
+ gpio_export_link(gps_dev, "GPS_PWR_EN", GPIO_GPS_PWR_EN);
+}
diff --git a/arch/arm/mach-exynos/board-manta-input.c b/arch/arm/mach-exynos/board-manta-input.c
new file mode 100644
index 0000000..fa0defe
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-input.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * 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 <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/i2c/atmel_mxt_ts.h>
+#include <linux/interrupt.h>
+
+#include <plat/devs.h>
+#include <plat/iic.h>
+#include <plat/gpio-cfg.h>
+
+#define GPIO_TOUCH_CHG EXYNOS5_GPG1(2)
+#define GPIO_TOUCH_RESET EXYNOS5_GPG1(3)
+#define GPIO_TOUCH_EN_BOOSTER EXYNOS5_GPD1(1)
+#define GPIO_TOUCH_EN_XVDD EXYNOS5_GPG0(1)
+
+static struct mxt_platform_data atmel_mxt_ts_pdata = {
+ .x_line = 32,
+ .y_line = 52,
+ .x_size = 4095,
+ .y_size = 4095,
+ .orient = MXT_DIAGONAL,
+ .irqflags = IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ .boot_address = 0x26,
+ .gpio_reset = GPIO_TOUCH_RESET,
+ .reset_msec = 75,
+};
+
+static struct i2c_board_info i2c_devs3[] __initdata = {
+ {
+ I2C_BOARD_INFO("atmel_mxt_ts", 0x4a),
+ .platform_data = &atmel_mxt_ts_pdata,
+ },
+};
+
+void __init exynos5_manta_input_init(void)
+{
+ int hw_rev;
+
+ gpio_request(GPIO_TOUCH_CHG, "TSP_INT");
+ s3c_gpio_cfgpin(GPIO_TOUCH_CHG, S3C_GPIO_SFN(0xf));
+ s3c_gpio_setpull(GPIO_TOUCH_CHG, S3C_GPIO_PULL_NONE);
+ s5p_register_gpio_interrupt(GPIO_TOUCH_CHG);
+
+ s5p_gpio_set_pd_cfg(GPIO_TOUCH_CHG, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_pull(GPIO_TOUCH_CHG, S5P_GPIO_PD_UPDOWN_DISABLE);
+ s5p_gpio_set_pd_cfg(GPIO_TOUCH_RESET, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_cfg(GPIO_TOUCH_EN_XVDD, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_cfg(GPIO_TOUCH_EN_BOOSTER, S5P_GPIO_PD_PREV_STATE);
+
+ i2c_devs3[0].irq = gpio_to_irq(GPIO_TOUCH_CHG);
+ i2c_register_board_info(3, i2c_devs3, ARRAY_SIZE(i2c_devs3));
+}
diff --git a/arch/arm/mach-exynos/board-manta-jack.c b/arch/arm/mach-exynos/board-manta-jack.c
new file mode 100644
index 0000000..5b2db67
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-jack.c
@@ -0,0 +1,133 @@
+/*
+ * 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/input.h>
+#include <linux/sec_jack.h>
+
+#include <plat/gpio-cfg.h>
+
+#include "board-manta.h"
+
+#define GPIO_DET_35 EXYNOS5_GPX0(1)
+
+static void sec_jack_set_micbias_state(bool on)
+{
+}
+
+static struct sec_jack_zone sec_jack_zones[] = {
+ {
+ /* adc < 50, unstable zone, default to 3pole if it stays
+ * in this range for a half second (20ms delays, 25 samples)
+ */
+ .adc_high = 50,
+ .delay_ms = 20,
+ .check_count = 25,
+ .jack_type = SEC_HEADSET_3POLE,
+ },
+ {
+ /* 50 < adc <= 490, unstable zone, default to 3pole if it stays
+ * in this range for a second (10ms delays, 100 samples)
+ */
+ .adc_high = 490,
+ .delay_ms = 10,
+ .check_count = 100,
+ .jack_type = SEC_HEADSET_3POLE,
+ },
+ {
+ /* 490 < adc <= 900, unstable zone, default to 4pole if it
+ * stays in this range for a second (10ms delays, 100 samples)
+ */
+ .adc_high = 900,
+ .delay_ms = 10,
+ .check_count = 100,
+ .jack_type = SEC_HEADSET_4POLE,
+ },
+ {
+ /* 900 < adc <= 1500, 4 pole zone, default to 4pole if it
+ * stays in this range for 200ms (20ms delays, 10 samples)
+ */
+ .adc_high = 1500,
+ .delay_ms = 20,
+ .check_count = 10,
+ .jack_type = SEC_HEADSET_4POLE,
+ },
+ {
+ /* adc > 1500, unstable zone, default to 3pole if it stays
+ * in this range for a second (10ms delays, 100 samples)
+ */
+ .adc_high = 0x7fffffff,
+ .delay_ms = 10,
+ .check_count = 100,
+ .jack_type = SEC_HEADSET_3POLE,
+ },
+};
+
+/* To support 3-buttons earjack */
+static struct sec_jack_buttons_zone sec_jack_buttons_zones[] = {
+ {
+ /* 0 <= adc <= 93, stable zone */
+ .code = KEY_MEDIA,
+ .adc_low = 0,
+ .adc_high = 93,
+ },
+ {
+ /* 94 <= adc <= 167, stable zone */
+ .code = KEY_PREVIOUSSONG,
+ .adc_low = 94,
+ .adc_high = 167,
+ },
+ {
+ /* 168 <= adc <= 370, stable zone */
+ .code = KEY_NEXTSONG,
+ .adc_low = 168,
+ .adc_high = 370,
+ },
+};
+
+static int sec_jack_get_adc_value(void)
+{
+ return 0;
+}
+
+struct sec_jack_platform_data sec_jack_pdata = {
+ .set_micbias_state = sec_jack_set_micbias_state,
+ .get_adc_value = sec_jack_get_adc_value,
+ .zones = sec_jack_zones,
+ .num_zones = ARRAY_SIZE(sec_jack_zones),
+ .buttons_zones = sec_jack_buttons_zones,
+ .num_buttons_zones = ARRAY_SIZE(sec_jack_buttons_zones),
+ .det_gpio = GPIO_DET_35,
+};
+
+static struct platform_device sec_device_jack = {
+ .name = "sec_jack",
+ .id = 1, /* will be used also for gpio_event id */
+ .dev.platform_data = &sec_jack_pdata,
+};
+
+void __init exynos5_manta_jack_init(void)
+{
+ s3c_gpio_cfgpin(GPIO_DET_35, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(GPIO_DET_35, S3C_GPIO_PULL_NONE);
+
+ /* GPIO_DET_35 is inverted on <= PRE_ALPHA boards */
+ if (exynos5_manta_get_revision() <= MANTA_REV_PRE_ALPHA)
+ sec_jack_pdata.det_active_high = true;
+
+ platform_device_register(&sec_device_jack);
+}
diff --git a/arch/arm/mach-exynos/board-manta-media.c b/arch/arm/mach-exynos/board-manta-media.c
new file mode 100644
index 0000000..fad23c8
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-media.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * 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 <linux/errno.h>
+#include <linux/platform_device.h>
+#include <media/exynos_gscaler.h>
+
+#include <plat/cpu.h>
+#include <plat/devs.h>
+#include <plat/fimg2d.h>
+#include <plat/jpeg.h>
+
+#include <mach/exynos-ion.h>
+#include <mach/exynos-mfc.h>
+#include <mach/sysmmu.h>
+
+static struct platform_device *media_devices[] __initdata = {
+ &s5p_device_mfc,
+ &exynos5_device_gsc0,
+ &exynos5_device_gsc1,
+ &exynos5_device_gsc2,
+ &exynos5_device_gsc3,
+ &s5p_device_jpeg,
+ &s5p_device_fimg2d,
+};
+
+static struct s5p_mfc_platdata manta_mfc_pd = {
+ .clock_rate = 333 * MHZ,
+};
+
+static struct fimg2d_platdata fimg2d_data __initdata = {
+ .hw_ver = 0x42,
+ .gate_clkname = "fimg2d",
+};
+
+static void __init manta_media_sysmmu_init(void)
+{
+ platform_set_sysmmu(&SYSMMU_PLATDEV(mfc_lr).dev, &s5p_device_mfc.dev);
+ platform_set_sysmmu(&SYSMMU_PLATDEV(gsc0).dev,
+ &exynos5_device_gsc0.dev);
+ platform_set_sysmmu(&SYSMMU_PLATDEV(gsc1).dev,
+ &exynos5_device_gsc1.dev);
+ platform_set_sysmmu(&SYSMMU_PLATDEV(gsc2).dev,
+ &exynos5_device_gsc2.dev);
+ platform_set_sysmmu(&SYSMMU_PLATDEV(gsc3).dev,
+ &exynos5_device_gsc3.dev);
+ platform_set_sysmmu(&SYSMMU_PLATDEV(jpeg).dev,
+ &s5p_device_jpeg.dev);
+ platform_set_sysmmu(&SYSMMU_PLATDEV(2d).dev,
+ &s5p_device_fimg2d.dev);
+}
+
+void __init exynos5_manta_media_init(void)
+{
+ manta_media_sysmmu_init();
+ s5p_mfc_set_platdata(&manta_mfc_pd);
+
+ s3c_set_platdata(&exynos_gsc0_default_data,
+ sizeof(exynos_gsc0_default_data),
+ &exynos5_device_gsc0);
+ s3c_set_platdata(&exynos_gsc1_default_data,
+ sizeof(exynos_gsc1_default_data),
+ &exynos5_device_gsc1);
+ s3c_set_platdata(&exynos_gsc2_default_data,
+ sizeof(exynos_gsc2_default_data),
+ &exynos5_device_gsc2);
+ s3c_set_platdata(&exynos_gsc3_default_data,
+ sizeof(exynos_gsc3_default_data),
+ &exynos5_device_gsc3);
+
+ s5p_fimg2d_set_platdata(&fimg2d_data);
+ exynos5_jpeg_setup_clock(&s5p_device_jpeg.dev, 150000000);
+
+ platform_add_devices(media_devices, ARRAY_SIZE(media_devices));
+}
+
diff --git a/arch/arm/mach-exynos/board-manta-nfc.c b/arch/arm/mach-exynos/board-manta-nfc.c
new file mode 100644
index 0000000..dcb7e5a
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-nfc.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * 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 <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/i2c-gpio.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/spi/spi.h>
+#include <linux/nfc/bcm2079x.h>
+
+#include <plat/devs.h>
+#include <plat/gpio-cfg.h>
+#include <plat/s3c64xx-spi.h>
+
+#include <mach/spi-clocks.h>
+
+#define GPIO_NFC_WAKE EXYNOS5_GPX1(5)
+#define GPIO_SPI_INT EXYNOS5_GPX1(6)
+#define GPIO_NFC_EN EXYNOS5_GPD1(7)
+#define GPIO_NFC_CS EXYNOS5_GPB1(2)
+
+static void bcm2079x_nfc_setup_gpio(void)
+{
+ s3c_gpio_cfgpin(GPIO_SPI_INT, S3C_GPIO_SFN(S3C_GPIO_INPUT));
+ s3c_gpio_setpull(GPIO_SPI_INT, S3C_GPIO_PULL_NONE);
+
+ s3c_gpio_cfgpin(GPIO_NFC_EN, S3C_GPIO_SFN(S3C_GPIO_OUTPUT));
+ s3c_gpio_setpull(GPIO_NFC_EN, S3C_GPIO_PULL_UP);
+
+ s3c_gpio_cfgpin(GPIO_NFC_WAKE, S3C_GPIO_SFN(S3C_GPIO_OUTPUT));
+ s3c_gpio_setpull(GPIO_NFC_WAKE, S3C_GPIO_PULL_UP);
+}
+
+static struct bcm2079x_platform_data bcm2079x_spi_pdata = {
+ .irq_gpio = GPIO_SPI_INT,
+ .en_gpio = GPIO_NFC_EN,
+ .wake_gpio = GPIO_NFC_WAKE,
+};
+
+static struct s3c64xx_spi_csinfo spi2_csi[] = {
+ [0] = {
+ .line = GPIO_NFC_CS,
+ .set_level = gpio_set_value,
+ .fb_delay = 0x0,
+ },
+};
+
+static struct spi_board_info spi2_board_info[] __initdata = {
+ {
+ .modalias = "bcm2079x-spi",
+ .max_speed_hz = 4 * MHZ,
+ .bus_num = 2,
+ .chip_select = 0,
+ .mode = SPI_MODE_0,
+ .irq = IRQ_EINT(14),
+ .controller_data = &spi2_csi[0],
+ .platform_data = &bcm2079x_spi_pdata,
+ }
+};
+
+void __init exynos5_manta_nfc_init(void)
+{
+ bcm2079x_nfc_setup_gpio();
+ exynos_spi_clock_setup(&s3c64xx_device_spi2.dev, 2);
+ if (!exynos_spi_cfg_cs(spi2_csi[0].line, 2)) {
+ s3c64xx_spi2_set_platdata(&s3c64xx_spi2_pdata,
+ EXYNOS_SPI_SRCCLK_SCLK, ARRAY_SIZE(spi2_csi));
+
+ spi_register_board_info(spi2_board_info,
+ ARRAY_SIZE(spi2_board_info));
+ } else {
+ pr_err("%s : Error requesting gpio for SPI-CH%d CS",
+ __func__, spi2_board_info->bus_num);
+ }
+
+ platform_device_register(&s3c64xx_device_spi2);
+}
diff --git a/arch/arm/mach-exynos/board-manta-pogo.c b/arch/arm/mach-exynos/board-manta-pogo.c
new file mode 100644
index 0000000..d0ad312
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-pogo.c
@@ -0,0 +1,1103 @@
+/* arch/arm/mach-exynos/board-manta-pogo.c
+ *
+ * Copyright (C) 2012 Samsung Electronics.
+ * 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/ktime.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/cpufreq.h>
+
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/switch.h>
+#include <linux/wakelock.h>
+#include <linux/sched.h>
+
+#include <plat/gpio-cfg.h>
+#include "board-manta.h"
+#include <linux/platform_data/android_battery.h>
+
+/* including start and stop bits */
+#define CMD_READ_SZ 8
+#define CMD_WRITE_SZ 16
+#define RESP_READ_SZ 12
+#define RESP_WRITE_SZ 5
+
+#define RW_OFFSET 2
+#define ID_OFFSET 3
+#define STOP_R_OFFSET 6
+#define DATA_OFFSET 6
+#define STOP_W_OFFSET 14
+
+#define START_BITS 0x3
+#define STOP_BITS 0x1
+
+/* states for dock */
+#define POGO_UNDOCKED 0
+#define POGO_DESK_DOCK 1
+
+/* states for usb_audio */
+#define POGO_NO_AUDIO 0
+#define POGO_DIGITAL_AUDIO 2
+
+#define DOCK_STAT_POWER_MASK 3
+#define DOCK_STAT_POWER_OFFSET 2
+#define DOCK_STAT_POWER_NONE 0
+#define DOCK_STAT_POWER_500MA 1
+#define DOCK_STAT_POWER_2A 2
+#define DOCK_STAT_POWER_1A 3
+
+#define DOCK_STAT_AUDIO_MASK 3
+#define DOCK_STAT_AUDIO_OFFSET 0
+#define DOCK_STAT_AUDIO_CONNECTED 1
+#define DOCK_STAT_AUDIO_DISCONNECTED 0
+
+#define MFM_DELAY_NS_DEFAULT (1000 * 10)
+#define RESPONSE_INTERVAL 5
+#define CMD_DELAY_USEC 100 /* 100us */
+#define TX_ERR_DELAY_USEC(delay_ns) ((delay_ns) / 1000 * 7 + CMD_DELAY_USEC)
+#define check_stop_bits(x, n) ((((x) >> ((n) - 2)) & 1) == (STOP_BITS & 1))
+#define raw_resp(x, n) ((x) & ((1 << ((n) - 2)) - 1));
+
+#define DEBOUNCE_DELAY_MS 250
+
+#define DOCK_CPU_FREQ_MIN 800000
+
+/* GPIO configuration */
+#define GPIO_POGO_DATA_BETA EXYNOS5_GPB0(0)
+#define GPIO_POGO_DATA_POST_BETA EXYNOS5_GPX0(5)
+#define GPIO_DOCK_PD_INT EXYNOS5_GPX0(6)
+#define GPIO_POGO_SPDIF EXYNOS5_GPB1(0)
+
+#define GPIO_POGO_SEL1 EXYNOS5_GPG1(0)
+#define GPIO_TA_CHECK_SEL EXYNOS5_GPD1(5)
+#define GPIO_LDO20_EN EXYNOS5_GPD1(4)
+
+static struct switch_dev dock_switch = {
+ .name = "dock",
+};
+
+static struct switch_dev usb_audio_switch = {
+ .name = "usb_audio",
+};
+
+enum pogo_debounce_state {
+ POGO_DEBOUNCE_UNDOCKED, /* interrupt enabled, timer stopped */
+ POGO_DEBOUNCE_UNSTABLE, /* interrupt disabled, timer running */
+ POGO_DEBOUNCE_WAIT_STABLE, /* interrupt enabled, timer running */
+ POGO_DEBOUNCE_DOCKED, /* interrupt enabled, timer stopped */
+};
+
+struct dock_state {
+ struct mutex lock;
+ u32 t;
+ u32 last_edge_t[2];
+ u32 last_edge_i[2];
+ bool level;
+ bool dock_connected_unknown;
+ bool ignore_data_irq_once;
+
+ u32 mfm_delay_ns;
+ bool powered_dock_present;
+ struct workqueue_struct *dock_wq;
+ struct work_struct dock_work;
+ struct wake_lock wake_lock;
+
+ wait_queue_head_t wait;
+ int dock_pd_irq;
+ bool debounce_pending;
+ int spdif_mux_state;
+ enum pogo_debounce_state debounce_state;
+ struct timer_list debounce_timer;
+ struct work_struct debounce_work;
+ struct wake_lock debounce_wake_lock;
+
+ int ta_check_wait;
+ bool ta_check_mode;
+
+ unsigned int cpufreq_min;
+};
+
+static struct dock_state ds = {
+ .lock = __MUTEX_INITIALIZER(ds.lock),
+ .mfm_delay_ns = MFM_DELAY_NS_DEFAULT,
+ .wait = __WAIT_QUEUE_HEAD_INITIALIZER(ds.wait),
+ .debounce_state = POGO_DEBOUNCE_UNDOCKED,
+};
+
+static struct gpio manta_pogo_gpios[] = {
+ {GPIO_POGO_DATA_POST_BETA, GPIOF_IN, "dock"},
+ {GPIO_POGO_SEL1, GPIOF_OUT_INIT_HIGH, "pogo_sel1"},
+ {GPIO_TA_CHECK_SEL, GPIOF_OUT_INIT_LOW, "pogo_spdif_sel"},
+ {GPIO_DOCK_PD_INT, GPIOF_IN, "dock_pd_int"},
+ {GPIO_LDO20_EN, GPIOF_OUT_INIT_HIGH, "ldo20_en"},
+};
+
+#define _GPIO_DOCK (manta_pogo_gpios[0].gpio)
+
+#define dock_out(n) gpio_direction_output(_GPIO_DOCK, n);
+#define dock_out2(n) gpio_set_value(_GPIO_DOCK, n)
+#define dock_in() gpio_direction_input(_GPIO_DOCK);
+#define dock_read() gpio_get_value(_GPIO_DOCK)
+#define dock_irq() s3c_gpio_cfgpin(_GPIO_DOCK, S3C_GPIO_SFN(0xF))
+
+static u32 pogo_read_fast_timer(void)
+{
+ return sched_clock();
+}
+
+static void pogo_set_min_cpu_freq(unsigned int khz)
+{
+ struct dock_state *s = &ds;
+ int i;
+
+ s->cpufreq_min = khz;
+
+ for_each_online_cpu(i)
+ cpufreq_update_policy(i);
+}
+
+static u16 make_cmd(int id, bool write, int data)
+{
+ u16 cmd = (id & 0x7) << ID_OFFSET | write << RW_OFFSET | START_BITS;
+ cmd |= STOP_BITS << (write ? STOP_W_OFFSET : STOP_R_OFFSET);
+ if (write)
+ cmd |= (data & 0xFF) << DATA_OFFSET;
+ return cmd;
+}
+
+static int dock_get_edge(struct dock_state *s, u32 timeout, u32 tmin, u32 tmax)
+{
+ bool lin;
+ bool in = s->level;
+ u32 t;
+ do {
+ lin = in;
+ in = dock_read();
+ t = pogo_read_fast_timer();
+ if (in != lin) {
+ s->last_edge_t[in] = t;
+ s->last_edge_i[in] = 0;
+ s->level = in;
+ if ((s32) (t - tmin) < 0 || (s32) (t - tmax) > 0)
+ return -1;
+ return 1;
+ }
+ } while ((s32) (t - timeout) < 0);
+ return 0;
+}
+
+static bool dock_sync(struct dock_state *s, u32 timeout)
+{
+ u32 t;
+
+ s->level = dock_read();
+ t = pogo_read_fast_timer();
+
+ if (!dock_get_edge(s, t + timeout, 0, 0))
+ return false;
+ s->last_edge_i[s->level] = 2;
+ return !!dock_get_edge(s, s->last_edge_t[s->level] + s->mfm_delay_ns *
+ 4, 0, 0);
+}
+
+static int dock_get_next_bit(struct dock_state *s)
+{
+ u32 i = s->last_edge_i[!s->level] + ++s->last_edge_i[s->level];
+ u32 target = s->last_edge_t[!s->level] + s->mfm_delay_ns * i;
+ u32 timeout = target + s->mfm_delay_ns / 2;
+ u32 tmin = target - s->mfm_delay_ns / 4;
+ u32 tmax = target + s->mfm_delay_ns / 4;
+ return dock_get_edge(s, timeout, tmin, tmax);
+}
+
+static u32 dock_get_bits(struct dock_state *s, int count, int *errp)
+{
+ u32 data = 0;
+ u32 m = 1;
+ int ret;
+ int err = 0;
+ while (count--) {
+ ret = dock_get_next_bit(s);
+ if (ret)
+ data |= m;
+ if (ret < 0)
+ err++;
+ m <<= 1;
+ }
+ if (errp)
+ *errp = err;
+ return data;
+}
+
+static int dock_send_bits(struct dock_state *s, u32 data, int count, int period)
+{
+ u32 t, t0, to;
+
+ dock_out(s->level);
+ t = to = 0;
+ t0 = pogo_read_fast_timer();
+
+ while (count--) {
+ if (data & 1)
+ dock_out2((s->level = !s->level));
+
+ t = pogo_read_fast_timer() - t0;
+ if (t - to > period / 2) {
+ pr_debug("dock: to = %d, t = %d\n", to, t);
+ return -EIO;
+ }
+
+ data >>= 1;
+
+ to += period;
+ do {
+ t = pogo_read_fast_timer() - t0;
+ } while (t < to);
+ if (t - to > period / 4) {
+ pr_debug("dock: to = %d, t = %d\n", to, t);
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+static u32 mfm_encode(u16 data, int count, bool p)
+{
+ u32 mask;
+ u32 mfm = 0;
+ u32 clock = ~data & ~(data << 1 | !!p);
+ for (mask = 1UL << (count - 1); mask; mask >>= 1) {
+ mfm |= (data & mask);
+ mfm <<= 1;
+ mfm |= (clock & mask);
+ }
+ return mfm;
+}
+
+static u32 mfm_decode(u32 mfm)
+{
+ u32 data = 0;
+ u32 clock = 0;
+ u32 mask = 1;
+ while (mfm) {
+ if (mfm & 1)
+ clock |= mask;
+ mfm >>= 1;
+ if (mfm & 1)
+ data |= mask;
+ mfm >>= 1;
+ mask <<= 1;
+ }
+ return data;
+}
+
+static int dock_command(struct dock_state *s, u16 cmd, int len, int retlen)
+{
+ u32 mfm;
+ int count;
+ u32 data = cmd;
+ int tx, ret;
+ int err = -1;
+ unsigned long flags;
+
+ if (!s->cpufreq_min)
+ pogo_set_min_cpu_freq(DOCK_CPU_FREQ_MIN);
+
+ mfm = mfm_encode(data, len, false);
+ count = len * 2;
+
+ pr_debug("%s: data 0x%x mfm 0x%x\n", __func__, cmd, mfm);
+
+ local_irq_save(flags);
+ tx = dock_send_bits(s, mfm, count, s->mfm_delay_ns);
+
+ if (!tx) {
+ dock_in();
+ if (dock_sync(s, s->mfm_delay_ns * RESPONSE_INTERVAL)) {
+ ret = dock_get_bits(s, retlen * 2, &err);
+ } else {
+ pr_debug("%s: response sync error\n", __func__);
+ s->ignore_data_irq_once = s->level;
+ ret = -1;
+ }
+ dock_irq();
+ } else if (!s->level)
+ dock_irq();
+
+ local_irq_restore(flags);
+
+ udelay(tx < 0 ? TX_ERR_DELAY_USEC(s->mfm_delay_ns) : CMD_DELAY_USEC);
+
+ if (tx < 0) {
+ pr_debug("dock_command: %x: transmission err %d\n", cmd, tx);
+ return tx;
+ }
+ if (ret < 0) {
+ pr_debug("dock_command: %x: no response\n", cmd);
+ return ret;
+ }
+ data = mfm_decode(ret);
+ mfm = mfm_encode(data, retlen, true);
+ if (mfm != ret || err || !check_stop_bits(data, retlen)) {
+ pr_debug("dock_command: %x: bad response, data %x, mfm %x %x, err %d\n",
+ cmd, data, mfm, ret, err);
+ return -EIO;
+ }
+
+ return raw_resp(data, retlen);
+}
+
+static int dock_command_retry(struct dock_state *s, u16 cmd, size_t len,
+ size_t retlen)
+{
+ int retry = 20;
+ int ret;
+ while (retry--) {
+ ret = dock_command(s, cmd, len, retlen);
+ if (ret >= 0)
+ return ret;
+ if (retry != 19)
+ usleep_range(10000, 11000);
+ }
+ s->dock_connected_unknown = true;
+ if (s->level) {
+ /* put the line low so we can receive dock interrupts */
+ dock_out(s->level = false);
+ udelay(1);
+ dock_irq();
+ }
+ return -EIO;
+}
+
+static int dock_send_cmd(struct dock_state *s, int id, bool write, int data)
+{
+ int ret;
+ u16 cmd = make_cmd(id, write, data);
+
+ ret = dock_command_retry(s, cmd, write ? CMD_WRITE_SZ : CMD_READ_SZ,
+ write ? RESP_WRITE_SZ - 2 : RESP_READ_SZ - 2);
+
+ if (ret < 0)
+ pr_warning("%s: failed, cmd: %x, write: %d, data: %x\n",
+ __func__, cmd, write, data);
+
+ return ret;
+}
+
+static int dock_read_multi(struct dock_state *s, int addr, u8 *data, size_t len)
+{
+ int ret;
+ int i;
+ u8 suml, sumr = -1;
+
+ int retry = 20;
+ while (retry--) {
+ suml = 0;
+ for (i = 0; i <= len; i++) {
+ ret = dock_send_cmd(s, addr + i, false, 0);
+ if (ret < 0)
+ return ret;
+ if (i < len) {
+ data[i] = ret;
+ suml += ret;
+ } else
+ sumr = ret;
+ }
+ if (sumr == suml)
+ return 0;
+
+ pr_warning("dock_read_multi(%x): bad checksum, %x != %x\n",
+ addr, sumr, suml);
+ }
+ return -EIO;
+}
+
+static int dock_write_multi(struct dock_state *s, int addr, u8 *data,
+ size_t len)
+{
+ int ret;
+ int i;
+ u8 sum;
+
+ int retry = 20;
+ while (retry--) {
+ sum = 0;
+ for (i = 0; i < len; i++) {
+ sum += data[i];
+ ret = dock_send_cmd(s, addr + i, true, data[i]);
+ if (ret < 0)
+ return ret;
+ }
+ ret = dock_send_cmd(s, addr + len, true, sum);
+ if (ret < 0)
+ return ret;
+ /* check sum error */
+ if (ret == 0)
+ continue;
+ return 0;
+ }
+ return -EIO;
+}
+
+static void manta_pogo_spdif_config(bool charger_detect_mode)
+{
+ if (charger_detect_mode) {
+ s3c_gpio_cfgpin(GPIO_POGO_SPDIF, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(GPIO_POGO_SPDIF, S3C_GPIO_PULL_NONE);
+ } else {
+ s3c_gpio_cfgpin(GPIO_POGO_SPDIF, S3C_GPIO_SFN(0x4));
+ s3c_gpio_setpull(GPIO_POGO_SPDIF, S3C_GPIO_PULL_NONE);
+ }
+}
+
+static void pogo_dock_pd_interrupt_locked(int irq, void *data)
+{
+ struct dock_state *s = data;
+
+ switch (s->debounce_state) {
+ case POGO_DEBOUNCE_UNDOCKED:
+ case POGO_DEBOUNCE_DOCKED:
+ wake_lock(&s->debounce_wake_lock);
+ mod_timer(&s->debounce_timer,
+ jiffies + msecs_to_jiffies(DEBOUNCE_DELAY_MS));
+ s->debounce_state = POGO_DEBOUNCE_WAIT_STABLE;
+ break;
+ case POGO_DEBOUNCE_WAIT_STABLE:
+ /*
+ * Disable IRQ line in case there is noise. It will be
+ * re-enabled when the timer expires.
+ */
+ s->debounce_state = POGO_DEBOUNCE_UNSTABLE;
+ disable_irq_nosync(s->dock_pd_irq);
+ break;
+ case POGO_DEBOUNCE_UNSTABLE:
+ break;
+ }
+}
+
+static void dock_spdif_switch_set(bool spdif_mode)
+{
+ struct dock_state *s = &ds;
+
+ pr_debug("%s: %d pending %d\n", __func__, spdif_mode,
+ s->debounce_pending);
+
+ s->spdif_mux_state = spdif_mode;
+ gpio_set_value(GPIO_TA_CHECK_SEL, spdif_mode);
+
+ if (!spdif_mode && s->debounce_pending) {
+ pogo_dock_pd_interrupt_locked(s->dock_pd_irq, s);
+ s->debounce_pending = false;
+ }
+}
+
+static void dock_set_audio_switch(bool audio_on)
+{
+ struct dock_state *s = &ds;
+ unsigned long flags;
+
+ spin_lock_irqsave(&s->wait.lock, flags);
+ dock_spdif_switch_set(audio_on);
+ if (audio_on && s->debounce_state == POGO_DEBOUNCE_UNDOCKED)
+ pogo_dock_pd_interrupt_locked(s->dock_pd_irq, s);
+ spin_unlock_irqrestore(&s->wait.lock, flags);
+
+ switch_set_state(&usb_audio_switch, audio_on ?
+ POGO_DIGITAL_AUDIO : POGO_NO_AUDIO);
+}
+
+int manta_pogo_charge_detect_start(bool spdif_mode_and_gpio_in)
+{
+ struct dock_state *s = &ds;
+ unsigned long flags;
+ int ret = 0;
+
+ pr_debug("%s: %d\n", __func__, spdif_mode_and_gpio_in);
+
+ mutex_lock(&s->lock);
+
+ spin_lock_irqsave(&s->wait.lock, flags);
+
+ if (s->debounce_state == POGO_DEBOUNCE_UNSTABLE ||
+ s->debounce_state == POGO_DEBOUNCE_WAIT_STABLE) {
+
+ s->ta_check_wait = 10;
+ ret = wait_event_interruptible_locked(s->wait,
+ s->debounce_state == POGO_DEBOUNCE_DOCKED ||
+ s->debounce_state == POGO_DEBOUNCE_UNDOCKED ||
+ !s->ta_check_wait);
+
+ if (!s->ta_check_wait || ret) {
+ mutex_unlock(&s->lock);
+ ret = -EBUSY;
+ goto done;
+ }
+ }
+
+ s->ta_check_mode = true;
+ s->ta_check_wait = 0;
+
+ if (spdif_mode_and_gpio_in)
+ manta_pogo_spdif_config(true);
+ dock_spdif_switch_set(spdif_mode_and_gpio_in);
+ gpio_set_value(GPIO_POGO_SEL1, 0);
+done:
+ spin_unlock_irqrestore(&s->wait.lock, flags);
+
+ return ret;
+}
+
+void manta_pogo_charge_detect_end(void)
+{
+ struct dock_state *s = &ds;
+ unsigned long flags;
+
+ pr_debug("%s\n", __func__);
+
+ BUG_ON(!s->ta_check_mode);
+
+ spin_lock_irqsave(&s->wait.lock, flags);
+
+ s->ta_check_mode = false;
+
+ gpio_set_value(GPIO_POGO_SEL1, 1);
+ dock_spdif_switch_set(false);
+ manta_pogo_spdif_config(false);
+
+ spin_unlock_irqrestore(&s->wait.lock, flags);
+
+ mutex_unlock(&s->lock);
+}
+
+static int dock_acquire(struct dock_state *s, bool check_docked)
+{
+ int ret = 0;
+
+ mutex_lock(&s->lock);
+
+ if (check_docked && !s->powered_dock_present) {
+ mutex_unlock(&s->lock);
+ return -ENODEV;
+ }
+
+ pr_debug("%s: acquired dock\n", __func__);
+
+ s->level = dock_read();
+ if (s->level) {
+ udelay(s->mfm_delay_ns / 1000 * 2);
+ s->level = dock_read();
+ }
+
+ return ret;
+}
+
+static void dock_release(struct dock_state *s)
+{
+ if (s->cpufreq_min)
+ pogo_set_min_cpu_freq(0);
+
+ mutex_unlock(&s->lock);
+ pr_debug("%s: released dock\n", __func__);
+}
+
+enum {
+ DOCK_STATUS = 0x1,
+ DOCK_ID_ADDR = 0x2,
+ DOCK_VERSION = 0x7,
+};
+
+static int dock_check_status(struct dock_state *s,
+ enum manta_charge_source *charge_source)
+{
+ int ret = 0;
+ int dock_stat, power;
+
+ if (s->dock_connected_unknown) {
+ /* force a new dock notification if a command failed */
+ dock_set_audio_switch(false);
+ s->dock_connected_unknown = false;
+ }
+
+ pr_debug("%s: sending status command\n", __func__);
+
+ dock_stat = dock_send_cmd(s, DOCK_STATUS, false, 0);
+
+ pr_debug("%s: Dock status %02x\n", __func__, dock_stat);
+ if (dock_stat >= 0) {
+ dock_set_audio_switch(dock_stat & DOCK_STAT_AUDIO_CONNECTED);
+
+ if (charge_source) {
+ s->powered_dock_present = true;
+ power = (dock_stat >> DOCK_STAT_POWER_OFFSET) &
+ DOCK_STAT_POWER_MASK;
+ switch (power) {
+ case DOCK_STAT_POWER_500MA:
+ *charge_source = MANTA_CHARGE_SOURCE_USB;
+ break;
+ case DOCK_STAT_POWER_1A:
+ *charge_source = MANTA_CHARGE_SOURCE_AC_OTHER;
+ break;
+ case DOCK_STAT_POWER_2A:
+ *charge_source = MANTA_CHARGE_SOURCE_AC_SAMSUNG;
+ break;
+ default:
+ pr_warn("%s: unknown dock power state %d, default to USB\n",
+ __func__, power);
+ *charge_source = MANTA_CHARGE_SOURCE_USB;
+ }
+ }
+
+ goto done;
+ }
+
+ dock_in();
+ ret = -ENOENT;
+ dock_set_audio_switch(false);
+done:
+ return ret;
+}
+
+int manta_pogo_set_vbus(bool status, enum manta_charge_source *charge_source)
+{
+ struct dock_state *s = &ds;
+ int ret = 0;
+
+ dock_acquire(s, false);
+
+ pr_debug("%s: status %d\n", __func__, status ? 1 : 0);
+
+ if (status) {
+ ret = dock_check_status(s, charge_source);
+ } else {
+ dock_in();
+ dock_set_audio_switch(false);
+ s->powered_dock_present = false;
+ s->dock_connected_unknown = false;
+
+ if (charge_source)
+ *charge_source = MANTA_CHARGE_SOURCE_NONE;
+ }
+
+ dock_release(s);
+ return ret;
+}
+
+static void dock_work_proc(struct work_struct *work)
+{
+ struct dock_state *s = container_of(work, struct dock_state,
+ dock_work);
+ wake_lock(&s->wake_lock);
+ manta_force_update_pogo_charger();
+ wake_unlock(&s->wake_lock);
+}
+
+static irqreturn_t pogo_data_interrupt(int irq, void *data)
+{
+ struct dock_state *s = data;
+ pr_debug("%s: irq %d\n", __func__, irq);
+
+ if (s->ignore_data_irq_once) {
+ pr_debug("%s: ignored unwanted data_irq\n", __func__);
+ s->ignore_data_irq_once = false;
+ goto done;
+ }
+
+ if (s->powered_dock_present) {
+ wake_lock(&s->wake_lock);
+ queue_work(s->dock_wq, &s->dock_work);
+ }
+done:
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pogo_dock_pd_interrupt(int irq, void *data)
+{
+ struct dock_state *s = data;
+ unsigned long flags;
+
+ pr_debug("%s: irq %d, state %d, gpio %d, mux %d, ta_mode %d\n",
+ __func__, irq, s->debounce_state,
+ gpio_get_value(GPIO_DOCK_PD_INT), s->spdif_mux_state,
+ s->ta_check_mode);
+
+ spin_lock_irqsave(&s->wait.lock, flags);
+
+ if (s->spdif_mux_state || s->ta_check_mode)
+ s->debounce_pending = true;
+ else
+ pogo_dock_pd_interrupt_locked(irq, data);
+
+ spin_unlock_irqrestore(&s->wait.lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static void pogo_debounce_timer(unsigned long data)
+{
+ struct dock_state *s = (struct dock_state *) data;
+ unsigned long flags;
+
+ pr_debug("%s: state %d, gpio %d\n", __func__, s->debounce_state,
+ gpio_get_value(GPIO_DOCK_PD_INT));
+
+ spin_lock_irqsave(&s->wait.lock, flags);
+
+ switch (s->debounce_state) {
+ case POGO_DEBOUNCE_UNSTABLE:
+ /*
+ * The detect gpio changed in one the previous timeslots,
+ * so enable the irq, reset the timer, and wait again. If the
+ * detect gpio changed after we last disabled the interrupt we
+ * will get anther interrupt right away and the state will go
+ * back to POGO_DET_UNSTABLE.
+ */
+ s->debounce_state = POGO_DEBOUNCE_WAIT_STABLE;
+ enable_irq(s->dock_pd_irq);
+ mod_timer(&s->debounce_timer,
+ jiffies + msecs_to_jiffies(DEBOUNCE_DELAY_MS));
+
+ if (s->ta_check_wait && !(--s->ta_check_wait))
+ wake_up_locked(&s->wait);
+
+ break;
+ case POGO_DEBOUNCE_WAIT_STABLE:
+ s->debounce_state = (s->spdif_mux_state ||
+ gpio_get_value(GPIO_DOCK_PD_INT)) ?
+ POGO_DEBOUNCE_DOCKED : POGO_DEBOUNCE_UNDOCKED;
+
+ if (s->ta_check_wait)
+ wake_up_locked(&s->wait);
+
+ queue_work(s->dock_wq, &s->debounce_work);
+ break;
+ default:
+ break;
+ }
+
+ spin_unlock_irqrestore(&s->wait.lock, flags);
+}
+
+static void debounce_work_proc(struct work_struct *work)
+{
+ struct dock_state *s = container_of(work, struct dock_state,
+ debounce_work);
+ unsigned long flags;
+ int state;
+
+ spin_lock_irqsave(&s->wait.lock, flags);
+ state = s->debounce_state;
+ spin_unlock_irqrestore(&s->wait.lock, flags);
+
+ switch (state) {
+ case POGO_DEBOUNCE_DOCKED:
+ switch_set_state(&dock_switch, POGO_DESK_DOCK);
+ break;
+ case POGO_DEBOUNCE_UNDOCKED:
+ switch_set_state(&dock_switch, POGO_UNDOCKED);
+ break;
+ default:
+ break;
+ }
+
+ spin_lock_irqsave(&s->wait.lock, flags);
+ if (state == s->debounce_state)
+ wake_unlock(&s->debounce_wake_lock);
+ spin_unlock_irqrestore(&s->wait.lock, flags);
+}
+
+#ifdef DEBUG
+static ssize_t dev_attr_vbus_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", ds.powered_dock_present ? 1 : 0);
+}
+
+static ssize_t dev_attr_powered_dock_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ if (size) {
+ manta_pogo_set_vbus(buf[0] == '1', NULL);
+ return size;
+ } else
+ return -EINVAL;
+}
+static DEVICE_ATTR(powered_dock, S_IRUGO | S_IWUSR,
+ dev_attr_powered_dock_show, dev_attr_powered_dock_store);
+#endif
+
+#ifdef DEBUG
+static ssize_t dev_attr_delay_ns_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", ds.mfm_delay_ns);
+}
+
+static ssize_t dev_attr_delay_ns_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ if (size) {
+ ds.mfm_delay_ns = simple_strtoul(buf, NULL, 10);
+ return size;
+ } else
+ return -EINVAL;
+}
+static DEVICE_ATTR(delay_ns, S_IRUGO | S_IWUSR, dev_attr_delay_ns_show,
+ dev_attr_delay_ns_store);
+#endif
+
+static ssize_t dev_attr_dock_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ u8 dock_id[4];
+
+ ret = dock_acquire(&ds, true);
+ if (ret < 0)
+ goto fail;
+ ret = dock_read_multi(&ds, DOCK_ID_ADDR, dock_id, 4);
+ dock_release(&ds);
+ if (ret < 0)
+ goto fail;
+
+ ret = sprintf(buf, "%02x:%02x:%02x:%02x\n\n",
+ dock_id[0], dock_id[1], dock_id[2], dock_id[3]);
+fail:
+ return ret;
+}
+
+static ssize_t dev_attr_dock_id_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ int ret, i;
+ int val[4];
+ u8 dock_id[4];
+
+ if (size < 11 || sscanf(buf, "%2x:%2x:%2x:%2x", &val[0], &val[1],
+ &val[2], &val[3]) != 4)
+ return -EINVAL;
+
+ for (i = 0; i < 4; i++)
+ dock_id[i] = val[i];
+
+ ret = dock_acquire(&ds, true);
+ if (ret < 0)
+ goto fail;
+ ret = dock_write_multi(&ds, DOCK_ID_ADDR, dock_id, 4);
+ dock_release(&ds);
+ if (ret < 0)
+ goto fail;
+
+ ret = size;
+fail:
+ return ret;
+}
+static DEVICE_ATTR(dock_id, S_IRUGO | S_IWUSR, dev_attr_dock_id_show,
+ dev_attr_dock_id_store);
+
+static ssize_t dev_attr_dock_ver_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+
+ ret = dock_acquire(&ds, true);
+ if (ret < 0)
+ goto fail;
+ ret = dock_send_cmd(&ds, DOCK_VERSION, false, 0);
+ dock_release(&ds);
+ if (ret < 0)
+ goto fail;
+
+ ret = sprintf(buf, "0x%02x\n", ret);
+fail:
+ return ret;
+}
+static DEVICE_ATTR(dock_ver, S_IRUGO, dev_attr_dock_ver_show, NULL);
+
+static ssize_t dev_attr_dock_status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+
+ ret = dock_acquire(&ds, true);
+ if (ret < 0)
+ goto fail;
+ ret = dock_send_cmd(&ds, DOCK_STATUS, false, 0);
+ dock_release(&ds);
+ if (ret < 0)
+ goto fail;
+
+ ret = sprintf(buf, "0x%02x\n", ret);
+fail:
+ return ret;
+}
+static DEVICE_ATTR(dock_status, S_IRUGO, dev_attr_dock_status_show, NULL);
+
+static int pogo_cpufreq_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct dock_state *s = &ds;
+ struct cpufreq_policy *policy = data;
+
+ if (event != CPUFREQ_ADJUST)
+ goto done;
+
+ pr_debug("%s: adjusting cpu%d cpufreq to %d", __func__,
+ policy->cpu, s->cpufreq_min);
+
+ cpufreq_verify_within_limits(policy, s->cpufreq_min,
+ policy->cpuinfo.max_freq);
+
+done:
+ return 0;
+}
+
+static struct notifier_block pogo_cpufreq_notifier_block = {
+ .notifier_call = pogo_cpufreq_notifier,
+};
+
+void __init exynos5_manta_pogo_init(void)
+{
+ struct dock_state *s = &ds;
+ int ret;
+
+ if (exynos5_manta_get_revision() <= MANTA_REV_BETA)
+ manta_pogo_gpios[0].gpio = GPIO_POGO_DATA_BETA;
+
+ wake_lock_init(&s->wake_lock, WAKE_LOCK_SUSPEND, "dock");
+ wake_lock_init(&s->debounce_wake_lock, WAKE_LOCK_SUSPEND, "dock_pd");
+
+ INIT_WORK(&s->dock_work, dock_work_proc);
+ INIT_WORK(&s->debounce_work, debounce_work_proc);
+ s->dock_wq = create_singlethread_workqueue("dock");
+
+ setup_timer(&s->debounce_timer, pogo_debounce_timer, (unsigned long)s);
+
+ s3c_gpio_cfgpin(GPIO_POGO_SEL1, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(GPIO_POGO_SEL1, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_pd_cfg(GPIO_POGO_SEL1, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_pull(GPIO_POGO_SEL1, S5P_GPIO_PD_UPDOWN_DISABLE);
+
+ s5p_gpio_set_pd_cfg(GPIO_TA_CHECK_SEL, S5P_GPIO_PD_PREV_STATE);
+ s5p_gpio_set_pd_pull(GPIO_TA_CHECK_SEL, S5P_GPIO_PD_UPDOWN_DISABLE);
+
+ ret = gpio_request_array(manta_pogo_gpios,
+ ARRAY_SIZE(manta_pogo_gpios));
+ if (ret)
+ pr_err("%s: cannot request gpios\n", __func__);
+
+ ret = gpio_request(GPIO_POGO_SPDIF, "pogo_spdif");
+ if (ret)
+ pr_err("%s: cannot request gpio POGO_SPDIF\n", __func__);
+
+ manta_pogo_spdif_config(false);
+
+ if (switch_dev_register(&dock_switch) == 0) {
+ ret = device_create_file(dock_switch.dev, &dev_attr_dock_id);
+ WARN_ON(ret);
+ ret = device_create_file(dock_switch.dev, &dev_attr_dock_ver);
+ WARN_ON(ret);
+ ret = device_create_file(dock_switch.dev,
+ &dev_attr_dock_status);
+ WARN_ON(ret);
+#ifdef DEBUG
+ ret = device_create_file(dock_switch.dev, &dev_attr_delay_ns);
+ WARN_ON(ret);
+ ret = device_create_file(dock_switch.dev,
+ &dev_attr_unpowered_dock);
+ WARN_ON(ret);
+#endif
+ }
+
+ WARN_ON(switch_dev_register(&usb_audio_switch));
+
+ ret = cpufreq_register_notifier(&pogo_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+ if (ret)
+ pr_warn("%s: cannot register cpufreq notifier\n", __func__);
+
+ manta_pogo_set_vbus(0, NULL);
+}
+
+int __init pogo_data_irq_subsys_init(void)
+{
+ struct dock_state *s = &ds;
+ int ret, data_irq;
+
+ dock_in();
+ s3c_gpio_setpull(_GPIO_DOCK, S3C_GPIO_PULL_NONE);
+
+ data_irq = gpio_to_irq(_GPIO_DOCK);
+
+ ret = request_irq(data_irq, pogo_data_interrupt, IRQF_TRIGGER_FALLING,
+ "dock", &ds);
+ if (ret < 0) {
+ pr_err("%s: failed to request irq %d, rc: %d\n", __func__,
+ data_irq, ret);
+ goto done;
+ }
+
+ ret = enable_irq_wake(data_irq);
+ if (ret) {
+ pr_err("%s: failed to enable irq_wake for POGO_DATA\n",
+ __func__);
+ goto fail_data_irq_wake;
+ }
+
+ s3c_gpio_cfgpin(GPIO_DOCK_PD_INT, S3C_GPIO_SFN(0xF));
+ s3c_gpio_setpull(GPIO_DOCK_PD_INT, S3C_GPIO_PULL_NONE);
+
+ s->dock_pd_irq = gpio_to_irq(GPIO_DOCK_PD_INT);
+
+ ret = request_irq(s->dock_pd_irq, pogo_dock_pd_interrupt,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "dock_pd", &ds);
+
+ if (ret < 0) {
+ pr_err("%s: failed to request dock_pd_int irq %d, rc: %d\n",
+ __func__, s->dock_pd_irq, ret);
+ goto fail_pd_irq_request;
+ }
+
+ ret = enable_irq_wake(s->dock_pd_irq);
+ if (ret < 0) {
+ pr_err("%s: failed to enable irq_wake for DOCK_PD_INT\n",
+ __func__);
+ goto fail_pd_irq_wake;
+ }
+
+ return 0;
+
+fail_pd_irq_wake:
+ free_irq(s->dock_pd_irq, &ds);
+
+fail_pd_irq_request:
+ disable_irq_wake(data_irq);
+
+fail_data_irq_wake:
+ free_irq(data_irq, &ds);
+
+done:
+ return ret;
+}
+
+subsys_initcall_sync(pogo_data_irq_subsys_init);
diff --git a/arch/arm/mach-exynos/board-manta-power.c b/arch/arm/mach-exynos/board-manta-power.c
new file mode 100644
index 0000000..45a2a6d
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-power.c
@@ -0,0 +1,459 @@
+/* linux/arch/arm/mach-exynos/board-manta-power.c
+ *
+ * Copyright (c) 2012 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.
+ */
+
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/mfd/max77686.h>
+#include <linux/regulator/machine.h>
+#include <linux/rtc.h>
+#include <linux/regulator/fixed.h>
+#include <linux/platform_device.h>
+
+#include <asm/system_misc.h>
+
+#include <mach/regs-pmu.h>
+#include <mach/asv-exynos.h>
+
+#include "board-manta.h"
+#include "common.h"
+
+#define REBOOT_MODE_PREFIX 0x12345670
+#define REBOOT_MODE_NONE 0
+#define REBOOT_MODE_RECOVERY 4
+#define REBOOT_MODE_FAST_BOOT 7
+
+#define REBOOT_MODE_NO_LPM 0x12345678
+#define REBOOT_MODE_LPM 0
+
+static struct regulator_consumer_supply ldo3_supply[] = {
+ REGULATOR_SUPPLY("vcc_1.8v", NULL),
+ REGULATOR_SUPPLY("AVDD2", NULL),
+ REGULATOR_SUPPLY("CPVDD", NULL),
+ REGULATOR_SUPPLY("DBVDD1", NULL),
+ REGULATOR_SUPPLY("DBVDD2", NULL),
+ REGULATOR_SUPPLY("DBVDD3", NULL)
+};
+
+static struct regulator_consumer_supply ldo8_supply[] = {
+ REGULATOR_SUPPLY("vmipi_1.0v", NULL),
+};
+
+static struct regulator_consumer_supply ldo9_supply[] = {
+ REGULATOR_SUPPLY("vdd", "3-004a"),
+};
+
+static struct regulator_consumer_supply ldo10_supply[] = {
+ REGULATOR_SUPPLY("vmipi_1.8v", NULL),
+};
+
+static struct regulator_consumer_supply ldo12_supply[] = {
+ REGULATOR_SUPPLY("votg_3.0v", NULL),
+};
+
+static struct regulator_consumer_supply ldo15_supply[] = {
+ REGULATOR_SUPPLY("vhsic_1.0v", NULL),
+};
+
+static struct regulator_consumer_supply ldo16_supply[] = {
+ REGULATOR_SUPPLY("vhsic_1.8v", NULL),
+};
+
+static struct regulator_consumer_supply ldo17_supply[] = {
+ REGULATOR_SUPPLY("5m_core_1.5v", NULL),
+};
+
+static struct regulator_consumer_supply ldo18_supply[] = {
+ REGULATOR_SUPPLY("cam_io_1.8v", NULL),
+};
+
+static struct regulator_consumer_supply ldo19_supply[] = {
+ REGULATOR_SUPPLY("vt_cam_1.8v", NULL),
+};
+
+static struct regulator_consumer_supply ldo20_supply[] = {
+ REGULATOR_SUPPLY("ta_check_1.35v", NULL),
+};
+
+static struct regulator_consumer_supply ldo23_supply[] = {
+ REGULATOR_SUPPLY("avdd", "3-004a"),
+};
+
+static struct regulator_consumer_supply ldo24_supply[] = {
+ REGULATOR_SUPPLY("cam_af_2.8v", NULL),
+};
+
+static struct regulator_consumer_supply ldo25_supply[] = {
+ REGULATOR_SUPPLY("vadc_3.3v", NULL),
+};
+
+static struct regulator_consumer_supply max77686_buck1 =
+REGULATOR_SUPPLY("vdd_mif", NULL);
+
+static struct regulator_consumer_supply max77686_buck2 =
+REGULATOR_SUPPLY("vdd_arm", NULL);
+
+static struct regulator_consumer_supply max77686_buck3 =
+REGULATOR_SUPPLY("vdd_int", NULL);
+
+static struct regulator_consumer_supply max77686_buck4 =
+REGULATOR_SUPPLY("vdd_g3d", NULL);
+
+static struct regulator_consumer_supply max77686_enp32khz[] = {
+ REGULATOR_SUPPLY("lpo_in", "bcm47511"),
+ REGULATOR_SUPPLY("lpo", "bcm4334_bluetooth"),
+};
+
+#define REGULATOR_INIT(_ldo, _name, _min_uV, _max_uV, _always_on, _ops_mask, \
+ _disabled) \
+ static struct regulator_init_data _ldo##_init_data = { \
+ .constraints = { \
+ .name = _name, \
+ .min_uV = _min_uV, \
+ .max_uV = _max_uV, \
+ .always_on = _always_on, \
+ .boot_on = _always_on, \
+ .apply_uV = 1, \
+ .valid_ops_mask = _ops_mask, \
+ .state_mem = { \
+ .disabled = \
+ (_disabled == -1 ? 0 : _disabled),\
+ .enabled = \
+ (_disabled == -1 ? 0 : !(_disabled)),\
+ }, \
+ }, \
+ .num_consumer_supplies = ARRAY_SIZE(_ldo##_supply), \
+ .consumer_supplies = &_ldo##_supply[0], \
+ };
+
+REGULATOR_INIT(ldo3, "VCC_1.8V_AP", 1800000, 1800000, 1, 0, 0);
+REGULATOR_INIT(ldo8, "VMIPI_1.0V", 1000000, 1000000, 1,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo9, "TOUCH_VDD_1.8V", 1800000, 1800000, 0,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo10, "VMIPI_1.8V", 1800000, 1800000, 1,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo12, "VUOTG_3.0V", 3000000, 3000000, 1,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo15, "VHSIC_1.0V", 1000000, 1000000, 1,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo16, "VHSIC_1.8V", 1800000, 1800000, 1,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo17, "5M_CORE_1.5V", 1500000, 1500000, 0,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo18, "CAM_IO_1.8V", 1800000, 1800000, 0,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo19, "VT_CAM_1.8V", 1800000, 1800000, 0,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo20, "TA_CHECK_1.35V", 1350000, 1350000, 0,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo23, "TSP_AVDD_2.8V", 2800000, 2800000, 0,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo24, "CAM_AF_2.8V", 2800000, 2800000, 0,
+ REGULATOR_CHANGE_STATUS, 1);
+REGULATOR_INIT(ldo25, "VADC_3.3V", 3300000, 3300000, 1,
+ REGULATOR_CHANGE_STATUS, 1);
+
+static struct regulator_init_data max77686_buck1_data = {
+ .constraints = {
+ .name = "vdd_mif range",
+ .min_uV = 850000,
+ .max_uV = 1200000,
+ .always_on = 1,
+ .boot_on = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE,
+ },
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &max77686_buck1,
+};
+
+static struct regulator_init_data max77686_buck2_data = {
+ .constraints = {
+ .name = "vdd_arm range",
+ .min_uV = 850000,
+ .max_uV = 1500000,
+ .always_on = 1,
+ .boot_on = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE,
+ },
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &max77686_buck2,
+};
+
+static struct regulator_init_data max77686_buck3_data = {
+ .constraints = {
+ .name = "vdd_int range",
+ .min_uV = 850000,
+ .max_uV = 1300000,
+ .always_on = 1,
+ .boot_on = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE,
+ },
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &max77686_buck3,
+};
+
+static struct regulator_init_data max77686_buck4_data = {
+ .constraints = {
+ .name = "vdd_g3d range",
+ .min_uV = 850000,
+ .max_uV = 1250000,
+ .boot_on = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &max77686_buck4,
+};
+
+static struct regulator_init_data max77686_enp32khz_data = {
+ .constraints = {
+ .name = "32KHZ_PMIC",
+ .always_on = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ .state_mem = {
+ .enabled = 1,
+ .disabled = 0,
+ },
+ },
+ .num_consumer_supplies = ARRAY_SIZE(max77686_enp32khz),
+ .consumer_supplies = max77686_enp32khz,
+};
+
+static struct max77686_regulator_data max77686_regulators[] = {
+ {MAX77686_BUCK1, &max77686_buck1_data,},
+ {MAX77686_BUCK2, &max77686_buck2_data,},
+ {MAX77686_BUCK3, &max77686_buck3_data,},
+ {MAX77686_BUCK4, &max77686_buck4_data,},
+ {MAX77686_LDO3, &ldo3_init_data,},
+ {MAX77686_LDO8, &ldo8_init_data,},
+ {MAX77686_LDO9, &ldo9_init_data,},
+ {MAX77686_LDO10, &ldo10_init_data,},
+ {MAX77686_LDO12, &ldo12_init_data,},
+ {MAX77686_LDO15, &ldo15_init_data,},
+ {MAX77686_LDO16, &ldo16_init_data,},
+ {MAX77686_LDO17, &ldo17_init_data,},
+ {MAX77686_LDO18, &ldo18_init_data,},
+ {MAX77686_LDO19, &ldo19_init_data,},
+ {MAX77686_LDO20, &ldo20_init_data,},
+ {MAX77686_LDO23, &ldo23_init_data,},
+ {MAX77686_LDO24, &ldo24_init_data,},
+ {MAX77686_LDO25, &ldo25_init_data,},
+ {MAX77686_P32KH, &max77686_enp32khz_data,},
+};
+
+struct max77686_opmode_data max77686_opmode_data[MAX77686_REG_MAX] = {
+ [MAX77686_LDO3] = {MAX77686_LDO3, MAX77686_OPMODE_NORMAL},
+ [MAX77686_LDO8] = {MAX77686_LDO8, MAX77686_OPMODE_STANDBY},
+ [MAX77686_LDO10] = {MAX77686_LDO10, MAX77686_OPMODE_STANDBY},
+ [MAX77686_LDO12] = {MAX77686_LDO12, MAX77686_OPMODE_STANDBY},
+ [MAX77686_LDO15] = {MAX77686_LDO15, MAX77686_OPMODE_STANDBY},
+ [MAX77686_LDO16] = {MAX77686_LDO16, MAX77686_OPMODE_STANDBY},
+ [MAX77686_BUCK1] = {MAX77686_BUCK1, MAX77686_OPMODE_STANDBY},
+ [MAX77686_BUCK2] = {MAX77686_BUCK2, MAX77686_OPMODE_STANDBY},
+ [MAX77686_BUCK3] = {MAX77686_BUCK3, MAX77686_OPMODE_STANDBY},
+ [MAX77686_BUCK4] = {MAX77686_BUCK4, MAX77686_OPMODE_STANDBY},
+};
+
+static struct max77686_wtsr_smpl wtsr_smpl_data = {
+ .wtsr_en = true,
+ .smpl_en = true,
+ .wtsr_timer_val = 3, /* 1000ms */
+ .smpl_timer_val = 0, /* 0.5s */
+ .check_jigon = true,
+};
+
+/* If it's first boot, reset rtc to 1/1/2012 12:00:00(SUN) */
+static struct rtc_time init_time_data = {
+ .tm_sec = 0,
+ .tm_min = 0,
+ .tm_hour = 12,
+ .tm_wday = 0,
+ .tm_mday = 1,
+ .tm_mon = 0,
+ .tm_year = 112,
+ .tm_yday = 0,
+ .tm_isdst = 0,
+};
+
+static struct max77686_platform_data manta_max77686_info = {
+ .num_regulators = ARRAY_SIZE(max77686_regulators),
+ .regulators = max77686_regulators,
+ .irq_gpio = EXYNOS5_GPX0(2), /* AP_PMIC_IRQ */
+ .irq_base = MANTA_IRQ_BOARD_PMIC_START,
+ .wakeup = 1,
+
+ .opmode_data = max77686_opmode_data,
+ .ramp_rate = MAX77686_RAMP_RATE_27MV,
+ .has_full_constraints = 1,
+
+ .buck234_gpio_dvs = {
+ EXYNOS5_GPV0(7), /* GPIO_PMIC_DVS1, */
+ EXYNOS5_GPV0(6), /* GPIO_PMIC_DVS2, */
+ EXYNOS5_GPV0(5), /* GPIO_PMIC_DVS3, */
+ },
+ .buck234_gpio_selb = {
+ EXYNOS5_GPV0(4), /* GPIO_BUCK2_SEL, */
+ EXYNOS5_GPV0(1), /* GPIO_BUCK3_SEL, */
+ EXYNOS5_GPV0(0), /* GPIO_BUCK4_SEL, */
+ },
+
+ .buck2_voltage[0] = 1075000, /* 1.075V */
+ .buck2_voltage[1] = 1075000, /* 1.075V */
+ .buck2_voltage[2] = 1075000, /* 1.075V */
+ .buck2_voltage[3] = 1075000, /* 1.075V */
+ .buck2_voltage[4] = 1075000, /* 1.075V */
+ .buck2_voltage[5] = 1075000, /* 1.075V */
+ .buck2_voltage[6] = 1075000, /* 1.075V */
+ .buck2_voltage[7] = 1075000, /* 1.075V */
+
+ .buck3_voltage[0] = 1037500, /* 1.0375V */
+ .buck3_voltage[1] = 1037500, /* 1.0375V */
+ .buck3_voltage[2] = 1037500, /* 1.0375V */
+ .buck3_voltage[3] = 1037500, /* 1.0375V */
+ .buck3_voltage[4] = 1037500, /* 1.0375V */
+ .buck3_voltage[5] = 1037500, /* 1.0375V */
+ .buck3_voltage[6] = 1037500, /* 1.0375V */
+ .buck3_voltage[7] = 1037500, /* 1.0375V */
+
+ .buck4_voltage[0] = 1200000, /* 1.2V */
+ .buck4_voltage[1] = 1200000, /* 1.2V */
+ .buck4_voltage[2] = 1200000, /* 1.2V */
+ .buck4_voltage[3] = 1200000, /* 1.2V */
+ .buck4_voltage[4] = 1200000, /* 1.2V */
+ .buck4_voltage[5] = 1200000, /* 1.2V */
+ .buck4_voltage[6] = 1200000, /* 1.2V */
+ .buck4_voltage[7] = 1200000, /* 1.2V */
+
+ /* for RTC */
+ .wtsr_smpl = &wtsr_smpl_data,
+ .init_time = &init_time_data,
+};
+
+static struct i2c_board_info i2c_devs5[] __initdata = {
+ {
+ I2C_BOARD_INFO("max77686", (0x12 >> 1)),
+ .platform_data = &manta_max77686_info,
+ },
+};
+
+/* Fixed regulators */
+static struct regulator_consumer_supply mxt_xvdd_supply =
+ REGULATOR_SUPPLY("xvdd", "3-004a");
+
+static struct regulator_init_data mxt_booster_voltage_init_data = {
+ .constraints = {
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+};
+
+static struct fixed_voltage_config mxt_booster_regulator_data = {
+ .supply_name = "MXT_BOOSTER",
+ .gpio = EXYNOS5_GPD1(1),
+ .enable_high = 1,
+ .init_data = &mxt_booster_voltage_init_data,
+};
+
+static struct platform_device mxt_booster_regulator_device = {
+ .name = "reg-fixed-voltage",
+ .id = 0,
+ .dev = {
+ .platform_data = &mxt_booster_regulator_data,
+ }
+};
+
+static struct regulator_init_data mxt_xvdd_voltage_init_data = {
+ .constraints = {
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &mxt_xvdd_supply,
+ .supply_regulator = "MXT_BOOSTER",
+};
+
+static struct fixed_voltage_config mxt_xvdd_regulator_data = {
+ .supply_name = "MXT_XVDD",
+ .gpio = EXYNOS5_GPG0(1),
+ .startup_delay = 3000,
+ .enable_high = 1,
+ .init_data = &mxt_xvdd_voltage_init_data,
+};
+
+static struct platform_device mxt_xvdd_regulator_device = {
+ .name = "reg-fixed-voltage",
+ .id = 1,
+ .dev = {
+ .platform_data = &mxt_xvdd_regulator_data,
+ }
+};
+
+static struct platform_device *mxt_fixed_regulator_devices[] __initdata = {
+ &mxt_booster_regulator_device,
+ &mxt_xvdd_regulator_device,
+};
+
+/* Always reboot to the bootloader. Let the bootloader
+ * figure out if we should truly power-off or charging.
+ */
+static void manta_power_off(void)
+{
+ local_irq_disable();
+
+ writel(REBOOT_MODE_LPM, EXYNOS_INFORM2); /* Enter lpm mode */
+ writel(REBOOT_MODE_PREFIX | REBOOT_MODE_NONE, EXYNOS_INFORM3);
+
+ exynos5_restart(0, 0);
+}
+
+static void manta_reboot(char str, const char *cmd)
+{
+ local_irq_disable();
+
+ writel(REBOOT_MODE_NO_LPM, EXYNOS_INFORM2); /* Don't enter lpm mode */
+ writel(REBOOT_MODE_PREFIX | REBOOT_MODE_NONE, EXYNOS_INFORM3);
+
+ if (cmd) {
+ if (!strcmp(cmd, "recovery"))
+ writel(REBOOT_MODE_PREFIX | REBOOT_MODE_RECOVERY,
+ EXYNOS_INFORM3);
+ else if (!strcmp(cmd, "bootloader"))
+ writel(REBOOT_MODE_PREFIX | REBOOT_MODE_FAST_BOOT,
+ EXYNOS_INFORM2);
+ }
+
+ exynos5_restart(str, cmd); /* S/W reset: INFORM0~3: Keep its value */
+}
+
+extern unsigned int
+exynos5250_set_volt(enum asv_type_id target_type, unsigned int target_freq,
+ unsigned int group, unsigned int volt);
+
+void __init exynos5_manta_adjust_mif_asv_table(void)
+{
+ int i;
+ unsigned int adj_mif_volt[] = { 1175000, 1125000, 1100000, 1037500 };
+
+ if (exynos5_manta_get_revision() > MANTA_REV_DOGFOOD02)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(adj_mif_volt); i++)
+ exynos5250_set_volt(ID_MIF, 800000, i, adj_mif_volt[i]);
+}
+
+void __init exynos5_manta_power_init(void)
+{
+ pm_power_off = manta_power_off;
+ arm_pm_restart = manta_reboot;
+
+ i2c_register_board_info(5, i2c_devs5, ARRAY_SIZE(i2c_devs5));
+
+ platform_add_devices(mxt_fixed_regulator_devices,
+ ARRAY_SIZE(mxt_fixed_regulator_devices));
+}
diff --git a/arch/arm/mach-exynos/board-manta-sensors.c b/arch/arm/mach-exynos/board-manta-sensors.c
new file mode 100644
index 0000000..e573af3
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-sensors.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * 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 <linux/platform_data/bh1721fvc.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/mpu.h>
+#include <plat/gpio-cfg.h>
+
+#define GPIO_ACC_INT EXYNOS5_GPX1(4)
+#define EINT_ACC 12
+#define GPIO_MSENSE_RST EXYNOS5_GPG2(0)
+
+static struct mpu_platform_data mpu_data = {
+ .int_config = 0x00,
+ .level_shifter = 0,
+ .orientation = {
+ 0, 1, 0,
+ 1, 0, 0,
+ 0, 0, -1,
+ },
+ .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS,
+ .sec_slave_id = COMPASS_ID_AK8963,
+ .secondary_i2c_addr = 0x0C,
+ .secondary_orientation = {
+ -1, 0, 0,
+ 0, 1, 0,
+ 0, 0, -1,
+ },
+ .key = {
+ 221, 22, 205, 7, 217, 186, 151, 55,
+ 206, 254, 35, 144, 225, 102, 47, 50,
+ },
+};
+
+static struct i2c_board_info __initdata manta_sensors_i2c1_boardinfo[] = {
+ {
+ I2C_BOARD_INFO("bmp182", 0x77),
+ },
+ {
+ I2C_BOARD_INFO("mpu6050", 0x68),
+ .irq = IRQ_EINT(EINT_ACC),
+ .platform_data = &mpu_data,
+ },
+};
+
+#define GPIO_ALS_NRST EXYNOS5_GPH1(2)
+
+static struct bh1721fvc_platform_data bh1721fvc_pdata = {
+ .reset_pin = GPIO_ALS_NRST,
+};
+
+static struct i2c_board_info __initdata manta_sensors_i2c2_boardinfo[] = {
+ {
+ I2C_BOARD_INFO("bh1721fvc", 0x23),
+ .platform_data = &bh1721fvc_pdata,
+ },
+};
+
+void __init exynos5_manta_sensors_init(void)
+{
+ s3c_gpio_setpull(GPIO_ACC_INT, S3C_GPIO_PULL_UP);
+
+ gpio_request_one(GPIO_ACC_INT, GPIOF_IN, "ACC_INT");
+ gpio_request_one(GPIO_MSENSE_RST, GPIOF_OUT_INIT_HIGH, "MSENSE_RST");
+
+ i2c_register_board_info(1, manta_sensors_i2c1_boardinfo,
+ ARRAY_SIZE(manta_sensors_i2c1_boardinfo));
+
+ s3c_gpio_setpull(GPIO_ALS_NRST, S3C_GPIO_PULL_NONE);
+ i2c_register_board_info(2, manta_sensors_i2c2_boardinfo,
+ ARRAY_SIZE(manta_sensors_i2c2_boardinfo));
+}
diff --git a/arch/arm/mach-exynos/board-manta-vibrator.c b/arch/arm/mach-exynos/board-manta-vibrator.c
new file mode 100644
index 0000000..4f3aabf
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-vibrator.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics Co. Ltd. All Rights Reserved.
+ *
+ * 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 <linux/platform_data/haptic_isa1200.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <plat/gpio-cfg.h>
+#include <plat/devs.h>
+
+#define VIB_PWM_GPIO EXYNOS5_GPB2(1)
+
+static struct isa1200_platform_data isa1200_pdata = {
+ .pwm_ch = 1, /* XPWMTOUT_1 */
+ .hap_en_gpio = EXYNOS5_GPD1(6),
+ .max_timeout = 10000, /* 10 seconds */
+};
+
+/* I2C4 */
+static struct i2c_board_info i2c_devs4[] __initdata = {
+ {
+ I2C_BOARD_INFO("isa1200", (0x90 >> 1)),
+ .platform_data = &isa1200_pdata,
+ },
+};
+
+void __init exynos5_manta_vib_init(void)
+{
+ int ret;
+ ret = s3c_gpio_cfgpin(VIB_PWM_GPIO, S3C_GPIO_SFN(2));
+ if (ret) {
+ printk(KERN_ERR "%s s3c_gpio_cfgpin error %d\n",
+ __func__, ret);
+ goto init_fail;
+ }
+ ret = i2c_register_board_info(4, i2c_devs4, ARRAY_SIZE(i2c_devs4));
+ if (ret) {
+ printk(KERN_ERR "%s i2c_register_board_info error %d\n",
+ __func__, ret);
+ goto init_fail;
+ }
+ ret = platform_device_register(&s3c_device_timer[isa1200_pdata.pwm_ch]);
+ if (ret)
+ printk(KERN_ERR "%s failed platform_device_register with error %d\n",
+ __func__, ret);
+
+init_fail:
+ return;
+}
diff --git a/arch/arm/mach-exynos/board-manta-wifi.c b/arch/arm/mach-exynos/board-manta-wifi.c
new file mode 100644
index 0000000..42b0735
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta-wifi.c
@@ -0,0 +1,377 @@
+/*
+ * 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <asm/mach-types.h>
+#include <asm/gpio.h>
+#include <asm/io.h>
+#include <asm/setup.h>
+#include <linux/if.h>
+#include <linux/skbuff.h>
+#include <linux/wlan_plat.h>
+#include <linux/mmc/host.h>
+#include <plat/gpio-cfg.h>
+#include <plat/devs.h>
+#include <mach/dwmci.h>
+#include <mach/map.h>
+
+#include <linux/random.h>
+#include <linux/jiffies.h>
+
+#define GPIO_WLAN_PMENA EXYNOS5_GPV1(0)
+#define GPIO_WLAN_IRQ EXYNOS5_GPX2(5)
+
+#define WLAN_SDIO_CMD EXYNOS5_GPC2(1)
+#define WLAN_SDIO_DATA0 EXYNOS5_GPC2(3)
+#define WLAN_SDIO_DATA1 EXYNOS5_GPC2(4)
+#define WLAN_SDIO_DATA2 EXYNOS5_GPC2(5)
+#define WLAN_SDIO_DATA3 EXYNOS5_GPC2(6)
+
+extern void bt_wlan_lock(void);
+extern void bt_wlan_unlock(void);
+
+static struct resource manta_wifi_resources[] = {
+ [0] = {
+ .name = "bcmdhd_wlan_irq",
+ .start = GPIO_WLAN_IRQ,
+ .end = GPIO_WLAN_IRQ,
+ .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL |
+ IORESOURCE_IRQ_SHAREABLE,
+ },
+};
+
+struct wifi_gpio_sleep_data {
+ uint num;
+ uint cfg;
+ uint pull;
+};
+
+static struct wifi_gpio_sleep_data manta_sleep_wifi_gpios[] = {
+ /* WLAN_SDIO_CMD */
+ {WLAN_SDIO_CMD, S5P_GPIO_PD_INPUT, S5P_GPIO_PD_UPDOWN_DISABLE},
+ /* WLAN_SDIO_D(0) */
+ {WLAN_SDIO_DATA0, S5P_GPIO_PD_INPUT, S5P_GPIO_PD_UPDOWN_DISABLE},
+ /* WLAN_SDIO_D(1) */
+ {WLAN_SDIO_DATA1, S5P_GPIO_PD_INPUT, S5P_GPIO_PD_UPDOWN_DISABLE},
+ /* WLAN_SDIO_D(2) */
+ {WLAN_SDIO_DATA2, S5P_GPIO_PD_INPUT, S5P_GPIO_PD_UPDOWN_DISABLE},
+ /* WLAN_SDIO_D(3) */
+ {WLAN_SDIO_DATA3, S5P_GPIO_PD_INPUT, S5P_GPIO_PD_UPDOWN_DISABLE},
+};
+
+static void (*wifi_status_cb)(struct platform_device *, int state);
+
+static int exynos5_manta_wlan_ext_cd_init(
+ void (*notify_func)(struct platform_device *, int))
+{
+ wifi_status_cb = notify_func;
+ return 0;
+}
+
+static int exynos5_manta_wlan_ext_cd_cleanup(
+ void (*notify_func)(struct platform_device *, int))
+{
+ wifi_status_cb = NULL;
+ return 0;
+}
+
+static int manta_wifi_set_carddetect(int val)
+{
+ pr_debug("%s: %d\n", __func__, val);
+
+ if (wifi_status_cb)
+ wifi_status_cb(&exynos5_device_dwmci1, val);
+ else
+ pr_warning("%s: Nobody to notify\n", __func__);
+
+ return 0;
+}
+
+static int manta_wifi_power_state = -1;
+
+static int manta_wifi_power(int on)
+{
+ int ret = 0;
+ int sleep_before_on = 600;
+ int sleep_after_on = 500;
+
+ bt_wlan_lock();
+ pr_debug("%s: %d\n", __func__, on);
+
+ if (manta_wifi_power_state == -1) { /* On probe */
+ sleep_before_on = 50;
+ sleep_after_on = 300;
+ }
+
+ if (on)
+ msleep(sleep_before_on);
+ else
+ msleep(50);
+ gpio_set_value(GPIO_WLAN_PMENA, on);
+ if (on)
+ msleep(sleep_after_on);
+ else
+ msleep(50);
+
+ manta_wifi_power_state = on;
+ bt_wlan_unlock();
+ return ret;
+}
+
+static int manta_wifi_reset_state;
+
+static int manta_wifi_reset(int on)
+{
+ pr_debug("%s: do nothing\n", __func__);
+ manta_wifi_reset_state = on;
+ return 0;
+}
+
+static unsigned char manta_mac_addr[IFHWADDRLEN] = { 0, 0x90, 0x4c, 0, 0, 0 };
+
+static int __init manta_mac_addr_setup(char *str)
+{
+ char macstr[IFHWADDRLEN*3];
+ char *macptr = macstr;
+ char *token;
+ int i = 0;
+
+ if (!str)
+ return 0;
+ pr_debug("wlan MAC = %s\n", str);
+ if (strlen(str) >= sizeof(macstr))
+ return 0;
+ strcpy(macstr, str);
+
+ while ((token = strsep(&macptr, ":")) != NULL) {
+ unsigned long val;
+ int res;
+
+ if (i >= IFHWADDRLEN)
+ break;
+ res = kstrtoul(token, 0x10, &val);
+ if (res < 0)
+ return 0;
+ manta_mac_addr[i++] = (u8)val;
+ }
+
+ return 1;
+}
+
+__setup("androidboot.wifimacaddr=", manta_mac_addr_setup);
+
+static int manta_wifi_get_mac_addr(unsigned char *buf)
+{
+ uint rand_mac;
+
+ if (!buf)
+ return -EFAULT;
+
+ if ((manta_mac_addr[4] == 0) && (manta_mac_addr[5] == 0)) {
+ srandom32((uint)jiffies);
+ rand_mac = random32();
+ manta_mac_addr[3] = (unsigned char)rand_mac;
+ manta_mac_addr[4] = (unsigned char)(rand_mac >> 8);
+ manta_mac_addr[5] = (unsigned char)(rand_mac >> 16);
+ }
+ memcpy(buf, manta_mac_addr, IFHWADDRLEN);
+ return 0;
+}
+
+/* Customized Locale table : OPTIONAL feature */
+#define WLC_CNTRY_BUF_SZ 4
+struct cntry_locales_custom {
+ char iso_abbrev[WLC_CNTRY_BUF_SZ];
+ char custom_locale[WLC_CNTRY_BUF_SZ];
+ int custom_locale_rev;
+};
+
+static struct cntry_locales_custom manta_wifi_translate_custom_table[] = {
+/* Table should be filled out based on custom platform regulatory requirement */
+ {"", "XY", 9}, /* universal */
+ {"US", "Q2", 32}, /* input ISO "US" to : Q2 regrev 32 */
+ {"CA", "Q2", 32}, /* input ISO "CA" to : Q2 regrev 32 */
+ {"EU", "EU", 51}, /* European union countries */
+ {"AT", "EU", 51},
+ {"BE", "EU", 51},
+ {"BG", "EU", 51},
+ {"CY", "EU", 51},
+ {"CZ", "EU", 51},
+ {"DK", "EU", 51},
+ {"EE", "EU", 51},
+ {"FI", "EU", 51},
+ {"FR", "EU", 51},
+ {"DE", "EU", 51},
+ {"GR", "EU", 51},
+ {"HU", "EU", 51},
+ {"IE", "EU", 51},
+ {"IT", "EU", 51},
+ {"LV", "EU", 51},
+ {"LI", "EU", 51},
+ {"LT", "EU", 51},
+ {"LU", "EU", 51},
+ {"MT", "EU", 51},
+ {"NL", "EU", 51},
+ {"PL", "EU", 51},
+ {"PT", "EU", 51},
+ {"RO", "EU", 51},
+ {"SK", "EU", 51},
+ {"SI", "EU", 51},
+ {"ES", "EU", 51},
+ {"SE", "EU", 51},
+ {"GB", "EU", 51}, /* input ISO "GB" to : EU regrev 51 */
+ {"IL", "IL", 0},
+ {"CH", "CH", 0},
+ {"TR", "TR", 0},
+ {"NO", "NO", 0},
+ {"KR", "KR", 25},
+ {"AU", "XY", 9},
+ {"CN", "CN", 0},
+ {"TW", "XY", 9},
+ {"AR", "XY", 9},
+ {"MX", "XY", 9},
+ {"JP", "EU", 51},
+ {"BR", "KR", 25}
+};
+
+static void *manta_wifi_get_country_code(char *ccode)
+{
+ int size = ARRAY_SIZE(manta_wifi_translate_custom_table);
+ int i;
+
+ if (!ccode)
+ return NULL;
+
+ for (i = 0; i < size; i++)
+ if (strcmp(ccode,
+ manta_wifi_translate_custom_table[i].iso_abbrev) == 0)
+ return &manta_wifi_translate_custom_table[i];
+ return &manta_wifi_translate_custom_table[0];
+}
+
+static struct wifi_platform_data manta_wifi_control = {
+ .set_power = manta_wifi_power,
+ .set_reset = manta_wifi_reset,
+ .set_carddetect = manta_wifi_set_carddetect,
+ .mem_prealloc = NULL,
+ .get_mac_addr = manta_wifi_get_mac_addr,
+ .get_country_code = manta_wifi_get_country_code,
+};
+
+static struct platform_device manta_wifi_device = {
+ .name = "bcmdhd_wlan",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(manta_wifi_resources),
+ .resource = manta_wifi_resources,
+ .dev = {
+ .platform_data = &manta_wifi_control,
+ },
+};
+
+static void exynos5_setup_wlan_cfg_gpio(int width)
+{
+ unsigned int gpio;
+
+ /* Set all the necessary GPC2[0:1] pins to special-function 2 */
+ for (gpio = EXYNOS5_GPC2(0); gpio < EXYNOS5_GPC2(2); gpio++) {
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV2);
+ }
+
+ for (gpio = EXYNOS5_GPC2(3); gpio <= EXYNOS5_GPC2(6); gpio++) {
+ /* Data pin GPC2[3:6] to special-function 2 */
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV2);
+ }
+}
+
+static struct dw_mci_board exynos_wlan_pdata __initdata = {
+ .num_slots = 1,
+ .cd_type = DW_MCI_CD_EXTERNAL,
+ .quirks = DW_MCI_QUIRK_HIGHSPEED |
+ DW_MCI_QUIRK_IDMAC_DTO,
+ .bus_hz = 50 * 1000 * 1000,
+ .max_bus_hz = 200 * 1000 * 1000,
+ .caps = MMC_CAP_UHS_SDR104 |
+ MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED,
+ .caps2 = MMC_CAP2_BROKEN_VOLTAGE,
+ .pm_caps = MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY,
+ .fifo_depth = 0x80,
+ .detect_delay_ms = 0,
+ .hclk_name = "dwmci",
+ .cclk_name = "sclk_dwmci",
+ .cfg_gpio = exynos5_setup_wlan_cfg_gpio,
+ .ext_cd_init = exynos5_manta_wlan_ext_cd_init,
+ .ext_cd_cleanup = exynos5_manta_wlan_ext_cd_cleanup,
+ .sdr_timing = 0x03040002,
+ .ddr_timing = 0x03030002,
+ .clk_drv = 2,
+};
+
+static struct platform_device *manta_wlan_devs[] __initdata = {
+ &exynos5_device_dwmci1,
+ &manta_wifi_device,
+};
+
+static void __init manta_wlan_gpio(void)
+{
+ int gpio;
+ int i;
+
+ pr_debug("%s: start\n", __func__);
+
+ /* Setup wlan Power Enable */
+ gpio = GPIO_WLAN_PMENA;
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
+ /* Keep power state during suspend */
+ s5p_gpio_set_pd_cfg(gpio, S5P_GPIO_PD_PREV_STATE);
+ gpio_set_value(gpio, 0);
+
+ /* Setup wlan IRQ */
+ gpio = GPIO_WLAN_IRQ;
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
+
+ manta_wifi_resources[0].start = gpio_to_irq(gpio);
+ manta_wifi_resources[0].end = gpio_to_irq(gpio);
+
+ /* Setup sleep GPIO for wifi */
+ for (i = 0; i < ARRAY_SIZE(manta_sleep_wifi_gpios); i++) {
+ gpio = manta_sleep_wifi_gpios[i].num;
+ s5p_gpio_set_pd_cfg(gpio, manta_sleep_wifi_gpios[i].cfg);
+ s5p_gpio_set_pd_pull(gpio, manta_sleep_wifi_gpios[i].pull);
+ }
+
+}
+
+void __init exynos5_manta_wlan_init(void)
+{
+ pr_debug("%s: start\n", __func__);
+
+ exynos_dwmci_set_platdata(&exynos_wlan_pdata, 1);
+ dev_set_name(&exynos5_device_dwmci1.dev, "exynos4-sdhci.1");
+ clk_add_alias("dwmci", "dw_mmc.1", "hsmmc", &exynos5_device_dwmci1.dev);
+ clk_add_alias("sclk_dwmci", "dw_mmc.1", "sclk_mmc",
+ &exynos5_device_dwmci1.dev);
+ manta_wlan_gpio();
+ platform_add_devices(manta_wlan_devs, ARRAY_SIZE(manta_wlan_devs));
+}
diff --git a/arch/arm/mach-exynos/board-manta.c b/arch/arm/mach-exynos/board-manta.c
new file mode 100644
index 0000000..c6d9e6c
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta.c
@@ -0,0 +1,894 @@
+/*
+ * Copyright (c) 2012 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.
+*/
+
+#include <linux/errno.h>
+#include <linux/cma.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio_event.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/ion.h>
+#include <linux/i2c.h>
+#include <linux/keyreset.h>
+#include <linux/mmc/host.h>
+#include <linux/memblock.h>
+#include <linux/persistent_ram.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/exynos_usb3_drd.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/fixed.h>
+#include <linux/serial_core.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/sys_soc.h>
+#include <linux/platform_data/stmpe811-adc.h>
+#include <linux/leds-as3668.h>
+
+#include <asm/mach/arch.h>
+#include <asm/hardware/gic.h>
+#include <asm/mach-types.h>
+#include <asm/system_info.h>
+#include <asm/system_misc.h>
+
+#include <plat/adc.h>
+#include <plat/clock.h>
+#include <plat/cpu.h>
+#include <plat/regs-serial.h>
+#include <plat/gpio-cfg.h>
+#include <plat/devs.h>
+#include <plat/iic.h>
+#include <plat/sdhci.h>
+#include <plat/udc-hs.h>
+#include <plat/ehci.h>
+
+#include <mach/map.h>
+#include <mach/sysmmu.h>
+#include <mach/exynos_fiq_debugger.h>
+#include <mach/exynos-ion.h>
+#include <mach/dwmci.h>
+#include <mach/ohci.h>
+#include <mach/tmu.h>
+#include <mach/exynos5_bus.h>
+
+#include "../../../drivers/staging/android/ram_console.h"
+#include "board-manta.h"
+#include "common.h"
+#include "resetreason.h"
+
+#define MANTA_CPU0_DEBUG_PA 0x10890000
+#define MANTA_CPU1_DEBUG_PA 0x10892000
+#define MANTA_CPU_DBGPCSR 0xa0
+
+static int manta_hw_rev;
+phys_addr_t manta_bootloader_fb_start;
+phys_addr_t manta_bootloader_fb_size = 2560 * 1600 * 4;
+static bool manta_charger_mode;
+static void __iomem *manta_cpu0_debug;
+static void __iomem *manta_cpu1_debug;
+
+static int __init s3cfb_bootloaderfb_arg(char *options)
+{
+ char *p = options;
+
+ manta_bootloader_fb_start = memparse(p, &p);
+ pr_debug("bootloader framebuffer found at %8X\n",
+ manta_bootloader_fb_start);
+
+ return 0;
+}
+early_param("s3cfb.bootloaderfb", s3cfb_bootloaderfb_arg);
+
+static int __init manta_androidboot_mode_arg(char *options)
+{
+ if (!strcmp(options, "charger"))
+ manta_charger_mode = true;
+ return 0;
+}
+early_param("androidboot.mode", manta_androidboot_mode_arg);
+
+static struct gpio manta_hw_rev_gpios[] = {
+ {EXYNOS5_GPV1(4), GPIOF_IN, "hw_rev0"},
+ {EXYNOS5_GPV1(3), GPIOF_IN, "hw_rev1"},
+ {EXYNOS5_GPV1(2), GPIOF_IN, "hw_rev2"},
+ {EXYNOS5_GPV1(1), GPIOF_IN, "hw_rev3"},
+};
+
+int exynos5_manta_get_revision(void)
+{
+ return manta_hw_rev;
+}
+
+static char manta_board_info_string[255];
+
+static void manta_init_hw_rev(void)
+{
+ int ret;
+ int i;
+
+ ret = gpio_request_array(manta_hw_rev_gpios,
+ ARRAY_SIZE(manta_hw_rev_gpios));
+
+ BUG_ON(ret);
+
+ for (i = 0; i < ARRAY_SIZE(manta_hw_rev_gpios); i++)
+ manta_hw_rev |= gpio_get_value(manta_hw_rev_gpios[i].gpio) << i;
+
+ snprintf(manta_board_info_string, sizeof(manta_board_info_string) - 1,
+ "Manta HW revision: %d, CPU EXYNOS5250 Rev%d.%d",
+ manta_hw_rev,
+ samsung_rev() >> 4,
+ samsung_rev() & 0xf);
+ pr_info("%s\n", manta_board_info_string);
+ mach_panic_string = manta_board_info_string;
+}
+
+static struct ram_console_platform_data ramconsole_pdata;
+
+static struct platform_device ramconsole_device = {
+ .name = "ram_console",
+ .id = -1,
+ .dev = {
+ .platform_data = &ramconsole_pdata,
+ },
+};
+
+static struct platform_device persistent_trace_device = {
+ .name = "persistent_trace",
+ .id = -1,
+};
+
+static struct resource persistent_clock_resource[] = {
+ [0] = DEFINE_RES_MEM(S3C_PA_RTC, SZ_256),
+};
+
+
+static struct platform_device persistent_clock = {
+ .name = "persistent_clock",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(persistent_clock_resource),
+ .resource = persistent_clock_resource,
+};
+
+/* Following are default values for UCON, ULCON and UFCON UART registers */
+#define MANTA_UCON_DEFAULT (S3C2410_UCON_TXILEVEL | \
+ S3C2410_UCON_RXILEVEL | \
+ S3C2410_UCON_TXIRQMODE | \
+ S3C2410_UCON_RXIRQMODE | \
+ S3C2410_UCON_RXFIFO_TOI | \
+ S3C2443_UCON_RXERR_IRQEN)
+
+#define MANTA_ULCON_DEFAULT S3C2410_LCON_CS8
+
+#define MANTA_UFCON_DEFAULT (S3C2410_UFCON_FIFOMODE | \
+ S5PV210_UFCON_TXTRIG4 | \
+ S5PV210_UFCON_RXTRIG4)
+
+static struct s3c2410_uartcfg manta_uartcfgs[] __initdata = {
+ [0] = {
+ .hwport = 0,
+ .flags = 0,
+ .ucon = MANTA_UCON_DEFAULT,
+ .ulcon = MANTA_ULCON_DEFAULT,
+ .ufcon = MANTA_UFCON_DEFAULT,
+ .wake_peer = bcm_bt_lpm_exit_lpm_locked,
+ },
+ [1] = {
+ .hwport = 1,
+ .flags = 0,
+ .ucon = MANTA_UCON_DEFAULT,
+ .ulcon = MANTA_ULCON_DEFAULT,
+ .ufcon = MANTA_UFCON_DEFAULT,
+ },
+ /* Do not initialize hwport 2, it will be handled by fiq_debugger */
+ [2] = {
+ .hwport = 3,
+ .flags = 0,
+ .ucon = MANTA_UCON_DEFAULT,
+ .ulcon = MANTA_ULCON_DEFAULT,
+ .ufcon = MANTA_UFCON_DEFAULT,
+ },
+};
+
+static struct gpio_event_direct_entry manta_keypad_key_map[] = {
+ {
+ .gpio = EXYNOS5_GPX2(7),
+ .code = KEY_POWER,
+ .dev = 0,
+ },
+ {
+ .gpio = EXYNOS5_GPX2(0),
+ .code = KEY_VOLUMEUP,
+ .dev = 0,
+ },
+ {
+ .gpio = EXYNOS5_GPX2(1),
+ .code = KEY_VOLUMEDOWN,
+ .dev = 0,
+ }
+};
+
+static struct gpio_event_direct_entry manta_switch_map[] = {
+ {
+ .gpio = EXYNOS5_GPX1(3),
+ .code = SW_LID,
+ .dev = 1,
+ }
+};
+
+static struct gpio_event_input_info manta_keypad_key_info = {
+ .info.func = gpio_event_input_func,
+ .info.no_suspend = true,
+ .debounce_time.tv64 = 5 * NSEC_PER_MSEC,
+ .type = EV_KEY,
+ .keymap = manta_keypad_key_map,
+ .keymap_size = ARRAY_SIZE(manta_keypad_key_map)
+};
+
+static struct gpio_event_input_info manta_switch_info = {
+ .info.func = gpio_event_input_func,
+ .info.no_suspend = true,
+ .debounce_time.tv64 = 10 * NSEC_PER_MSEC,
+ .type = EV_SW,
+ .keymap = manta_switch_map,
+ .keymap_size = ARRAY_SIZE(manta_switch_map)
+};
+
+static struct gpio_event_info *manta_event_input_info[] = {
+ &manta_keypad_key_info.info,
+ &manta_switch_info.info,
+};
+
+static struct gpio_event_platform_data manta_event_data = {
+ .names = {
+ "manta-keypad",
+ "manta-switch",
+ NULL,
+ },
+ .info = manta_event_input_info,
+ .info_count = ARRAY_SIZE(manta_event_input_info),
+};
+
+static struct platform_device manta_event_device = {
+ .name = GPIO_EVENT_DEV_NAME,
+ .id = 0,
+ .dev = {
+ .platform_data = &manta_event_data,
+ },
+};
+
+static void __init manta_gpio_power_init(void)
+{
+ int err = 0;
+
+ err = gpio_request_one(EXYNOS5_GPX2(7), 0, "GPX2(7)");
+ if (err) {
+ printk(KERN_ERR "failed to request GPX2(7) for "
+ "suspend/resume control\n");
+ return;
+ }
+ s3c_gpio_setpull(EXYNOS5_GPX2(7), S3C_GPIO_PULL_NONE);
+
+ gpio_free(EXYNOS5_GPX2(7));
+}
+
+static int manta_keyreset_fn(void)
+{
+ arm_pm_restart('h', NULL);
+ return 1;
+}
+
+static struct keyreset_platform_data manta_reset_keys_pdata = {
+ .keys_down = {
+ KEY_POWER,
+ 0,
+ },
+ .down_time_ms = 1500,
+ .reset_fn = manta_keyreset_fn,
+};
+
+struct platform_device manta_keyreset_device = {
+ .name = KEYRESET_NAME,
+ .dev = {
+ .platform_data = &manta_reset_keys_pdata,
+ },
+};
+
+static struct as3668_platform_data as3668_pdata = {
+ .led_array = {AS3668_RED, AS3668_GREEN, AS3668_BLUE, AS3668_WHITE},
+ .vbat_monitor_voltage_index = AS3668_VMON_VBAT_3_0V,
+ .shutdown_enable = AS3668_SHUTDOWN_ENABLE_OFF,
+ .pattern_start_source = AS3668_PATTERN_START_SOURCE_SW,
+ .pwm_source = AS3668_PWM_SOURCE_INTERNAL,
+ .gpio_input_invert = AS3668_GPIO_INPUT_NONINVERT,
+ .gpio_input_mode = AS3668_GPIO_INPUT_MODE_ANALOG,
+ .gpio_mode = AS3668_GPIO_MODE_INPUT_ONLY,
+ .audio_input_pin = AS3668_AUDIO_CTRL_INPUT_CURR4,
+ .audio_pulldown_off = AS3668_AUDIO_CTRL_PLDN_ENABLE,
+ .audio_adc_characteristic = AS3668_AUDIO_CTRL_ADC_CHAR_250,
+ .audio_dis_start = AS3668_AUDIO_INPUT_CAP_PRECHARGE,
+ .audio_man_start = AS3668_AUDIO_INPUT_AUTO_PRECHARGE,
+};
+
+/* I2C0 */
+static struct i2c_board_info i2c_devs0[] __initdata = {
+ {
+ I2C_BOARD_INFO("exynos_hdcp", (0x74 >> 1)),
+ },
+ {
+ I2C_BOARD_INFO("exynos_edid", (0xA0 >> 1)),
+ },
+};
+
+struct s3c2410_platform_i2c i2c0_data __initdata = {
+ .flags = 0,
+ .slave_addr = 0x10,
+ .frequency = 100*1000,
+ .sda_delay = 100,
+};
+
+/* I2C1 */
+static struct i2c_board_info i2c_devs1[] __initdata = {
+ {
+ I2C_BOARD_INFO("as3668", 0x42),
+ .platform_data = &as3668_pdata,
+ },
+};
+
+static struct stmpe811_callbacks *stmpe811_cbs;
+static void stmpe811_register_callback(struct stmpe811_callbacks *cb)
+{
+ stmpe811_cbs = cb;
+}
+
+int manta_stmpe811_read_adc_data(u8 channel)
+{
+ if (stmpe811_cbs && stmpe811_cbs->get_adc_data)
+ return stmpe811_cbs->get_adc_data(channel);
+
+ return -EINVAL;
+}
+
+struct stmpe811_platform_data stmpe811_pdata = {
+ .register_cb = stmpe811_register_callback,
+};
+
+/* ADC */
+static struct s3c_adc_platdata manta_adc_data __initdata = {
+ .phy_init = s3c_adc_phy_init,
+ .phy_exit = s3c_adc_phy_exit,
+};
+
+/* I2C2 */
+static struct i2c_board_info i2c_devs2[] __initdata = {
+ {
+ I2C_BOARD_INFO("stmpe811-adc", (0x82 >> 1)),
+ .platform_data = &stmpe811_pdata,
+ },
+};
+
+/* TMU */
+static struct tmu_data manta_tmu_pdata __initdata = {
+ .ts = {
+ .stop_throttle = 78,
+ .start_throttle = 80,
+ .start_tripping = 110,
+ .start_emergency = 120,
+ .stop_mem_throttle = 80,
+ .start_mem_throttle = 85,
+ },
+
+ .efuse_value = 80,
+ .slope = 0x10608802,
+};
+
+static struct persistent_ram_descriptor manta_prd[] __initdata = {
+ {
+ .name = "ram_console",
+ .size = SZ_2M,
+ },
+#ifdef CONFIG_PERSISTENT_TRACER
+ {
+ .name = "persistent_trace",
+ .size = SZ_1M,
+ },
+#endif
+};
+
+static struct persistent_ram manta_pr __initdata = {
+ .descs = manta_prd,
+ .num_descs = ARRAY_SIZE(manta_prd),
+ .start = PLAT_PHYS_OFFSET + SZ_1G + SZ_512M,
+#ifdef CONFIG_PERSISTENT_TRACER
+ .size = 3 * SZ_1M,
+#else
+ .size = SZ_2M,
+#endif
+};
+
+
+/* defined in arch/arm/mach-exynos/reserve-mem.c */
+extern void exynos_cma_region_reserve(struct cma_region *,
+ struct cma_region *, size_t, const char *);
+extern int kbase_carveout_mem_reserve(phys_addr_t size);
+
+static void __init exynos_reserve_mem(void)
+{
+ static struct cma_region regions[] = {
+ {
+ .name = "ion",
+#ifdef CONFIG_ION_EXYNOS_CONTIGHEAP_SIZE
+ .size = CONFIG_ION_EXYNOS_CONTIGHEAP_SIZE * SZ_1K,
+#endif
+ {
+ .alignment = SZ_1M
+ }
+ },
+#ifdef CONFIG_EXYNOS_CONTENT_PATH_PROTECTION
+#ifdef CONFIG_ION_EXYNOS_DRM_MFC_SH
+ {
+ .name = "drm_mfc_sh",
+ .size = SZ_1M,
+ .alignment = SZ_1M,
+ },
+#endif
+#ifdef CONFIG_ION_EXYNOS_DRM_MSGBOX_SH
+ {
+ .name = "drm_msgbox_sh",
+ .size = SZ_1M,
+ .alignment = SZ_1M,
+ },
+#endif
+#endif
+ {
+ .size = 0 /* END OF REGION DEFINITIONS */
+ }
+ };
+#ifdef CONFIG_EXYNOS_CONTENT_PATH_PROTECTION
+ static struct cma_region regions_secure[] = {
+#ifdef CONFIG_ION_EXYNOS_DRM_MEMSIZE_FIMD_VIDEO
+ {
+ .name = "drm_fimd_video",
+ .size = CONFIG_ION_EXYNOS_DRM_MEMSIZE_FIMD_VIDEO *
+ SZ_1K,
+ .alignment = SZ_1M,
+ },
+#endif
+#ifdef CONFIG_ION_EXYNOS_DRM_MEMSIZE_MFC_OUTPUT
+ {
+ .name = "drm_mfc_output",
+ .size = CONFIG_ION_EXYNOS_DRM_MEMSIZE_MFC_OUTPUT *
+ SZ_1K,
+ .alignment = SZ_1M,
+ },
+#endif
+#ifdef CONFIG_ION_EXYNOS_DRM_MEMSIZE_MFC_INPUT
+ {
+ .name = "drm_mfc_input",
+ .size = CONFIG_ION_EXYNOS_DRM_MEMSIZE_MFC_INPUT *
+ SZ_1K,
+ .alignment = SZ_1M,
+ },
+#endif
+#ifdef CONFIG_ION_EXYNOS_DRM_MFC_FW
+ {
+ .name = "drm_mfc_fw",
+ .size = SZ_1M,
+ .alignment = SZ_1M,
+ },
+#endif
+#ifdef CONFIG_ION_EXYNOS_DRM_SECTBL
+ {
+ .name = "drm_sectbl",
+ .size = SZ_1M,
+ .alignment = SZ_1M,
+ },
+#endif
+ {
+ .size = 0
+ },
+ };
+#else /* !CONFIG_EXYNOS_CONTENT_PATH_PROTECTION */
+ struct cma_region *regions_secure = NULL;
+#endif /* CONFIG_EXYNOS_CONTENT_PATH_PROTECTION */
+
+ static const char map[] __initconst =
+#ifdef CONFIG_EXYNOS_CONTENT_PATH_PROTECTION
+ "ion-exynos/mfc_sh=drm_mfc_sh;"
+ "ion-exynos/msgbox_sh=drm_msgbox_sh;"
+ "ion-exynos/fimd_video=drm_fimd_video;"
+ "ion-exynos/mfc_output=drm_mfc_output;"
+ "ion-exynos/mfc_input=drm_mfc_input;"
+ "ion-exynos/mfc_fw=drm_mfc_fw;"
+ "ion-exynos/sectbl=drm_sectbl;"
+ "s5p-smem/mfc_sh=drm_mfc_sh;"
+ "s5p-smem/msgbox_sh=drm_msgbox_sh;"
+ "s5p-smem/fimd_video=drm_fimd_video;"
+ "s5p-smem/mfc_output=drm_mfc_output;"
+ "s5p-smem/mfc_input=drm_mfc_input;"
+ "s5p-smem/mfc_fw=drm_mfc_fw;"
+ "s5p-smem/sectbl=drm_sectbl;"
+#endif
+ "ion-exynos=ion;"
+ "s5p-mfc-v6/f=fw;"
+ "s5p-mfc-v6/a=b1;";
+
+ persistent_ram_early_init(&manta_pr);
+ if (manta_bootloader_fb_start) {
+ int err = memblock_reserve(manta_bootloader_fb_start,
+ manta_bootloader_fb_size);
+ if (err)
+ pr_warn("failed to reserve old framebuffer location\n");
+ } else {
+ pr_warn("bootloader framebuffer start address not set\n");
+ }
+
+ exynos_cma_region_reserve(regions, regions_secure, 0, map);
+ kbase_carveout_mem_reserve(384 * SZ_1M);
+ ion_reserve(&exynos_ion_pdata);
+}
+
+static void exynos_dwmci0_cfg_gpio(int width)
+{
+ unsigned int gpio;
+
+ for (gpio = EXYNOS5_GPC0(0); gpio < EXYNOS5_GPC0(2); gpio++) {
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV4);
+ }
+
+ switch (width) {
+ case 8:
+ for (gpio = EXYNOS5_GPC1(0); gpio <= EXYNOS5_GPC1(3); gpio++) {
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV4);
+ }
+ case 4:
+ for (gpio = EXYNOS5_GPC0(3); gpio <= EXYNOS5_GPC0(6); gpio++) {
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV4);
+ }
+ break;
+ case 1:
+ gpio = EXYNOS5_GPC0(3);
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV4);
+ default:
+ break;
+ }
+}
+
+static void exynos_dwmci0_hw_reset(u32 slot_id)
+{
+ unsigned int emmc_en, gpio;
+
+ if (slot_id != 0)
+ return;
+
+ emmc_en = EXYNOS5_GPC0(2);
+ s3c_gpio_cfgpin(emmc_en, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(emmc_en, S3C_GPIO_PULL_NONE);
+
+ /* eMMC Card Power Off */
+ for (gpio = EXYNOS5_GPC0(0); gpio < EXYNOS5_GPC0(2); gpio++) {
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ gpio_set_value(gpio, 0);
+ }
+ for (gpio = EXYNOS5_GPC1(0); gpio <= EXYNOS5_GPC1(3); gpio++) {
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ gpio_set_value(gpio, 0);
+ }
+ gpio_set_value(emmc_en, 0);
+
+ /* waiting ramp down time for certainly power off */
+ msleep(100);
+
+ /* eMMC Card Power On */
+ for (gpio = EXYNOS5_GPC0(0); gpio < EXYNOS5_GPC0(2); gpio++) {
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ gpio_set_value(gpio, 0);
+ }
+ for (gpio = EXYNOS5_GPC1(0); gpio <= EXYNOS5_GPC1(3); gpio++) {
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
+ s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+ gpio_set_value(gpio, 0);
+ }
+ gpio_set_value(emmc_en, 1);
+
+ /* waiting ramp up time for certainly power on */
+ msleep(50);
+}
+
+static struct dw_mci_board exynos_dwmci0_pdata __initdata = {
+ .num_slots = 1,
+ .quirks = DW_MCI_QUIRK_BROKEN_CARD_DETECTION |
+ DW_MCI_QUIRK_HIGHSPEED |
+ DW_MMC_QUIRK_HW_RESET_PW |
+ DW_MCI_QUIRK_NO_DETECT_EBIT,
+ .bus_hz = 50 * 1000 * 1000,
+ .max_bus_hz = 200 * 1000 * 1000,
+ .caps = MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR |
+ MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23 | MMC_CAP_ERASE |
+ MMC_CAP_HW_RESET,
+ .caps2 = MMC_CAP2_HS200_1_8V_SDR,
+ .fifo_depth = 0x80,
+ .detect_delay_ms = 200,
+ .hclk_name = "dwmci",
+ .cclk_name = "sclk_dwmci",
+ .cfg_gpio = exynos_dwmci0_cfg_gpio,
+ .hw_reset = exynos_dwmci0_hw_reset,
+ .sdr_timing = 0x03020001,
+ .ddr_timing = 0x03030002,
+ .clk_drv = 0x3,
+};
+
+/* DEVFREQ controlling mif */
+static struct exynos5_bus_mif_platform_data manta_bus_mif_platform_data;
+
+static struct platform_device exynos_bus_mif_devfreq = {
+ .name = "exynos5-bus-mif",
+ .dev = {
+ .platform_data = &manta_bus_mif_platform_data,
+ },
+};
+
+/* DEVFREQ controlling int */
+static struct platform_device exynos_bus_int_devfreq = {
+ .name = "exynos5-bus-int",
+};
+
+static struct platform_device *manta_devices[] __initdata = {
+ &ramconsole_device,
+ &persistent_trace_device,
+ &persistent_clock,
+ &s3c_device_i2c0,
+ &s3c_device_i2c1,
+ &s3c_device_i2c2,
+ &s3c_device_i2c3,
+ &s3c_device_i2c4,
+ &s3c_device_i2c5,
+ &s3c_device_i2c7,
+ &s3c_device_adc,
+ &s3c_device_wdt,
+ &exynos5_device_dwmci0,
+ &exynos_device_ion,
+ &exynos_device_tmu,
+ &s3c_device_usb_hsotg,
+ &s5p_device_ehci,
+ &exynos4_device_ohci,
+ &exynos_bus_mif_devfreq,
+ &exynos_bus_int_devfreq,
+ &exynos5_device_g3d,
+};
+
+static struct s3c_hsotg_plat manta_hsotg_pdata;
+
+static void __init manta_udc_init(void)
+{
+ struct s3c_hsotg_plat *pdata = &manta_hsotg_pdata;
+
+ s3c_hsotg_set_platdata(pdata);
+}
+
+static void __init manta_dwmci_init(void)
+{
+ exynos_dwmci_set_platdata(&exynos_dwmci0_pdata, 0);
+ dev_set_name(&exynos5_device_dwmci0.dev, "exynos4-sdhci.0");
+ clk_add_alias("dwmci", "dw_mmc.0", "hsmmc", &exynos5_device_dwmci0.dev);
+ clk_add_alias("sclk_dwmci", "dw_mmc.0", "sclk_mmc",
+ &exynos5_device_dwmci0.dev);
+}
+
+static void __init manta_map_io(void)
+{
+ clk_xusbxti.rate = 24000000;
+ clk_xxti.rate = 24000000;
+ exynos_init_io(NULL, 0);
+ s3c24xx_init_clocks(clk_xusbxti.rate);
+ s3c24xx_init_uarts(manta_uartcfgs, ARRAY_SIZE(manta_uartcfgs));
+}
+
+static void __init manta_sysmmu_init(void)
+{
+}
+
+static void __init manta_init_early(void)
+{
+}
+
+static void __init soc_info_populate(struct soc_device_attribute *soc_dev_attr)
+{
+ soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%08x%08x\n",
+ system_serial_high, system_serial_low);
+ soc_dev_attr->machine = kasprintf(GFP_KERNEL, "Exynos 5250\n");
+ soc_dev_attr->family = kasprintf(GFP_KERNEL, "Exynos 5\n");
+ soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d.%d\n",
+ samsung_rev() >> 4,
+ samsung_rev() & 0xf);
+}
+
+static ssize_t manta_get_board_revision(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", manta_hw_rev);
+}
+
+struct device_attribute manta_soc_attr =
+ __ATTR(board_rev, S_IRUGO, manta_get_board_revision, NULL);
+
+static void __init exynos5_manta_sysfs_soc_init(void)
+{
+ struct device *parent;
+ struct soc_device *soc_dev;
+ struct soc_device_attribute *soc_dev_attr;
+
+ soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL);
+ if (!soc_dev_attr) {
+ printk(KERN_ERR "Failed to allocate memory for soc_dev_attr\n");
+ return;
+ }
+
+ soc_info_populate(soc_dev_attr);
+
+ soc_dev = soc_device_register(soc_dev_attr);
+ if (IS_ERR_OR_NULL(soc_dev)) {
+ kfree(soc_dev_attr);
+ printk(KERN_ERR "Failed to register a soc device under /sys\n");
+ return;
+ }
+
+ parent = soc_device_to_device(soc_dev);
+ if (!IS_ERR_OR_NULL(parent))
+ device_create_file(parent, &manta_soc_attr);
+
+ return; /* Or return parent should you need to use one later */
+}
+
+/* USB EHCI */
+static struct s5p_ehci_platdata exynos5_manta_ehci_pdata;
+
+static void __init exynos5_manta_ehci_init(void)
+{
+ struct s5p_ehci_platdata *pdata = &exynos5_manta_ehci_pdata;
+
+ s5p_ehci_set_platdata(pdata);
+}
+
+/* USB OHCI */
+static struct exynos4_ohci_platdata exynos5_manta_ohci_pdata;
+
+static void __init exynos5_manta_ohci_init(void)
+{
+ struct exynos4_ohci_platdata *pdata = &exynos5_manta_ohci_pdata;
+
+ exynos4_ohci_set_platdata(pdata);
+}
+
+void manta_panic_dump_cpu_pc(int cpu, unsigned long dbgpcsr)
+{
+ void *pc = NULL;
+
+ pr_err("CPU%d DBGPCSR: %08lx\n", cpu, dbgpcsr);
+ if ((dbgpcsr & 3) == 0)
+ pc = (void *)(dbgpcsr - 8);
+ else if ((dbgpcsr & 1) == 1)
+ pc = (void *)((dbgpcsr & ~1) - 4);
+
+ pr_err("CPU%d PC: <%p> %pF\n", cpu, pc, pc);
+}
+
+int manta_panic_notify(struct notifier_block *nb, unsigned long event, void *p)
+{
+ unsigned long dbgpcsr;
+
+ if (manta_cpu0_debug && cpu_online(0)) {
+ dbgpcsr = __raw_readl(manta_cpu0_debug + MANTA_CPU_DBGPCSR);
+ manta_panic_dump_cpu_pc(0, dbgpcsr);
+ }
+ if (manta_cpu1_debug && cpu_online(1)) {
+ dbgpcsr = __raw_readl(manta_cpu1_debug + MANTA_CPU_DBGPCSR);
+ manta_panic_dump_cpu_pc(1, dbgpcsr);
+ }
+ return NOTIFY_OK;
+}
+
+struct notifier_block manta_panic_nb = {
+ .notifier_call = manta_panic_notify,
+};
+
+static void __init manta_panic_init(void)
+{
+ manta_cpu0_debug = ioremap(MANTA_CPU0_DEBUG_PA, SZ_4K);
+ manta_cpu1_debug = ioremap(MANTA_CPU1_DEBUG_PA, SZ_4K);
+
+ atomic_notifier_chain_register(&panic_notifier_list, &manta_panic_nb);
+}
+
+static void __init manta_machine_init(void)
+{
+ manta_init_hw_rev();
+
+ if (manta_hw_rev <= MANTA_REV_DOGFOOD02)
+ manta_bus_mif_platform_data.max_freq = 667000;
+
+ exynos_serial_debug_init(2, 0);
+ manta_panic_init();
+
+ manta_gpio_power_init();
+ platform_device_register(&manta_event_device);
+
+ manta_sysmmu_init();
+ manta_dwmci_init();
+
+ if (manta_charger_mode)
+ platform_device_register(&manta_keyreset_device);
+
+ s3c_i2c0_set_platdata(&i2c0_data);
+ s3c_i2c1_set_platdata(NULL);
+ s3c_i2c2_set_platdata(NULL);
+ s3c_i2c3_set_platdata(NULL);
+ s3c_i2c4_set_platdata(NULL);
+ s3c_i2c5_set_platdata(NULL);
+ s3c_i2c7_set_platdata(NULL);
+
+ i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
+ i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
+ if (exynos5_manta_get_revision() <= MANTA_REV_LUNCHBOX)
+ i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));
+ else
+ s3c_adc_set_platdata(&manta_adc_data);
+
+ exynos_tmu_set_platdata(&manta_tmu_pdata);
+
+ manta_udc_init();
+ exynos5_manta_ehci_init();
+ exynos5_manta_ohci_init();
+ ramconsole_pdata.bootinfo = exynos_get_resetreason();
+ platform_add_devices(manta_devices, ARRAY_SIZE(manta_devices));
+
+ exynos5_manta_power_init();
+ exynos5_manta_display_init();
+ exynos5_manta_input_init();
+ exynos5_manta_battery_init();
+ exynos5_manta_pogo_init();
+ exynos5_manta_wlan_init();
+ exynos5_manta_audio_init();
+ exynos5_manta_media_init();
+ exynos5_manta_camera_init();
+ exynos5_manta_sensors_init();
+ exynos5_manta_gps_init();
+ exynos5_manta_jack_init();
+ exynos5_manta_vib_init();
+ exynos5_manta_sysfs_soc_init();
+ exynos5_manta_nfc_init();
+ exynos5_manta_bt_init();
+ exynos5_manta_connector_init();
+ exynos5_manta_adjust_mif_asv_table();
+}
+
+MACHINE_START(MANTA, "Manta")
+ .atag_offset = 0x100,
+ .init_early = manta_init_early,
+ .init_irq = exynos5_init_irq,
+ .map_io = manta_map_io,
+ .handle_irq = gic_handle_irq,
+ .init_machine = manta_machine_init,
+ .timer = &exynos4_timer,
+ .restart = exynos5_restart,
+ .reserve = exynos_reserve_mem,
+MACHINE_END
diff --git a/arch/arm/mach-exynos/board-manta.h b/arch/arm/mach-exynos/board-manta.h
new file mode 100755
index 0000000..b9bc177
--- /dev/null
+++ b/arch/arm/mach-exynos/board-manta.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * 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 __MACH_EXYNOS_BOARD_MANTA_H
+#define __MACH_EXYNOS_BOARD_MANTA_H
+
+#include <mach/irqs.h>
+#include <linux/serial_core.h>
+
+#define MANTA_REV_LUNCHBOX 0x1
+#define MANTA_REV_PRE_ALPHA 0x2
+#define MANTA_REV_ALPHA 0x3
+#define MANTA_REV_BETA 0x4
+#define MANTA_REV_DOGFOOD01 0x5
+#define MANTA_REV_DOGFOOD02 0x6
+#define MANTA_REV_DOGFOOD03 0x7
+#define MANTA_REV_DOGFOOD04 0x8
+#define MANTA_REV_DOGFOOD05 0x9
+
+/* board IRQ allocations */
+#define MANTA_IRQ_BOARD_PMIC_START IRQ_BOARD_START
+#define MANTA_IRQ_BOARD_PMIC_NR 16
+#define MANTA_IRQ_BOARD_AUDIO_START (IRQ_BOARD_START + \
+ MANTA_IRQ_BOARD_PMIC_NR)
+#define MANTA_IRQ_BOARD_AUDIO_NR 27
+
+/* Manta-specific charger info */
+
+enum manta_charge_source {
+ MANTA_CHARGE_SOURCE_NONE,
+ MANTA_CHARGE_SOURCE_UNKNOWN,
+ MANTA_CHARGE_SOURCE_USB,
+ MANTA_CHARGE_SOURCE_AC_OTHER,
+ MANTA_CHARGE_SOURCE_AC_SAMSUNG,
+};
+
+
+void exynos5_manta_audio_init(void);
+void exynos5_manta_display_init(void);
+void exynos5_manta_input_init(void);
+void exynos5_manta_power_init(void);
+void exynos5_manta_battery_init(void);
+void exynos5_manta_pogo_init(void);
+void exynos5_manta_wlan_init(void);
+void exynos5_manta_media_init(void);
+void exynos5_manta_camera_init(void);
+void exynos5_manta_sensors_init(void);
+void exynos5_manta_gps_init(void);
+void exynos5_manta_jack_init(void);
+void exynos5_manta_vib_init(void);
+void exynos5_manta_nfc_init(void);
+void exynos5_manta_bt_init(void);
+void exynos5_manta_connector_init(void);
+
+int exynos5_manta_get_revision(void);
+int manta_stmpe811_read_adc_data(u8 channel);
+extern int manta_bat_otg_enable(bool enable);
+void manta_otg_set_usb_state(bool connected);
+int manta_pogo_set_vbus(bool status, enum manta_charge_source *charge_source);
+extern int manta_pogo_charge_detect_start(bool spdif_mode_and_gpio_in);
+extern void manta_pogo_charge_detect_end(void);
+void bcm_bt_lpm_exit_lpm_locked(struct uart_port *uport);
+void exynos5_manta_adjust_mif_asv_table(void);
+void manta_force_update_pogo_charger(void);
+
+#endif
diff --git a/arch/arm/mach-exynos/dev-audio.c b/arch/arm/mach-exynos/dev-audio.c
index 79c0f57..96896bd 100644
--- a/arch/arm/mach-exynos/dev-audio.c
+++ b/arch/arm/mach-exynos/dev-audio.c
@@ -47,7 +47,7 @@
struct exynos_gpio_cfg exynos5_cfg[3] = {
{ EXYNOS5_GPZ(0), 7, S3C_GPIO_SFN(2) },
{ EXYNOS5_GPB0(0), 5, S3C_GPIO_SFN(2) },
- { EXYNOS5_GPB1(0), 5, S3C_GPIO_SFN(2) }
+ { EXYNOS5_GPB1(0), 1, S3C_GPIO_SFN(2) }
};
if (pdev->id < 0 || pdev->id > 2) {
@@ -204,7 +204,7 @@
struct exynos_gpio_cfg exynos5_cfg[3] = {
{ EXYNOS5_GPZ(0), 5, S3C_GPIO_SFN(3) },
{ EXYNOS5_GPB0(0), 5, S3C_GPIO_SFN(3) },
- { EXYNOS5_GPB1(0), 5, S3C_GPIO_SFN(3) }
+ { EXYNOS5_GPB1(0), 1, S3C_GPIO_SFN(3) }
};
if (pdev->id < 0 || pdev->id > 2) {
@@ -369,7 +369,7 @@
{
/* configure GPIO for SPDIF port */
if (soc_is_exynos5250())
- s3c_gpio_cfgpin_range(EXYNOS5_GPB1(0), 2, S3C_GPIO_SFN(4));
+ s3c_gpio_cfgpin_range(EXYNOS5_GPB1(0), 1, S3C_GPIO_SFN(4));
else /* EXYNOS4210, EXYNOS4212 and EXYNOS4412 */
s3c_gpio_cfgpin_range(EXYNOS4_GPC1(0), 2, S3C_GPIO_SFN(4));
diff --git a/arch/arm/mach-exynos/mach-smdk5250.c b/arch/arm/mach-exynos/mach-smdk5250.c
index f4f0dfd..f8517d8 100644
--- a/arch/arm/mach-exynos/mach-smdk5250.c
+++ b/arch/arm/mach-exynos/mach-smdk5250.c
@@ -919,8 +919,7 @@
.max_bus_hz = 200 * 1000 * 1000,
.caps = MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR |
MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
- .caps2 = MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_PACKED_WR,
- .desc_sz = 4,
+ .caps2 = MMC_CAP2_HS200_1_8V_SDR,
.fifo_depth = 0x80,
.detect_delay_ms = 200,
.hclk_name = "dwmci",
diff --git a/arch/arm/mach-exynos/pm.c b/arch/arm/mach-exynos/pm.c
index bff00b3..9c21aff 100644
--- a/arch/arm/mach-exynos/pm.c
+++ b/arch/arm/mach-exynos/pm.c
@@ -48,6 +48,7 @@
#define REG_INFORM1 (EXYNOS_INFORM1)
#endif
+#define EXYNOS_USB20PHY_CFG (S3C_VA_SYS + 0x230)
#define EXYNOS_I2C_CFG (S3C_VA_SYS + 0x234)
#define EXYNOS_WAKEUP_STAT_EINT (1 << 0)
@@ -98,6 +99,8 @@
SAVE_ITEM(S5P_SROM_BC2),
SAVE_ITEM(S5P_SROM_BC3),
+ /* USB 2.0 PHY CFG */
+ SAVE_ITEM(EXYNOS_USB20PHY_CFG),
/* I2C CFG */
SAVE_ITEM(EXYNOS_I2C_CFG),
};
diff --git a/arch/arm/mach-exynos/setup-usb-phy.c b/arch/arm/mach-exynos/setup-usb-phy.c
index 9a76110..f342e48 100644
--- a/arch/arm/mach-exynos/setup-usb-phy.c
+++ b/arch/arm/mach-exynos/setup-usb-phy.c
@@ -292,8 +292,6 @@
return 0;
}
- exynos_usb_mux_change(pdev, 1);
-
exynos_usb_phy_control(USB_PHY1, PHY_ENABLE);
/* Host and Device should be set at the same time */
@@ -383,6 +381,12 @@
writel(hostphy_ctrl0, EXYNOS5_PHY_HOST_CTRL0);
otgphy_sys = readl(EXYNOS5_PHY_OTG_SYS);
+
+ /* Issue a OTG_SYS_PHYLINK_SW_RESET to release pulldowns on D+/D- */
+ writel(otgphy_sys | OTG_SYS_PHYLINK_SW_RESET, EXYNOS5_PHY_OTG_SYS);
+ udelay(10);
+ writel(otgphy_sys, EXYNOS5_PHY_OTG_SYS);
+
otgphy_sys |= (OTG_SYS_FORCE_SUSPEND |
OTG_SYS_SIDDQ_UOTG |
OTG_SYS_FORCE_SLEEP);
@@ -393,13 +397,114 @@
return 0;
}
-static int exynos_usb_dev_phy20_init(struct platform_device *pdev)
+static int s5p_usb_otg_phy_tune(struct s3c_hsotg_plat *pdata, int def_mode)
+{
+ u32 phytune;
+
+ if (!pdata)
+ return -EINVAL;
+
+ pr_debug("usb: %s read original tune\n", __func__);
+ phytune = readl(EXYNOS5_PHY_OTG_TUNE);
+ if (!pdata->def_phytune) {
+ pdata->def_phytune = phytune;
+ pr_debug("usb: %s save default phytune (0x%x)\n",
+ __func__, pdata->def_phytune);
+ }
+
+ pr_debug("usb: %s original tune=0x%x\n",
+ __func__, phytune);
+
+ pr_debug("usb: %s tune_mask=0x%x, tune=0x%x\n",
+ __func__, pdata->phy_tune_mask, pdata->phy_tune);
+
+ if (pdata->phy_tune_mask) {
+ if (def_mode) {
+ pr_debug("usb: %s set defult tune=0x%x\n",
+ __func__, pdata->def_phytune);
+ writel(pdata->def_phytune, EXYNOS5_PHY_OTG_TUNE);
+ } else {
+ phytune &= ~(pdata->phy_tune_mask);
+ phytune |= pdata->phy_tune;
+ udelay(10);
+ pr_debug("usb: %s custom tune=0x%x\n",
+ __func__, phytune);
+ writel(phytune, EXYNOS5_PHY_OTG_TUNE);
+ }
+ phytune = readl(EXYNOS5_PHY_OTG_TUNE);
+ pr_debug("usb: %s modified tune=0x%x\n",
+ __func__, phytune);
+ } else {
+ pr_debug("usb: %s default tune\n", __func__);
+ }
+
+ return 0;
+}
+
+static void set_exynos5_usb_host_phy_tune(void)
+{
+ u32 phytune;
+
+ phytune = readl(EXYNOS5_PHY_HOST_TUNE0);
+ pr_debug("usb: %s old phy tune for host =0x%x\n",
+ __func__, phytune);
+
+ /* sqrxtune [14:12] 3b110 : -15% */
+ phytune &= ~HOST_TUNE0_SQRXTUNE(0x7);
+ phytune |= HOST_TUNE0_SQRXTUNE(0x6);
+ udelay(10);
+ writel(phytune, EXYNOS5_PHY_HOST_TUNE0);
+ phytune = readl(EXYNOS5_PHY_HOST_TUNE0);
+
+ pr_debug("usb: %s new phy tune for host =0x%x\n",
+ __func__, phytune);
+}
+
+static void set_exynos5_usb_device_phy_tune(void)
+{
+ u32 phytune;
+
+ phytune = readl(EXYNOS5_PHY_OTG_TUNE);
+ pr_debug("usb: %s old phy tune for device =0x%x\n",
+ __func__, phytune);
+
+ /* sqrxtune [13:11] 3b110 : -15% */
+ phytune &= ~OTG_TUNE_SQRXTUNE(0x7);
+ phytune |= OTG_TUNE_SQRXTUNE(0x6);
+ /* txpreempamptune [22:21] 2b10 : 2X */
+ phytune &= ~OTG_TUNE_TXPREEMPAMPTUNE(0x3);
+ phytune |= OTG_TUNE_TXPREEMPAMPTUNE(0x2);
+ /* txvreftune [ 3: 0] 8b1000 : +10% */
+ phytune &= ~OTG_TUNE_TXVREFTUNE(0xf);
+ phytune |= OTG_TUNE_TXVREFTUNE(0x8);
+ udelay(10);
+ writel(phytune, EXYNOS5_PHY_OTG_TUNE);
+ phytune = readl(EXYNOS5_PHY_OTG_TUNE);
+
+ pr_debug("usb: %s new phy tune for device =0x%x\n",
+ __func__, phytune);
+}
+
+static int exynos5_usb_host_phy20_init(struct platform_device *pdev)
{
if (exynos_usb_phy_clock_enable(pdev))
return -EINVAL;
+ /* usb mode change from device to host */
+ exynos_usb_mux_change(pdev, 1);
+
exynos5_usb_phy20_init(pdev);
+ /* usb host phy tune */
+ set_exynos5_usb_host_phy_tune();
+
+ return 0;
+}
+
+static int exynos5_usb_host_phy20_exit(struct platform_device *pdev)
+{
+ exynos5_usb_phy20_exit(pdev);
+
/* usb mode change from host to device */
exynos_usb_mux_change(pdev, 0);
@@ -408,19 +513,45 @@
return 0;
}
-static int exynos_usb_dev_phy20_exit(struct platform_device *pdev)
+static int exynos_usb_dev_phy20_init(struct platform_device *pdev)
{
+ int ret = 0;
+
if (exynos_usb_phy_clock_enable(pdev))
return -EINVAL;
- exynos5_usb_phy20_exit(pdev);
+ /* usb mode change from host to device */
+ exynos_usb_mux_change(pdev, 0);
- /* usb mode change from device to host */
- exynos_usb_mux_change(pdev, 1);
+ exynos5_usb_phy20_init(pdev);
+
+ /* usb device phy tune */
+ set_exynos5_usb_device_phy_tune();
+ /* set custom usb device phy tune */
+ if (pdev->dev.platform_data)
+ ret = s5p_usb_otg_phy_tune(pdev->dev.platform_data, 0);
exynos_usb_phy_clock_disable(pdev);
- return 0;
+ return ret;
+}
+
+static int exynos_usb_dev_phy20_exit(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ if (exynos_usb_phy_clock_enable(pdev))
+ return -EINVAL;
+
+ /* set custom usb device phy tune */
+ if (pdev->dev.platform_data)
+ ret = s5p_usb_otg_phy_tune(pdev->dev.platform_data, 1);
+
+ exynos5_usb_phy20_exit(pdev);
+
+ exynos_usb_phy_clock_disable(pdev);
+
+ return ret;
}
static int exynos5_usb_phy30_init(struct platform_device *pdev)
@@ -484,104 +615,17 @@
return 0;
}
-static int s5p_usb_otg_phy_tune(struct s3c_hsotg_plat *pdata, int def_mode)
-{
- u32 phytune;
-
- if (!pdata)
- return -EINVAL;
-
- pr_debug("usb: %s read original tune\n", __func__);
- phytune = readl(EXYNOS5_PHY_OTG_TUNE);
- if (!pdata->def_phytune) {
- pdata->def_phytune = phytune;
- pr_debug("usb: %s save default phytune (0x%x)\n",
- __func__, pdata->def_phytune);
- }
-
- pr_debug("usb: %s original tune=0x%x\n",
- __func__, phytune);
-
- pr_debug("usb: %s tune_mask=0x%x, tune=0x%x\n",
- __func__, pdata->phy_tune_mask, pdata->phy_tune);
-
- if (pdata->phy_tune_mask) {
- if (def_mode) {
- pr_debug("usb: %s set defult tune=0x%x\n",
- __func__, pdata->def_phytune);
- writel(pdata->def_phytune, EXYNOS5_PHY_OTG_TUNE);
- } else {
- phytune &= ~(pdata->phy_tune_mask);
- phytune |= pdata->phy_tune;
- udelay(10);
- pr_debug("usb: %s custom tune=0x%x\n",
- __func__, phytune);
- writel(phytune, EXYNOS5_PHY_OTG_TUNE);
- }
- phytune = readl(EXYNOS5_PHY_OTG_TUNE);
- pr_debug("usb: %s modified tune=0x%x\n",
- __func__, phytune);
- } else {
- pr_debug("usb: %s default tune\n", __func__);
- }
-
- return 0;
-}
-
-static void set_exynos_usb_phy_tune(int type)
-{
- u32 phytune;
-
- if (!soc_is_exynos5250()) {
- pr_debug("usb: %s it is not exynos5250.(t=%d)\n",
- __func__, type);
- return;
- }
-
- if (type == S5P_USB_PHY_DEVICE) {
- phytune = readl(EXYNOS5_PHY_OTG_TUNE);
- pr_debug("usb: %s old phy tune for device =0x%x\n",
- __func__, phytune);
- /* sqrxtune [13:11] 3b110 : -15% */
- phytune &= ~OTG_TUNE_SQRXTUNE(0x7);
- phytune |= OTG_TUNE_SQRXTUNE(0x6);
- udelay(10);
- writel(phytune, EXYNOS5_PHY_OTG_TUNE);
- phytune = readl(EXYNOS5_PHY_OTG_TUNE);
- pr_debug("usb: %s new phy tune for device =0x%x\n",
- __func__, phytune);
- } else if (type == S5P_USB_PHY_HOST) {
- phytune = readl(EXYNOS5_PHY_HOST_TUNE0);
- pr_debug("usb: %s old phy tune for host =0x%x\n",
- __func__, phytune);
- /* sqrxtune [14:12] 3b110 : -15% */
- phytune &= ~HOST_TUNE0_SQRXTUNE(0x7);
- phytune |= HOST_TUNE0_SQRXTUNE(0x6);
- udelay(10);
- writel(phytune, EXYNOS5_PHY_HOST_TUNE0);
- phytune = readl(EXYNOS5_PHY_HOST_TUNE0);
- pr_debug("usb: %s new phy tune for host =0x%x\n",
- __func__, phytune);
- }
-}
-
int s5p_usb_phy_init(struct platform_device *pdev, int type)
{
int ret = -EINVAL;
if (type == S5P_USB_PHY_HOST) {
- if (soc_is_exynos5250()) {
- ret = exynos5_usb_phy20_init(pdev);
- set_exynos_usb_phy_tune(type);
- } else {
+ if (soc_is_exynos5250())
+ ret = exynos5_usb_host_phy20_init(pdev);
+ else
ret = exynos4_usb_phy1_init(pdev);
- }
} else if (type == S5P_USB_PHY_DEVICE) {
ret = exynos_usb_dev_phy20_init(pdev);
- set_exynos_usb_phy_tune(type);
- /* set custom usb phy tune */
- if (pdev->dev.platform_data)
- ret = s5p_usb_otg_phy_tune(pdev->dev.platform_data, 0);
} else if (type == S5P_USB_PHY_DRD)
ret = exynos5_usb_phy30_init(pdev);
@@ -594,13 +638,10 @@
if (type == S5P_USB_PHY_HOST) {
if (soc_is_exynos5250())
- ret = exynos5_usb_phy20_exit(pdev);
+ ret = exynos5_usb_host_phy20_exit(pdev);
else
ret = exynos4_usb_phy1_exit(pdev);
} else if (type == S5P_USB_PHY_DEVICE) {
- /* set custom usb phy tune */
- if (pdev->dev.platform_data)
- ret = s5p_usb_otg_phy_tune(pdev->dev.platform_data, 1);
ret = exynos_usb_dev_phy20_exit(pdev);
} else if (type == S5P_USB_PHY_DRD) {
ret = exynos5_usb_phy30_exit(pdev);
diff --git a/arch/arm/mm/context.c b/arch/arm/mm/context.c
index 806cc4f..2ac3737 100644
--- a/arch/arm/mm/context.c
+++ b/arch/arm/mm/context.c
@@ -2,6 +2,9 @@
* linux/arch/arm/mm/context.c
*
* Copyright (C) 2002-2003 Deep Blue Solutions Ltd, all rights reserved.
+ * Copyright (C) 2012 ARM Limited
+ *
+ * Author: Will Deacon <will.deacon@arm.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
@@ -14,13 +17,43 @@
#include <linux/percpu.h>
#include <asm/mmu_context.h>
+#include <asm/smp_plat.h>
+#include <asm/thread_notify.h>
#include <asm/tlbflush.h>
+/*
+ * On ARMv6, we have the following structure in the Context ID:
+ *
+ * 31 7 0
+ * +-------------------------+-----------+
+ * | process ID | ASID |
+ * +-------------------------+-----------+
+ * | context ID |
+ * +-------------------------------------+
+ *
+ * The ASID is used to tag entries in the CPU caches and TLBs.
+ * The context ID is used by debuggers and trace logic, and
+ * should be unique within all running processes.
+ *
+ * In big endian operation, the two 32 bit words are swapped if accesed by
+ * non 64-bit operations.
+ */
+#define ASID_FIRST_VERSION (1ULL << ASID_BITS)
+#define NUM_USER_ASIDS (ASID_FIRST_VERSION - 1)
+
+#define ASID_TO_IDX(asid) ((asid & ~ASID_MASK) - 1)
+#define IDX_TO_ASID(idx) ((idx + 1) & ~ASID_MASK)
+
static DEFINE_RAW_SPINLOCK(cpu_asid_lock);
-unsigned int cpu_last_asid = ASID_FIRST_VERSION;
+static atomic64_t asid_generation = ATOMIC64_INIT(ASID_FIRST_VERSION);
+static DECLARE_BITMAP(asid_map, NUM_USER_ASIDS);
+
+DEFINE_PER_CPU(atomic64_t, active_asids);
+static DEFINE_PER_CPU(u64, reserved_asids);
+static cpumask_t tlb_flush_pending;
#ifdef CONFIG_ARM_LPAE
-void cpu_set_reserved_ttbr0(void)
+static void cpu_set_reserved_ttbr0(void)
{
unsigned long ttbl = __pa(swapper_pg_dir);
unsigned long ttbh = 0;
@@ -36,7 +69,7 @@
isb();
}
#else
-void cpu_set_reserved_ttbr0(void)
+static void cpu_set_reserved_ttbr0(void)
{
u32 ttb;
/* Copy TTBR1 into TTBR0 */
@@ -48,124 +81,147 @@
}
#endif
-/*
- * We fork()ed a process, and we need a new context for the child
- * to run in.
- */
-void __init_new_context(struct task_struct *tsk, struct mm_struct *mm)
+#ifdef CONFIG_PID_IN_CONTEXTIDR
+static int contextidr_notifier(struct notifier_block *unused, unsigned long cmd,
+ void *t)
{
- mm->context.id = 0;
- raw_spin_lock_init(&mm->context.id_lock);
+ u32 contextidr;
+ pid_t pid;
+ struct thread_info *thread = t;
+
+ if (cmd != THREAD_NOTIFY_SWITCH)
+ return NOTIFY_DONE;
+
+ pid = task_pid_nr(thread->task) << ASID_BITS;
+ asm volatile(
+ " mrc p15, 0, %0, c13, c0, 1\n"
+ " and %0, %0, %2\n"
+ " orr %0, %0, %1\n"
+ " mcr p15, 0, %0, c13, c0, 1\n"
+ : "=r" (contextidr), "+r" (pid)
+ : "I" (~ASID_MASK));
+ isb();
+
+ return NOTIFY_OK;
}
-static void flush_context(void)
+static struct notifier_block contextidr_notifier_block = {
+ .notifier_call = contextidr_notifier,
+};
+
+static int __init contextidr_notifier_init(void)
{
- cpu_set_reserved_ttbr0();
- local_flush_tlb_all();
- if (icache_is_vivt_asid_tagged()) {
- __flush_icache_all();
- dsb();
+ return thread_register_notifier(&contextidr_notifier_block);
+}
+arch_initcall(contextidr_notifier_init);
+#endif
+
+static void flush_context(unsigned int cpu)
+{
+ int i;
+ u64 asid;
+
+ /* Update the list of reserved ASIDs and the ASID bitmap. */
+ bitmap_clear(asid_map, 0, NUM_USER_ASIDS);
+ for_each_possible_cpu(i) {
+ if (i == cpu) {
+ asid = 0;
+ } else {
+ asid = atomic64_xchg(&per_cpu(active_asids, i), 0);
+ __set_bit(ASID_TO_IDX(asid), asid_map);
+ }
+ per_cpu(reserved_asids, i) = asid;
}
+
+ /* Queue a TLB invalidate and flush the I-cache if necessary. */
+ if (!tlb_ops_need_broadcast())
+ cpumask_set_cpu(cpu, &tlb_flush_pending);
+ else
+ cpumask_setall(&tlb_flush_pending);
+
+ if (icache_is_vivt_asid_tagged())
+ __flush_icache_all();
}
-#ifdef CONFIG_SMP
-
-static void set_mm_context(struct mm_struct *mm, unsigned int asid)
+static int is_reserved_asid(u64 asid)
{
- unsigned long flags;
+ int cpu;
+ for_each_possible_cpu(cpu)
+ if (per_cpu(reserved_asids, cpu) == asid)
+ return 1;
+ return 0;
+}
- /*
- * Locking needed for multi-threaded applications where the
- * same mm->context.id could be set from different CPUs during
- * the broadcast. This function is also called via IPI so the
- * mm->context.id_lock has to be IRQ-safe.
- */
- raw_spin_lock_irqsave(&mm->context.id_lock, flags);
- if (likely((mm->context.id ^ cpu_last_asid) >> ASID_BITS)) {
+static u64 new_context(struct mm_struct *mm, unsigned int cpu)
+{
+ u64 asid = atomic64_read(&mm->context.id);
+ u64 generation = atomic64_read(&asid_generation);
+
+ if (asid != 0 && is_reserved_asid(asid)) {
/*
- * Old version of ASID found. Set the new one and
- * reset mm_cpumask(mm).
+ * Our current ASID was active during a rollover, we can
+ * continue to use it and this was just a false alarm.
*/
- mm->context.id = asid;
+ asid = generation | (asid & ~ASID_MASK);
+ } else {
+ /*
+ * Allocate a free ASID. If we can't find one, take a
+ * note of the currently active ASIDs and mark the TLBs
+ * as requiring flushes.
+ */
+ asid = find_first_zero_bit(asid_map, NUM_USER_ASIDS);
+ if (asid == NUM_USER_ASIDS) {
+ generation = atomic64_add_return(ASID_FIRST_VERSION,
+ &asid_generation);
+ flush_context(cpu);
+ asid = find_first_zero_bit(asid_map, NUM_USER_ASIDS);
+ }
+ __set_bit(asid, asid_map);
+ asid = generation | IDX_TO_ASID(asid);
cpumask_clear(mm_cpumask(mm));
}
- raw_spin_unlock_irqrestore(&mm->context.id_lock, flags);
- /*
- * Set the mm_cpumask(mm) bit for the current CPU.
- */
- cpumask_set_cpu(smp_processor_id(), mm_cpumask(mm));
+ return asid;
}
-/*
- * Reset the ASID on the current CPU. This function call is broadcast
- * from the CPU handling the ASID rollover and holding cpu_asid_lock.
- */
-static void reset_context(void *info)
+void check_and_switch_context(struct mm_struct *mm, struct task_struct *tsk)
{
- unsigned int asid;
+ unsigned long flags;
unsigned int cpu = smp_processor_id();
- struct mm_struct *mm = current->active_mm;
+ u64 asid;
- smp_rmb();
- asid = cpu_last_asid + cpu + 1;
+ if (unlikely(mm->context.vmalloc_seq != init_mm.context.vmalloc_seq))
+ __check_vmalloc_seq(mm);
- flush_context();
- set_mm_context(mm, asid);
+ /*
+ * Required during context switch to avoid speculative page table
+ * walking with the wrong TTBR.
+ */
+ cpu_set_reserved_ttbr0();
- /* set the new ASID */
+ asid = atomic64_read(&mm->context.id);
+ if (!((asid ^ atomic64_read(&asid_generation)) >> ASID_BITS)
+ && atomic64_xchg(&per_cpu(active_asids, cpu), asid))
+ goto switch_mm_fastpath;
+
+ raw_spin_lock_irqsave(&cpu_asid_lock, flags);
+ /* Check that our ASID belongs to the current generation. */
+ asid = atomic64_read(&mm->context.id);
+ if ((asid ^ atomic64_read(&asid_generation)) >> ASID_BITS) {
+ asid = new_context(mm, cpu);
+ atomic64_set(&mm->context.id, asid);
+ }
+
+ if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending)) {
+ local_flush_bp_all();
+ local_flush_tlb_all();
+ dummy_flush_tlb_a15_erratum();
+ }
+
+ atomic64_set(&per_cpu(active_asids, cpu), asid);
+ cpumask_set_cpu(cpu, mm_cpumask(mm));
+ raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);
+
+switch_mm_fastpath:
cpu_switch_mm(mm->pgd, mm);
}
-
-#else
-
-static inline void set_mm_context(struct mm_struct *mm, unsigned int asid)
-{
- mm->context.id = asid;
- cpumask_copy(mm_cpumask(mm), cpumask_of(smp_processor_id()));
-}
-
-#endif
-
-void __new_context(struct mm_struct *mm)
-{
- unsigned int asid;
-
- raw_spin_lock(&cpu_asid_lock);
-#ifdef CONFIG_SMP
- /*
- * Check the ASID again, in case the change was broadcast from
- * another CPU before we acquired the lock.
- */
- if (unlikely(((mm->context.id ^ cpu_last_asid) >> ASID_BITS) == 0)) {
- cpumask_set_cpu(smp_processor_id(), mm_cpumask(mm));
- raw_spin_unlock(&cpu_asid_lock);
- return;
- }
-#endif
- /*
- * At this point, it is guaranteed that the current mm (with
- * an old ASID) isn't active on any other CPU since the ASIDs
- * are changed simultaneously via IPI.
- */
- asid = ++cpu_last_asid;
- if (asid == 0)
- asid = cpu_last_asid = ASID_FIRST_VERSION;
-
- /*
- * If we've used up all our ASIDs, we need
- * to start a new version and flush the TLB.
- */
- if (unlikely((asid & ~ASID_MASK) == 0)) {
- asid = cpu_last_asid + smp_processor_id() + 1;
- flush_context();
-#ifdef CONFIG_SMP
- smp_wmb();
- smp_call_function(reset_context, NULL, 1);
-#endif
- cpu_last_asid += NR_CPUS;
- }
-
- set_mm_context(mm, asid);
- raw_spin_unlock(&cpu_asid_lock);
-}
diff --git a/arch/arm/mm/idmap.c b/arch/arm/mm/idmap.c
index ab88ed4..a249dfb 100644
--- a/arch/arm/mm/idmap.c
+++ b/arch/arm/mm/idmap.c
@@ -108,6 +108,7 @@
/* Switch to the identity mapping. */
cpu_switch_mm(idmap_pgd, &init_mm);
+ local_flush_bp_all();
/* Flush the TLB. */
local_flush_tlb_all();
diff --git a/arch/arm/mm/ioremap.c b/arch/arm/mm/ioremap.c
index 4f55f50..135ebf1 100644
--- a/arch/arm/mm/ioremap.c
+++ b/arch/arm/mm/ioremap.c
@@ -46,18 +46,18 @@
}
EXPORT_SYMBOL(ioremap_page);
-void __check_kvm_seq(struct mm_struct *mm)
+void __check_vmalloc_seq(struct mm_struct *mm)
{
unsigned int seq;
do {
- seq = init_mm.context.kvm_seq;
+ seq = init_mm.context.vmalloc_seq;
memcpy(pgd_offset(mm, VMALLOC_START),
pgd_offset_k(VMALLOC_START),
sizeof(pgd_t) * (pgd_index(VMALLOC_END) -
pgd_index(VMALLOC_START)));
- mm->context.kvm_seq = seq;
- } while (seq != init_mm.context.kvm_seq);
+ mm->context.vmalloc_seq = seq;
+ } while (seq != init_mm.context.vmalloc_seq);
}
#if !defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE)
@@ -88,13 +88,13 @@
if (!pmd_none(pmd)) {
/*
* Clear the PMD from the page table, and
- * increment the kvm sequence so others
+ * increment the vmalloc sequence so others
* notice this change.
*
* Note: this is still racy on SMP machines.
*/
pmd_clear(pmdp);
- init_mm.context.kvm_seq++;
+ init_mm.context.vmalloc_seq++;
/*
* Free the page table, if there was one.
@@ -111,8 +111,8 @@
* Ensure that the active_mm is up to date - we want to
* catch any use-after-iounmap cases.
*/
- if (current->active_mm->context.kvm_seq != init_mm.context.kvm_seq)
- __check_kvm_seq(current->active_mm);
+ if (current->active_mm->context.vmalloc_seq != init_mm.context.vmalloc_seq)
+ __check_vmalloc_seq(current->active_mm);
flush_tlb_kernel_range(virt, end);
}
diff --git a/arch/arm/mm/proc-macros.S b/arch/arm/mm/proc-macros.S
index 2d8ff3a..74303e6 100644
--- a/arch/arm/mm/proc-macros.S
+++ b/arch/arm/mm/proc-macros.S
@@ -38,9 +38,14 @@
/*
* mmid - get context id from mm pointer (mm->context.id)
+ * note, this field is 64bit, so in big-endian the two words are swapped too.
*/
.macro mmid, rd, rn
+#ifdef __ARMEB__
+ ldr \rd, [\rn, #MM_CONTEXT_ID + 4 ]
+#else
ldr \rd, [\rn, #MM_CONTEXT_ID]
+#endif
.endm
/*
diff --git a/arch/arm/mm/proc-v6.S b/arch/arm/mm/proc-v6.S
index 5900cd5..86b8b48 100644
--- a/arch/arm/mm/proc-v6.S
+++ b/arch/arm/mm/proc-v6.S
@@ -107,6 +107,12 @@
mcr p15, 0, r2, c7, c5, 6 @ flush BTAC/BTB
mcr p15, 0, r2, c7, c10, 4 @ drain write buffer
mcr p15, 0, r0, c2, c0, 0 @ set TTB 0
+#ifdef CONFIG_PID_IN_CONTEXTIDR
+ mrc p15, 0, r2, c13, c0, 1 @ read current context ID
+ bic r2, r2, #0xff @ extract the PID
+ and r1, r1, #0xff
+ orr r1, r1, r2 @ insert into new context ID
+#endif
mcr p15, 0, r1, c13, c0, 1 @ set context ID
#endif
mov pc, lr
diff --git a/arch/arm/mm/proc-v7-2level.S b/arch/arm/mm/proc-v7-2level.S
index 05d8fe5..c663b2e 100644
--- a/arch/arm/mm/proc-v7-2level.S
+++ b/arch/arm/mm/proc-v7-2level.S
@@ -46,6 +46,11 @@
#ifdef CONFIG_ARM_ERRATA_430973
mcr p15, 0, r2, c7, c5, 6 @ flush BTAC/BTB
#endif
+#ifdef CONFIG_PID_IN_CONTEXTIDR
+ mrc p15, 0, r2, c13, c0, 1 @ read current context ID
+ lsr r2, r2, #8 @ extract the PID
+ bfi r1, r2, #8, #24 @ insert into new context ID
+#endif
#ifdef CONFIG_ARM_ERRATA_754322
dsb
#elif CONFIG_ARM_ERRATA_766421
diff --git a/arch/arm/plat-samsung/include/plat/regs-serial.h b/arch/arm/plat-samsung/include/plat/regs-serial.h
index 29c26a8..33ac5cf 100644
--- a/arch/arm/plat-samsung/include/plat/regs-serial.h
+++ b/arch/arm/plat-samsung/include/plat/regs-serial.h
@@ -246,6 +246,8 @@
#ifndef __ASSEMBLY__
+struct uart_port;
+
/* configuration structure for per-machine configurations for the
* serial port
*
@@ -265,6 +267,8 @@
unsigned long ucon; /* value of ucon for port */
unsigned long ulcon; /* value of ulcon for port */
unsigned long ufcon; /* value of ufcon for port */
+
+ void (*wake_peer)(struct uart_port *);
};
/* s3c24xx_uart_devs
diff --git a/arch/arm/tools/mach-types b/arch/arm/tools/mach-types
index f9c9f33..678972c 100644
--- a/arch/arm/tools/mach-types
+++ b/arch/arm/tools/mach-types
@@ -1169,3 +1169,4 @@
pov2 MACH_POV2 POV2 3889
ipod_touch_2g MACH_IPOD_TOUCH_2G IPOD_TOUCH_2G 3890
da850_pqab MACH_DA850_PQAB DA850_PQAB 3891
+manta MACH_MANTA MANTA 3991
diff --git a/arch/microblaze/kernel/ptrace.c b/arch/microblaze/kernel/ptrace.c
index 6eb2aa9..ab1b9db 100644
--- a/arch/microblaze/kernel/ptrace.c
+++ b/arch/microblaze/kernel/ptrace.c
@@ -136,7 +136,7 @@
{
long ret = 0;
- secure_computing(regs->r12);
+ secure_computing_strict(regs->r12);
if (test_thread_flag(TIF_SYSCALL_TRACE) &&
tracehook_report_syscall_entry(regs))
diff --git a/arch/mips/kernel/ptrace.c b/arch/mips/kernel/ptrace.c
index 7c24c29..4812c6d 100644
--- a/arch/mips/kernel/ptrace.c
+++ b/arch/mips/kernel/ptrace.c
@@ -535,7 +535,7 @@
asmlinkage void syscall_trace_enter(struct pt_regs *regs)
{
/* do the secure computing check first */
- secure_computing(regs->regs[2]);
+ secure_computing_strict(regs->regs[2]);
if (!(current->ptrace & PT_PTRACED))
goto out;
diff --git a/arch/powerpc/kernel/ptrace.c b/arch/powerpc/kernel/ptrace.c
index 8d8e028..dd5e214 100644
--- a/arch/powerpc/kernel/ptrace.c
+++ b/arch/powerpc/kernel/ptrace.c
@@ -1710,7 +1710,7 @@
{
long ret = 0;
- secure_computing(regs->gpr[0]);
+ secure_computing_strict(regs->gpr[0]);
if (test_thread_flag(TIF_SYSCALL_TRACE) &&
tracehook_report_syscall_entry(regs))
diff --git a/arch/s390/kernel/ptrace.c b/arch/s390/kernel/ptrace.c
index 02f300f..4993e68 100644
--- a/arch/s390/kernel/ptrace.c
+++ b/arch/s390/kernel/ptrace.c
@@ -719,7 +719,7 @@
long ret = 0;
/* Do the secure computing check first. */
- secure_computing(regs->gprs[2]);
+ secure_computing_strict(regs->gprs[2]);
/*
* The sysc_tracesys code in entry.S stored the system
diff --git a/arch/sh/kernel/ptrace_32.c b/arch/sh/kernel/ptrace_32.c
index 9698671..81f999a 100644
--- a/arch/sh/kernel/ptrace_32.c
+++ b/arch/sh/kernel/ptrace_32.c
@@ -503,7 +503,7 @@
{
long ret = 0;
- secure_computing(regs->regs[0]);
+ secure_computing_strict(regs->regs[0]);
if (test_thread_flag(TIF_SYSCALL_TRACE) &&
tracehook_report_syscall_entry(regs))
diff --git a/arch/sh/kernel/ptrace_64.c b/arch/sh/kernel/ptrace_64.c
index bc81e07..af90339 100644
--- a/arch/sh/kernel/ptrace_64.c
+++ b/arch/sh/kernel/ptrace_64.c
@@ -522,7 +522,7 @@
{
long long ret = 0;
- secure_computing(regs->regs[9]);
+ secure_computing_strict(regs->regs[9]);
if (test_thread_flag(TIF_SYSCALL_TRACE) &&
tracehook_report_syscall_entry(regs))
diff --git a/arch/sparc/kernel/ptrace_64.c b/arch/sparc/kernel/ptrace_64.c
index 6f97c07..484daba 100644
--- a/arch/sparc/kernel/ptrace_64.c
+++ b/arch/sparc/kernel/ptrace_64.c
@@ -1062,7 +1062,7 @@
int ret = 0;
/* do the secure computing check first */
- secure_computing(regs->u_regs[UREG_G1]);
+ secure_computing_strict(regs->u_regs[UREG_G1]);
if (test_thread_flag(TIF_SYSCALL_TRACE))
ret = tracehook_report_syscall_entry(regs);
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index b1478f4..357953f 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -81,7 +81,8 @@
select CLKEVT_I8253
select ARCH_HAVE_NMI_SAFE_CMPXCHG
select GENERIC_IOMAP
- select DCACHE_WORD_ACCESS
+ select DCACHE_WORD_ACCESS if !DEBUG_PAGEALLOC
+ select HAVE_ARCH_SECCOMP_FILTER
config INSTRUCTION_DECODER
def_bool (KPROBES || PERF_EVENTS)
diff --git a/arch/x86/ia32/ia32_signal.c b/arch/x86/ia32/ia32_signal.c
index 4f5bfac..b154661 100644
--- a/arch/x86/ia32/ia32_signal.c
+++ b/arch/x86/ia32/ia32_signal.c
@@ -67,6 +67,10 @@
switch (from->si_code >> 16) {
case __SI_FAULT >> 16:
break;
+ case __SI_SYS >> 16:
+ put_user_ex(from->si_syscall, &to->si_syscall);
+ put_user_ex(from->si_arch, &to->si_arch);
+ break;
case __SI_CHLD >> 16:
if (ia32) {
put_user_ex(from->si_utime, &to->si_utime);
diff --git a/arch/x86/include/asm/ia32.h b/arch/x86/include/asm/ia32.h
index ee52760..b04cbdb 100644
--- a/arch/x86/include/asm/ia32.h
+++ b/arch/x86/include/asm/ia32.h
@@ -144,6 +144,12 @@
int _band; /* POLL_IN, POLL_OUT, POLL_MSG */
int _fd;
} _sigpoll;
+
+ struct {
+ unsigned int _call_addr; /* calling insn */
+ int _syscall; /* triggering system call number */
+ unsigned int _arch; /* AUDIT_ARCH_* of syscall */
+ } _sigsys;
} _sifields;
} compat_siginfo_t;
diff --git a/arch/x86/include/asm/syscall.h b/arch/x86/include/asm/syscall.h
index 386b786..1ace47b 100644
--- a/arch/x86/include/asm/syscall.h
+++ b/arch/x86/include/asm/syscall.h
@@ -13,9 +13,11 @@
#ifndef _ASM_X86_SYSCALL_H
#define _ASM_X86_SYSCALL_H
+#include <linux/audit.h>
#include <linux/sched.h>
#include <linux/err.h>
#include <asm/asm-offsets.h> /* For NR_syscalls */
+#include <asm/thread_info.h> /* for TS_COMPAT */
#include <asm/unistd.h>
extern const unsigned long sys_call_table[];
@@ -88,6 +90,12 @@
memcpy(®s->bx + i, args, n * sizeof(args[0]));
}
+static inline int syscall_get_arch(struct task_struct *task,
+ struct pt_regs *regs)
+{
+ return AUDIT_ARCH_I386;
+}
+
#else /* CONFIG_X86_64 */
static inline void syscall_get_arguments(struct task_struct *task,
@@ -212,6 +220,25 @@
}
}
+static inline int syscall_get_arch(struct task_struct *task,
+ struct pt_regs *regs)
+{
+#ifdef CONFIG_IA32_EMULATION
+ /*
+ * TS_COMPAT is set for 32-bit syscall entry and then
+ * remains set until we return to user mode.
+ *
+ * TIF_IA32 tasks should always have TS_COMPAT set at
+ * system call time.
+ *
+ * x32 tasks should be considered AUDIT_ARCH_X86_64.
+ */
+ if (task_thread_info(task)->status & TS_COMPAT)
+ return AUDIT_ARCH_I386;
+#endif
+ /* Both x32 and x86_64 are considered "64-bit". */
+ return AUDIT_ARCH_X86_64;
+}
#endif /* CONFIG_X86_32 */
#endif /* _ASM_X86_SYSCALL_H */
diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c
index c4410fb..9ee1787 100644
--- a/arch/x86/kernel/ptrace.c
+++ b/arch/x86/kernel/ptrace.c
@@ -1504,7 +1504,11 @@
regs->flags |= X86_EFLAGS_TF;
/* do the secure computing check first */
- secure_computing(regs->orig_ax);
+ if (secure_computing(regs->orig_ax)) {
+ /* seccomp failures shouldn't expose any additional code. */
+ ret = -1L;
+ goto out;
+ }
if (unlikely(test_thread_flag(TIF_SYSCALL_EMU)))
ret = -1L;
@@ -1529,6 +1533,7 @@
regs->dx, regs->r10);
#endif
+out:
return ret ?: regs->orig_ax;
}
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 021a79c..22e7f12 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -142,6 +142,8 @@
source "drivers/devfreq/Kconfig"
+source "drivers/nfc/Kconfig"
+
source "drivers/gud/Kconfig"
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 0ddf519..2f13629 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -125,7 +125,7 @@
obj-y += clk/
obj-$(CONFIG_HWSPINLOCK) += hwspinlock/
-obj-$(CONFIG_NFC) += nfc/
+obj-$(CONFIG_NFC_DEVICES) += nfc/
obj-$(CONFIG_IOMMU_SUPPORT) += iommu/
obj-$(CONFIG_REMOTEPROC) += remoteproc/
obj-$(CONFIG_RPMSG) += rpmsg/
diff --git a/drivers/gpio/gpio-wm8994.c b/drivers/gpio/gpio-wm8994.c
index aa61ad2..fa8620c 100644
--- a/drivers/gpio/gpio-wm8994.c
+++ b/drivers/gpio/gpio-wm8994.c
@@ -254,7 +254,8 @@
struct wm8994_gpio *wm8994_gpio;
int ret;
- wm8994_gpio = kzalloc(sizeof(*wm8994_gpio), GFP_KERNEL);
+ wm8994_gpio = devm_kzalloc(&pdev->dev, sizeof(*wm8994_gpio),
+ GFP_KERNEL);
if (wm8994_gpio == NULL)
return -ENOMEM;
@@ -279,20 +280,14 @@
return ret;
err:
- kfree(wm8994_gpio);
return ret;
}
static int __devexit wm8994_gpio_remove(struct platform_device *pdev)
{
struct wm8994_gpio *wm8994_gpio = platform_get_drvdata(pdev);
- int ret;
- ret = gpiochip_remove(&wm8994_gpio->gpio_chip);
- if (ret == 0)
- kfree(wm8994_gpio);
-
- return ret;
+ return gpiochip_remove(&wm8994_gpio->gpio_chip);
}
static struct platform_driver wm8994_gpio_driver = {
diff --git a/drivers/gpu/ion/Kconfig b/drivers/gpu/ion/Kconfig
index 442f697..4b78adf 100644
--- a/drivers/gpu/ion/Kconfig
+++ b/drivers/gpu/ion/Kconfig
@@ -46,7 +46,7 @@
config ION_EXYNOS_DRM_MEMSIZE_FIMD_VIDEO
int "Reserved memsize in kilobytes for FIMD VIDEO"
depends on EXYNOS_CONTENT_PATH_PROTECTION
- default 98304
+ default 184320
config ION_EXYNOS_DRM_MEMSIZE_MFC_OUTPUT
int "Reserved memsize in kilobytes for MFC OUTPUT"
diff --git a/drivers/input/input.c b/drivers/input/input.c
index 8921c61..aeccc69 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -515,11 +515,14 @@
handle->open++;
- if (!dev->users++ && dev->open)
+ dev->users_private++;
+ if (!dev->disabled && !dev->users++ && dev->open)
retval = dev->open(dev);
if (retval) {
- dev->users--;
+ dev->users_private--;
+ if (!dev->disabled)
+ dev->users--;
if (!--handle->open) {
/*
* Make sure we are not delivering any more events
@@ -567,7 +570,8 @@
__input_release_device(handle);
- if (!--dev->users && dev->close)
+ --dev->users_private;
+ if (!dev->disabled && !--dev->users && dev->close)
dev->close(dev);
if (!--handle->open) {
@@ -583,6 +587,50 @@
}
EXPORT_SYMBOL(input_close_device);
+static int input_enable_device(struct input_dev *dev)
+{
+ int retval;
+
+ retval = mutex_lock_interruptible(&dev->mutex);
+ if (retval)
+ return retval;
+
+ if (!dev->disabled)
+ goto out;
+
+ if (dev->users_private && dev->open) {
+ retval = dev->open(dev);
+ if (retval)
+ goto out;
+ }
+ dev->users = dev->users_private;
+ dev->disabled = false;
+
+out:
+ mutex_unlock(&dev->mutex);
+
+ return retval;
+}
+
+static int input_disable_device(struct input_dev *dev)
+{
+ int retval;
+
+ retval = mutex_lock_interruptible(&dev->mutex);
+ if (retval)
+ return retval;
+
+ if (!dev->disabled) {
+ dev->disabled = true;
+ if (dev->users && dev->close)
+ dev->close(dev);
+ dev->users = 0;
+ }
+
+ mutex_unlock(&dev->mutex);
+ return 0;
+}
+
/*
* Simulate keyup events for all keys that are marked as pressed.
* The function must be called with dev->event_lock held.
@@ -1291,12 +1339,46 @@
}
static DEVICE_ATTR(properties, S_IRUGO, input_dev_show_properties, NULL);
+static ssize_t input_dev_show_enabled(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct input_dev *input_dev = to_input_dev(dev);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", !input_dev->disabled);
+}
+
+static ssize_t input_dev_store_enabled(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ bool enable;
+ struct input_dev *input_dev = to_input_dev(dev);
+
+ ret = strtobool(buf, &enable);
+ if (ret)
+ return ret;
+
+ if (enable)
+ ret = input_enable_device(input_dev);
+ else
+ ret = input_disable_device(input_dev);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static DEVICE_ATTR(enabled, S_IRUGO | S_IWUSR,
+ input_dev_show_enabled, input_dev_store_enabled);
+
static struct attribute *input_dev_attrs[] = {
&dev_attr_name.attr,
&dev_attr_phys.attr,
&dev_attr_uniq.attr,
&dev_attr_modalias.attr,
&dev_attr_properties.attr,
+ &dev_attr_enabled.attr,
NULL
};
diff --git a/drivers/input/keyreset.c b/drivers/input/keyreset.c
index 36208fe..5b6c73b 100644
--- a/drivers/input/keyreset.c
+++ b/drivers/input/keyreset.c
@@ -21,6 +21,7 @@
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
+#include <linux/workqueue.h>
struct keyreset_state {
@@ -33,18 +34,28 @@
int key_down;
int key_up;
int restart_disabled;
+ int restart_requested;
int (*reset_fn)(void);
+ int down_time_ms;
+ struct delayed_work restart_work;
};
-int restart_requested;
-static void deferred_restart(struct work_struct *dummy)
+static void deferred_restart(struct work_struct *work)
{
- restart_requested = 2;
- sys_sync();
- restart_requested = 3;
- kernel_restart(NULL);
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct keyreset_state *state =
+ container_of(dwork, struct keyreset_state, restart_work);
+
+ pr_info("keyreset: restarting system\n");
+ if (state->reset_fn) {
+ state->restart_requested = state->reset_fn();
+ } else {
+ state->restart_requested = 2;
+ sys_sync();
+ state->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)
@@ -77,8 +88,16 @@
else
state->key_down--;
}
- if (state->key_down == 0 && state->key_up == 0)
+ if (state->key_down == 0 && state->key_up == 0) {
state->restart_disabled = 0;
+ if (state->down_time_ms) {
+ __cancel_delayed_work(&state->restart_work);
+ if (state->restart_requested) {
+ pr_info("keyboard reset canceled\n");
+ state->restart_requested = 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);
@@ -86,14 +105,17 @@
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();
+ if (state->restart_requested)
+ panic("keyboard reset failed, %d",
+ state->restart_requested);
+ if (state->reset_fn && state->down_time_ms == 0) {
+ state->restart_requested = state->reset_fn();
} else {
- pr_info("keyboard reset\n");
- schedule_work(&restart_work);
- restart_requested = 1;
+ pr_info("keyboard reset (delayed %dms)\n",
+ state->down_time_ms);
+ schedule_delayed_work(&state->restart_work,
+ msecs_to_jiffies(state->down_time_ms));
+ state->restart_requested = 1;
}
}
done:
@@ -136,6 +158,13 @@
pr_info("using input dev %s for key reset\n", dev->name);
+ /* process already pressed keys */
+ for_each_set_bit(i, state->keybit, KEY_CNT) {
+ if (!test_bit(i, dev->keybit) || !test_bit(i, dev->key))
+ continue;
+ keyreset_event(handle, EV_KEY, i, 1);
+ }
+
return 0;
err_input_open_device:
@@ -196,6 +225,11 @@
if (pdata->reset_fn)
state->reset_fn = pdata->reset_fn;
+ if (pdata->down_time_ms)
+ state->down_time_ms = pdata->down_time_ms;
+
+ INIT_DELAYED_WORK(&state->restart_work, deferred_restart);
+
state->input_handler.event = keyreset_event;
state->input_handler.connect = keyreset_connect;
state->input_handler.disconnect = keyreset_disconnect;
@@ -214,6 +248,7 @@
{
struct keyreset_state *state = platform_get_drvdata(pdev);
input_unregister_handler(&state->input_handler);
+ cancel_delayed_work_sync(&state->restart_work);
kfree(state);
return 0;
}
@@ -235,5 +270,5 @@
return platform_driver_unregister(&keyreset_driver);
}
-module_init(keyreset_init);
+subsys_initcall(keyreset_init);
module_exit(keyreset_exit);
diff --git a/drivers/input/misc/gpio_event.c b/drivers/input/misc/gpio_event.c
index c7f396a..ceb1d2f 100644
--- a/drivers/input/misc/gpio_event.c
+++ b/drivers/input/misc/gpio_event.c
@@ -231,7 +231,7 @@
platform_driver_unregister(&gpio_event_driver);
}
-module_init(gpio_event_init);
+subsys_initcall(gpio_event_init);
module_exit(gpio_event_exit);
MODULE_DESCRIPTION("GPIO Event Driver");
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 19d4ea6..341fd7d 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -20,6 +20,8 @@
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
/* Version */
#define MXT_VER_20 20
@@ -75,6 +77,12 @@
#define MXT_SPT_DIGITIZER_T43 43
#define MXT_SPT_MESSAGECOUNT_T44 44
#define MXT_SPT_CTECONFIG_T46 46
+#define MXT_ADAPTIVE_T55 55
+#define MXT_PROCI_SHIELDLESS_T56 56
+#define MXT_PROCI_EXTRATOUCHSCREENDATA_T57 57
+#define MXT_SPT_TIMER_T61 61
+#define MXT_PROCG_NOISESUPPRESSION_T62 62
+#define MXT_PROCI_ACTIVESTYLUS_63 63
/* MXT_GEN_COMMAND_T6 field */
#define MXT_COMMAND_RESET 0
@@ -175,8 +183,10 @@
/* Define for MXT_GEN_COMMAND_T6 */
#define MXT_BOOT_VALUE 0xa5
#define MXT_BACKUP_VALUE 0x55
+#define MXT_DISABLE_EVENT_VALUE 0x33
+
#define MXT_BACKUP_TIME 25 /* msec */
-#define MXT_RESET_TIME 65 /* msec */
+#define MXT_RESET_TIME 300 /* msec */
#define MXT_FWRESET_TIME 175 /* msec */
@@ -212,6 +222,17 @@
#define MXT_MAX_FINGER 10
+/* Touchscreen configuration infomation */
+#define MXT_FW_MAGIC 0x4D3C2B1A
+
+/* Voltage supplies */
+static const char * const mxt_supply_names[] = {
+ /* keep the order for power sequence */
+ "vdd",
+ "avdd",
+ "xvdd",
+};
+
struct mxt_info {
u8 family_id;
u8 variant_id;
@@ -245,11 +266,18 @@
int y;
int area;
int pressure;
+ int vector;
+};
+
+struct mxt_reportid {
+ u8 type;
+ u8 index;
};
/* Each client has this additional data */
struct mxt_data {
struct i2c_client *client;
+ struct i2c_client *client_boot;
struct input_dev *input_dev;
const struct mxt_platform_data *pdata;
struct mxt_object *object_table;
@@ -258,6 +286,59 @@
unsigned int irq;
unsigned int max_x;
unsigned int max_y;
+ struct completion init_done;
+ unsigned int max_reportid;
+ struct mxt_reportid *reportid_table;
+ bool enabled;
+ struct regulator_bulk_data supplies[ARRAY_SIZE(mxt_supply_names)];
+};
+
+/**
+ * struct mxt_fw_image - Represents a firmware file.
+ * @magic_code: Identifier of file type.
+ * @hdr_len: Size of file header (struct mxt_fw_image).
+ * @cfg_len: Size of configuration data.
+ * @fw_len: Size of firmware data.
+ * @cfg_crc: CRC of configuration settings.
+ * @fw_ver: Version of firmware.
+ * @build_ver: Build version of firmware.
+ * @data: Configuration data followed by firmware image.
+ */
+struct mxt_fw_image {
+ __le32 magic_code;
+ __le32 hdr_len;
+ __le32 cfg_len;
+ __le32 fw_len;
+ __le32 cfg_crc;
+ u8 fw_ver;
+ u8 build_ver;
+ u8 data[0];
+} __packed;
+
+/**
+ * struct mxt_cfg_data - Represents a configuration data item.
+ * @type: Type of object.
+ * @instance: Instance number of object.
+ * @size: Size of object.
+ * @register_val: Series of register values for object.
+ */
+struct mxt_cfg_data {
+ u8 type;
+ u8 instance;
+ u8 size;
+ u8 register_val[0];
+} __packed;
+
+struct mxt_fw_info {
+ u8 fw_ver;
+ u8 build_ver;
+ u32 hdr_len;
+ u32 cfg_len;
+ u32 fw_len;
+ u32 cfg_crc;
+ const u8 *cfg_raw_data; /* start address of configuration data */
+ const u8 *fw_raw_data; /* start address of firmware data */
+ struct mxt_data *data;
};
static bool mxt_object_readable(unsigned int type)
@@ -288,6 +369,12 @@
case MXT_SPT_USERDATA_T38:
case MXT_SPT_DIGITIZER_T43:
case MXT_SPT_CTECONFIG_T46:
+ case MXT_ADAPTIVE_T55:
+ case MXT_PROCI_SHIELDLESS_T56:
+ case MXT_PROCI_EXTRATOUCHSCREENDATA_T57:
+ case MXT_SPT_TIMER_T61:
+ case MXT_PROCG_NOISESUPPRESSION_T62:
+ case MXT_PROCI_ACTIVESTYLUS_63:
return true;
default:
return false;
@@ -319,6 +406,12 @@
case MXT_SPT_CTECONFIG_T28:
case MXT_SPT_DIGITIZER_T43:
case MXT_SPT_CTECONFIG_T46:
+ case MXT_ADAPTIVE_T55:
+ case MXT_PROCI_SHIELDLESS_T56:
+ case MXT_PROCI_EXTRATOUCHSCREENDATA_T57:
+ case MXT_SPT_TIMER_T61:
+ case MXT_PROCG_NOISESUPPRESSION_T62:
+ case MXT_PROCI_ACTIVESTYLUS_63:
return true;
default:
return false;
@@ -326,17 +419,52 @@
}
static void mxt_dump_message(struct device *dev,
- struct mxt_message *message)
+ struct mxt_message *message, u8 object_type)
{
- dev_dbg(dev, "reportid:\t0x%x\n", message->reportid);
- dev_dbg(dev, "message1:\t0x%x\n", message->message[0]);
- dev_dbg(dev, "message2:\t0x%x\n", message->message[1]);
- dev_dbg(dev, "message3:\t0x%x\n", message->message[2]);
- dev_dbg(dev, "message4:\t0x%x\n", message->message[3]);
- dev_dbg(dev, "message5:\t0x%x\n", message->message[4]);
- dev_dbg(dev, "message6:\t0x%x\n", message->message[5]);
- dev_dbg(dev, "message7:\t0x%x\n", message->message[6]);
- dev_dbg(dev, "checksum:\t0x%x\n", message->checksum);
+ switch (object_type) {
+ case MXT_GEN_COMMAND_T6:
+ /* Normal mode */
+ if (message->message[0] == 0x00)
+ dev_dbg(dev, "Normal mode\n");
+ /* Config error */
+ if (message->message[0] & 0x08)
+ dev_err(dev, "Configuration error\n");
+ /* Calibration */
+ if (message->message[0] & 0x10)
+ dev_dbg(dev, "Calibration is on going !!\n");
+ /* Signal error */
+ if (message->message[0] & 0x20)
+ dev_err(dev, "Signal error\n");
+ /* Overflow */
+ if (message->message[0] & 0x40)
+ dev_err(dev, "Overflow detected\n");
+ /* Reset */
+ if (message->message[0] & 0x80)
+ dev_dbg(dev, "Reset is ongoing\n");
+ break;
+
+ case MXT_SPT_SELFTEST_T25:
+ if (message->message[0] != 0xFE)
+ dev_err(dev, "Self-test error:[0x%x][0x%x][0x%x][0x%x]\n",
+ message->message[0], message->message[1],
+ message->message[2], message->message[3]);
+ break;
+
+ case MXT_PROCI_TOUCHSUPPRESSION_T42:
+ if (message->message[0] & 0x01)
+ dev_dbg(dev, "Start touch suppression\n");
+ else
+ dev_dbg(dev, "Stop touch suppression\n");
+ break;
+ default:
+ dev_dbg(dev, "T%d:\t[0x%x][0x%x][0x%x][0x%x][0x%x][0x%x][0x%x][0x%x][0x%x]\n",
+ object_type, message->reportid,
+ message->message[0], message->message[1],
+ message->message[2], message->message[3],
+ message->message[4], message->message[5],
+ message->message[6], message->checksum);
+ break;
+ }
}
static int mxt_check_bootloader(struct i2c_client *client,
@@ -353,18 +481,24 @@
switch (state) {
case MXT_WAITING_BOOTLOAD_CMD:
case MXT_WAITING_FRAME_DATA:
+ case MXT_APP_CRC_FAIL:
val &= ~MXT_BOOT_STATUS_MASK;
break;
case MXT_FRAME_CRC_PASS:
if (val == MXT_FRAME_CRC_CHECK)
goto recheck;
+ if (val == MXT_FRAME_CRC_FAIL) {
+ dev_err(&client->dev, "Bootloader CRC fail\n");
+ return -EINVAL;
+ }
break;
default:
return -EINVAL;
}
if (val != state) {
- dev_err(&client->dev, "Unvalid bootloader mode state\n");
+ dev_err(&client->dev,
+ "Invalid bootloader mode state 0x%X\n", val);
return -EINVAL;
}
@@ -386,6 +520,28 @@
return 0;
}
+static int mxt_probe_bootloader(struct i2c_client *client)
+{
+ u8 val;
+
+ if (i2c_master_recv(client, &val, 1) != 1) {
+ dev_err(&client->dev, "%s: i2c recv failed\n", __func__);
+ return -EIO;
+ }
+
+ if (val & (~MXT_BOOT_STATUS_MASK)) {
+ if (val & MXT_APP_CRC_FAIL)
+ dev_err(&client->dev, "Application CRC failure\n");
+ else
+ dev_err(&client->dev, "Device in bootloader mode\n");
+ } else {
+ dev_err(&client->dev, "%s: Unknow status\n", __func__);
+ return -EIO;
+ }
+
+ return 0;
+}
+
static int mxt_fw_write(struct i2c_client *client,
const u8 *data, unsigned int frame_size)
{
@@ -460,6 +616,9 @@
struct mxt_object *object;
int i;
+ if (!data->object_table)
+ return NULL;
+
for (i = 0; i < data->info.object_num; i++) {
object = data->object_table + i;
if (object->type == type)
@@ -485,6 +644,30 @@
sizeof(struct mxt_message), message);
}
+static int mxt_read_message_reportid(struct mxt_data *data,
+ struct mxt_message *message, u8 reportid)
+{
+ int try = 0;
+ int error;
+ int fail_count;
+
+ fail_count = data->max_reportid * 2;
+
+ while (++try < fail_count) {
+ error = mxt_read_message(data, message);
+ if (error)
+ return error;
+
+ if (message->reportid == 0xff)
+ continue;
+
+ if (message->reportid == reportid)
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
static int mxt_read_object(struct mxt_data *data,
u8 type, u8 offset, u8 *val)
{
@@ -513,11 +696,10 @@
return mxt_write_reg(data->client, reg + offset, val);
}
-static void mxt_input_report(struct mxt_data *data, int single_id)
+static void mxt_input_report(struct mxt_data *data)
{
struct mxt_finger *finger = data->finger;
struct input_dev *input_dev = data->input_dev;
- int status = finger[single_id].status;
int finger_num = 0;
int id;
@@ -539,20 +721,13 @@
finger[id].y);
input_report_abs(input_dev, ABS_MT_PRESSURE,
finger[id].pressure);
+ input_report_abs(input_dev, ABS_MT_ORIENTATION,
+ finger[id].vector);
} else {
finger[id].status = 0;
}
}
- input_report_key(input_dev, BTN_TOUCH, finger_num > 0);
-
- if (status != MXT_RELEASE) {
- input_report_abs(input_dev, ABS_X, finger[single_id].x);
- input_report_abs(input_dev, ABS_Y, finger[single_id].y);
- input_report_abs(input_dev,
- ABS_PRESSURE, finger[single_id].pressure);
- }
-
input_sync(input_dev);
}
@@ -566,6 +741,12 @@
int y;
int area;
int pressure;
+ int vector;
+
+ if (id >= MXT_MAX_FINGER) {
+ dev_err(dev, "MXT_MAX_FINGER exceeded!\n");
+ return;
+ }
/* Check the touch is present on the screen */
if (!(status & MXT_DETECT)) {
@@ -573,15 +754,16 @@
dev_dbg(dev, "[%d] released\n", id);
finger[id].status = MXT_RELEASE;
- mxt_input_report(data, id);
+ mxt_input_report(data);
+ } else if (status & MXT_SUPPRESS) {
+ dev_dbg(dev, "[%d] suppressed\n", id);
+
+ finger[id].status = MXT_RELEASE;
+ mxt_input_report(data);
}
return;
}
- /* Check only AMP detection */
- if (!(status & (MXT_PRESS | MXT_MOVE)))
- return;
-
x = (message->message[1] << 4) | ((message->message[3] >> 4) & 0xf);
y = (message->message[2] << 4) | ((message->message[3] & 0xf));
if (data->max_x < 1024)
@@ -591,6 +773,7 @@
area = message->message[4];
pressure = message->message[5];
+ vector = message->message[6];
dev_dbg(dev, "[%d] %s x: %d, y: %d, area: %d\n", id,
status & MXT_MOVE ? "moved" : "pressed",
@@ -602,20 +785,19 @@
finger[id].y = y;
finger[id].area = area;
finger[id].pressure = pressure;
+ finger[id].vector = vector;
- mxt_input_report(data, id);
+ mxt_input_report(data);
}
static irqreturn_t mxt_interrupt(int irq, void *dev_id)
{
struct mxt_data *data = dev_id;
struct mxt_message message;
- struct mxt_object *object;
struct device *dev = &data->client->dev;
int id;
u8 reportid;
- u8 max_reportid;
- u8 min_reportid;
+ u8 object_type = 0;
do {
if (mxt_read_message(data, &message)) {
@@ -625,25 +807,173 @@
reportid = message.reportid;
- /* whether reportid is thing of MXT_TOUCH_MULTI_T9 */
- object = mxt_get_object(data, MXT_TOUCH_MULTI_T9);
- if (!object)
+ if (reportid > data->max_reportid)
goto end;
- max_reportid = object->max_reportid;
- min_reportid = max_reportid - object->num_report_ids + 1;
- id = reportid - min_reportid;
+ object_type = data->reportid_table[reportid].type;
- if (reportid >= min_reportid && reportid <= max_reportid)
+ if (object_type == MXT_TOUCH_MULTI_T9) {
+ id = data->reportid_table[reportid].index;
mxt_input_touchevent(data, &message, id);
- else
- mxt_dump_message(dev, &message);
+ } else {
+ mxt_dump_message(dev, &message, object_type);
+ }
} while (reportid != 0xff);
end:
return IRQ_HANDLED;
}
+static int mxt_read_config_crc(struct mxt_data *data, u32 *crc)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+ struct mxt_message message;
+ struct mxt_object *object;
+
+ object = mxt_get_object(data, MXT_GEN_COMMAND_T6);
+ if (!object)
+ return -EIO;
+
+ /* Try to read the config checksum of the existing cfg */
+ mxt_write_object(data, MXT_GEN_COMMAND_T6,
+ MXT_COMMAND_REPORTALL, 1);
+
+ /* Read message from command processor, which only has one report ID */
+ error = mxt_read_message_reportid(data, &message, object->max_reportid);
+ if (error) {
+ dev_err(dev, "Failed to retrieve CRC\n");
+ return error;
+ }
+
+ /* Bytes 1-3 are the checksum. */
+ *crc = message.message[1] | (message.message[2] << 8) |
+ (message.message[3] << 16);
+
+ return 0;
+}
+
+static int mxt_download_config(struct mxt_fw_info *fw_info)
+{
+ struct mxt_data *data = fw_info->data;
+ struct device *dev = &data->client->dev;
+ struct mxt_object *object;
+ struct mxt_cfg_data *cfg_data;
+ u32 current_crc;
+ u8 i;
+ u16 reg, index;
+ int ret;
+
+ if (!fw_info->cfg_raw_data) {
+ dev_err(dev, "Configuration data is Null\n");
+ return -EINVAL;
+ }
+
+ /* Get config CRC from device */
+ ret = mxt_read_config_crc(data, ¤t_crc);
+ if (ret)
+ return ret;
+
+ /* Check Version information */
+ if (fw_info->fw_ver != data->info.version) {
+ dev_err(dev, "Warning: version mismatch! %s\n", __func__);
+ return 0;
+ }
+ if (fw_info->build_ver != data->info.build) {
+ dev_err(dev, "Warning: build num mismatch! %s\n", __func__);
+ return 0;
+ }
+
+ /* Check config CRC */
+ if (current_crc == fw_info->cfg_crc) {
+ dev_info(dev, "Skip writing Config:[CRC 0x%06X]\n",
+ current_crc);
+ return 0;
+ }
+
+ dev_info(dev, "Writing Config:[CRC 0x%06X!=0x%06X]\n",
+ current_crc, fw_info->cfg_crc);
+
+ /* Write config info */
+ for (index = 0; index < fw_info->cfg_len;) {
+
+ if (index + sizeof(struct mxt_cfg_data) >= fw_info->cfg_len) {
+ dev_err(dev, "index(%d) of cfg_data exceeded total size(%d)!!\n",
+ index + sizeof(struct mxt_cfg_data),
+ fw_info->cfg_len);
+ return -EINVAL;
+ }
+
+ /* Get the info about each object */
+ cfg_data = (struct mxt_cfg_data *)
+ (&fw_info->cfg_raw_data[index]);
+
+ index += sizeof(struct mxt_cfg_data) + cfg_data->size;
+ if (index > fw_info->cfg_len) {
+ dev_err(dev, "index(%d) of cfg_data exceeded total size(%d) in T%d object!!\n",
+ index, fw_info->cfg_len, cfg_data->type);
+ return -EINVAL;
+ }
+
+ object = mxt_get_object(data, cfg_data->type);
+ if (!object) {
+ dev_err(dev, "T%d is Invalid object type\n",
+ cfg_data->type);
+ return -EINVAL;
+ }
+
+ /* Check and compare the size, instance of each object */
+ if (cfg_data->size > object->size) {
+ dev_err(dev, "T%d Object length exceeded!\n",
+ cfg_data->type);
+ return -EINVAL;
+ }
+ if (cfg_data->instance >= object->instances) {
+ dev_err(dev, "T%d Object instances exceeded!\n",
+ cfg_data->type);
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "Writing config for obj %d len %d instance %d (%d/%d)\n",
+ cfg_data->type, object->size,
+ cfg_data->instance, index, fw_info->cfg_len);
+
+ reg = object->start_address + object->size * cfg_data->instance;
+
+ /* Write register values of each object */
+ for (i = 0; i < cfg_data->size; i++) {
+ ret = mxt_write_reg(data->client, reg + i,
+ cfg_data->register_val[i]);
+ if (ret) {
+ dev_err(dev, "Write %dth byte of T%d Object failed\n",
+ object->type, i);
+ return ret;
+ }
+ }
+
+ /*
+ * If firmware is upgraded, new bytes may be added to end of
+ * objects. It is generally forward compatible to zero these
+ * bytes - previous behaviour will be retained. However
+ * this does invalidate the CRC and will force a config
+ * download every time until the configuration is updated.
+ */
+ if (cfg_data->size < object->size) {
+ dev_err(dev, "Warning: zeroing %d byte(s) in T%d\n",
+ object->size - cfg_data->size, cfg_data->type);
+
+ for (i = cfg_data->size + 1; i < object->size; i++) {
+ ret = mxt_write_reg(data->client, reg + i, 0);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+ dev_info(dev, "Updated configuration\n");
+
+ return ret;
+}
+
static int mxt_check_reg_init(struct mxt_data *data)
{
const struct mxt_platform_data *pdata = data->pdata;
@@ -664,7 +994,7 @@
continue;
for (j = 0;
- j < (object->size + 1) * (object->instances + 1);
+ j < object->size * object->instances;
j++) {
config_offset = index + j;
if (config_offset > pdata->config_length) {
@@ -674,7 +1004,7 @@
mxt_write_object(data, object->type, j,
pdata->config[config_offset]);
}
- index += (object->size + 1) * (object->instances + 1);
+ index += object->size * object->instances;
}
return 0;
@@ -684,7 +1014,7 @@
{
struct device *dev = &data->client->dev;
struct mxt_message message;
- int count = 10;
+ int count = data->max_reportid * 2;
int error;
/* Read dummy message to make high CHG pin */
@@ -718,12 +1048,14 @@
pdata->orient);
/* Set touchscreen burst length */
- mxt_write_object(data, MXT_TOUCH_MULTI_T9,
- MXT_TOUCH_BLEN, pdata->blen);
+ if (pdata->blen)
+ mxt_write_object(data, MXT_TOUCH_MULTI_T9,
+ MXT_TOUCH_BLEN, pdata->blen);
/* Set touchscreen threshold */
- mxt_write_object(data, MXT_TOUCH_MULTI_T9,
- MXT_TOUCH_TCHTHR, pdata->threshold);
+ if (pdata->threshold)
+ mxt_write_object(data, MXT_TOUCH_MULTI_T9,
+ MXT_TOUCH_TCHTHR, pdata->threshold);
/* Set touchscreen resolution */
mxt_write_object(data, MXT_TOUCH_MULTI_T9,
@@ -803,22 +1135,55 @@
object->type = buf[0];
object->start_address = (buf[2] << 8) | buf[1];
- object->size = buf[3];
- object->instances = buf[4];
+ /* the real size of object is buf[3]+1 */
+ object->size = buf[3] + 1;
+ /* the real instances of object is buf[4]+1 */
+ object->instances = buf[4] + 1;
object->num_report_ids = buf[5];
+ dev_dbg(&data->client->dev,
+ "obj %d addr 0x%x size %d insts %d num_reps %d\n",
+ object->type, object->start_address, object->size,
+ object->instances, object->num_report_ids);
+
if (object->num_report_ids) {
- reportid += object->num_report_ids *
- (object->instances + 1);
+ reportid += object->num_report_ids * object->instances;
object->max_reportid = reportid;
}
}
+ /* Store maximum reportid */
+ data->max_reportid = reportid;
return 0;
}
-static int mxt_initialize(struct mxt_data *data)
+static void mxt_make_reportid_table(struct mxt_data *data)
{
+ struct mxt_object *objects = data->object_table;
+ struct mxt_reportid *reportids = data->reportid_table;
+ struct device *dev = &data->client->dev;
+ int i, j;
+ int id = 0;
+
+ for (i = 0; i < data->info.object_num; i++) {
+ for (j = 0; j < objects[i].num_report_ids *
+ objects[i].instances; j++) {
+ id++;
+ reportids[id].type = objects[i].type;
+ reportids[id].index = j;
+ dev_dbg(dev, "Report_id[%d]:\tT%d\tindex%d\n",
+ id, reportids[id].type,
+ reportids[id].index);
+ }
+ }
+
+ dev_dbg(&data->client->dev, "maXTouch: %d report ID\n",
+ data->max_reportid);
+}
+
+static int mxt_initialize(struct mxt_fw_info *fw_info)
+{
+ struct mxt_data *data = fw_info->data;
struct i2c_client *client = data->client;
struct mxt_info *info = &data->info;
int error;
@@ -841,11 +1206,33 @@
if (error)
return error;
- /* Check register init values */
- error = mxt_check_reg_init(data);
- if (error)
- return error;
+ data->reportid_table = kcalloc(data->max_reportid + 1,
+ sizeof(struct mxt_reportid),
+ GFP_KERNEL);
+ if (!data->reportid_table) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+ /* Make the report id table infomation */
+ mxt_make_reportid_table(data);
+
+ /* Stop the event handler before backup memory */
+ mxt_write_object(data, MXT_GEN_COMMAND_T6,
+ MXT_COMMAND_BACKUPNV,
+ MXT_DISABLE_EVENT_VALUE);
+ msleep(MXT_BACKUP_TIME);
+
+ /* Check register init values */
+ if (fw_info->cfg_raw_data) {
+ error = mxt_download_config(fw_info);
+ if (error)
+ return error;
+ } else {
+ error = mxt_check_reg_init(data);
+ if (error)
+ return error;
+ }
mxt_handle_pdata(data);
/* Backup to memory */
@@ -924,7 +1311,7 @@
continue;
}
- for (j = 0; j < object->size + 1; j++) {
+ for (j = 0; j < object->size; j++) {
error = mxt_read_object(data,
object->type, j, &val);
if (error)
@@ -943,47 +1330,103 @@
return count;
}
-
-static int mxt_load_fw(struct device *dev, const char *fn)
+static int mxt_verify_fw(struct mxt_fw_info *fw_info, const struct firmware *fw)
{
- struct mxt_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- const struct firmware *fw = NULL;
+ struct mxt_data *data = fw_info->data;
+ struct device *dev = &data->client->dev;
+ struct mxt_fw_image *fw_img;
+
+ if (!fw) {
+ dev_err(dev, "could not find firmware file\n");
+ return -ENOENT;
+ }
+
+ fw_img = (struct mxt_fw_image *)fw->data;
+
+ if (le32_to_cpu(fw_img->magic_code) != MXT_FW_MAGIC) {
+ /* In case, firmware file only consist of firmware */
+ dev_dbg(dev, "Firmware file only consist of raw firmware\n");
+ fw_info->fw_len = fw->size;
+ fw_info->fw_raw_data = fw->data;
+ } else {
+ /*
+ * In case, firmware file consist of header,
+ * configuration, firmware.
+ */
+ dev_dbg(dev, "Firmware file consist of header, configuration, firmware\n");
+ fw_info->fw_ver = fw_img->fw_ver;
+ fw_info->build_ver = fw_img->build_ver;
+ fw_info->hdr_len = le32_to_cpu(fw_img->hdr_len);
+ fw_info->cfg_len = le32_to_cpu(fw_img->cfg_len);
+ fw_info->fw_len = le32_to_cpu(fw_img->fw_len);
+ fw_info->cfg_crc = le32_to_cpu(fw_img->cfg_crc);
+
+ /* Check the firmware file with header */
+ if (fw_info->hdr_len != sizeof(struct mxt_fw_image)
+ || fw_info->hdr_len + fw_info->cfg_len
+ + fw_info->fw_len != fw->size) {
+ dev_err(dev, "Firmware file is invaild !!hdr size[%d] cfg,fw size[%d,%d] filesize[%d]\n",
+ fw_info->hdr_len, fw_info->cfg_len,
+ fw_info->fw_len, fw->size);
+ return -EINVAL;
+ }
+
+ if (!fw_info->cfg_len) {
+ dev_err(dev, "Firmware file dose not include configuration data\n");
+ return -EINVAL;
+ }
+ if (!fw_info->fw_len) {
+ dev_err(dev, "Firmware file dose not include raw firmware data\n");
+ return -EINVAL;
+ }
+
+ /* Get the address of configuration data */
+ fw_info->cfg_raw_data = fw_img->data;
+
+ /* Get the address of firmware data */
+ fw_info->fw_raw_data = fw_img->data + fw_info->cfg_len;
+
+ }
+
+ return 0;
+}
+
+static int mxt_load_fw(struct mxt_fw_info *fw_info)
+{
+ struct mxt_data *data = fw_info->data;
+ struct i2c_client *client = data->client_boot;
+ struct device *dev = &data->client->dev;
+ const u8 *fw_data = fw_info->fw_raw_data;
+ size_t fw_size = fw_info->fw_len;
unsigned int frame_size;
unsigned int pos = 0;
int ret;
+ u8 val, retry_count = 5;
- ret = request_firmware(&fw, fn, dev);
- if (ret) {
- dev_err(dev, "Unable to open firmware %s\n", fn);
- return ret;
+ if (!fw_data) {
+ dev_err(dev, "firmware data is Null\n");
+ return -ENOMEM;
}
- /* Change to the bootloader mode */
- mxt_write_object(data, MXT_GEN_COMMAND_T6,
- MXT_COMMAND_RESET, MXT_BOOT_VALUE);
- msleep(MXT_RESET_TIME);
-
- /* Change to slave address of bootloader */
- if (client->addr == MXT_APP_LOW)
- client->addr = MXT_BOOT_LOW;
- else
- client->addr = MXT_BOOT_HIGH;
-
ret = mxt_check_bootloader(client, MXT_WAITING_BOOTLOAD_CMD);
- if (ret)
- goto out;
+ if (ret) {
+ /*may still be unlocked from previous update attempt */
+ ret = mxt_check_bootloader(client, MXT_WAITING_FRAME_DATA);
+ if (ret)
+ goto out;
+ } else {
+ dev_info(dev, "Unlocking bootloader\n");
+ /* Unlock bootloader */
+ mxt_unlock_bootloader(client);
+ }
- /* Unlock bootloader */
- mxt_unlock_bootloader(client);
-
- while (pos < fw->size) {
+ while (pos < fw_size) {
ret = mxt_check_bootloader(client,
MXT_WAITING_FRAME_DATA);
if (ret)
goto out;
- frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1));
+ frame_size = ((*(fw_data + pos) << 8) | *(fw_data + pos + 1));
/* We should add 2 at frame size as the the firmware data is not
* included the CRC bytes.
@@ -991,7 +1434,7 @@
frame_size += 2;
/* Write one frame to device */
- mxt_fw_write(client, fw->data + pos, frame_size);
+ mxt_fw_write(client, fw_data + pos, frame_size);
ret = mxt_check_bootloader(client,
MXT_FRAME_CRC_PASS);
@@ -1000,18 +1443,95 @@
pos += frame_size;
- dev_dbg(dev, "Updated %d bytes / %zd bytes\n", pos, fw->size);
+ dev_dbg(dev, "Updated %d bytes / %zd bytes\n", pos, fw_size);
+ }
+
+ /* Wait for IC enter to app mode */
+ do {
+ ret = mxt_read_reg(data->client, MXT_FAMILY_ID, &val);
+ if (!ret)
+ break;
+
+ dev_err(dev, "Waiting IC enter app mode\n");
+ msleep(MXT_FWRESET_TIME);
+
+ } while (--retry_count);
+
+ if (!retry_count) {
+ dev_err(dev, "No response after firmware update\n");
+ return -EIO;
}
out:
- release_firmware(fw);
+ return ret;
+}
- /* Change to slave address of application */
- if (client->addr == MXT_BOOT_LOW)
- client->addr = MXT_APP_LOW;
- else
- client->addr = MXT_APP_HIGH;
+static int mxt_power_on(struct mxt_data *data)
+{
+ const struct mxt_platform_data *pdata = data->pdata;
+ struct device *dev = &data->client->dev;
+ int i, ret;
+ if (data->enabled)
+ return 0;
+
+ /* Enable regulators according to order */
+ for (i = 0; i < ARRAY_SIZE(data->supplies); i++) {
+ ret = regulator_enable(data->supplies[i].consumer);
+ if (ret) {
+ dev_err(dev, "Fail to enable regulator %s\n",
+ data->supplies[i].supply);
+ goto err;
+ }
+ }
+ /* Deassert reset pin */
+ if (pdata->gpio_reset) {
+ usleep_range(1000, 1500);
+ gpio_set_value(pdata->gpio_reset, 1);
+ }
+
+ if (pdata->reset_msec)
+ msleep(pdata->reset_msec);
+
+ data->enabled = true;
+
+ return 0;
+
+err:
+ while (--i >= 0)
+ regulator_disable(data->supplies[i].consumer);
+ return ret;
+}
+
+static int mxt_power_off(struct mxt_data *data)
+{
+ const struct mxt_platform_data *pdata = data->pdata;
+ int ret;
+
+ if (!data->enabled)
+ return 0;
+
+ /* Assert reset pin */
+ if (pdata->gpio_reset)
+ gpio_set_value(pdata->gpio_reset, 0);
+
+ /* Disable regulator */
+ ret = regulator_bulk_disable(ARRAY_SIZE(data->supplies),
+ data->supplies);
+
+ if (ret)
+ goto err;
+
+ data->enabled = false;
+
+ return 0;
+
+err:
+ /* Deassert reset pin */
+ if (pdata->gpio_reset) {
+ usleep_range(1000, 1500);
+ gpio_set_value(pdata->gpio_reset, 1);
+ }
return ret;
}
@@ -1020,29 +1540,85 @@
const char *buf, size_t count)
{
struct mxt_data *data = dev_get_drvdata(dev);
+ struct mxt_fw_info fw_info;
+ const struct firmware *fw = NULL;
+ const char *firmware_name = data->pdata->firmware_name ?: MXT_FW_NAME;
int error;
+ struct input_dev *input_dev = data->input_dev;
+ bool enabled_status;
+ error = wait_for_completion_interruptible_timeout(&data->init_done,
+ msecs_to_jiffies(90 * MSEC_PER_SEC));
+
+ if (error <= 0) {
+ dev_err(dev, "error while waiting for device to init (%d)\n",
+ error);
+ return -EBUSY;
+ }
+ mutex_lock(&input_dev->mutex);
+
+ enabled_status = data->enabled;
+ if (!enabled_status) {
+ error = mxt_power_on(data);
+ if (error) {
+ dev_err(dev, "Failed to turn on touch\n");
+ goto err_power_on;
+ }
+ }
disable_irq(data->irq);
- error = mxt_load_fw(dev, MXT_FW_NAME);
+ dev_info(dev, "Updating firmware from sysfs\n");
+
+ error = request_firmware(&fw, firmware_name, dev);
if (error) {
- dev_err(dev, "The firmware update failed(%d)\n", error);
- count = error;
- } else {
- dev_dbg(dev, "The firmware update succeeded\n");
-
- /* Wait for reset */
- msleep(MXT_FWRESET_TIME);
-
- kfree(data->object_table);
- data->object_table = NULL;
-
- mxt_initialize(data);
+ dev_err(dev, "Unable to open firmware %s\n", firmware_name);
+ goto err_request_firmware;
}
- enable_irq(data->irq);
+ memset(&fw_info, 0, sizeof(struct mxt_fw_info));
+ fw_info.data = data;
+ error = mxt_verify_fw(&fw_info, fw);
+ if (error)
+ goto err_verify_fw;
+
+ /* Change to the bootloader mode */
+ error = mxt_write_object(data, MXT_GEN_COMMAND_T6,
+ MXT_COMMAND_RESET, MXT_BOOT_VALUE);
+ if (error)
+ goto err_write_object;
+
+ msleep(MXT_RESET_TIME);
+
+ error = mxt_load_fw(&fw_info);
+ if (error) {
+ dev_err(dev, "The firmware update failed(%d)\n", error);
+ } else {
+ dev_info(dev, "The firmware update succeeded\n");
+ kfree(data->object_table);
+ data->object_table = NULL;
+ kfree(data->reportid_table);
+ data->reportid_table = NULL;
+
+ error = mxt_initialize(&fw_info);
+ if (error) {
+ dev_err(dev, "Failed to initialize\n");
+ goto err_initialize;
+ }
+ }
error = mxt_make_highchg(data);
+
+err_initialize:
+err_write_object:
+err_verify_fw:
+ release_firmware(fw);
+err_request_firmware:
+ enable_irq(data->irq);
+ if (!enabled_status)
+ mxt_power_off(data);
+err_power_on:
+ mutex_unlock(&input_dev->mutex);
+
if (error)
return error;
@@ -1062,27 +1638,76 @@
.attrs = mxt_attrs,
};
-static void mxt_start(struct mxt_data *data)
+static int mxt_start(struct mxt_data *data)
{
- /* Touch enable */
- mxt_write_object(data,
- MXT_TOUCH_MULTI_T9, MXT_TOUCH_CTRL, 0x83);
+ int error = 0;
+
+ if (data->enabled) {
+ dev_err(&data->client->dev, "Touch is already started\n");
+ return error;
+ }
+
+ error = mxt_power_on(data);
+ if (error)
+ dev_err(&data->client->dev, "Fail to start touch\n");
+ else
+ enable_irq(data->irq);
+
+ return error;
}
static void mxt_stop(struct mxt_data *data)
{
- /* Touch disable */
- mxt_write_object(data,
- MXT_TOUCH_MULTI_T9, MXT_TOUCH_CTRL, 0);
+ int id, count = 0;
+
+ if (!data->enabled) {
+ dev_err(&data->client->dev, "Touch is already stopped\n");
+ return;
+ }
+ disable_irq(data->irq);
+ mxt_power_off(data);
+
+ /* release the finger which is remained */
+ for (id = 0; id < MXT_MAX_FINGER; id++) {
+ if (!data->finger[id].status)
+ continue;
+ data->finger[id].status = MXT_RELEASE;
+ count++;
+ }
+
+ if (count)
+ mxt_input_report(data);
}
static int mxt_input_open(struct input_dev *dev)
{
struct mxt_data *data = input_get_drvdata(dev);
+ int ret;
- mxt_start(data);
+ ret = wait_for_completion_interruptible_timeout(&data->init_done,
+ msecs_to_jiffies(90 * MSEC_PER_SEC));
+
+ if (ret < 0) {
+ dev_err(&dev->dev,
+ "error while waiting for device to init (%d)\n", ret);
+ ret = -ENXIO;
+ goto err_open;
+ }
+ if (ret == 0) {
+ dev_err(&dev->dev,
+ "timedout while waiting for device to init\n");
+ ret = -ENXIO;
+ goto err_open;
+ }
+
+ ret = mxt_start(data);
+ if (ret)
+ goto err_open;
return 0;
+
+err_open:
+ return ret;
}
static void mxt_input_close(struct input_dev *dev)
@@ -1092,23 +1717,233 @@
mxt_stop(data);
}
+static int mxt_ts_finish_init(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+
+ error = request_threaded_irq(client->irq, NULL, mxt_interrupt,
+ data->pdata->irqflags, client->dev.driver->name, data);
+ if (error) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ goto err_req_irq;
+ }
+
+ error = mxt_make_highchg(data);
+ if (error) {
+ dev_err(&client->dev, "Failed to clear CHG pin\n");
+ goto err_req_irq;
+ }
+
+ dev_info(&client->dev, "Mxt touch controller initialized\n");
+
+ /*
+ * to prevent unnecessary report of touch event
+ * it will be enabled in open function
+ */
+ mxt_stop(data);
+
+ /* for blocking to be excuted open function untile finishing ts init */
+ complete_all(&data->init_done);
+ return 0;
+
+err_req_irq:
+ return error;
+}
+
+static int mxt_ts_rest_init(struct mxt_fw_info *fw_info)
+{
+ struct mxt_data *data = fw_info->data;
+ struct device *dev = &data->client->dev;
+ int error;
+
+ error = mxt_initialize(fw_info);
+ if (error) {
+ dev_err(dev, "Failed to initialize\n");
+ goto err_free_mem;
+ }
+
+ error = mxt_ts_finish_init(data);
+ if (error)
+ goto err_free_mem;
+
+ return 0;
+
+err_free_mem:
+ kfree(data->object_table);
+ data->object_table = NULL;
+ kfree(data->reportid_table);
+ data->reportid_table = NULL;
+ return error;
+}
+
+static int mxt_enter_bootloader(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+
+ data->object_table = kcalloc(data->info.object_num,
+ sizeof(struct mxt_object),
+ GFP_KERNEL);
+ if (!data->object_table) {
+ dev_err(dev, "%s Failed to allocate memory\n",
+ __func__);
+ error = -ENOMEM;
+ goto out;
+ }
+
+ /* Get object table information*/
+ error = mxt_get_object_table(data);
+ if (error)
+ goto err_free_mem;
+
+ /* Change to the bootloader mode */
+ mxt_write_object(data, MXT_GEN_COMMAND_T6,
+ MXT_COMMAND_RESET, MXT_BOOT_VALUE);
+ msleep(MXT_RESET_TIME);
+
+err_free_mem:
+ kfree(data->object_table);
+ data->object_table = NULL;
+
+out:
+ return error;
+}
+
+static int mxt_update_fw_on_probe(struct mxt_fw_info *fw_info)
+{
+ struct mxt_data *data = fw_info->data;
+ struct device *dev = &data->client->dev;
+ int error;
+
+ error = mxt_get_info(data);
+ if (error) {
+ /* need to check IC is in boot mode */
+ error = mxt_probe_bootloader(data->client_boot);
+ if (error) {
+ dev_err(dev, "Failed to verify bootloader's status\n");
+ goto out;
+ }
+
+ dev_info(dev, "Updating firmware from boot-mode\n");
+ goto load_fw;
+ }
+
+ /* compare the version to verify necessity of firmware updating */
+ if (data->info.version == fw_info->fw_ver
+ && data->info.build == fw_info->build_ver) {
+ dev_dbg(dev, "Firmware version is same with in IC\n");
+ goto out;
+ }
+
+ dev_info(dev, "Updating firmware from app-mode : IC:0x%x,0x%x =! FW:0x%x,0x%x\n",
+ data->info.version, data->info.build,
+ fw_info->fw_ver, fw_info->build_ver);
+
+ error = mxt_enter_bootloader(data);
+ if (error) {
+ dev_err(dev, "Failed updating firmware\n");
+ goto out;
+ }
+
+load_fw:
+ error = mxt_load_fw(fw_info);
+ if (error)
+ dev_err(dev, "Failed updating firmware\n");
+ else
+ dev_info(dev, "succeeded updating firmware\n");
+out:
+ return error;
+}
+
+static void mxt_request_firmware_work_func(const struct firmware *fw,
+ void *context)
+{
+ struct mxt_data *data = context;
+ struct mxt_fw_info fw_info;
+ int error;
+
+ memset(&fw_info, 0, sizeof(struct mxt_fw_info));
+ fw_info.data = data;
+
+ error = mxt_verify_fw(&fw_info, fw);
+ if (error)
+ goto ts_rest_init;
+
+ /* Skip update on boot up if firmware file does not have a header */
+ if (!fw_info.hdr_len)
+ goto ts_rest_init;
+
+ error = mxt_update_fw_on_probe(&fw_info);
+ if (error)
+ goto out;
+
+ts_rest_init:
+ error = mxt_ts_rest_init(&fw_info);
+out:
+ if (error)
+ /* complete anyway, so open() doesn't get blocked */
+ complete_all(&data->init_done);
+
+ release_firmware(fw);
+}
+
+static int __devinit mxt_ts_init(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ const char *firmware_name = data->pdata->firmware_name ?: MXT_FW_NAME;
+ int ret = 0;
+
+ ret = request_firmware_nowait(THIS_MODULE, true, firmware_name,
+ &client->dev, GFP_KERNEL,
+ data, mxt_request_firmware_work_func);
+ if (ret)
+ dev_err(&client->dev,
+ "cannot schedule firmware update (%d)\n", ret);
+
+ return ret;
+}
+
static int __devinit mxt_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
const struct mxt_platform_data *pdata = client->dev.platform_data;
struct mxt_data *data;
struct input_dev *input_dev;
- int error;
+ u16 boot_address;
+ int error, i;
if (!pdata)
return -EINVAL;
data = kzalloc(sizeof(struct mxt_data), GFP_KERNEL);
- input_dev = input_allocate_device();
- if (!data || !input_dev) {
+ if (!data) {
dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mxt_supply_names); i++)
+ data->supplies[i].supply = mxt_supply_names[i];
+
+ error = regulator_bulk_get(&client->dev, ARRAY_SIZE(data->supplies),
+ data->supplies);
+ if (error)
+ goto err_get_regulator;
+
+ if (pdata->gpio_reset) {
+ error = gpio_request_one(pdata->gpio_reset, GPIOF_OUT_INIT_LOW,
+ "atmel_mxt_ts nRESET");
+ if (error) {
+ dev_err(&client->dev, "Unable to get the reset gpio.\n");
+ goto err_request_gpio;
+ }
+ }
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ dev_err(&client->dev, "Failed to allocate input device\n");
error = -ENOMEM;
- goto err_free_mem;
+ goto err_allocate_input_device;
}
input_dev->name = "Atmel maXTouch Touchscreen";
@@ -1121,22 +1956,13 @@
data->input_dev = input_dev;
data->pdata = pdata;
data->irq = client->irq;
+ init_completion(&data->init_done);
mxt_calc_resolution(data);
__set_bit(EV_ABS, input_dev->evbit);
- __set_bit(EV_KEY, input_dev->evbit);
- __set_bit(BTN_TOUCH, input_dev->keybit);
+ __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
- /* For single touch */
- input_set_abs_params(input_dev, ABS_X,
- 0, data->max_x, 0, 0);
- input_set_abs_params(input_dev, ABS_Y,
- 0, data->max_y, 0, 0);
- input_set_abs_params(input_dev, ABS_PRESSURE,
- 0, 255, 0, 0);
-
- /* For multi touch */
input_mt_init_slots(input_dev, MXT_MAX_FINGER);
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
0, MXT_MAX_AREA, 0, 0);
@@ -1146,45 +1972,67 @@
0, data->max_y, 0, 0);
input_set_abs_params(input_dev, ABS_MT_PRESSURE,
0, 255, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_ORIENTATION,
+ 0, 255, 0, 0);
input_set_drvdata(input_dev, data);
i2c_set_clientdata(client, data);
- error = mxt_initialize(data);
- if (error)
- goto err_free_object;
-
- error = request_threaded_irq(client->irq, NULL, mxt_interrupt,
- pdata->irqflags, client->dev.driver->name, data);
- if (error) {
- dev_err(&client->dev, "Failed to register interrupt\n");
- goto err_free_object;
+ if (data->pdata->boot_address) {
+ boot_address = data->pdata->boot_address;
+ } else {
+ if (client->addr == MXT_APP_LOW)
+ boot_address = MXT_BOOT_LOW;
+ else
+ boot_address = MXT_BOOT_HIGH;
}
- error = mxt_make_highchg(data);
- if (error)
- goto err_free_irq;
+ data->client_boot = i2c_new_dummy(client->adapter, boot_address);
+ if (!data->client_boot) {
+ dev_err(&client->dev, "Fail to register sub client[0x%x]\n",
+ boot_address);
+ error = -ENODEV;
+ goto err_create_sub_client;
+ }
error = input_register_device(input_dev);
if (error)
- goto err_free_irq;
+ goto err_register_input_device;
error = sysfs_create_group(&client->dev.kobj, &mxt_attr_group);
if (error)
- goto err_unregister_device;
+ goto err_create_attr_group;
+
+ error = mxt_power_on(data);
+ if (error)
+ goto err_power_on;
+
+ error = mxt_ts_init(data);
+ if (error)
+ goto err_init;
return 0;
-err_unregister_device:
+err_init:
+ mxt_power_off(data);
+err_power_on:
+ sysfs_remove_group(&client->dev.kobj, &mxt_attr_group);
+err_create_attr_group:
input_unregister_device(input_dev);
input_dev = NULL;
-err_free_irq:
- free_irq(client->irq, data);
-err_free_object:
- kfree(data->object_table);
-err_free_mem:
+
+err_register_input_device:
+ i2c_unregister_device(data->client_boot);
+err_create_sub_client:
input_free_device(input_dev);
+err_allocate_input_device:
+ if (pdata->gpio_reset)
+ gpio_free(pdata->gpio_reset);
+err_request_gpio:
+ regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies);
+err_get_regulator:
kfree(data);
+
return error;
}
@@ -1193,9 +2041,16 @@
struct mxt_data *data = i2c_get_clientdata(client);
sysfs_remove_group(&client->dev.kobj, &mxt_attr_group);
+ enable_irq(data->irq);
free_irq(data->irq, data);
input_unregister_device(data->input_dev);
+ i2c_unregister_device(data->client_boot);
kfree(data->object_table);
+ kfree(data->reportid_table);
+ mxt_power_off(data);
+ regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies);
+ if (data->pdata->gpio_reset)
+ gpio_free(data->pdata->gpio_reset);
kfree(data);
return 0;
@@ -1224,12 +2079,6 @@
struct mxt_data *data = i2c_get_clientdata(client);
struct input_dev *input_dev = data->input_dev;
- /* Soft reset */
- mxt_write_object(data, MXT_GEN_COMMAND_T6,
- MXT_COMMAND_RESET, 1);
-
- msleep(MXT_RESET_TIME);
-
mutex_lock(&input_dev->mutex);
if (input_dev->users)
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ef2ddd4..16a91a8 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -394,6 +394,14 @@
This option enables support for on-chip LED drivers on
MAXIM MAX8997 PMIC.
+config LEDS_AS3668
+ tristate "LED Support for Austriamicrosystems AS3668"
+ depends on LEDS_CLASS && LEDS_TRIGGERS && I2C
+ help
+ This option enables support for LEDs connected to Austriamicrosystems
+ AS3668 led controller accessed via the I2C bus.
+ Brightness control and hardware-assisted blinking are supported.
+
config LEDS_OT200
tristate "LED support for the Bachmann OT200"
depends on LEDS_CLASS && HAS_IOMEM
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 9d3b1094..e22d47f 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -45,6 +45,7 @@
obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o
obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
+obj-$(CONFIG_LEDS_AS3668) += leds-as3668.o
# LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
diff --git a/drivers/leds/leds-as3668.c b/drivers/leds/leds-as3668.c
new file mode 100644
index 0000000..b42e9c3
--- /dev/null
+++ b/drivers/leds/leds-as3668.c
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics. 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 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 St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/leds-as3668.h>
+
+#define AS3668_DRV_NAME "as3668"
+#define AS3668_CHIPID1 0xA5
+
+/* AS3668 registers */
+#define AS3668_REG_CURRX_CTRL 0x01
+#define AS3668_REG_CURR1_CURRENT 0x02
+#define AS3668_REG_CURR2_CURRENT 0x03
+#define AS3668_REG_CURR3_CURRENT 0x04
+#define AS3668_REG_CURR4_CURRENT 0x05
+#define AS3668_REG_GPIO_CTRL 0x06
+#define AS3668_REG_PWM_CTRL 0x15
+#define AS3668_REG_PWM_TIMING 0x16
+#define AS3668_REG_PATTERN_TIMING 0x18
+#define AS3668_REG_FRAME_MASK 0x1A
+#define AS3668_REG_PATTERN_START_CTRL 0x1B
+#define AS3668_REG_FRAME_START_DELAY 0x1C
+#define AS3668_REG_OVERTEMP_CTRL 0x29
+#define AS3668_REG_CHIPID1 0x3E
+#define AS3668_REG_CHIPID2 0x3F
+#define AS3668_REG_AUDIO_INPUT_BUFFER 0x41
+#define AS3668_REG_AUDIO_CTRL 0x42
+#define AS3668_REG_AUDIO_OUTPUT 0x43
+
+#define AS3668_PATTERN_START_ON 0x02
+
+struct as3668_led {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct led_classdev ldev;
+ u32 color; /* 0xRRGGBBWW */
+ u8 led_array[AS3668_LED_NUM]; /* C1 C2 C3 C4 */
+ u8 pwm_dim_speed;
+ u8 pattern_frame_mask; /* 0b F4 F4 F3 F3 F2 F2 F1 F1 */
+ u8 pattern_frame_delay; /* 0b D4 D4 D3 D3 D2 D2 D1 D0 */
+ bool blinking;
+ u32 pattern;
+};
+
+enum AS3668_MODE {
+ MODE_OFF = 0x00,
+ MODE_ON = 0x55,
+ MODE_PWM = 0xAA,
+ MODE_PATTERN = 0xFF,
+};
+
+/* voodoo from AS3668 data sheet Revision 1.11, page 41 */
+static const u16 pattern_toff_time[8] = {80, 150, 280, 540,
+ 1100, 2100, 4200, 8400};
+static const u16 pattern_ton_time[8] = {40, 70, 140, 270,
+ 530, 1100, 2100, 4200};
+/* voodoo from AS3668 data sheet Revision 1.11, page 39 */
+static const u16 pattern_dim_speed_time[16] = {0, 120, 250,
+ 380, 510, 770, 1000, 1600, 2100, 2600,
+ 3100, 4200, 5200, 6200, 7300, 8300};
+
+static s32 as3668_i2c_update_bits(struct i2c_client *client,
+ u8 addr, u8 mask, u8 val)
+{
+ s32 ret;
+ u8 value;
+ ret = i2c_smbus_read_byte_data(client, addr);
+ if (ret < 0) {
+ pr_err("Can't read 0x%02x via i2c\n", addr);
+ return ret;
+ }
+
+ value = ret & ~mask;
+ value |= (val & mask);
+
+ ret = i2c_smbus_write_byte_data(client, addr, value);
+ if (ret) {
+ pr_err("Can't write to 0x%02x via i2c\n", addr);
+ return ret;
+ }
+ return 0;
+}
+
+static int as3668_binary_search(u16 value, const u16 array[], int low, int high)
+{
+ int mid;
+ if (value >= array[high])
+ return high;
+ else if (value <= array[low])
+ return low;
+
+ while (high > low + 1) {
+ mid = (high + low) / 2;
+ if (value < array[mid])
+ high = mid;
+ else
+ low = mid;
+ }
+ if ((array[high] - value) > (value - array[low]))
+ mid = low;
+ else
+ mid = high;
+
+ return mid;
+}
+
+static s32 as3668_set_color(const struct as3668_led *led, u8 weight,
+ u32 color)
+{
+ int ret;
+ int i;
+ for (i = AS3668_REG_CURR1_CURRENT; i <= AS3668_REG_CURR4_CURRENT; i++) {
+ ret = i2c_smbus_write_byte_data(led->client, i,
+ ((weight + 1) * (u8)(color >>
+ led->led_array[i - AS3668_REG_CURR1_CURRENT]))
+ >> 8);
+ if (ret) {
+ pr_err("Fail to set color\n");
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int as3668_update(struct as3668_led *led)
+{
+ int ret;
+ enum AS3668_MODE mode;
+
+ if (led->ldev.brightness == 0)
+ mode = MODE_OFF;
+ else if (led->blinking)
+ mode = MODE_PATTERN;
+ else
+ mode = MODE_ON;
+
+ if (led->blinking) {
+ ret = i2c_smbus_write_byte_data(led->client,
+ AS3668_REG_PATTERN_TIMING, led->pattern);
+ if (ret) {
+ pr_err("Can't update AS3668_REG_CURRX_CTRL\n");
+ return ret;
+ }
+ }
+
+ ret = i2c_smbus_write_byte_data(led->client, AS3668_REG_CURRX_CTRL,
+ mode);
+ if (ret) {
+ pr_err("Can't write AS3668_REG_CURRX_CTRL\n");
+ return ret;
+ }
+
+ ret = as3668_set_color(led, led->ldev.brightness, led->color);
+ if (ret) {
+ pr_err("Can't set color\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void as3668_set_led_brightness(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+
+ mutex_lock(&led->lock);
+ if (value == 0)
+ led->blinking = false;
+ led->ldev.brightness = (u8)value;
+ as3668_update(led);
+ mutex_unlock(&led->lock);
+}
+
+static int as3668_set_led_blink(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ int ret;
+ int pattern_on;
+ int pattern_off;
+
+ mutex_lock(&led->lock);
+ led->blinking = true;
+
+ pattern_on = as3668_binary_search(*delay_on, pattern_ton_time,
+ 0, ARRAY_SIZE(pattern_ton_time) - 1);
+ *delay_on = pattern_ton_time[pattern_on];
+
+ pattern_off = as3668_binary_search(*delay_off,
+ pattern_toff_time, 0,
+ ARRAY_SIZE(pattern_toff_time) - 1);
+ *delay_off = pattern_toff_time[pattern_off];
+
+ led->pattern = (0x07 & pattern_on) | (0x38 & (pattern_off << 3));
+ ret = as3668_update(led);
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static ssize_t as3668_color_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+
+ return sprintf(buf, "0x%08x\n", led->color);
+}
+
+static ssize_t as3668_color_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ unsigned int new_value;
+ int err;
+
+ err = kstrtouint(buf, 16, &new_value);
+ if (err)
+ return err;
+
+ mutex_lock(&led->lock);
+ led->color = new_value;
+ as3668_update(led);
+ mutex_unlock(&led->lock);
+ return size;
+}
+
+static ssize_t as3668_slope_up_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ return sprintf(buf, "%d\n",
+ pattern_dim_speed_time[(led->pwm_dim_speed & 0xF0) >> 4]);
+}
+
+static ssize_t as3668_slope_up_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ u16 new_value;
+ int err;
+ int index;
+
+ err = kstrtou16(buf, 10, &new_value);
+ if (err)
+ return err;
+
+ index = as3668_binary_search(new_value, pattern_dim_speed_time,
+ 0, ARRAY_SIZE(pattern_dim_speed_time) - 1);
+
+ mutex_lock(&led->lock);
+ led->pwm_dim_speed &= 0x0F;
+ led->pwm_dim_speed |= (0xF0 & index << 4);
+ i2c_smbus_write_byte_data(led->client, AS3668_REG_PWM_TIMING,
+ led->pwm_dim_speed);
+ mutex_unlock(&led->lock);
+
+ return size;
+}
+
+static ssize_t as3668_slope_down_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ return sprintf(buf, "%d\n",
+ pattern_dim_speed_time[led->pwm_dim_speed & 0x0F]);
+}
+
+static ssize_t as3668_slope_down_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ u16 new_value;
+ int err;
+ int index;
+
+ err = kstrtou16(buf, 10, &new_value);
+ if (err)
+ return err;
+
+ index = as3668_binary_search(new_value, pattern_dim_speed_time,
+ 0, ARRAY_SIZE(pattern_dim_speed_time) - 1);
+
+ mutex_lock(&led->lock);
+ led->pwm_dim_speed &= 0xF0;
+ led->pwm_dim_speed |= 0x0F & index;
+ i2c_smbus_write_byte_data(led->client, AS3668_REG_PWM_TIMING,
+ led->pwm_dim_speed);
+ mutex_unlock(&led->lock);
+
+ return size;
+}
+
+static ssize_t as3668_frame_mask_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ return sprintf(buf, "0x%08x\n", led->pattern_frame_mask);
+}
+
+static ssize_t as3668_frame_mask_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ u8 new_value;
+ int err;
+
+ err = kstrtou8(buf, 16, &new_value);
+ if (err)
+ return err;
+
+ mutex_lock(&led->lock);
+ led->pattern_frame_mask = new_value;
+ i2c_smbus_write_byte_data(led->client, AS3668_REG_FRAME_MASK,
+ new_value);
+ mutex_unlock(&led->lock);
+
+ return size;
+}
+
+static ssize_t as3668_frame_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ return sprintf(buf, "0x%08x\n", led->pattern_frame_delay);
+}
+
+static ssize_t as3668_frame_delay_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct as3668_led *led =
+ container_of(led_cdev, struct as3668_led, ldev);
+ u8 new_value;
+ int err;
+
+ err = kstrtou8(buf, 16, &new_value);
+ if (err)
+ return err;
+
+ mutex_lock(&led->lock);
+ led->pattern_frame_delay = new_value;
+ i2c_smbus_write_byte_data(led->client, AS3668_REG_FRAME_START_DELAY,
+ new_value);
+ mutex_unlock(&led->lock);
+
+ return size;
+}
+
+static int __devinit as3668_initialize(struct i2c_client *client,
+ struct as3668_platform_data *pdata)
+{
+ s32 ret;
+
+ ret = i2c_smbus_read_byte_data(client, AS3668_REG_CHIPID1);
+ if (ret < 0) {
+ pr_err("Fail to read chip ID1\n");
+ goto err_init;
+ }
+ if (ret != AS3668_CHIPID1) {
+ pr_err("Unsupported chip_id=0x%02x).\n", ret);
+ ret = -ENODEV;
+ goto err_init;
+ }
+ ret = i2c_smbus_read_byte_data(client, AS3668_REG_CHIPID2);
+ if (ret < 0) {
+ pr_err("Fail to read chip ID2\n");
+ goto err_init;
+ }
+ pr_debug("AS3668 chip id2:0x%02x rev:%d\n", ret & 0xF0, ret & 0x0F);
+
+ ret = i2c_smbus_write_byte_data(client, AS3668_REG_CURRX_CTRL,
+ MODE_OFF);
+ if (ret) {
+ pr_err("Can't write AS3668_REG_CURRX_CTRL\n");
+ goto err_init;
+ }
+ ret = as3668_i2c_update_bits(client, AS3668_REG_PATTERN_START_CTRL,
+ 0x03, pdata->pattern_start_source |
+ AS3668_PATTERN_START_ON);
+ if (ret) {
+ pr_err("Can't update AS3668_REG_PATTERN_START_CTRL\n");
+ goto err_init;
+ }
+ ret = as3668_i2c_update_bits(client, AS3668_REG_PWM_CTRL, 0x01,
+ pdata->pwm_source);
+ if (ret) {
+ pr_err("Can't update AS3668_REG_PWM_CTRL\n");
+ goto err_init;
+ }
+ ret = as3668_i2c_update_bits(client, AS3668_REG_GPIO_CTRL, 0x07,
+ pdata->gpio_input_invert << 2 |
+ pdata->gpio_input_mode << 1 |
+ pdata->gpio_mode);
+ if (ret) {
+ pr_err("Can't update AS3668_REG_GPIO_CTRL\n");
+ goto err_init;
+ }
+ ret = as3668_i2c_update_bits(client, AS3668_REG_AUDIO_CTRL, 0xE0,
+ pdata->audio_input_pin << 7 |
+ pdata->audio_pulldown_off << 6 |
+ pdata->audio_adc_characteristic << 5);
+ if (ret) {
+ pr_err("Can't update AS3668_REG_AUDIO_CTRL\n");
+ goto err_init;
+ }
+ ret = as3668_i2c_update_bits(client, AS3668_REG_AUDIO_INPUT_BUFFER,
+ 0xC0,
+ pdata->audio_dis_start << 7 |
+ pdata->audio_man_start << 6);
+ if (ret) {
+ pr_err("Can't update AS3668_REG_AUDIO_INPUT_BUFFER\n");
+ goto err_init;
+ }
+
+ ret = as3668_i2c_update_bits(client, AS3668_REG_OVERTEMP_CTRL, 0x70,
+ pdata->vbat_monitor_voltage_index << 5 |
+ pdata->shutdown_enable << 4);
+ if (ret) {
+ pr_err("Can't update AS3668_REG_OVERTEMP_CTRL\n");
+ goto err_init;
+ }
+
+ return 0;
+
+err_init:
+ return ret;
+}
+
+static int __devinit as3668_led_initialize(struct i2c_client *client,
+ struct as3668_led *led, struct as3668_platform_data *pdata)
+{
+ int ret;
+
+ led->client = client;
+ led->color = 0xFFFFFFFF;
+ led->ldev.name = AS3668_DRV_NAME;
+ led->ldev.max_brightness = LED_FULL;
+ led->ldev.brightness = LED_OFF;
+ led->ldev.brightness_set = as3668_set_led_brightness;
+ led->ldev.blink_brightness = LED_FULL;
+ led->ldev.blink_set = as3668_set_led_blink;
+ memcpy(led->led_array, pdata->led_array, AS3668_LED_NUM);
+
+ ret = i2c_smbus_read_byte_data(client, AS3668_REG_PWM_TIMING);
+ if (ret < 0) {
+ pr_err("Fail to read AS3668_REG_PWM_TIMING\n");
+ return ret;
+ }
+ led->pwm_dim_speed = ret;
+ led->ldev.default_trigger = "none";
+ ret = i2c_smbus_read_byte_data(client, AS3668_REG_FRAME_MASK);
+ if (ret < 0) {
+ pr_err("Fail to read AS3668_REG_FRAME_MASK\n");
+ return ret;
+ }
+ led->pattern_frame_mask = ret;
+ ret = i2c_smbus_read_byte_data(client, AS3668_REG_FRAME_START_DELAY);
+ if (ret < 0) {
+ pr_err("Fail to read AS3668_REG_FRAME_START_DELAY\n");
+ return ret;
+ }
+ led->pattern_frame_delay = ret;
+
+ ret = led_classdev_register(&client->dev, &led->ldev);
+ if (ret) {
+ pr_err("Couldn't register LED %s\n", led->ldev.name);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void as3668_led_cleanup(struct as3668_led *led)
+{
+ as3668_set_led_brightness(&led->ldev, LED_OFF);
+ led_classdev_unregister(&led->ldev);
+}
+
+static DEVICE_ATTR(color, S_IRUGO | S_IWUSR | S_IWGRP,
+ as3668_color_show, as3668_color_store);
+static DEVICE_ATTR(slope_up, S_IRUGO | S_IWUSR | S_IWGRP,
+ as3668_slope_up_show, as3668_slope_up_store);
+static DEVICE_ATTR(slope_down, S_IRUGO | S_IWUSR | S_IWGRP,
+ as3668_slope_down_show, as3668_slope_down_store);
+static DEVICE_ATTR(frame_mask, S_IRUGO | S_IWUSR | S_IWGRP,
+ as3668_frame_mask_show, as3668_frame_mask_store);
+static DEVICE_ATTR(frame_delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ as3668_frame_delay_show, as3668_frame_delay_store);
+
+static struct attribute *as3668_sysfs_attrs[] = {
+ &dev_attr_color.attr,
+ &dev_attr_slope_up.attr,
+ &dev_attr_slope_down.attr,
+ &dev_attr_frame_mask.attr,
+ &dev_attr_frame_delay.attr,
+ NULL
+};
+
+static struct attribute_group as3668_attribute_group = {
+ .attrs = as3668_sysfs_attrs,
+};
+
+static int __devinit as3668_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct as3668_led *led;
+ struct as3668_platform_data *pdata = client->dev.platform_data;
+ int ret;
+
+ if (pdata == NULL) {
+ pr_err("No platform data\n");
+ return -EINVAL;
+ }
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA)) {
+ pr_err("Client not i2c capable\n");
+ return -ENOSYS;
+ }
+
+ led = kzalloc(sizeof(*led), GFP_KERNEL);
+ if (!led) {
+ pr_err("Failed to allocate memory for module\n");
+ return -ENOMEM;
+ }
+
+ ret = as3668_initialize(client, pdata);
+ if (ret) {
+ pr_err("Fail to initialize as3668\n");
+ return ret;
+ }
+
+ i2c_set_clientdata(client, led);
+ mutex_init(&led->lock);
+
+ ret = as3668_led_initialize(client, led, pdata);
+ if (ret) {
+ pr_err("Fail to initialize led device\n");
+ goto err_led_init;
+ }
+
+ ret = sysfs_create_group(&led->ldev.dev->kobj,
+ &as3668_attribute_group);
+ if (ret) {
+ pr_err("could not create sysfs group\n");
+ goto err_sysfs_create_group;
+ }
+ return 0;
+
+err_sysfs_create_group:
+ as3668_led_cleanup(led);
+err_led_init:
+ mutex_destroy(&led->lock);
+ kfree(led);
+ return ret;
+}
+
+static int __devexit as3668_remove(struct i2c_client *client)
+{
+ struct as3668_led *led = i2c_get_clientdata(client);
+
+ sysfs_remove_group(&led->ldev.dev->kobj,
+ &as3668_attribute_group);
+ as3668_led_cleanup(led);
+ mutex_destroy(&led->lock);
+ kfree(led);
+ return 0;
+}
+
+static void as3668_shutdown(struct i2c_client *client)
+{
+ struct as3668_led *led = i2c_get_clientdata(client);
+
+ as3668_set_led_brightness(&led->ldev, LED_OFF);
+}
+
+static const struct i2c_device_id as3668_id[] = {
+ {"as3668", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, as3668_id);
+static struct i2c_driver as3668_driver = {
+ .driver = {
+ .name = AS3668_DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = as3668_probe,
+ .remove = __devexit_p(as3668_remove),
+ .shutdown = as3668_shutdown,
+ .id_table = as3668_id,
+};
+
+module_i2c_driver(as3668_driver);
+
+MODULE_AUTHOR("Hyoung Wook Ham <hwham@sta.samsung.com>");
+MODULE_DESCRIPTION("AS3668 LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/video/exynos/tv/Kconfig b/drivers/media/video/exynos/tv/Kconfig
index aed965b..079bfd4 100644
--- a/drivers/media/video/exynos/tv/Kconfig
+++ b/drivers/media/video/exynos/tv/Kconfig
@@ -28,6 +28,7 @@
depends on VIDEO_EXYNOS_TV
depends on SWITCH
select VIDEO_EXYNOS_HDMIPHY
+ select FB_MODE_HELPERS
help
Say Y here if you want support for the HDMI output
interface in S5P Samsung SoC. The driver can be compiled
@@ -38,7 +39,7 @@
config VIDEO_EXYNOS_HDMI_AUDIO_I2S
bool "Enable HDMI audio using I2S path"
depends on VIDEO_EXYNOS_HDMI
- depends on SND_SOC_SAMSUNG_SMDK_WM8994
+ depends on SND_SOC_SAMSUNG_SMDK_WM8994 || SND_SOC_SAMSUNG_MANTA_WM1811
default y
help
Enables HDMI audio through I2S path.
diff --git a/drivers/media/video/exynos/tv/Makefile b/drivers/media/video/exynos/tv/Makefile
index 29cef3f..0fb127a 100644
--- a/drivers/media/video/exynos/tv/Makefile
+++ b/drivers/media/video/exynos/tv/Makefile
@@ -8,7 +8,7 @@
obj-$(CONFIG_VIDEO_EXYNOS_HDMIPHY) += s5p-hdmiphy.o
s5p-hdmiphy-y += hdmiphy_drv.o
obj-$(CONFIG_VIDEO_EXYNOS_HDMI) += s5p-hdmi.o
-s5p-hdmi-y += hdcp_drv.o hdmi_drv.o
+s5p-hdmi-y += hdcp_drv.o hdmi_drv.o hdmi_edid.o
obj-$(CONFIG_VIDEO_EXYNOS_SDO) += s5p-sdo.o
s5p-sdo-y += sdo_drv.o
obj-$(CONFIG_VIDEO_EXYNOS_MIXER) += s5p-mixer.o
diff --git a/drivers/media/video/exynos/tv/hdcp_drv.c b/drivers/media/video/exynos/tv/hdcp_drv.c
index 00cd209..d0d3190 100644
--- a/drivers/media/video/exynos/tv/hdcp_drv.c
+++ b/drivers/media/video/exynos/tv/hdcp_drv.c
@@ -416,12 +416,13 @@
{
struct device *dev = hdev->dev;
u8 val;
- unsigned long spin_flags;
- if (!is_hdmi_streaming(hdev))
+ mutex_lock(&hdev->mutex);
+
+ if (!is_hdmi_streaming(hdev)) {
+ mutex_unlock(&hdev->mutex);
return -ENODEV;
-
- spin_lock_irqsave(&hdev->hdcp_info.reset_lock, spin_flags);
+ }
hdev->hdcp_info.event = HDCP_EVENT_STOP;
hdev->hdcp_info.auth_status = NOT_AUTHENTICATED;
@@ -441,7 +442,7 @@
hdmi_writeb(hdev, HDMI_HDCP_CHECK_RESULT, HDMI_HDCP_CLR_ALL_RESULTS);
/* need some delay (at least 1 frame) */
- mdelay(16);
+ msleep(16);
hdcp_sw_reset(hdev);
@@ -449,7 +450,8 @@
HDMI_WATCHDOG_INT_EN | HDMI_WTFORACTIVERX_INT_EN;
hdmi_write_mask(hdev, HDMI_STATUS_EN, ~0, val);
hdmi_write_mask(hdev, HDMI_HDCP_CTRL1, ~0, HDMI_HDCP_CP_DESIRED_EN);
- spin_unlock_irqrestore(&hdev->hdcp_info.reset_lock, spin_flags);
+
+ mutex_unlock(&hdev->mutex);
return 0;
}
@@ -916,14 +918,12 @@
int hdcp_prepare(struct hdmi_device *hdev)
{
- hdev->hdcp_wq = create_workqueue("khdcpd");
+ hdev->hdcp_wq = create_singlethread_workqueue("khdcpd");
if (hdev->hdcp_wq == NULL)
return -ENOMEM;
INIT_WORK(&hdev->work, hdcp_work);
- spin_lock_init(&hdev->hdcp_info.reset_lock);
-
#if defined(CONFIG_VIDEO_EXYNOS_HDCP)
hdev->hdcp_info.hdcp_enable = 1;
#else
diff --git a/drivers/media/video/exynos/tv/hdmi.h b/drivers/media/video/exynos/tv/hdmi.h
index 1858ecc..f388a0b 100644
--- a/drivers/media/video/exynos/tv/hdmi.h
+++ b/drivers/media/video/exynos/tv/hdmi.h
@@ -21,6 +21,7 @@
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
+#include <linux/mutex.h>
#include <linux/regulator/consumer.h>
#include <linux/switch.h>
@@ -29,6 +30,9 @@
#define INFOFRAME_CNT 2
+/* default preset configured on probe */
+#define HDMI_DEFAULT_PRESET V4L2_DV_720P60
+
#define HDMI_VSI_VERSION 0x01
#define HDMI_AVI_VERSION 0x02
#define HDMI_AUI_VERSION 0x01
@@ -36,10 +40,14 @@
#define HDMI_AVI_LENGTH 0x0d
#define HDMI_AUI_LENGTH 0x0a
+#define AVI_UNDERSCAN (2 << 0)
#define AVI_ACTIVE_FORMAT_VALID (1 << 4)
#define AVI_PIC_ASPECT_RATIO_4_3 (1 << 4)
#define AVI_PIC_ASPECT_RATIO_16_9 (2 << 4)
#define AVI_SAME_AS_PIC_ASPECT_RATIO 8
+#define AVI_LIMITED_RANGE (1 << 2)
+#define AVI_FULL_RANGE (2 << 2)
+#define AVI_ITU709 (2 << 6)
/* HDMI audio configuration value */
#define DEFAULT_SAMPLE_RATE 44100
@@ -258,7 +266,6 @@
u8 is_repeater;
u32 hdcp_start;
int hdcp_enable;
- spinlock_t reset_lock;
enum HDCP_EVENT event;
enum HDCP_STATE auth_status;
@@ -294,6 +301,8 @@
struct hdmi_infoframe infoframe[INFOFRAME_CNT];
/** audio on/off control flag */
int audio_enable;
+ /** number of audio channels */
+ int audio_channel_count;
/** audio sample rate */
int sample_rate;
/** audio bits per sample */
@@ -312,11 +321,15 @@
/* HPD releated */
struct work_struct hpd_work;
- struct work_struct hpd_work_ext;
+ struct delayed_work hpd_work_ext;
struct switch_dev hpd_switch;
+ struct switch_dev hpd_audio_switch;
/* choose DVI or HDMI mode */
int dvi_mode;
+
+ struct mutex mutex;
+ int color_range;
};
struct hdmi_conf {
@@ -341,6 +354,7 @@
int is_hdmiphy_ready(struct hdmi_device *hdev);
void hdmi_enable(struct hdmi_device *hdev, int on);
void hdmi_hpd_enable(struct hdmi_device *hdev, int on);
+void hdmi_hpd_clear_int(struct hdmi_device *hdev);
void hdmi_tg_enable(struct hdmi_device *hdev, int on);
void hdmi_reg_stop_vsi(struct hdmi_device *hdev);
void hdmi_reg_infoframe(struct hdmi_device *hdev,
@@ -362,6 +376,7 @@
void hdmi_dumpregs(struct hdmi_device *hdev, char *prefix);
void hdmi_set_3d_info(struct hdmi_device *hdev);
void hdmi_set_dvi_mode(struct hdmi_device *hdev);
+void hdmi_debugfs_init(struct hdmi_device *hdev);
/** HDCP functions */
irqreturn_t hdcp_irq_handler(struct hdmi_device *hdev);
@@ -371,6 +386,13 @@
int hdcp_i2c_read(struct hdmi_device *hdev, u8 offset, int bytes, u8 *buf);
int hdcp_i2c_write(struct hdmi_device *hdev, u8 offset, int bytes, u8 *buf);
+/** EDID functions */
+int edid_update(struct hdmi_device *hdev);
+u32 edid_enum_presets(struct hdmi_device *hdev, int index);
+u32 edid_preferred_preset(struct hdmi_device *hdev);
+bool edid_supports_hdmi(struct hdmi_device *hdev);
+int edid_max_audio_channels(struct hdmi_device *hdev);
+
static inline
void hdmi_write(struct hdmi_device *hdev, u32 reg_id, u32 value)
{
diff --git a/drivers/media/video/exynos/tv/hdmi_drv.c b/drivers/media/video/exynos/tv/hdmi_drv.c
index edc8e33..ade018f 100644
--- a/drivers/media/video/exynos/tv/hdmi_drv.c
+++ b/drivers/media/video/exynos/tv/hdmi_drv.c
@@ -42,9 +42,6 @@
MODULE_DESCRIPTION("Samsung HDMI");
MODULE_LICENSE("GPL");
-/* default preset configured on probe */
-#define HDMI_DEFAULT_PRESET V4L2_DV_1080P60
-
/* I2C module and id for HDMIPHY */
static struct i2c_board_info hdmiphy_info = {
I2C_BOARD_INFO("hdmiphy", 0x38),
@@ -276,14 +273,16 @@
#endif
disable_irq(hdev->ext_irq);
- cancel_work_sync(&hdev->hpd_work_ext);
+ cancel_delayed_work_sync(&hdev->hpd_work_ext);
s5p_v4l2_int_src_hdmi_hpd();
hdmi_hpd_enable(hdev, 1);
+ hdmi_hpd_clear_int(hdev);
enable_irq(hdev->int_irq);
dev_info(hdev->dev, "HDMI interrupt changed to internal\n");
} else {
+ cancel_work_sync(&hdev->work);
hdmi_hpd_enable(hdev, 0);
disable_irq(hdev->int_irq);
cancel_work_sync(&hdev->hpd_work);
@@ -320,6 +319,35 @@
case V4L2_CID_TV_SET_ASPECT_RATIO:
hdev->aspect = ctrl->value;
break;
+ case V4L2_CID_TV_ENABLE_HDMI_AUDIO:
+ mutex_lock(&hdev->mutex);
+ hdev->audio_enable = !!ctrl->value;
+ if (is_hdmi_streaming(hdev)) {
+ hdmi_set_infoframe(hdev);
+
+ hdmi_audio_enable(hdev, hdev->audio_enable);
+ }
+ mutex_unlock(&hdev->mutex);
+ break;
+ case V4L2_CID_TV_SET_NUM_CHANNELS:
+ mutex_lock(&hdev->mutex);
+ if ((ctrl->value == 2) || (ctrl->value == 6) ||
+ (ctrl->value == 8)) {
+ hdev->audio_channel_count = ctrl->value;
+ } else {
+ dev_err(dev, "invalid channel count\n");
+ hdev->audio_channel_count = 2;
+ }
+ if (is_hdmi_streaming(hdev))
+ hdmi_set_infoframe(hdev);
+ mutex_unlock(&hdev->mutex);
+ break;
+ case V4L2_CID_TV_SET_COLOR_RANGE:
+ hdev->color_range = ctrl->value;
+ break;
+ case V4L2_CID_TV_HDCP_ENABLE:
+ hdev->hdcp_info.hdcp_enable = ctrl->value;
+ break;
default:
dev_err(dev, "invalid control id\n");
ret = -EINVAL;
@@ -333,12 +361,25 @@
{
struct hdmi_device *hdev = sd_to_hdmi_dev(sd);
struct device *dev = hdev->dev;
+ int ret = 0;
- ctrl->value = switch_get_state(&hdev->hpd_switch);
- dev_dbg(dev, "HDMI cable is %s\n", ctrl->value ?
- "connected" : "disconnected");
+ switch (ctrl->id) {
+ case V4L2_CID_TV_HPD_STATUS:
+ ctrl->value = switch_get_state(&hdev->hpd_switch);
+ break;
+ case V4L2_CID_TV_GET_DVI_MODE:
+ ctrl->value = hdev->dvi_mode;
+ break;
+ case V4L2_CID_TV_MAX_AUDIO_CHANNELS:
+ ctrl->value = edid_max_audio_channels(hdev);
+ break;
+ default:
+ dev_err(dev, "invalid control id\n");
+ ret = -EINVAL;
+ break;
+ }
- return 0;
+ return ret;
}
static int hdmi_s_dv_preset(struct v4l2_subdev *sd,
@@ -395,11 +436,15 @@
}
static int hdmi_enum_dv_presets(struct v4l2_subdev *sd,
- struct v4l2_dv_enum_preset *preset)
+ struct v4l2_dv_enum_preset *enum_preset)
{
- if (preset->index >= hdmi_pre_cnt)
+ struct hdmi_device *hdev = sd_to_hdmi_dev(sd);
+ u32 preset = edid_enum_presets(hdev, enum_preset->index);
+
+ if (preset == V4L2_DV_INVALID)
return -EINVAL;
- return v4l_fill_dv_preset_info(hdmi_conf[preset->index].preset, preset);
+
+ return v4l_fill_dv_preset_info(preset, enum_preset);
}
static const struct v4l2_subdev_core_ops hdmi_sd_core_ops = {
@@ -680,33 +725,54 @@
irqreturn_t hdmi_irq_handler_ext(int irq, void *dev_data)
{
struct hdmi_device *hdev = dev_data;
- queue_work(system_nrt_wq, &hdev->hpd_work_ext);
+ queue_delayed_work(system_nrt_wq, &hdev->hpd_work_ext, 0);
return IRQ_HANDLED;
}
+static void hdmi_hpd_changed(struct hdmi_device *hdev, int state)
+{
+ u32 preset;
+ int ret;
+
+ if (state == switch_get_state(&hdev->hpd_switch))
+ return;
+
+ if (state) {
+ ret = edid_update(hdev);
+ if (ret == -ENODEV)
+ return;
+
+ preset = edid_preferred_preset(hdev);
+ if (preset == V4L2_DV_INVALID)
+ preset = HDMI_DEFAULT_PRESET;
+
+ hdev->dvi_mode = !edid_supports_hdmi(hdev);
+ hdev->cur_preset = preset;
+ hdev->cur_conf = hdmi_preset2conf(preset);
+ }
+
+ switch_set_state(&hdev->hpd_switch, state);
+ switch_set_state(&hdev->hpd_audio_switch, state ? !hdev->dvi_mode : 0);
+
+ dev_info(hdev->dev, "%s\n", state ? "plugged" : "unplugged");
+}
+
static void hdmi_hpd_work_ext(struct work_struct *work)
{
int state;
struct hdmi_device *hdev = container_of(work, struct hdmi_device,
- hpd_work_ext);
-
+ hpd_work_ext.work);
state = s5p_v4l2_hpd_read_gpio();
- switch_set_state(&hdev->hpd_switch, state);
-
- dev_info(hdev->dev, "%s (ext)\n", state ? "plugged" : "unplugged");
+ hdmi_hpd_changed(hdev, state);
}
static void hdmi_hpd_work(struct work_struct *work)
{
- int state;
struct hdmi_device *hdev = container_of(work, struct hdmi_device,
hpd_work);
- state = hdmi_hpd_status(hdev);
- switch_set_state(&hdev->hpd_switch, state);
-
- dev_info(hdev->dev, "%s (int)\n", state ? "plugged" : "unplugged");
+ hdmi_hpd_changed(hdev, 0);
}
static int __devinit hdmi_probe(struct platform_device *pdev)
@@ -769,7 +835,8 @@
hdmi_dev->int_irq = res->start;
INIT_WORK(&hdmi_dev->hpd_work, hdmi_hpd_work);
- INIT_WORK(&hdmi_dev->hpd_work_ext, hdmi_hpd_work_ext);
+ INIT_DELAYED_WORK(&hdmi_dev->hpd_work_ext, hdmi_hpd_work_ext);
+ mutex_init(&hdmi_dev->mutex);
/* setting v4l2 name to prevent WARN_ON in v4l2_device_register */
strlcpy(hdmi_dev->v4l2_dev.name, dev_name(dev),
@@ -827,6 +894,8 @@
hdmi_dev->hpd_switch.name = "hdmi";
switch_dev_register(&hdmi_dev->hpd_switch);
+ hdmi_dev->hpd_audio_switch.name = "hdmi_audio";
+ switch_dev_register(&hdmi_dev->hpd_audio_switch);
ret = request_irq(hdmi_dev->int_irq, hdmi_irq_handler,
0, "hdmi-int", hdmi_dev);
@@ -844,18 +913,15 @@
goto fail_ext;
}
- if (s5p_v4l2_hpd_read_gpio())
- switch_set_state(&hdmi_dev->hpd_switch, 1);
- else
- switch_set_state(&hdmi_dev->hpd_switch, 0);
-
hdmi_dev->cur_preset = HDMI_DEFAULT_PRESET;
/* FIXME: missing fail preset is not supported */
hdmi_dev->cur_conf = hdmi_preset2conf(hdmi_dev->cur_preset);
- /* default audio configuration : enable audio */
- hdmi_dev->audio_enable = 1;
+ /* default audio configuration : disable audio */
+ hdmi_dev->audio_enable = 0;
+ hdmi_dev->audio_channel_count = 2;
hdmi_dev->sample_rate = DEFAULT_SAMPLE_RATE;
+ hdmi_dev->color_range = 3;
hdmi_dev->bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
hdmi_dev->audio_codec = DEFAULT_AUDIO_CODEC;
@@ -874,8 +940,13 @@
if (ret)
goto fail_irq;
+ queue_delayed_work(system_nrt_wq, &hdmi_dev->hpd_work_ext,
+ msecs_to_jiffies(1000));
+
dev_info(dev, "probe sucessful\n");
+ hdmi_debugfs_init(hdmi_dev);
+
return 0;
fail_irq:
@@ -913,6 +984,7 @@
free_irq(hdmi_dev->ext_irq, hdmi_dev);
free_irq(hdmi_dev->int_irq, hdmi_dev);
switch_dev_unregister(&hdmi_dev->hpd_switch);
+ switch_dev_unregister(&hdmi_dev->hpd_audio_switch);
iounmap(hdmi_dev->regs);
hdmi_resources_cleanup(hdmi_dev);
flush_workqueue(hdmi_dev->hdcp_wq);
diff --git a/drivers/media/video/exynos/tv/hdmi_edid.c b/drivers/media/video/exynos/tv/hdmi_edid.c
new file mode 100644
index 0000000..150216c
--- /dev/null
+++ b/drivers/media/video/exynos/tv/hdmi_edid.c
@@ -0,0 +1,337 @@
+/*
+ * 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 <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+#include <linux/module.h>
+
+#include "hdmi.h"
+
+#define EDID_SEGMENT_ADDR (0x60 >> 1)
+#define EDID_ADDR (0xA0 >> 1)
+#define EDID_BLOCK_SIZE 128
+#define EDID_SEGMENT(x) ((x) >> 1)
+#define EDID_OFFSET(x) (((x) & 1) * EDID_BLOCK_SIZE)
+#define EDID_EXTENSION_FLAG 0x7E
+
+static struct i2c_client *edid_client;
+
+static struct edid_preset {
+ u32 preset;
+ u16 xres;
+ u16 yres;
+ u16 refresh;
+ char *name;
+ bool supported;
+} edid_presets[] = {
+ { V4L2_DV_480P59_94, 720, 480, 59, "480p@59.94"},
+ { V4L2_DV_576P50, 720, 576, 50, "576p@50" },
+ { V4L2_DV_720P24, 1280, 720, 24, "720p@24" },
+ { V4L2_DV_720P25, 1280, 720, 25, "720p@25" },
+ { V4L2_DV_720P30, 1280, 720, 30, "720p@30" },
+ { V4L2_DV_720P50, 1280, 720, 50, "720p@50" },
+ { V4L2_DV_720P59_94, 1280, 720, 59, "720p@59.94" },
+ { V4L2_DV_720P60, 1280, 720, 60, "720p@60" },
+ { V4L2_DV_1080P24, 1920, 1080, 24, "1080p@24" },
+ { V4L2_DV_1080P25, 1920, 1080, 25, "1080p@25" },
+ { V4L2_DV_1080P30, 1920, 1080, 30, "1080p@30" },
+ { V4L2_DV_1080P50, 1920, 1080, 50, "1080p@50" },
+ { V4L2_DV_1080P60, 1920, 1080, 60, "1080p@60" },
+};
+
+static u32 preferred_preset = HDMI_DEFAULT_PRESET;
+static u32 edid_misc;
+static int max_audio_channels;
+
+static int edid_i2c_read(struct hdmi_device *hdev, u8 segment, u8 offset,
+ u8 *buf, size_t len)
+{
+ struct device *dev = hdev->dev;
+ struct i2c_client *i2c = edid_client;
+ int cnt = 0;
+ int ret;
+ struct i2c_msg msg[] = {
+ {
+ .addr = EDID_SEGMENT_ADDR,
+ .flags = segment ? 0 : I2C_M_IGNORE_NAK,
+ .len = 1,
+ .buf = &segment
+ },
+ {
+ .addr = EDID_ADDR,
+ .flags = 0,
+ .len = 1,
+ .buf = &offset
+ },
+ {
+ .addr = EDID_ADDR,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = buf
+ }
+ };
+
+ if (!i2c)
+ return -ENODEV;
+
+ do {
+ ret = i2c_transfer(i2c->adapter, msg, ARRAY_SIZE(msg));
+ if (ret == ARRAY_SIZE(msg))
+ break;
+
+ dev_dbg(dev, "%s: can't read data, retry %d\n", __func__, cnt);
+ msleep(25);
+ cnt++;
+ } while (cnt < 5);
+
+ if (cnt == 5) {
+ dev_err(dev, "%s: can't read data, timeout\n", __func__);
+ return -ETIME;
+ }
+
+ return 0;
+}
+
+static int
+edid_read_block(struct hdmi_device *hdev, int block, u8 *buf, size_t len)
+{
+ struct device *dev = hdev->dev;
+ int ret, i;
+ u8 segment = EDID_SEGMENT(block);
+ u8 offset = EDID_OFFSET(block);
+ u8 sum = 0;
+
+ if (len < EDID_BLOCK_SIZE)
+ return -EINVAL;
+
+ ret = edid_i2c_read(hdev, segment, offset, buf, EDID_BLOCK_SIZE);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < EDID_BLOCK_SIZE; i++)
+ sum += buf[i];
+
+ if (sum) {
+ dev_err(dev, "%s: checksum error block=%d sum=%d\n", __func__,
+ block, sum);
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static int edid_read(struct hdmi_device *hdev, u8 **data)
+{
+ u8 block0[EDID_BLOCK_SIZE];
+ u8 *edid;
+ int block = 0;
+ int block_cnt, ret;
+
+ ret = edid_read_block(hdev, 0, block0, sizeof(block0));
+ if (ret)
+ return ret;
+
+ block_cnt = block0[EDID_EXTENSION_FLAG] + 1;
+
+ edid = kmalloc(block_cnt * EDID_BLOCK_SIZE, GFP_KERNEL);
+ if (!edid)
+ return -ENOMEM;
+
+ memcpy(edid, block0, sizeof(block0));
+
+ while (++block < block_cnt) {
+ ret = edid_read_block(hdev, block,
+ edid + block * EDID_BLOCK_SIZE,
+ EDID_BLOCK_SIZE);
+ if (ret) {
+ kfree(edid);
+ return ret;
+ }
+ }
+
+ *data = edid;
+ return block_cnt;
+}
+
+static struct edid_preset *edid_find_preset(struct fb_videomode *mode)
+{
+ struct edid_preset *preset = edid_presets;
+ int i;
+
+ if (mode->vmode & FB_VMODE_INTERLACED)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(edid_presets); i++, preset++) {
+ if (mode->refresh == preset->refresh &&
+ mode->xres == preset->xres &&
+ mode->yres == preset->yres) {
+ return preset;
+ }
+ }
+
+ return NULL;
+}
+
+static void edid_use_default_preset(void)
+{
+ int i;
+
+ preferred_preset = HDMI_DEFAULT_PRESET;
+ for (i = 0; i < ARRAY_SIZE(edid_presets); i++)
+ edid_presets[i].supported =
+ (edid_presets[i].preset == preferred_preset);
+ max_audio_channels = 2;
+}
+
+int edid_update(struct hdmi_device *hdev)
+{
+ struct fb_monspecs specs;
+ struct edid_preset *preset;
+ bool first = true;
+ u8 *edid = NULL;
+ int channels_max = 0;
+ int ret = 0;
+ int i;
+
+ edid_misc = 0;
+
+ ret = edid_read(hdev, &edid);
+ if (ret < 0)
+ goto out;
+
+ print_hex_dump_bytes("EDID: ", DUMP_PREFIX_OFFSET, edid,
+ ret * EDID_BLOCK_SIZE);
+
+ fb_edid_to_monspecs(edid, &specs);
+ for (i = 1; i < ret; i++)
+ fb_edid_add_monspecs(edid + i * EDID_BLOCK_SIZE, &specs);
+
+ preferred_preset = V4L2_DV_INVALID;
+ for (i = 0; i < ARRAY_SIZE(edid_presets); i++)
+ edid_presets[i].supported = false;
+
+ for (i = 0; i < specs.modedb_len; i++) {
+ preset = edid_find_preset(&specs.modedb[i]);
+ if (preset) {
+ pr_info("EDID: found %s", preset->name);
+ preset->supported = true;
+ if (first) {
+ preferred_preset = preset->preset;
+ first = false;
+ }
+ }
+ }
+
+ edid_misc = specs.misc;
+ pr_info("EDID: misc flags %08x", edid_misc);
+
+ for (i = 0; i < specs.audiodb_len; i++) {
+ if (specs.audiodb[i].format != FB_AUDIO_LPCM)
+ continue;
+ if (specs.audiodb[i].channel_count > channels_max)
+ channels_max = specs.audiodb[i].channel_count;
+ }
+
+ if (edid_misc & FB_MISC_HDMI) {
+ if (channels_max)
+ max_audio_channels = channels_max;
+ else
+ max_audio_channels = 2;
+ } else {
+ max_audio_channels = 0;
+ }
+ pr_info("EDID: Audio channels %d", max_audio_channels);
+
+out:
+ /* No supported preset found, use default */
+ if (first)
+ edid_use_default_preset();
+
+ kfree(edid);
+ return ret;
+}
+
+u32 edid_enum_presets(struct hdmi_device *hdev, int index)
+{
+ int i, j = 0;
+
+ for (i = 0; i < ARRAY_SIZE(edid_presets); i++) {
+ if (edid_presets[i].supported) {
+ if (j++ == index)
+ return edid_presets[i].preset;
+ }
+ }
+
+ return V4L2_DV_INVALID;
+}
+
+u32 edid_preferred_preset(struct hdmi_device *hdev)
+{
+ return preferred_preset;
+}
+
+bool edid_supports_hdmi(struct hdmi_device *hdev)
+{
+ return edid_misc & FB_MISC_HDMI;
+}
+
+int edid_max_audio_channels(struct hdmi_device *hdev)
+{
+ return max_audio_channels;
+}
+
+static int __devinit edid_probe(struct i2c_client *client,
+ const struct i2c_device_id *dev_id)
+{
+ edid_client = client;
+ edid_use_default_preset();
+ dev_info(&client->adapter->dev, "probed exynos edid\n");
+ return 0;
+}
+
+static int edid_remove(struct i2c_client *client)
+{
+ edid_client = NULL;
+ dev_info(&client->adapter->dev, "removed exynos edid\n");
+ return 0;
+}
+
+static struct i2c_device_id edid_idtable[] = {
+ {"exynos_edid", 0},
+};
+MODULE_DEVICE_TABLE(i2c, edid_idtable);
+
+static struct i2c_driver edid_driver = {
+ .driver = {
+ .name = "exynos_edid",
+ .owner = THIS_MODULE,
+ },
+ .id_table = edid_idtable,
+ .probe = edid_probe,
+ .remove = __devexit_p(edid_remove),
+};
+
+static int __init edid_init(void)
+{
+ return i2c_add_driver(&edid_driver);
+}
+
+static void __exit edid_exit(void)
+{
+ i2c_del_driver(&edid_driver);
+}
+module_init(edid_init);
+module_exit(edid_exit);
diff --git a/drivers/media/video/exynos/tv/hdmi_reg_5250.c b/drivers/media/video/exynos/tv/hdmi_reg_5250.c
index 3120a7a..37e33d4 100644
--- a/drivers/media/video/exynos/tv/hdmi_reg_5250.c
+++ b/drivers/media/video/exynos/tv/hdmi_reg_5250.c
@@ -12,8 +12,10 @@
*/
#include "hdmi.h"
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
+#include <linux/seq_file.h>
#include <plat/devs.h>
#include <plat/tv-core.h>
@@ -2257,21 +2259,21 @@
hdcp_stop(hdev);
hdmi_write_mask(hdev, HDMI_INTC_FLAG_0, ~0,
HDMI_INTC_FLAG_HPD_UNPLUG);
+ queue_work(system_nrt_wq, &hdev->hpd_work);
}
if (intc_flag & HDMI_INTC_FLAG_HPD_PLUG) {
hdmi_write_mask(hdev, HDMI_INTC_FLAG_0, ~0,
HDMI_INTC_FLAG_HPD_PLUG);
}
+
if (intc_flag & HDMI_INTC_FLAG_HDCP) {
- pr_info("hdcp interrupt occur\n");
+ pr_debug("%s: hdcp interrupt occur\n", __func__);
hdcp_irq_handler(hdev);
hdmi_write_mask(hdev, HDMI_INTC_FLAG_0, ~0,
HDMI_INTC_FLAG_HDCP);
}
}
- queue_work(system_nrt_wq, &hdev->hpd_work);
-
return IRQ_HANDLED;
}
@@ -2290,7 +2292,10 @@
/* RGB888 is default output format of HDMI,
* look to CEA-861-D, table 7 for more detail */
hdmi_writeb(hdev, HDMI_AVI_BYTE(1), 0 << 5);
- hdmi_write_mask(hdev, HDMI_CON_1, 2, 3 << 5);
+ if (hdev->color_range == 0 || hdev->color_range == 2)
+ hdmi_write_mask(hdev, HDMI_CON_1, 0, 3 << 5);
+ else
+ hdmi_write_mask(hdev, HDMI_CON_1, 1 << 5, 3 << 5);
}
void hdmi_set_dvi_mode(struct hdmi_device *hdev)
@@ -2476,9 +2481,19 @@
void hdmi_hpd_enable(struct hdmi_device *hdev, int on)
{
- /* enable HPD interrupts */
- hdmi_write_mask(hdev, HDMI_INTC_CON_0, ~0, HDMI_INTC_EN_GLOBAL |
- HDMI_INTC_EN_HPD_PLUG | HDMI_INTC_EN_HPD_UNPLUG);
+ /* enable/disable HPD interrupts */
+ if (on)
+ hdmi_write_mask(hdev, HDMI_INTC_CON_0, ~0, HDMI_INTC_EN_GLOBAL |
+ HDMI_INTC_EN_HPD_PLUG | HDMI_INTC_EN_HPD_UNPLUG);
+ else
+ hdmi_write_mask(hdev, HDMI_INTC_CON_0, 0, HDMI_INTC_EN_GLOBAL |
+ HDMI_INTC_EN_HPD_PLUG | HDMI_INTC_EN_HPD_UNPLUG);
+}
+
+void hdmi_hpd_clear_int(struct hdmi_device *hdev)
+{
+ hdmi_write_mask(hdev, HDMI_INTC_FLAG_0, ~0,
+ HDMI_INTC_FLAG_HPD_PLUG | HDMI_INTC_FLAG_HPD_UNPLUG);
}
void hdmi_tg_enable(struct hdmi_device *hdev, int on)
@@ -2565,7 +2580,7 @@
hdmi_writeb(hdev, HDMI_AVI_HEADER2, infoframe->len);
hdr_sum = infoframe->type + infoframe->ver + infoframe->len;
hdmi_writeb(hdev, HDMI_AVI_BYTE(1), hdev->output_fmt << 5 |
- AVI_ACTIVE_FORMAT_VALID);
+ AVI_ACTIVE_FORMAT_VALID | AVI_UNDERSCAN);
if (hdev->aspect == HDMI_ASPECT_RATIO_4_3 &&
(hdev->cur_preset == V4L2_DV_480P59_94 ||
hdev->cur_preset == V4L2_DV_480P60)) {
@@ -2583,7 +2598,11 @@
}
hdmi_writeb(hdev, HDMI_AVI_BYTE(2), aspect_ratio |
- AVI_SAME_AS_PIC_ASPECT_RATIO);
+ AVI_SAME_AS_PIC_ASPECT_RATIO | AVI_ITU709);
+ if (hdev->color_range == 0 || hdev->color_range == 2)
+ hdmi_writeb(hdev, HDMI_AVI_BYTE(3), AVI_FULL_RANGE);
+ else
+ hdmi_writeb(hdev, HDMI_AVI_BYTE(3), AVI_LIMITED_RANGE);
dev_dbg(dev, "VIC code = %d\n", vic);
hdmi_writeb(hdev, HDMI_AVI_BYTE(4), vic);
chksum = hdmi_chksum(hdev, HDMI_AVI_BYTE(1), infoframe->len, hdr_sum);
@@ -2596,6 +2615,13 @@
hdmi_writeb(hdev, HDMI_AUI_HEADER1, infoframe->ver);
hdmi_writeb(hdev, HDMI_AUI_HEADER2, infoframe->len);
hdr_sum = infoframe->type + infoframe->ver + infoframe->len;
+ /* speaker placement */
+ if (hdev->audio_channel_count == 6)
+ hdmi_writeb(hdev, HDMI_AUI_BYTE(4), 0x0b);
+ else if (hdev->audio_channel_count == 8)
+ hdmi_writeb(hdev, HDMI_AUI_BYTE(4), 0x13);
+ else
+ hdmi_writeb(hdev, HDMI_AUI_BYTE(4), 0x00);
chksum = hdmi_chksum(hdev, HDMI_AUI_BYTE(1), infoframe->len, hdr_sum);
dev_dbg(dev, "AUI checksum = 0x%x\n", chksum);
hdmi_writeb(hdev, HDMI_AUI_CHECK_SUM, chksum);
@@ -2751,7 +2777,20 @@
HDMI_I2S_CONSUMER_FORMAT;
hdmi_write(hdev, HDMI_I2S_CH_ST_0, val);
hdmi_write(hdev, HDMI_I2S_CH_ST_1, HDMI_I2S_CD_PLAYER);
- hdmi_write(hdev, HDMI_I2S_CH_ST_2, HDMI_I2S_SET_SOURCE_NUM(0));
+ hdmi_writeb(hdev, HDMI_I2S_CH_ST_2, HDMI_I2S_SET_SOURCE_NUM(0) |
+ HDMI_I2S_SET_CHANNEL_NUM(0x6));
+ hdmi_writeb(hdev, HDMI_ASP_CON,
+ HDMI_AUD_MODE_MULTI_CH | HDMI_AUD_SP_AUD2_EN |
+ HDMI_AUD_SP_AUD1_EN | HDMI_AUD_SP_AUD0_EN);
+ hdmi_writeb(hdev, HDMI_ASP_CHCFG0,
+ HDMI_SPK0R_SEL_I_PCM0R | HDMI_SPK0L_SEL_I_PCM0L);
+ hdmi_writeb(hdev, HDMI_ASP_CHCFG1,
+ HDMI_SPK0R_SEL_I_PCM1L | HDMI_SPK0L_SEL_I_PCM1R);
+ hdmi_writeb(hdev, HDMI_ASP_CHCFG2,
+ HDMI_SPK0R_SEL_I_PCM2R | HDMI_SPK0L_SEL_I_PCM2L);
+ hdmi_writeb(hdev, HDMI_ASP_CHCFG3,
+ HDMI_SPK0R_SEL_I_PCM3R | HDMI_SPK0L_SEL_I_PCM3L);
+
val = HDMI_I2S_CLK_ACCUR_LEVEL_1 |
HDMI_I2S_SET_SAMPLING_FREQ(sample_frq);
hdmi_write(hdev, HDMI_I2S_CH_ST_3, val);
@@ -2778,7 +2817,7 @@
void hdmi_audio_enable(struct hdmi_device *hdev, int on)
{
if (on) {
- if (hdev->dvi_mode)
+ if (hdev->dvi_mode || !hdev->audio_enable)
return;
hdmi_write_mask(hdev, HDMI_CON_0, ~0, HDMI_ASP_ENABLE);
} else
@@ -2806,7 +2845,7 @@
int is_hdmi_streaming(struct hdmi_device *hdev)
{
- if (hdmi_hpd_status(hdev) && hdev->streaming)
+ if (hdev->streaming && hdmi_hpd_status(hdev))
return 1;
return 0;
}
@@ -2856,6 +2895,89 @@
hdmi_write_mask(hdev, HDMI_CORE_RSTOUT, ~0, HDMI_CORE_SW_RSTOUT);
}
+static int hdmi_debugfs_show(struct seq_file *s, void *unused)
+{
+ struct hdmi_device *hdev = s->private;
+ int i;
+
+ mutex_lock(&hdev->mutex);
+
+ if (!hdev->streaming) {
+ mutex_unlock(&hdev->mutex);
+ seq_printf(s, "Not streaming\n");
+ return 0;
+ }
+
+#define DUMPREG(reg_id) \
+ seq_printf(s, "%-20s %08x\n", #reg_id, \
+ readl(hdev->regs + reg_id))
+
+ DUMPREG(HDMI_INTC_CON_0);
+ DUMPREG(HDMI_INTC_FLAG_0);
+ DUMPREG(HDMI_HPD_STATUS);
+ DUMPREG(HDMI_INTC_CON_1);
+ DUMPREG(HDMI_INTC_FLAG_1);
+ DUMPREG(HDMI_PHY_STATUS_0);
+ DUMPREG(HDMI_PHY_STATUS_PLL);
+ DUMPREG(HDMI_PHY_CON_0);
+ DUMPREG(HDMI_PHY_RSTOUT);
+ DUMPREG(HDMI_PHY_VPLL);
+ DUMPREG(HDMI_PHY_CMU);
+ DUMPREG(HDMI_CORE_RSTOUT);
+
+ DUMPREG(HDMI_CON_0);
+ DUMPREG(HDMI_CON_1);
+ DUMPREG(HDMI_CON_2);
+ DUMPREG(HDMI_STATUS);
+ DUMPREG(HDMI_PHY_STATUS);
+ DUMPREG(HDMI_STATUS_EN);
+ DUMPREG(HDMI_HPD);
+ DUMPREG(HDMI_MODE_SEL);
+ DUMPREG(HDMI_ENC_EN);
+ DUMPREG(HDMI_DC_CONTROL);
+ DUMPREG(HDMI_VIDEO_PATTERN_GEN);
+
+ DUMPREG(HDMI_AVI_CON);
+ DUMPREG(HDMI_AVI_HEADER0);
+ DUMPREG(HDMI_AVI_HEADER1);
+ DUMPREG(HDMI_AVI_HEADER2);
+ DUMPREG(HDMI_AVI_CHECK_SUM);
+ for (i = 1; i < 6; ++i)
+ DUMPREG(HDMI_AVI_BYTE(i));
+
+ DUMPREG(HDMI_VSI_CON);
+ DUMPREG(HDMI_VSI_HEADER0);
+ DUMPREG(HDMI_VSI_HEADER1);
+ DUMPREG(HDMI_VSI_HEADER2);
+ for (i = 0; i < 7; ++i)
+ DUMPREG(HDMI_VSI_DATA(i));
+ DUMPREG(HDMI_AUI_CON);
+ DUMPREG(HDMI_ACR_CON);
+
+#undef DUMPREG
+
+ mutex_unlock(&hdev->mutex);
+ return 0;
+}
+
+static int hdmi_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, hdmi_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations hdmi_debugfs_fops = {
+ .open = hdmi_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+void hdmi_debugfs_init(struct hdmi_device *hdev)
+{
+ debugfs_create_file(dev_name(hdev->dev), S_IRUGO, NULL,
+ hdev, &hdmi_debugfs_fops);
+}
+
void hdmi_dumpregs(struct hdmi_device *hdev, char *prefix)
{
#define DUMPREG(reg_id) \
diff --git a/drivers/media/video/exynos/tv/hdmiphy_conf_5250.c b/drivers/media/video/exynos/tv/hdmiphy_conf_5250.c
index 5cc0643..fcf7142 100644
--- a/drivers/media/video/exynos/tv/hdmiphy_conf_5250.c
+++ b/drivers/media/video/exynos/tv/hdmiphy_conf_5250.c
@@ -48,10 +48,10 @@
};
static const u8 hdmiphy_conf148_5[32] = {
- 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,
+ 0x01, 0xd1, 0x3e, 0x15, 0x40, 0x40, 0xf0, 0x08,
+ 0x82, 0xa0, 0x73, 0xd9, 0x45, 0xa0, 0xac, 0x20,
+ 0xca, 0x80, 0x11, 0x07, 0x02, 0x22, 0x44, 0x87,
+ 0x54, 0x4b, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
};
const struct hdmiphy_conf hdmiphy_conf[] = {
diff --git a/drivers/media/video/exynos/tv/mixer.h b/drivers/media/video/exynos/tv/mixer.h
index ad30225..e980c90 100644
--- a/drivers/media/video/exynos/tv/mixer.h
+++ b/drivers/media/video/exynos/tv/mixer.h
@@ -120,6 +120,13 @@
MXR_GEOMETRY_SOURCE,
};
+enum s5p_mixer_rgb {
+ MIXER_RGB601_0_255,
+ MIXER_RGB601_16_235,
+ MIXER_RGB709_0_255,
+ MIXER_RGB709_16_235
+};
+
/** description of transformation from source to destination image */
struct mxr_geometry {
/** cropping for source image */
@@ -141,6 +148,19 @@
struct list_head wait;
};
+struct mxr_layer_update {
+ bool update;
+ struct mxr_buffer *buffer;
+ const struct mxr_format *fmt;
+ struct mxr_geometry geo;
+};
+
+struct mxr_update {
+ struct work_struct work;
+ struct mxr_device *mdev;
+ struct mxr_layer_update layers[MXR_MAX_LAYERS];
+};
+
/** TV graphic layer pipeline state */
enum mxr_pipeline_state {
/** graphic layer is not shown */
@@ -174,7 +194,8 @@
/** setting buffer to HW */
void (*buffer_set)(struct mxr_layer *, struct mxr_buffer *);
/** setting format and geometry in HW */
- void (*format_set)(struct mxr_layer *);
+ void (*format_set)(struct mxr_layer *, const struct mxr_format *fmt,
+ struct mxr_geometry *geo);
/** streaming stop/start */
void (*stream_set)(struct mxr_layer *, int);
/** adjusting geometry */
@@ -217,8 +238,6 @@
/** list for buffers waiting on a fence */
struct list_head fence_wait_list;
- struct workqueue_struct *fence_wq;
- struct work_struct fence_work;
/** buffer currently owned by hardware in temporary registers */
struct mxr_buffer *update_buf;
@@ -300,11 +319,6 @@
struct clk *sclk_hdmi;
};
-/* event flags used */
-enum mxr_devide_flags {
- MXR_EVENT_VSYNC = 0,
-};
-
/** videobuf2 context of mixer */
struct mxr_vb2 {
const struct vb2_mem_ops *ops;
@@ -354,14 +368,16 @@
const struct mxr_vb2 *vb2;
/** context of allocator */
void *alloc_ctx;
- /** event wait queue */
- wait_queue_head_t event_queue;
- /** state flags */
- unsigned long event_flags;
+
+ /** vsync wait queue */
+ wait_queue_head_t vsync_wait;
+ ktime_t vsync_timestamp;
/** spinlock for protection of registers */
spinlock_t reg_slock;
+ struct workqueue_struct *update_wq;
+
/** mutex for protection of fields below */
struct mutex mutex;
/** mutex for protection of streamer */
@@ -396,6 +412,8 @@
struct exynos5_bus_mif_handle *mif_handle;
struct exynos5_bus_int_handle *int_handle;
+
+ int color_range;
};
#if defined(CONFIG_VIDEOBUF2_CMA_PHYS)
@@ -520,8 +538,11 @@
void mxr_layer_sync(struct mxr_device *mdev, int en);
void mxr_vsync_set_update(struct mxr_device *mdev, int en);
+void mxr_vsync_enable_update(struct mxr_device *mdev);
+void mxr_vsync_disable_update(struct mxr_device *mdev);
void mxr_reg_reset(struct mxr_device *mdev);
void mxr_reg_set_layer_prio(struct mxr_device *mdev);
+void mxr_reg_set_color_range(struct mxr_device *mdev);
void mxr_reg_set_layer_blend(struct mxr_device *mdev, int sub_mxr, int num,
int en);
void mxr_reg_layer_alpha(struct mxr_device *mdev, int sub_mxr, int num, u32 a);
@@ -534,7 +555,7 @@
void mxr_reg_s_output(struct mxr_device *mdev, int cookie);
void mxr_reg_streamon(struct mxr_device *mdev);
void mxr_reg_streamoff(struct mxr_device *mdev);
-int mxr_reg_wait4vsync(struct mxr_device *mdev);
+int mxr_reg_wait4update(struct mxr_device *mdev);
void mxr_reg_set_mbus_fmt(struct mxr_device *mdev,
struct v4l2_mbus_framefmt *fmt, u32 dvi_mode);
void mxr_reg_local_path_clear(struct mxr_device *mdev);
diff --git a/drivers/media/video/exynos/tv/mixer_drv.c b/drivers/media/video/exynos/tv/mixer_drv.c
index 9c9e46e..9108632 100644
--- a/drivers/media/video/exynos/tv/mixer_drv.c
+++ b/drivers/media/video/exynos/tv/mixer_drv.c
@@ -131,7 +131,8 @@
layer = sub_mxr->layer[MXR_LAYER_VIDEO];
layer->pipe.state = MXR_PIPELINE_STREAMING;
mxr_layer_geo_fix(layer);
- layer->ops.format_set(layer);
+ layer->ops.format_set(layer, layer->fmt,
+ &layer->geo);
layer->ops.stream_set(layer, 1);
local += sub_mxr->local;
}
@@ -147,6 +148,7 @@
/* Alpha blending configuration always can be changed
* whenever streaming */
mxr_set_alpha_blend(mdev);
+ mxr_reg_set_color_range(mdev);
mxr_reg_set_layer_prio(mdev);
if ((mdev->n_streamer == 1 && local == 1) ||
@@ -210,7 +212,7 @@
goto out;
}
- ret = mxr_reg_wait4vsync(mdev);
+ ret = mxr_reg_wait4update(mdev);
if (ret) {
mxr_err(mdev, "failed to get vsync (%d) from output\n",
ret);
@@ -275,7 +277,7 @@
mxr_reg_streamoff(mdev);
/* vsync applies Mixer setup */
- ret = mxr_reg_wait4vsync(mdev);
+ ret = mxr_reg_wait4update(mdev);
if (ret) {
mxr_err(mdev, "failed to get vsync (%d) from output\n",
ret);
@@ -1392,6 +1394,7 @@
/* setup pointer to master device */
mdev->dev = dev;
+ mdev->color_range = 3;
/* use only sub mixer0 as default */
mdev->sub_mxr[MXR_SUB_MIXER0].use = 1;
@@ -1406,7 +1409,14 @@
mutex_init(&mdev->mutex);
mutex_init(&mdev->s_mutex);
spin_lock_init(&mdev->reg_slock);
- init_waitqueue_head(&mdev->event_queue);
+ init_waitqueue_head(&mdev->vsync_wait);
+
+ mdev->update_wq = create_singlethread_workqueue("hdmi-mixer");
+ if (mdev->update_wq == NULL) {
+ ret = -ENOMEM;
+ mxr_err(mdev, "failed to create work queue\n");
+ goto fail_mem;
+ }
/* acquire resources: regs, irqs, clocks, regulators */
ret = mxr_acquire_resources(mdev, pdev);
@@ -1455,6 +1465,8 @@
mxr_release_resources(mdev);
fail_mem:
+ if (mdev->update_wq)
+ destroy_workqueue(mdev->update_wq);
kfree(mdev);
fail:
@@ -1469,6 +1481,7 @@
pm_runtime_disable(dev);
+ destroy_workqueue(mdev->update_wq);
mxr_release_layers(mdev);
mxr_release_video(mdev);
mxr_release_resources(mdev);
diff --git a/drivers/media/video/exynos/tv/mixer_grp_layer.c b/drivers/media/video/exynos/tv/mixer_grp_layer.c
index 447e0e3..973aa09 100644
--- a/drivers/media/video/exynos/tv/mixer_grp_layer.c
+++ b/drivers/media/video/exynos/tv/mixer_grp_layer.c
@@ -100,10 +100,11 @@
mxr_reg_graph_layer_stream(layer->mdev, layer->idx, en);
}
-static void mxr_graph_format_set(struct mxr_layer *layer)
+static void mxr_graph_format_set(struct mxr_layer *layer,
+ const struct mxr_format *fmt,
+ struct mxr_geometry *geo)
{
- mxr_reg_graph_format(layer->mdev, layer->idx,
- layer->fmt, &layer->geo);
+ mxr_reg_graph_format(layer->mdev, layer->idx, fmt, geo);
}
static void mxr_graph_fix_geometry(struct mxr_layer *layer)
diff --git a/drivers/media/video/exynos/tv/mixer_reg.c b/drivers/media/video/exynos/tv/mixer_reg.c
index 37c10bd..a959369 100644
--- a/drivers/media/video/exynos/tv/mixer_reg.c
+++ b/drivers/media/video/exynos/tv/mixer_reg.c
@@ -75,6 +75,17 @@
MXR_STATUS_LAYER_SYNC);
}
+void mxr_vsync_enable_update(struct mxr_device *mdev)
+{
+ mxr_write_mask(mdev, MXR_CFG, ~0, MXR_CFG_LAYER_UPDATE);
+ mxr_write_mask(mdev, MXR_STATUS, ~0, MXR_STATUS_SYNC_ENABLE);
+}
+
+void mxr_vsync_disable_update(struct mxr_device *mdev)
+{
+ mxr_write_mask(mdev, MXR_STATUS, 0, MXR_STATUS_SYNC_ENABLE);
+}
+
void mxr_vsync_set_update(struct mxr_device *mdev, int en)
{
/* block update on vsync */
@@ -317,10 +328,6 @@
void mxr_reg_graph_buffer(struct mxr_device *mdev, int idx, dma_addr_t addr)
{
u32 val = addr ? ~0 : 0;
- unsigned long flags;
-
- spin_lock_irqsave(&mdev->reg_slock, flags);
- mxr_vsync_set_update(mdev, MXR_DISABLE);
if (idx == 0) {
mxr_write_mask(mdev, MXR_CFG, val, MXR_CFG_GRP0_ENABLE);
@@ -335,9 +342,6 @@
mxr_write_mask(mdev, MXR_CFG, val, MXR_CFG_MX1_GRP1_ENABLE);
mxr_write(mdev, MXR1_GRAPHIC_BASE(1), addr);
}
-
- mxr_vsync_set_update(mdev, MXR_ENABLE);
- spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
void mxr_reg_vp_buffer(struct mxr_device *mdev,
@@ -458,17 +462,21 @@
if (sub_mxr == MXR_SUB_MIXER0 && num == MXR_LAYER_GRP0)
mxr_write_mask(mdev, MXR_GRAPHIC_CFG(0), val,
- MXR_GRP_CFG_PIXEL_BLEND_EN);
+ MXR_GRP_CFG_PIXEL_BLEND_EN |
+ MXR_GRP_CFG_PRE_MUL_MODE);
else if (sub_mxr == MXR_SUB_MIXER0 && num == MXR_LAYER_GRP1)
mxr_write_mask(mdev, MXR_GRAPHIC_CFG(1), val,
- MXR_GRP_CFG_PIXEL_BLEND_EN);
+ MXR_GRP_CFG_PIXEL_BLEND_EN |
+ MXR_GRP_CFG_PRE_MUL_MODE);
#if defined(CONFIG_ARCH_EXYNOS5)
else if (sub_mxr == MXR_SUB_MIXER1 && num == MXR_LAYER_GRP0)
mxr_write_mask(mdev, MXR1_GRAPHIC_CFG(0), val,
- MXR_GRP_CFG_PIXEL_BLEND_EN);
+ MXR_GRP_CFG_PIXEL_BLEND_EN |
+ MXR_GRP_CFG_PRE_MUL_MODE);
else if (sub_mxr == MXR_SUB_MIXER1 && num == MXR_LAYER_GRP1)
mxr_write_mask(mdev, MXR1_GRAPHIC_CFG(1), val,
- MXR_GRP_CFG_PIXEL_BLEND_EN);
+ MXR_GRP_CFG_PIXEL_BLEND_EN |
+ MXR_GRP_CFG_PRE_MUL_MODE);
#endif
mxr_vsync_set_update(mdev, MXR_ENABLE);
@@ -524,42 +532,6 @@
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
-static void mxr_irq_layer_handle(struct mxr_layer *layer)
-{
- struct list_head *head = &layer->enq_list;
- struct mxr_pipeline *pipe = &layer->pipe;
- struct mxr_buffer *done;
-
- /* skip non-existing layer */
- if (layer == NULL)
- return;
-
- spin_lock(&layer->enq_slock);
- if (pipe->state == MXR_PIPELINE_IDLE)
- goto done;
-
- done = layer->shadow_buf;
- layer->shadow_buf = layer->update_buf;
-
- if (list_empty(head)) {
- if (pipe->state != MXR_PIPELINE_STREAMING)
- layer->update_buf = NULL;
- } else {
- struct mxr_buffer *next;
- next = list_first_entry(head, struct mxr_buffer, list);
- list_del(&next->list);
- layer->update_buf = next;
- }
-
- layer->ops.buffer_set(layer, layer->update_buf);
-
- if (done && done != layer->shadow_buf)
- vb2_buffer_done(&done->vb, VB2_BUF_STATE_DONE);
-
-done:
- spin_unlock(&layer->enq_slock);
-}
-
u32 mxr_irq_underrun_handle(struct mxr_device *mdev, u32 val)
{
if (val & MXR_INT_STATUS_MX0_VIDEO) {
@@ -588,15 +560,15 @@
irqreturn_t mxr_irq_handler(int irq, void *dev_data)
{
struct mxr_device *mdev = dev_data;
- u32 i, val;
+ u32 val;
spin_lock(&mdev->reg_slock);
val = mxr_read(mdev, MXR_INT_STATUS);
/* wake up process waiting for VSYNC */
if (val & MXR_INT_STATUS_VSYNC) {
- set_bit(MXR_EVENT_VSYNC, &mdev->event_flags);
- wake_up(&mdev->event_queue);
+ mdev->vsync_timestamp = ktime_get();
+ wake_up_interruptible_all(&mdev->vsync_wait);
}
/* clear interrupts.
@@ -608,24 +580,6 @@
mxr_write(mdev, MXR_INT_STATUS, val);
spin_unlock(&mdev->reg_slock);
- /* leave on non-vsync event */
- if (~val & MXR_INT_CLEAR_VSYNC)
- return IRQ_HANDLED;
-
- for (i = 0; i < MXR_MAX_SUB_MIXERS; ++i) {
-#if defined(CONFIG_ARCH_EXYNOS4)
- mxr_irq_layer_handle(mdev->sub_mxr[i].layer[MXR_LAYER_VIDEO]);
-#endif
- mxr_irq_layer_handle(mdev->sub_mxr[i].layer[MXR_LAYER_GRP0]);
- mxr_irq_layer_handle(mdev->sub_mxr[i].layer[MXR_LAYER_GRP1]);
- }
-
- if (test_bit(MXR_EVENT_VSYNC, &mdev->event_flags)) {
- spin_lock(&mdev->reg_slock);
- mxr_write_mask(mdev, MXR_CFG, ~0, MXR_CFG_LAYER_UPDATE);
- spin_unlock(&mdev->reg_slock);
- }
-
return IRQ_HANDLED;
}
@@ -663,15 +617,20 @@
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
-int mxr_reg_wait4vsync(struct mxr_device *mdev)
+static int mxr_update_pending(struct mxr_device *mdev)
{
+ return MXR_CFG_LAYER_UPDATE_COUNT(mxr_read(mdev, MXR_CFG));
+}
+
+int mxr_reg_wait4update(struct mxr_device *mdev)
+{
+ ktime_t timestamp = mdev->vsync_timestamp;
int ret;
- clear_bit(MXR_EVENT_VSYNC, &mdev->event_flags);
- /* TODO: consider adding interruptible */
- ret = wait_event_timeout(mdev->event_queue,
- test_bit(MXR_EVENT_VSYNC, &mdev->event_flags),
- msecs_to_jiffies(1000));
+ ret = wait_event_interruptible_timeout(mdev->vsync_wait,
+ !ktime_equal(timestamp, mdev->vsync_timestamp)
+ && !mxr_update_pending(mdev),
+ msecs_to_jiffies(100));
if (ret > 0)
return 0;
if (ret < 0)
@@ -733,6 +692,19 @@
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
+void mxr_reg_set_color_range(struct mxr_device *mdev)
+{
+ mxr_write(mdev, MXR_CM_COEFF_Y,
+ (1 << 30) | (94 << 20) | (314 << 10) | (32 << 0));
+ mxr_write(mdev, MXR_CM_COEFF_CB,
+ (972 << 20) | (851 << 10) | (225 << 0));
+ mxr_write(mdev, MXR_CM_COEFF_CR,
+ (225 << 20) | (820 << 10) | (1004 << 0));
+
+ mxr_write_mask(mdev, MXR_CFG, mdev->color_range << 9,
+ MXR_CFG_COLOR_RANGE_MASK);
+}
+
void mxr_reg_local_path_clear(struct mxr_device *mdev)
{
u32 val;
diff --git a/drivers/media/video/exynos/tv/mixer_video.c b/drivers/media/video/exynos/tv/mixer_video.c
index 4f69d48..9e6575a 100644
--- a/drivers/media/video/exynos/tv/mixer_video.c
+++ b/drivers/media/video/exynos/tv/mixer_video.c
@@ -27,6 +27,8 @@
#include <media/videobuf2-ion.h>
#endif
+static int mxr_update(struct mxr_device *mdev);
+
int __devinit mxr_acquire_video(struct mxr_device *mdev,
struct mxr_output_conf *output_conf, int output_count)
{
@@ -423,20 +425,27 @@
case V4L2_CID_TV_CHROMA_VALUE:
layer->chroma_val = (u32)v;
break;
- case V4L2_CID_TV_HPD_STATUS:
- v4l2_subdev_call(to_outsd(mdev), core, s_ctrl, ctrl);
- break;
- case V4L2_CID_TV_SET_DVI_MODE:
- v4l2_subdev_call(to_outsd(mdev), core, s_ctrl, ctrl);
- break;
- case V4L2_CID_TV_SET_ASPECT_RATIO:
- v4l2_subdev_call(to_outsd(mdev), core, s_ctrl, ctrl);
case V4L2_CID_TV_LAYER_PRIO:
layer->prio = (u8)v;
/* This can be turned on/off each layer while streaming */
if (layer->pipe.state == MXR_PIPELINE_STREAMING)
mxr_reg_set_layer_prio(mdev);
break;
+ case V4L2_CID_TV_SET_COLOR_RANGE:
+ mdev->color_range = v;
+ v4l2_subdev_call(to_outsd(mdev), core, s_ctrl, ctrl);
+ break;
+ case V4L2_CID_TV_UPDATE:
+ ret = mxr_update(mdev);
+ break;
+ case V4L2_CID_TV_ENABLE_HDMI_AUDIO:
+ case V4L2_CID_TV_SET_NUM_CHANNELS:
+ case V4L2_CID_TV_HPD_STATUS:
+ case V4L2_CID_TV_SET_DVI_MODE:
+ case V4L2_CID_TV_SET_ASPECT_RATIO:
+ case V4L2_CID_TV_HDCP_ENABLE:
+ v4l2_subdev_call(to_outsd(mdev), core, s_ctrl, ctrl);
+ break;
default:
mxr_err(mdev, "invalid control id\n");
ret = -EINVAL;
@@ -466,6 +475,7 @@
switch (ctrl->id) {
case V4L2_CID_TV_HPD_STATUS:
+ case V4L2_CID_TV_MAX_AUDIO_CHANNELS:
v4l2_subdev_call(to_outsd(mdev), core, g_ctrl, ctrl);
break;
default:
@@ -885,44 +895,148 @@
return 0;
}
-static void fence_work(struct work_struct *work)
+static void layer_update(struct mxr_layer *layer,
+ struct mxr_layer_update *update)
{
- struct mxr_layer *layer = container_of(work, struct mxr_layer, fence_work);
+ layer->shadow_buf = layer->update_buf;
+ layer->update_buf = update->buffer;
+
+ if (layer->update_buf)
+ layer->ops.format_set(layer, update->fmt, &update->geo);
+
+ layer->ops.buffer_set(layer, layer->update_buf);
+}
+
+static void layer_buffer_done(struct mxr_layer *layer)
+{
struct mxr_pipeline *pipe = &layer->pipe;
- struct mxr_buffer *buffer;
- struct sync_fence *fence;
unsigned long flags;
- int ret;
spin_lock_irqsave(&layer->enq_slock, flags);
- while (!list_empty(&layer->fence_wait_list)) {
- buffer = list_first_entry(&layer->fence_wait_list,
- struct mxr_buffer, wait);
- list_del(&buffer->wait);
+ if (layer->shadow_buf && layer->shadow_buf != layer->update_buf)
+ vb2_buffer_done(&layer->shadow_buf->vb, VB2_BUF_STATE_DONE);
- fence = buffer->vb.acquire_fence;
- if (fence) {
- buffer->vb.acquire_fence = NULL;
- spin_unlock_irqrestore(&layer->enq_slock, flags);
-
- ret = sync_fence_wait(fence, 100);
- if (ret)
- mxr_err(layer->mdev, "sync_fence_wait() timeout");
- sync_fence_put(fence);
-
- spin_lock_irqsave(&layer->enq_slock, flags);
- }
-
- list_add_tail(&buffer->list, &layer->enq_list);
- }
-
- if (pipe->state == MXR_PIPELINE_STREAMING_START)
+ if (layer->update_buf && pipe->state == MXR_PIPELINE_STREAMING_START)
pipe->state = MXR_PIPELINE_STREAMING;
+ if (!layer->update_buf && pipe->state == MXR_PIPELINE_STREAMING_FINISH)
+ pipe->state = MXR_PIPELINE_IDLE;
+
spin_unlock_irqrestore(&layer->enq_slock, flags);
}
+static int buffer_fence_wait(struct mxr_device *mdev, struct mxr_buffer *buffer)
+{
+ struct sync_fence *fence;
+ int ret = 0;
+
+ fence = buffer->vb.acquire_fence;
+ if (!fence)
+ return 0;
+
+ ret = sync_fence_wait(fence, 1000);
+ if (ret == -ETIME) {
+ mxr_warn(mdev, "sync_fence_wait timeout");
+ ret = sync_fence_wait(fence, 10 * MSEC_PER_SEC);
+ }
+ if (ret)
+ mxr_warn(mdev, "sync_fence_wait error");
+
+ buffer->vb.acquire_fence = NULL;
+ sync_fence_put(fence);
+ return ret;
+}
+
+static void mxr_update_work(struct work_struct *work)
+{
+ struct mxr_update *update = container_of(work, struct mxr_update, work);
+ struct mxr_device *mdev = update->mdev;
+ struct mxr_layer **layers = mdev->sub_mxr[MXR_SUB_MIXER0].layer;
+ struct mxr_buffer *buffer;
+ int i;
+
+ mutex_lock(&mdev->s_mutex);
+
+ for (i = 0; i < MXR_MAX_LAYERS; i++) {
+ buffer = update->layers[i].buffer;
+ if (buffer)
+ buffer_fence_wait(mdev, buffer);
+ }
+
+ if (!mdev->n_streamer)
+ goto out;
+
+ mxr_vsync_disable_update(mdev);
+
+ for (i = 0; i < MXR_MAX_LAYERS; i++) {
+ if (update->layers[i].update)
+ layer_update(layers[i], &update->layers[i]);
+ }
+
+ mxr_vsync_enable_update(mdev);
+
+ mxr_reg_wait4update(mdev);
+
+ for (i = 0; i < ARRAY_SIZE(update->layers); i++)
+ if (update->layers[i].update)
+ layer_buffer_done(layers[i]);
+
+out:
+ mutex_unlock(&mdev->s_mutex);
+ kfree(update);
+}
+
+static void mxr_fill_update(struct mxr_layer *layer,
+ struct mxr_layer_update *update)
+{
+ struct mxr_pipeline *pipe = &layer->pipe;
+ unsigned long flags;
+
+ spin_lock_irqsave(&layer->enq_slock, flags);
+
+ if ((pipe->state == MXR_PIPELINE_STREAMING && layer->prio == 0)
+ || pipe->state == MXR_PIPELINE_STREAMING_FINISH) {
+ update->update = true;
+ update->buffer = NULL;
+ } else if (!list_empty(&layer->fence_wait_list)) {
+ update->update = true;
+ update->buffer = list_first_entry(&layer->fence_wait_list,
+ struct mxr_buffer, wait);
+ list_del(&update->buffer->wait);
+ } else {
+ update->update = false;
+ }
+
+ spin_unlock_irqrestore(&layer->enq_slock, flags);
+
+ if (update->buffer) {
+ update->fmt = layer->fmt;
+ memcpy(&update->geo, &layer->geo, sizeof(layer->geo));
+ }
+}
+
+static int mxr_update(struct mxr_device *mdev)
+{
+ struct mxr_layer **layers = mdev->sub_mxr[MXR_SUB_MIXER0].layer;
+ struct mxr_update *update;
+ int i;
+
+ update = kzalloc(sizeof(struct mxr_update), GFP_KERNEL);
+ if (!update)
+ return -ENOMEM;
+
+ /* start from 1, only the graphic layers are supported */
+ for (i = 1; i < ARRAY_SIZE(update->layers); i++)
+ mxr_fill_update(layers[i], &update->layers[i]);
+
+ update->mdev = mdev;
+ INIT_WORK(&update->work, mxr_update_work);
+
+ queue_work(mdev->update_wq, &update->work);
+ return 0;
+}
+
static void buf_queue(struct vb2_buffer *vb)
{
struct mxr_buffer *buffer = container_of(vb, struct mxr_buffer, vb);
@@ -932,8 +1046,6 @@
spin_lock_irqsave(&layer->enq_slock, flags);
list_add_tail(&buffer->wait, &layer->fence_wait_list);
spin_unlock_irqrestore(&layer->enq_slock, flags);
-
- queue_work(layer->fence_wq, &layer->fence_work);
}
static void wait_lock(struct vb2_queue *vq)
@@ -1021,8 +1133,6 @@
mxr_layer_geo_fix(layer);
mxr_geometry_dump(mdev, &layer->geo);
- layer->ops.format_set(layer);
-
spin_lock_irqsave(&layer->enq_slock, flags);
if (list_empty(&layer->enq_list))
pipe->state = MXR_PIPELINE_STREAMING_START;
@@ -1040,42 +1150,16 @@
return 0;
}
-static void mxr_watchdog(unsigned long arg)
-{
- struct mxr_layer *layer = (struct mxr_layer *) arg;
- struct mxr_device *mdev = layer->mdev;
- unsigned long flags;
-
- mxr_err(mdev, "watchdog fired for layer %s\n", layer->vfd.name);
-
- spin_lock_irqsave(&layer->enq_slock, flags);
-
- if (layer->update_buf == layer->shadow_buf)
- layer->update_buf = NULL;
- if (layer->update_buf) {
- vb2_buffer_done(&layer->update_buf->vb, VB2_BUF_STATE_ERROR);
- layer->update_buf = NULL;
- }
- if (layer->shadow_buf) {
- vb2_buffer_done(&layer->shadow_buf->vb, VB2_BUF_STATE_ERROR);
- layer->shadow_buf = NULL;
- }
- spin_unlock_irqrestore(&layer->enq_slock, flags);
-}
-
static int stop_streaming(struct vb2_queue *vq)
{
struct mxr_layer *layer = vb2_get_drv_priv(vq);
struct mxr_device *mdev = layer->mdev;
unsigned long flags;
- struct timer_list watchdog;
struct mxr_buffer *buf, *buf_tmp;
struct mxr_pipeline *pipe = &layer->pipe;
mxr_dbg(mdev, "%s\n", __func__);
- cancel_work_sync(&layer->fence_work);
-
spin_lock_irqsave(&layer->enq_slock, flags);
list_for_each_entry_safe(buf, buf_tmp, &layer->fence_wait_list, wait) {
@@ -1104,24 +1188,12 @@
spin_unlock_irqrestore(&layer->enq_slock, flags);
- /* give 1 seconds to complete to complete last buffers */
- setup_timer_on_stack(&watchdog, mxr_watchdog,
- (unsigned long)layer);
- mod_timer(&watchdog, jiffies + msecs_to_jiffies(1000));
+ /* to complete last buffer */
+ mxr_update(mdev);
/* wait until all buffers are goes to done state */
vb2_wait_for_all_buffers(vq);
- /* stop timer if all synchronization is done */
- del_timer_sync(&watchdog);
- destroy_timer_on_stack(&watchdog);
-
- /* stopping hardware */
- spin_lock_irqsave(&layer->enq_slock, flags);
-
- pipe->state = MXR_PIPELINE_IDLE;
- spin_unlock_irqrestore(&layer->enq_slock, flags);
-
/* disabling layer in hardware */
layer->ops.stream_set(layer, MXR_DISABLE);
@@ -1216,13 +1288,7 @@
INIT_LIST_HEAD(&layer->enq_list);
INIT_LIST_HEAD(&layer->fence_wait_list);
mutex_init(&layer->mutex);
- INIT_WORK(&layer->fence_work, fence_work);
- layer->fence_wq = create_singlethread_workqueue(name);
- if (layer->fence_wq == NULL) {
- mxr_err(mdev, "failed to create work queue\n");
- goto fail_alloc;
- }
layer->vfd = (struct video_device) {
.minor = -1,
@@ -1261,8 +1327,6 @@
return layer;
fail_alloc:
- if (layer->fence_wq)
- destroy_workqueue(layer->fence_wq);
kfree(layer);
fail:
diff --git a/drivers/media/video/exynos/tv/mixer_video_layer.c b/drivers/media/video/exynos/tv/mixer_video_layer.c
index f2f8921..d94ea07 100644
--- a/drivers/media/video/exynos/tv/mixer_video_layer.c
+++ b/drivers/media/video/exynos/tv/mixer_video_layer.c
@@ -31,9 +31,11 @@
mxr_reg_video_layer_stream(layer->mdev, layer->idx, en);
}
-static void mxr_video_format_set(struct mxr_layer *layer)
+static void mxr_video_format_set(struct mxr_layer *layer,
+ const struct mxr_format *fmt,
+ struct mxr_geometry *geo)
{
- mxr_reg_video_geo(layer->mdev, layer->cur_mxr, layer->idx, &layer->geo);
+ mxr_reg_video_geo(layer->mdev, layer->cur_mxr, layer->idx, geo);
}
static void mxr_video_fix_geometry(struct mxr_layer *layer)
diff --git a/drivers/media/video/exynos/tv/regs-hdmi-5250.h b/drivers/media/video/exynos/tv/regs-hdmi-5250.h
index 140ec96..3101fb7 100644
--- a/drivers/media/video/exynos/tv/regs-hdmi-5250.h
+++ b/drivers/media/video/exynos/tv/regs-hdmi-5250.h
@@ -1069,7 +1069,7 @@
/* I2S_CH_ST_2 / I2S_CH_ST_SH_2 */
#define HDMI_I2S_CHANNEL_NUM_MASK (0xF << 4)
#define HDMI_I2S_SOURCE_NUM_MASK (0xF)
-#define HDMI_I2S_SET_CHANNEL_NUM(x) ((x) & (0xF) << 4)
+#define HDMI_I2S_SET_CHANNEL_NUM(x) (((x) & (0xF)) << 4)
#define HDMI_I2S_SET_SOURCE_NUM(x) ((x) & (0xF))
/* I2S_CH_ST_3 / I2S_CH_ST_SH_3 */
diff --git a/drivers/media/video/exynos/tv/regs-mixer.h b/drivers/media/video/exynos/tv/regs-mixer.h
index 15ad119..59a7eb4 100644
--- a/drivers/media/video/exynos/tv/regs-mixer.h
+++ b/drivers/media/video/exynos/tv/regs-mixer.h
@@ -129,10 +129,11 @@
/* bits for MXR_CFG */
#define MXR_CFG_LAYER_UPDATE (1 << 31)
-#define MXR_CFG_LAYER_UPDATE_COUNTER (3 << 29)
+#define MXR_CFG_LAYER_UPDATE_COUNT(x) (((x) >> 29) & 3)
#define MXR_CFG_MX1_GRP1_ENABLE (1 << 15)
#define MXR_CFG_MX1_GRP0_ENABLE (1 << 14)
#define MXR_CFG_MX1_VIDEO_ENABLE (1 << 13)
+#define MXR_CFG_COLOR_RANGE_MASK (3 << 9)
#define MXR_CFG_OUT_YUV444 (0 << 8)
#define MXR_CFG_OUT_RGB888 (1 << 8)
#define MXR_CFG_OUT_MASK (1 << 8)
@@ -154,6 +155,7 @@
/* bits for MXR_GRAPHICn_CFG */
#define MXR_GRP_CFG_BLANK_KEY_OFF (1 << 21)
+#define MXR_GRP_CFG_PRE_MUL_MODE (1 << 20)
#define MXR_GRP_CFG_LAYER_BLEND_EN (1 << 17)
#define MXR_GRP_CFG_PIXEL_BLEND_EN (1 << 16)
#define MXR_GRP_CFG_FORMAT_VAL(x) MXR_MASK_VAL(x, 11, 8)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 11e44386..db0e395 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -441,6 +441,17 @@
additional drivers must be enabled in order to use the functionality
of the device.
+config MFD_MAX77686
+ bool "Maxim Semiconductor MAX77686 PMIC Support"
+ depends on I2C=y && GENERIC_HARDIRQS
+ select MFD_CORE
+ help
+ Say yes here to support for Maxim Semiconductor MAX77686.
+ This is a Power Management IC with RTC on chip.
+ 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_S5M_CORE
bool "SAMSUNG S5M Series Support"
depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 05fa538..3338eb4 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -79,6 +79,7 @@
obj-$(CONFIG_MFD_MAX8925) += max8925.o
obj-$(CONFIG_MFD_MAX8997) += max8997.o max8997-irq.o
obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o
+obj-$(CONFIG_MFD_MAX77686) += max77686.o max77686-irq.o
pcf50633-objs := pcf50633-core.o pcf50633-irq.o
obj-$(CONFIG_MFD_PCF50633) += pcf50633.o
diff --git a/drivers/mfd/max77686-irq.c b/drivers/mfd/max77686-irq.c
new file mode 100644
index 0000000..2be1ca1
--- /dev/null
+++ b/drivers/mfd/max77686-irq.c
@@ -0,0 +1,319 @@
+/*
+ * max77686-irq.c - Interrupt controller support for MAX77686
+ *
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ * Chiwoong Byun <woong.byun@samsung.com>
+ *
+ * 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 <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mfd/max77686.h>
+#include <linux/mfd/max77686-private.h>
+
+enum {
+ MAX77686_DEBUG_IRQ_INFO = 1 << 0,
+ MAX77686_DEBUG_IRQ_MASK = 1 << 1,
+ MAX77686_DEBUG_IRQ_INT = 1 << 2,
+};
+
+static int debug_mask;
+
+static const u8 max77686_mask_reg[] = {
+ [PMIC_INT1] = MAX77686_REG_INT1MSK,
+ [PMIC_INT2] = MAX77686_REG_INT2MSK,
+ [RTC_INT] = MAX77686_RTC_INTM,
+};
+
+static struct i2c_client *max77686_get_i2c(struct max77686_dev *max77686,
+ enum max77686_irq_source src)
+{
+ switch (src) {
+ case PMIC_INT1...PMIC_INT2:
+ return max77686->i2c;
+ case RTC_INT:
+ return max77686->rtc;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+struct max77686_irq_data {
+ int mask;
+ enum max77686_irq_source group;
+};
+
+#define DECLARE_IRQ(_group, _mask) \
+ { .group = (_group), .mask = (_mask) }
+static const struct max77686_irq_data max77686_irqs[] = {
+ [MAX77686_PMICIRQ_PWRONF] = DECLARE_IRQ(PMIC_INT1, 1 << 0),
+ [MAX77686_PMICIRQ_PWRONR] = DECLARE_IRQ(PMIC_INT1, 1 << 1),
+ [MAX77686_PMICIRQ_JIGONBF] = DECLARE_IRQ(PMIC_INT1, 1 << 2),
+ [MAX77686_PMICIRQ_JIGONBR] = DECLARE_IRQ(PMIC_INT1, 1 << 3),
+ [MAX77686_PMICIRQ_ACOKBF] = DECLARE_IRQ(PMIC_INT1, 1 << 4),
+ [MAX77686_PMICIRQ_ACOKBR] = DECLARE_IRQ(PMIC_INT1, 1 << 5),
+ [MAX77686_PMICIRQ_ONKEY1S] = DECLARE_IRQ(PMIC_INT1, 1 << 6),
+ [MAX77686_PMICIRQ_MRSTB] = DECLARE_IRQ(PMIC_INT1, 1 << 7),
+ [MAX77686_PMICIRQ_140C] = DECLARE_IRQ(PMIC_INT2, 1 << 0),
+ [MAX77686_PMICIRQ_120C] = DECLARE_IRQ(PMIC_INT2, 1 << 1),
+ [MAX77686_RTCIRQ_RTC60S] = DECLARE_IRQ(RTC_INT, 1 << 0),
+ [MAX77686_RTCIRQ_RTCA1] = DECLARE_IRQ(RTC_INT, 1 << 1),
+ [MAX77686_RTCIRQ_RTCA2] = DECLARE_IRQ(RTC_INT, 1 << 2),
+ [MAX77686_RTCIRQ_SMPL] = DECLARE_IRQ(RTC_INT, 1 << 3),
+ [MAX77686_RTCIRQ_RTC1S] = DECLARE_IRQ(RTC_INT, 1 << 4),
+ [MAX77686_RTCIRQ_WTSR] = DECLARE_IRQ(RTC_INT, 1 << 5),
+};
+
+static void max77686_irq_lock(struct irq_data *data)
+{
+ struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
+
+ if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
+ pr_info("%s\n", __func__);
+
+ mutex_lock(&max77686->irqlock);
+}
+
+static void max77686_irq_sync_unlock(struct irq_data *data)
+{
+ struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
+ int i;
+
+ for (i = 0; i < MAX77686_IRQ_GROUP_NR; i++) {
+ u8 mask_reg = max77686_mask_reg[i];
+ struct i2c_client *i2c = max77686_get_i2c(max77686, i);
+
+ if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
+ pr_info("%s: mask_reg[%d]=0x%x, cur=0x%x\n",
+ __func__, i, mask_reg,
+ max77686->irq_masks_cur[i]);
+
+ if (mask_reg == MAX77686_REG_INVALID || IS_ERR_OR_NULL(i2c))
+ continue;
+
+ max77686->irq_masks_cache[i] = max77686->irq_masks_cur[i];
+
+ max77686_write_reg(i2c, max77686_mask_reg[i],
+ max77686->irq_masks_cur[i]);
+ }
+
+ mutex_unlock(&max77686->irqlock);
+}
+
+static inline const struct max77686_irq_data *irq_to_max77686_irq(
+ struct max77686_dev *max77686, int irq)
+{
+ return &max77686_irqs[irq - max77686->irq_base];
+}
+
+static void max77686_irq_mask(struct irq_data *data)
+{
+ struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
+ const struct max77686_irq_data *irq_data =
+ irq_to_max77686_irq(max77686, data->irq);
+
+ max77686->irq_masks_cur[irq_data->group] |= irq_data->mask;
+
+ if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
+ pr_info("%s: group=%d, cur=0x%x\n",
+ __func__, irq_data->group,
+ max77686->irq_masks_cur[irq_data->group]);
+}
+
+static void max77686_irq_unmask(struct irq_data *data)
+{
+ struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
+ const struct max77686_irq_data *irq_data =
+ irq_to_max77686_irq(max77686, data->irq);
+
+ max77686->irq_masks_cur[irq_data->group] &= ~irq_data->mask;
+
+ if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
+ pr_info("%s: group=%d, cur=0x%x\n",
+ __func__, irq_data->group,
+ max77686->irq_masks_cur[irq_data->group]);
+}
+
+static struct irq_chip max77686_irq_chip = {
+ .name = "max77686",
+ .irq_bus_lock = max77686_irq_lock,
+ .irq_bus_sync_unlock = max77686_irq_sync_unlock,
+ .irq_mask = max77686_irq_mask,
+ .irq_unmask = max77686_irq_unmask,
+};
+
+static irqreturn_t max77686_irq_thread(int irq, void *data)
+{
+ struct max77686_dev *max77686 = data;
+ u8 irq_reg[MAX77686_IRQ_GROUP_NR] = { };
+ u8 irq_src;
+ int ret;
+ int i;
+
+ ret = max77686_read_reg(max77686->i2c, MAX77686_REG_INTSRC, &irq_src);
+ if (ret < 0) {
+ dev_err(max77686->dev, "Failed to read interrupt source: %d\n",
+ ret);
+ return IRQ_NONE;
+ }
+
+ if (debug_mask & MAX77686_DEBUG_IRQ_INT)
+ pr_info("%s: irq_src=0x%x\n", __func__, irq_src);
+
+ /* MAX77686_IRQSRC_RTC may be set even if there are pending at INT1/2 */
+ ret = max77686_read_reg(max77686->i2c, MAX77686_REG_INT1, &irq_reg[0]);
+ ret = max77686_read_reg(max77686->i2c, MAX77686_REG_INT2, &irq_reg[1]);
+ if (ret < 0) {
+ dev_err(max77686->dev, "Failed to read pmic interrupt: %d\n",
+ ret);
+ return IRQ_NONE;
+ }
+
+ if (debug_mask & MAX77686_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 & MAX77686_IRQSRC_RTC) {
+#ifdef CONFIG_RTC_DRV_MAX77686
+ ret =
+ max77686_read_reg(max77686->rtc, MAX77686_RTC_INT,
+ &irq_reg[RTC_INT]);
+#else
+ ret = -ENODEV;
+#endif
+ if (ret < 0) {
+ dev_err(max77686->dev,
+ "Failed to read rtc interrupt: %d\n", ret);
+ return IRQ_NONE;
+ }
+
+ if (debug_mask & MAX77686_DEBUG_IRQ_INT)
+ pr_info("%s: rtc int=0x%x\n", __func__,
+ irq_reg[RTC_INT]);
+
+ }
+
+ for (i = 0; i < MAX77686_IRQ_NR; i++) {
+ if (irq_reg[max77686_irqs[i].group] & max77686_irqs[i].mask)
+ handle_nested_irq(max77686->irq_base + i);
+ }
+
+ return IRQ_HANDLED;
+}
+
+int max77686_irq_resume(struct max77686_dev *max77686)
+{
+ if (max77686->irq && max77686->irq_base)
+ max77686_irq_thread(max77686->irq_base, max77686);
+ return 0;
+}
+
+int max77686_irq_init(struct max77686_dev *max77686)
+{
+ int i;
+ int cur_irq;
+ int ret;
+ int val;
+
+ if (debug_mask & MAX77686_DEBUG_IRQ_INFO)
+ pr_info("%s+\n", __func__);
+
+ if (!max77686->irq_gpio) {
+ dev_warn(max77686->dev, "No interrupt gpio specified.\n");
+ max77686->irq_base = 0;
+ return 0;
+ }
+
+ if (!max77686->irq_base) {
+ dev_err(max77686->dev, "No interrupt base specified.\n");
+ return 0;
+ }
+
+ mutex_init(&max77686->irqlock);
+
+ max77686->irq = gpio_to_irq(max77686->irq_gpio);
+ ret = gpio_request(max77686->irq_gpio, "pmic_irq");
+ if (ret < 0 && ret != -EBUSY) {
+ dev_err(max77686->dev,
+ "Failed to request gpio %d with ret: %d\n",
+ max77686->irq_gpio, ret);
+ return IRQ_NONE;
+ }
+ if (ret == -EBUSY)
+ dev_warn(max77686->dev, "gpio pmic_irq is already requested\n");
+
+ gpio_direction_input(max77686->irq_gpio);
+ val = gpio_get_value(max77686->irq_gpio);
+ gpio_free(max77686->irq_gpio);
+
+ if (debug_mask & MAX77686_DEBUG_IRQ_INT)
+ pr_info("%s: gpio_irq=%x\n", __func__, val);
+
+ /* Mask individual interrupt sources */
+ for (i = 0; i < MAX77686_IRQ_GROUP_NR; i++) {
+ struct i2c_client *i2c;
+
+ max77686->irq_masks_cur[i] = 0xff;
+ max77686->irq_masks_cache[i] = 0xff;
+ i2c = max77686_get_i2c(max77686, i);
+
+ if (IS_ERR_OR_NULL(i2c))
+ continue;
+ if (max77686_mask_reg[i] == MAX77686_REG_INVALID)
+ continue;
+
+ max77686_write_reg(i2c, max77686_mask_reg[i], 0xff);
+ }
+
+ /* Register with genirq */
+ for (i = 0; i < MAX77686_IRQ_NR; i++) {
+ cur_irq = i + max77686->irq_base;
+ irq_set_chip_data(cur_irq, max77686);
+ irq_set_chip_and_handler(cur_irq, &max77686_irq_chip,
+ handle_edge_irq);
+ irq_set_nested_thread(cur_irq, 1);
+#ifdef CONFIG_ARM
+ set_irq_flags(cur_irq, IRQF_VALID);
+#else
+ irq_set_noprobe(cur_irq);
+#endif
+ }
+
+ ret = request_threaded_irq(max77686->irq, NULL, max77686_irq_thread,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "max77686-irq", max77686);
+
+ if (ret) {
+ dev_err(max77686->dev, "Failed to request IRQ %d: %d\n",
+ max77686->irq, ret);
+ return ret;
+ }
+
+ if (debug_mask & MAX77686_DEBUG_IRQ_INFO)
+ pr_info("%s-\n", __func__);
+
+ return 0;
+}
+
+void max77686_irq_exit(struct max77686_dev *max77686)
+{
+ if (max77686->irq)
+ free_irq(max77686->irq, max77686);
+}
diff --git a/drivers/mfd/max77686.c b/drivers/mfd/max77686.c
new file mode 100644
index 0000000..7359f21
--- /dev/null
+++ b/drivers/mfd/max77686.c
@@ -0,0 +1,255 @@
+/*
+ * max77686.c - mfd core driver for the Maxim 77686
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Chiwoong Byun <woong.byun@smasung.com>
+ *
+ * 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 <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max77686.h>
+#include <linux/mfd/max77686-private.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#define I2C_ADDR_RTC (0x0C >> 1)
+
+static struct mfd_cell max77686_devs[] = {
+ { .name = "max77686-pmic", },
+ { .name = "max77686-rtc", },
+};
+
+int max77686_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest)
+{
+ struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max77686->iolock);
+ ret = i2c_smbus_read_byte_data(i2c, reg);
+ mutex_unlock(&max77686->iolock);
+ if (ret < 0)
+ return ret;
+
+ ret &= 0xff;
+ *dest = ret;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(max77686_read_reg);
+
+int max77686_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
+{
+ struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max77686->iolock);
+ ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf);
+ mutex_unlock(&max77686->iolock);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(max77686_bulk_read);
+
+int max77686_write_reg(struct i2c_client *i2c, u8 reg, u8 value)
+{
+ struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max77686->iolock);
+ ret = i2c_smbus_write_byte_data(i2c, reg, value);
+ mutex_unlock(&max77686->iolock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(max77686_write_reg);
+
+int max77686_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
+{
+ struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max77686->iolock);
+ ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf);
+ mutex_unlock(&max77686->iolock);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(max77686_bulk_write);
+
+int max77686_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask)
+{
+ struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max77686->iolock);
+ ret = i2c_smbus_read_byte_data(i2c, reg);
+ if (ret >= 0) {
+ u8 old_val = ret & 0xff;
+ u8 new_val = (val & mask) | (old_val & (~mask));
+ ret = i2c_smbus_write_byte_data(i2c, reg, new_val);
+ }
+ mutex_unlock(&max77686->iolock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(max77686_update_reg);
+
+static int max77686_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct max77686_dev *max77686;
+ struct max77686_platform_data *pdata = i2c->dev.platform_data;
+ u8 data;
+ int ret = 0;
+
+ max77686 = kzalloc(sizeof(struct max77686_dev), GFP_KERNEL);
+ if (max77686 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, max77686);
+ max77686->dev = &i2c->dev;
+ max77686->i2c = i2c;
+ max77686->type = id->driver_data;
+
+ if (!pdata) {
+ ret = -EIO;
+ goto err;
+ }
+
+ max77686->wakeup = pdata->wakeup;
+ max77686->irq_gpio = pdata->irq_gpio;
+ max77686->irq_base = pdata->irq_base;
+
+ mutex_init(&max77686->iolock);
+
+ if (max77686_read_reg(i2c, MAX77686_REG_DEVICE_ID, &data) < 0) {
+ dev_err(max77686->dev,
+ "device not found on this channel (this is not an error)\n");
+ ret = -ENODEV;
+ goto err;
+ } else
+ dev_info(max77686->dev, "device found\n");
+
+ max77686->rtc = i2c_new_dummy(i2c->adapter, I2C_ADDR_RTC);
+ i2c_set_clientdata(max77686->rtc, max77686);
+
+ max77686_irq_init(max77686);
+
+ ret = mfd_add_devices(max77686->dev, -1, max77686_devs,
+ ARRAY_SIZE(max77686_devs), NULL, 0);
+
+ if (ret < 0)
+ goto err_mfd;
+
+ device_init_wakeup(max77686->dev, pdata->wakeup);
+
+ return ret;
+
+err_mfd:
+ mfd_remove_devices(max77686->dev);
+ i2c_unregister_device(max77686->rtc);
+err:
+ kfree(max77686);
+ return ret;
+}
+
+static int max77686_i2c_remove(struct i2c_client *i2c)
+{
+ struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
+
+ mfd_remove_devices(max77686->dev);
+ i2c_unregister_device(max77686->rtc);
+ kfree(max77686);
+
+ return 0;
+}
+
+static const struct i2c_device_id max77686_i2c_id[] = {
+ { "max77686", TYPE_MAX77686 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max77686_i2c_id);
+
+#ifdef CONFIG_PM
+static int max77686_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
+
+ disable_irq(max77686->irq);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(max77686->irq);
+
+ return 0;
+}
+
+static int max77686_resume(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(max77686->irq);
+
+ enable_irq(max77686->irq);
+
+ return max77686_irq_resume(max77686);
+}
+#else
+#define max77686_suspend NULL
+#define max77686_resume NULL
+#endif /* CONFIG_PM */
+
+const struct dev_pm_ops max77686_pm = {
+ .suspend = max77686_suspend,
+ .resume = max77686_resume,
+};
+
+static struct i2c_driver max77686_i2c_driver = {
+ .driver = {
+ .name = "max77686",
+ .owner = THIS_MODULE,
+ .pm = &max77686_pm,
+ },
+ .probe = max77686_i2c_probe,
+ .remove = max77686_i2c_remove,
+ .id_table = max77686_i2c_id,
+};
+
+static int __init max77686_i2c_init(void)
+{
+ return i2c_add_driver(&max77686_i2c_driver);
+}
+/* init early so consumer devices can complete system boot */
+subsys_initcall(max77686_i2c_init);
+
+static void __exit max77686_i2c_exit(void)
+{
+ i2c_del_driver(&max77686_i2c_driver);
+}
+module_exit(max77686_i2c_exit);
+
+MODULE_DESCRIPTION("MAXIM 77686 multi-function core driver");
+MODULE_AUTHOR("Chiwoong Byun <woong.byun@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c
index 8698a8e..0d36891 100644
--- a/drivers/mfd/wm8994-core.c
+++ b/drivers/mfd/wm8994-core.c
@@ -196,6 +196,7 @@
{
struct wm8994 *wm8994 = dev_get_drvdata(dev);
int ret;
+ int gpio_regs[WM8994_NUM_GPIO_REGS];
/* Don't actually go through with the suspend if the CODEC is
* still active (eg, for audio passthrough from CP. */
@@ -277,12 +278,24 @@
WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD,
WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD);
+ /* Save GPIO registers before reset */
+ regmap_bulk_read(wm8994->regmap, WM8994_GPIO_1, gpio_regs,
+ WM8994_NUM_GPIO_REGS);
+
/* Explicitly put the device into reset in case regulators
* don't get disabled in order to ensure consistent restart.
*/
wm8994_reg_write(wm8994, WM8994_SOFTWARE_RESET,
wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET));
+ /* Restore GPIO registers to prevent problems with mismatched
+ * pin configurations.
+ */
+ ret = regmap_bulk_write(wm8994->regmap, WM8994_GPIO_1, gpio_regs,
+ WM8994_NUM_GPIO_REGS);
+ if (ret != 0)
+ dev_err(dev, "Failed to restore GPIO registers: %d\n", ret);
+
regcache_cache_only(wm8994->regmap, true);
regcache_mark_dirty(wm8994->regmap);
@@ -500,7 +513,8 @@
ret);
goto err_enable;
}
- wm8994->revision = ret;
+ wm8994->revision = ret & WM8994_CHIP_REV_MASK;
+ wm8994->cust_id = (ret & WM8994_CUST_ID_MASK) >> WM8994_CUST_ID_SHIFT;
switch (wm8994->type) {
case WM8994:
@@ -554,8 +568,8 @@
break;
}
- dev_info(wm8994->dev, "%s revision %c\n", devname,
- 'A' + wm8994->revision);
+ dev_info(wm8994->dev, "%s revision %c CUST_ID %02x\n", devname,
+ 'A' + wm8994->revision, wm8994->cust_id);
switch (wm8994->type) {
case WM1811:
@@ -733,23 +747,7 @@
.id_table = wm8994_i2c_id,
};
-static int __init wm8994_i2c_init(void)
-{
- int ret;
-
- ret = i2c_add_driver(&wm8994_i2c_driver);
- if (ret != 0)
- pr_err("Failed to register wm8994 I2C driver: %d\n", ret);
-
- return ret;
-}
-module_init(wm8994_i2c_init);
-
-static void __exit wm8994_i2c_exit(void)
-{
- i2c_del_driver(&wm8994_i2c_driver);
-}
-module_exit(wm8994_i2c_exit);
+module_i2c_driver(wm8994_i2c_driver);
MODULE_DESCRIPTION("Core support for the WM8994 audio CODEC");
MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/wm8994-irq.c b/drivers/mfd/wm8994-irq.c
index 46b20c4..2dc368d 100644
--- a/drivers/mfd/wm8994-irq.c
+++ b/drivers/mfd/wm8994-irq.c
@@ -21,6 +21,7 @@
#include <linux/regmap.h>
#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/pdata.h>
#include <linux/mfd/wm8994/registers.h>
#include <linux/delay.h>
@@ -139,6 +140,8 @@
int wm8994_irq_init(struct wm8994 *wm8994)
{
int ret;
+ unsigned long irqflags;
+ struct wm8994_pdata *pdata = wm8994->dev->platform_data;
if (!wm8994->irq) {
dev_warn(wm8994->dev,
@@ -153,8 +156,13 @@
return 0;
}
+ /* select user or default irq flags */
+ irqflags = IRQF_TRIGGER_HIGH | IRQF_ONESHOT;
+ if (pdata->irq_flags)
+ irqflags = pdata->irq_flags;
+
ret = regmap_add_irq_chip(wm8994->regmap, wm8994->irq,
- IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+ irqflags,
wm8994->irq_base, &wm8994_irq_chip,
&wm8994->irq_data);
if (ret != 0) {
diff --git a/drivers/mfd/wm8994-regmap.c b/drivers/mfd/wm8994-regmap.c
index bfd25af..2fbce9c 100644
--- a/drivers/mfd/wm8994-regmap.c
+++ b/drivers/mfd/wm8994-regmap.c
@@ -1122,7 +1122,6 @@
case WM8994_RATE_STATUS:
case WM8958_MIC_DETECT_3:
case WM8994_DC_SERVO_4E:
- case WM8994_CHIP_REVISION:
case WM8994_INTERRUPT_STATUS_1:
case WM8994_INTERRUPT_STATUS_2:
return true;
@@ -1137,7 +1136,7 @@
switch (reg) {
case WM8994_GPIO_6:
- if (wm8994->revision > 1)
+ if (wm8994->cust_id > 1 || wm8994->revision > 1)
return true;
else
return false;
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 1a0254a..8607422 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -267,6 +267,17 @@
MFGPTs have a better resolution and max interval than the
generic PIT, and are suitable for use as high-res timers.
+config HAPTIC_ISA1200
+ tristate "Imagis ISA1200 Haptic Controller"
+ depends on I2C && ANDROID_TIMED_OUTPUT && HAVE_PWM
+ default n
+ help
+ If you say yes here you get support for the Imagis ISA1200 haptic
+ controller. ISA1200 is a haptic motor driver with register based
+ architecture, allowing for I2C control.
+ This driver provides haptic motor control and is accessed as a
+ timed output device.
+
config HP_ILO
tristate "Channel interface driver for the HP iLO processor"
depends on PCI
@@ -304,6 +315,16 @@
This option enables addition debugging code for the SGI GRU driver. If
you are unsure, say N.
+config SAMSUNG_JACK
+ bool "3.5MM ear jack driver for Samsung devices"
+ depends on INPUT
+ depends on SWITCH
+ default n
+ ---help---
+ This is 3.5MM ear jack driver for Samsung devices.
+
+ If unsure, say N.
+
config APDS9802ALS
tristate "Medfield Avago APDS9802 ALS Sensor module"
depends on I2C
@@ -517,7 +538,22 @@
---help---
Creates an rfkill entry in sysfs for power control of Bluetooth
TI wl127x chips.
-
+
+config STMPE811_ADC
+ tristate "STMPE811 ADC driver"
+ depends on I2C
+ help
+ If you say yes here you get support for the STMicroelectronics
+ STMPE811 8 channel ADC driver.
+
+config AUDIENCE_ES305
+ tristate "Audience ES305 Voice Processor"
+ depends on I2C
+ default n
+ help
+ The ES305 is a Voice Processor with integrated DSP that processes
+ i2s audio data to provide noise suppression and echo cancellation.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 89540d1..f42af7a 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -25,8 +25,10 @@
obj-$(CONFIG_SGI_XP) += sgi-xp/
obj-$(CONFIG_SGI_GRU) += sgi-gru/
obj-$(CONFIG_CS5535_MFGPT) += cs5535-mfgpt.o
+obj-$(CONFIG_HAPTIC_ISA1200) += haptic_isa1200.o
obj-$(CONFIG_HP_ILO) += hpilo.o
obj-$(CONFIG_APDS9802ALS) += apds9802als.o
+obj-$(CONFIG_SAMSUNG_JACK) += sec_jack.o
obj-$(CONFIG_ISL29003) += isl29003.o
obj-$(CONFIG_ISL29020) += isl29020.o
obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o
@@ -52,3 +54,5 @@
obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o
obj-$(CONFIG_WL127X_RFKILL) += wl127x-rfkill.o
obj-$(CONFIG_SENSORS_AK8975) += akm8975.o
+obj-$(CONFIG_STMPE811_ADC) += stmpe811-adc.o
+obj-$(CONFIG_AUDIENCE_ES305) += es305.o
diff --git a/drivers/misc/es305.c b/drivers/misc/es305.c
new file mode 100644
index 0000000..98887a5
--- /dev/null
+++ b/drivers/misc/es305.c
@@ -0,0 +1,808 @@
+/*
+ * drivers/misc/es305.c - Audience ES305 Voice Processor driver
+ *
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (C) 2012 Samsung 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 <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/platform_data/es305.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define ES305_FIRMWARE_NAME "es305_fw.bin"
+#define MAX_CMD_SEND_SIZE 32
+#define RETRY_COUNT 5
+
+/* ES305 commands and values */
+#define ES305_BOOT 0x0001
+#define ES305_BOOT_ACK 0x01
+
+#define ES305_SYNC_POLLING 0x80000000
+
+#define ES305_RESET_IMMEDIATE 0x80020000
+
+#define ES305_SET_POWER_STATE_SLEEP 0x80100001
+
+#define ES305_GET_ALGORITHM_PARM 0x80160000
+#define ES305_SET_ALGORITHM_PARM_ID 0x80170000
+#define ES305_SET_ALGORITHM_PARM 0x80180000
+#define ES305_AEC_MODE 0x0003
+#define ES305_TX_AGC 0x0004
+#define ES305_RX_AGC 0x0028
+#define ES305_TX_NS_LEVEL 0x004B
+#define ES305_RX_NS_LEVEL 0x004C
+#define ES305_ALGORITHM_RESET 0x001C
+
+#define ES305_GET_VOICE_PROCESSING 0x80430000
+#define ES305_SET_VOICE_PROCESSING 0x801C0000
+
+#define ES305_GET_AUDIO_ROUTING 0x80270000
+#define ES305_SET_AUDIO_ROUTING 0x80260000
+
+#define ES305_SET_PRESET 0x80310000
+
+#define ES305_GET_MIC_SAMPLE_RATE 0x80500000
+#define ES305_SET_MIC_SAMPLE_RATE 0x80510000
+#define ES305_8KHZ 0x0008
+#define ES305_16KHZ 0x000A
+#define ES305_48KHZ 0x0030
+
+#define ES305_DIGITAL_PASSTHROUGH 0x80520000
+
+struct es305_data {
+ struct es305_platform_data *pdata;
+ struct device *dev;
+ struct i2c_client *client;
+ const struct firmware *fw;
+ struct mutex lock;
+ u32 passthrough;
+ bool passthrough_on;
+ bool asleep;
+ bool device_ready;
+};
+
+static int es305_send_cmd(struct es305_data *es305, u32 command, u16 *response)
+{
+ u8 send[4];
+ u8 recv[4];
+ int ret = 0;
+ int retry = RETRY_COUNT;
+
+ send[0] = (command >> 24) & 0xff;
+ send[1] = (command >> 16) & 0xff;
+ send[2] = (command >> 8) & 0xff;
+ send[3] = command & 0xff;
+
+ ret = i2c_master_send(es305->client, send, 4);
+ if (ret < 0) {
+ dev_err(es305->dev, "i2c_master_send failed\n");
+ return ret;
+ }
+
+ /* The sleep command cannot be acked before the device goes to sleep */
+ if (command == ES305_SET_POWER_STATE_SLEEP)
+ return ret;
+
+ usleep_range(1000, 2000);
+ while (retry--) {
+ ret = i2c_master_recv(es305->client, recv, 4);
+ if (ret < 0) {
+ dev_err(es305->dev, "i2c_master_recv failed\n");
+ return ret;
+ }
+ /*
+ * Check that the first two bytes of the response match
+ * (the ack is in those bytes)
+ */
+ if ((send[0] == recv[0]) && (send[1] == recv[1])) {
+ if (response)
+ *response = (recv[2] << 8) | recv[3];
+ ret = 0;
+ break;
+ } else {
+ dev_err(es305->dev,
+ "incorrect ack (got 0x%.2x%.2x)\n",
+ recv[0], recv[1]);
+ ret = -EINVAL;
+ }
+
+ /* Wait before polling again */
+ if (retry > 0)
+ msleep(20);
+ }
+
+ return ret;
+}
+
+static int es305_load_firmware(struct es305_data *es305)
+{
+ int ret = 0;
+ const u8 *i2c_cmds;
+ int size;
+
+ i2c_cmds = es305->fw->data;
+ size = es305->fw->size;
+
+ while (size > 0) {
+ ret = i2c_master_send(es305->client, i2c_cmds,
+ min(size, MAX_CMD_SEND_SIZE));
+ if (ret < 0) {
+ dev_err(es305->dev, "i2c_master_send failed\n");
+ break;
+ }
+ size -= MAX_CMD_SEND_SIZE;
+ i2c_cmds += MAX_CMD_SEND_SIZE;
+ }
+
+ return ret;
+}
+
+static int es305_reset(struct es305_data *es305)
+{
+ int ret = 0;
+ struct es305_platform_data *pdata = es305->pdata;
+ static const u8 boot[2] = {ES305_BOOT >> 8, ES305_BOOT};
+ u8 ack;
+ int retry = RETRY_COUNT;
+
+ while (retry--) {
+ /* Reset ES305 chip */
+ gpio_set_value(pdata->gpio_reset, 0);
+ usleep_range(200, 400);
+ gpio_set_value(pdata->gpio_reset, 1);
+
+ /* Delay before sending i2c commands */
+ msleep(50);
+
+ /*
+ * Send boot command and check response. The boot command
+ * is different from the others in that it's only 2 bytes,
+ * and the ack retry mechanism is different too.
+ */
+ ret = i2c_master_send(es305->client, boot, 2);
+ if (ret < 0) {
+ dev_err(es305->dev, "i2c_master_send failed\n");
+ continue;
+ }
+ usleep_range(1000, 2000);
+ ret = i2c_master_recv(es305->client, &ack, 1);
+ if (ret < 0) {
+ dev_err(es305->dev, "i2c_master_recv failed\n");
+ continue;
+ }
+ if (ack != ES305_BOOT_ACK) {
+ dev_err(es305->dev, "boot ack incorrect (got 0x%.2x)\n",
+ ack);
+ continue;
+ }
+
+ ret = es305_load_firmware(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "load firmware error\n");
+ continue;
+ }
+
+ /* Delay before issuing a sync command */
+ msleep(120);
+
+ ret = es305_send_cmd(es305, ES305_SYNC_POLLING, NULL);
+ if (ret < 0) {
+ dev_err(es305->dev, "sync error\n");
+ continue;
+ }
+
+ break;
+ }
+
+ return ret;
+}
+
+static int es305_sleep(struct es305_data *es305)
+{
+ int ret = 0;
+ struct es305_platform_data *pdata = es305->pdata;
+
+ if (es305->asleep)
+ return ret;
+
+ ret = es305_send_cmd(es305, ES305_SET_POWER_STATE_SLEEP, NULL);
+ if (ret < 0) {
+ dev_err(es305->dev, "set power state error\n");
+ return ret;
+ }
+
+ /* The clock can be disabled after the device has had time to sleep */
+ msleep(20);
+ pdata->clk_enable(false);
+ gpio_set_value(pdata->gpio_wakeup, 1);
+
+ es305->asleep = true;
+
+ return ret;
+}
+
+static int es305_wake(struct es305_data *es305)
+{
+ int ret = 0;
+ struct es305_platform_data *pdata = es305->pdata;
+
+ if (!es305->asleep)
+ return ret;
+
+ pdata->clk_enable(true);
+ gpio_set_value(pdata->gpio_wakeup, 0);
+ msleep(30);
+
+ ret = es305_send_cmd(es305, ES305_SYNC_POLLING, NULL);
+ if (ret < 0) {
+ dev_err(es305->dev, "sync error\n");
+
+ /* Go back to sleep */
+ pdata->clk_enable(false);
+ gpio_set_value(pdata->gpio_wakeup, 1);
+ return ret;
+ }
+
+ es305->asleep = false;
+
+ return ret;
+}
+
+static int es305_set_passthrough(struct es305_data *es305, u32 path)
+{
+ int ret;
+
+ ret = es305_send_cmd(es305, ES305_DIGITAL_PASSTHROUGH | path, NULL);
+ if (ret < 0) {
+ dev_err(es305->dev, "set passthrough error\n");
+ return ret;
+ }
+
+ es305->passthrough_on = !!path;
+
+ return ret;
+}
+
+static ssize_t es305_audio_routing_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct es305_data *es305 = dev_get_drvdata(dev);
+ int ret;
+ u16 val;
+
+ if (!es305->device_ready)
+ return -EAGAIN;
+
+ mutex_lock(&es305->lock);
+ ret = es305_wake(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to wake\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ ret = es305_send_cmd(es305, ES305_GET_AUDIO_ROUTING, &val);
+ if (ret < 0) {
+ dev_err(es305->dev, "get audio routing error\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ mutex_unlock(&es305->lock);
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t es305_audio_routing_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct es305_data *es305 = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ if (!es305->device_ready)
+ return -EAGAIN;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return -EINVAL;
+
+ mutex_lock(&es305->lock);
+ ret = es305_wake(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to wake\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ ret = es305_send_cmd(es305, ES305_SET_AUDIO_ROUTING | (u32)val, NULL);
+ if (ret < 0) {
+ dev_err(es305->dev, "set audio routing error\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ mutex_unlock(&es305->lock);
+ return size;
+}
+
+static ssize_t es305_preset_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct es305_data *es305 = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ if (!es305->device_ready)
+ return -EAGAIN;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return -EINVAL;
+
+ mutex_lock(&es305->lock);
+ ret = es305_wake(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to wake\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ ret = es305_send_cmd(es305, ES305_SET_PRESET | (u32)val, NULL);
+ if (ret < 0) {
+ dev_err(es305->dev, "set preset error\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ mutex_unlock(&es305->lock);
+ return size;
+}
+
+static ssize_t es305_voice_processing_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct es305_data *es305 = dev_get_drvdata(dev);
+ int ret;
+ u16 val;
+
+ if (!es305->device_ready)
+ return -EAGAIN;
+
+ mutex_lock(&es305->lock);
+ ret = es305_wake(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to wake\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ ret = es305_send_cmd(es305, ES305_GET_VOICE_PROCESSING, &val);
+ if (ret < 0) {
+ dev_err(es305->dev, "get voice processing error\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ mutex_unlock(&es305->lock);
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t es305_voice_processing_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct es305_data *es305 = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ if (!es305->device_ready)
+ return -EAGAIN;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return -EINVAL;
+
+ mutex_lock(&es305->lock);
+ ret = es305_wake(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to wake\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ /*
+ * If voice processing is being switched on and passthrough is
+ * enabled, disable passthrough first.
+ */
+ if (val && es305->passthrough_on) {
+ ret = es305_set_passthrough(es305, 0);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to disable passthrough\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+ }
+
+ ret = es305_send_cmd(es305, ES305_SET_VOICE_PROCESSING | (u32)val,
+ NULL);
+ if (ret < 0) {
+ dev_err(es305->dev, "set voice processing error\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ mutex_unlock(&es305->lock);
+ return size;
+}
+
+static ssize_t es305_sleep_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct es305_data *es305 = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", es305->asleep ? 1 : 0);
+}
+
+static ssize_t es305_sleep_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct es305_data *es305 = dev_get_drvdata(dev);
+ unsigned long state;
+ int ret;
+
+ if (!es305->device_ready)
+ return -EAGAIN;
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ return -EINVAL;
+
+ if (!!state ^ es305->asleep) {
+ /* requested sleep state is different to current state */
+ mutex_lock(&es305->lock);
+ if (state) {
+ if (es305->passthrough != 0) {
+ ret = es305_set_passthrough(es305,
+ es305->passthrough);
+ if (ret < 0)
+ dev_err(es305->dev,
+ "unable to set passthrough\n");
+ }
+ ret = es305_sleep(es305);
+ } else {
+ ret = es305_wake(es305);
+ }
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to change sleep state\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+ mutex_unlock(&es305->lock);
+ }
+
+ return size;
+}
+
+static ssize_t es305_algorithm_parm_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct es305_data *es305 = dev_get_drvdata(dev);
+ int ret;
+ u16 val;
+ u32 parm;
+
+ if (!es305->device_ready)
+ return -EAGAIN;
+
+ if (strcmp(attr->attr.name, "aec_enable") == 0)
+ parm = ES305_AEC_MODE;
+ else if (strcmp(attr->attr.name, "tx_agc_enable") == 0)
+ parm = ES305_TX_AGC;
+ else if (strcmp(attr->attr.name, "rx_agc_enable") == 0)
+ parm = ES305_RX_AGC;
+ else if (strcmp(attr->attr.name, "tx_ns_level") == 0)
+ parm = ES305_TX_NS_LEVEL;
+ else if (strcmp(attr->attr.name, "rx_ns_level") == 0)
+ parm = ES305_RX_NS_LEVEL;
+ else
+ return -EINVAL;
+
+ mutex_lock(&es305->lock);
+ ret = es305_wake(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to wake\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ ret = es305_send_cmd(es305, ES305_GET_ALGORITHM_PARM | parm, &val);
+ if (ret < 0) {
+ dev_err(es305->dev, "get algorithm parm error\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ mutex_unlock(&es305->lock);
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t es305_algorithm_parm_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct es305_data *es305 = dev_get_drvdata(dev);
+ unsigned long val;
+ unsigned long val_max = 1;
+ int ret;
+ u32 parm;
+
+ if (!es305->device_ready)
+ return -EAGAIN;
+
+ if (strcmp(attr->attr.name, "aec_enable") == 0) {
+ parm = ES305_AEC_MODE;
+ } else if (strcmp(attr->attr.name, "tx_agc_enable") == 0) {
+ parm = ES305_TX_AGC;
+ } else if (strcmp(attr->attr.name, "rx_agc_enable") == 0) {
+ parm = ES305_RX_AGC;
+ } else if (strcmp(attr->attr.name, "tx_ns_level") == 0) {
+ parm = ES305_TX_NS_LEVEL;
+ val_max = 10;
+ } else if (strcmp(attr->attr.name, "rx_ns_level") == 0) {
+ parm = ES305_RX_NS_LEVEL;
+ val_max = 10;
+ } else if (strcmp(attr->attr.name, "algorithm_reset") == 0) {
+ parm = ES305_ALGORITHM_RESET;
+ val_max = 0xffff;
+ } else {
+ return -EINVAL;
+ }
+
+ /* Check that a valid value was obtained */
+ ret = kstrtoul(buf, 0, &val);
+ if (ret || (val > val_max))
+ return -EINVAL;
+
+ mutex_lock(&es305->lock);
+ ret = es305_wake(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to wake\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ ret = es305_send_cmd(es305, ES305_SET_ALGORITHM_PARM_ID | parm, NULL);
+ if (ret < 0) {
+ dev_err(es305->dev, "set algorithm parm id error\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+ ret = es305_send_cmd(es305, ES305_SET_ALGORITHM_PARM | (u32)val, NULL);
+ if (ret < 0) {
+ dev_err(es305->dev, "set algorithm parm error\n");
+ mutex_unlock(&es305->lock);
+ return ret;
+ }
+
+ mutex_unlock(&es305->lock);
+ return size;
+}
+
+static DEVICE_ATTR(audio_routing, S_IRUGO | S_IWUSR,
+ es305_audio_routing_show, es305_audio_routing_store);
+static DEVICE_ATTR(preset, S_IWUSR,
+ NULL, es305_preset_store);
+static DEVICE_ATTR(voice_processing, S_IRUGO | S_IWUSR,
+ es305_voice_processing_show, es305_voice_processing_store);
+static DEVICE_ATTR(sleep, S_IRUGO | S_IWUSR,
+ es305_sleep_show, es305_sleep_store);
+static DEVICE_ATTR(aec_enable, S_IRUGO | S_IWUSR,
+ es305_algorithm_parm_show, es305_algorithm_parm_store);
+static DEVICE_ATTR(tx_agc_enable, S_IRUGO | S_IWUSR,
+ es305_algorithm_parm_show, es305_algorithm_parm_store);
+static DEVICE_ATTR(rx_agc_enable, S_IRUGO | S_IWUSR,
+ es305_algorithm_parm_show, es305_algorithm_parm_store);
+static DEVICE_ATTR(tx_ns_level, S_IRUGO | S_IWUSR,
+ es305_algorithm_parm_show, es305_algorithm_parm_store);
+static DEVICE_ATTR(rx_ns_level, S_IRUGO | S_IWUSR,
+ es305_algorithm_parm_show, es305_algorithm_parm_store);
+static DEVICE_ATTR(algorithm_reset, S_IWUSR,
+ NULL, es305_algorithm_parm_store);
+
+static struct attribute *es305_attributes[] = {
+ &dev_attr_audio_routing.attr,
+ &dev_attr_preset.attr,
+ &dev_attr_voice_processing.attr,
+ &dev_attr_sleep.attr,
+ &dev_attr_aec_enable.attr,
+ &dev_attr_tx_agc_enable.attr,
+ &dev_attr_rx_agc_enable.attr,
+ &dev_attr_tx_ns_level.attr,
+ &dev_attr_rx_ns_level.attr,
+ &dev_attr_algorithm_reset.attr,
+ NULL,
+};
+
+static const struct attribute_group es305_attribute_group = {
+ .attrs = es305_attributes,
+};
+
+/*
+ * This is the callback function passed to request_firmware_nowait(),
+ * and will be called as soon as the firmware is ready.
+ */
+static void es305_firmware_ready(const struct firmware *fw, void *context)
+{
+ struct es305_data *es305 = (struct es305_data *)context;
+ struct es305_platform_data *pdata = es305->pdata;
+ int ret;
+
+ if (!fw) {
+ dev_err(es305->dev, "firmware request failed\n");
+ return;
+ }
+ es305->fw = fw;
+
+ pdata->clk_enable(true);
+
+ ret = es305_reset(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to reset device\n");
+ goto err;
+ }
+
+ /* Enable passthrough if needed */
+ if (es305->passthrough != 0) {
+ ret = es305_set_passthrough(es305, es305->passthrough);
+ if (ret < 0) {
+ dev_err(es305->dev,
+ "unable to enable digital passthrough\n");
+ goto err;
+ }
+ }
+
+ ret = es305_sleep(es305);
+ if (ret < 0) {
+ dev_err(es305->dev, "unable to sleep\n");
+ goto err;
+ }
+
+ es305->device_ready = true;
+
+err:
+ release_firmware(es305->fw);
+ es305->fw = NULL;
+}
+
+static int __devinit es305_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct es305_data *es305;
+ struct es305_platform_data *pdata = client->dev.platform_data;
+ int ret = 0;
+
+ es305 = kzalloc(sizeof(*es305), GFP_KERNEL);
+ if (es305 == NULL) {
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ es305->client = client;
+ i2c_set_clientdata(client, es305);
+
+ es305->dev = &client->dev;
+ dev_set_drvdata(es305->dev, es305);
+
+ es305->pdata = pdata;
+
+ if ((pdata->passthrough_src < 0) || (pdata->passthrough_src > 4) ||
+ (pdata->passthrough_dst < 0) ||
+ (pdata->passthrough_dst > 4) ||
+ !pdata->clk_enable) {
+ dev_err(es305->dev, "invalid pdata\n");
+ ret = -EINVAL;
+ goto err_pdata;
+ } else if ((pdata->passthrough_src != 0) &&
+ (pdata->passthrough_dst != 0)) {
+ es305->passthrough = ((pdata->passthrough_src + 3) << 4) |
+ ((pdata->passthrough_dst - 1) << 2);
+ }
+
+ ret = gpio_request(pdata->gpio_wakeup, "ES305 wakeup");
+ if (ret < 0) {
+ dev_err(es305->dev, "error requesting wakeup gpio\n");
+ goto err_gpio_wakeup;
+ }
+ gpio_direction_output(pdata->gpio_wakeup, 0);
+
+ ret = gpio_request(pdata->gpio_reset, "ES305 reset");
+ if (ret < 0) {
+ dev_err(es305->dev, "error requesting reset gpio\n");
+ goto err_gpio_reset;
+ }
+ gpio_direction_output(pdata->gpio_reset, 0);
+
+ mutex_init(&es305->lock);
+
+ request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ ES305_FIRMWARE_NAME, es305->dev, GFP_KERNEL,
+ es305, es305_firmware_ready);
+
+ /*
+ * This is not a critical failure, since the device can still be left
+ * in passthrough mode.
+ */
+ ret = sysfs_create_group(&es305->dev->kobj, &es305_attribute_group);
+ if (ret)
+ dev_err(es305->dev, "failed to create sysfs group\n");
+
+ return 0;
+
+err_gpio_reset:
+ gpio_free(pdata->gpio_wakeup);
+err_gpio_wakeup:
+err_pdata:
+ kfree(es305);
+err_kzalloc:
+ return ret;
+}
+
+static int __devexit es305_remove(struct i2c_client *client)
+{
+ struct es305_data *es305 = i2c_get_clientdata(client);
+
+ sysfs_remove_group(&es305->dev->kobj, &es305_attribute_group);
+
+ i2c_set_clientdata(client, NULL);
+
+ gpio_free(es305->pdata->gpio_wakeup);
+ gpio_free(es305->pdata->gpio_reset);
+ kfree(es305);
+
+ return 0;
+}
+
+static const struct i2c_device_id es305_id[] = {
+ {"audience_es305", 0},
+ {},
+};
+
+static struct i2c_driver es305_driver = {
+ .driver = {
+ .name = "audience_es305",
+ .owner = THIS_MODULE,
+ },
+ .probe = es305_probe,
+ .remove = __devexit_p(es305_remove),
+ .id_table = es305_id,
+};
+
+static int __init es305_init(void)
+{
+ return i2c_add_driver(&es305_driver);
+}
+
+static void __exit es305_exit(void)
+{
+ i2c_del_driver(&es305_driver);
+}
+
+module_init(es305_init);
+module_exit(es305_exit);
+
+MODULE_DESCRIPTION("Audience ES305 Voice Processor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/haptic_isa1200.c b/drivers/misc/haptic_isa1200.c
new file mode 100644
index 0000000..fe88ff03
--- /dev/null
+++ b/drivers/misc/haptic_isa1200.c
@@ -0,0 +1,286 @@
+/*
+ * haptic_isa1200.c - Haptic Controller
+ *
+ * Copyright (C) 2012 Samsung Electronics Co. Ltd. All Rights Reserved.
+ * Author: Vishnudev Ramakrishnan <vramakri@sta.samsung.com>
+ *
+ * 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 pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <../../../drivers/staging/android/timed_output.h>
+#include <linux/wakelock.h>
+#include <linux/platform_data/haptic_isa1200.h>
+
+#define ISA1200_HCTRL0 0x30
+#define ISA1200_HCTRL0_DEFAULT 1 /* power down mode, default */
+#define ISA1200_HCTRL0_HAPDREN BIT(7)
+#define ISA1200_HCTRL0_HAPDIGMOD_PWM_IN BIT(3)
+#define ISA1200_HCTRL0_PWMMOD_DIVIDER_128 0
+#define ISA1200_HCTRL0_PWMMOD_DIVIDER_256 1
+#define ISA1200_HCTRL0_PWMMOD_DIVIDER_512 2
+#define ISA1200_HCTRL0_PWMMOD_DIVIDER_1024 3
+
+#define ISA1200_HCTRL1 0x31
+#define ISA1200_HCTRL1_RESERVED_BIT6_ON BIT(6)
+
+#define ISA1200_HCTRL1_MOTTYP_ERM BIT(5)
+#define ISA1200_HCTRL1_MOTTYP_LRA 0
+
+#define PWM_HAPTIC_PERIOD 38676 /* 202 Hz with 128 divider */
+/* duty cycle for max vibration effect */
+#define PWM_MAX_VIBRATE_DUTY (PWM_HAPTIC_PERIOD * 99/100)
+/* duty cycle for no vibration effect */
+#define PWM_NO_VIBRATE_DUTY (PWM_HAPTIC_PERIOD * 50/100)
+
+struct isa1200_data {
+ struct i2c_client *client;
+ struct isa1200_platform_data *pdata;
+ struct hrtimer timer;
+ struct timed_output_dev dev;
+ struct mutex lock;
+ struct wake_lock wklock;
+ struct pwm_device *pwm;
+ bool vibrate;
+};
+
+static int isa1200_vibrate_on(struct isa1200_data *haptic)
+{
+ int ret;
+ u8 value;
+
+ pr_debug("vibrate is %d\n", haptic->vibrate);
+ if (!haptic->vibrate) {
+ wake_lock(&haptic->wklock);
+ ret = pwm_config(haptic->pwm, PWM_NO_VIBRATE_DUTY,
+ PWM_HAPTIC_PERIOD);
+ if (ret) {
+ pr_err("pwm_config failed %d\n", ret);
+ goto pwm_no_duty_cfg_failed;
+ }
+ pwm_enable(haptic->pwm);
+ gpio_set_value(haptic->pdata->hap_en_gpio, 1);
+ usleep_range(100, 200);
+ value = ISA1200_HCTRL0_HAPDREN |
+ ISA1200_HCTRL0_HAPDIGMOD_PWM_IN |
+ ISA1200_HCTRL0_PWMMOD_DIVIDER_128;
+ ret = i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0,
+ value);
+ if (ret < 0) {
+ pr_err("write HCTRL0 failed %d\n", ret);
+ goto err_return;
+ }
+ value = ISA1200_HCTRL1_RESERVED_BIT6_ON |
+ ISA1200_HCTRL1_MOTTYP_LRA;
+ ret = i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL1,
+ value);
+ if (ret < 0) {
+ pr_err("write HCTRL1 failed %d\n", ret);
+ goto err_return;
+ }
+ ret = pwm_config(haptic->pwm, PWM_MAX_VIBRATE_DUTY,
+ PWM_HAPTIC_PERIOD);
+ if (ret) {
+ pr_err("pwm_config for max duty failed %d\n", ret);
+ goto err_return;
+ }
+ haptic->vibrate = true;
+ }
+ return 0;
+
+err_return:
+ gpio_set_value(haptic->pdata->hap_en_gpio, 0);
+ pwm_disable(haptic->pwm);
+pwm_no_duty_cfg_failed:
+ wake_unlock(&haptic->wklock);
+ return ret;
+}
+
+static void isa1200_vibrate_off(struct isa1200_data *haptic)
+{
+ int ret;
+ pr_debug("vibrate is %d\n", haptic->vibrate);
+ if (haptic->vibrate) {
+ ret = pwm_config(haptic->pwm, PWM_NO_VIBRATE_DUTY,
+ PWM_HAPTIC_PERIOD);
+ if (ret)
+ pr_err("pwm_config failed %d\n", ret);
+ gpio_set_value(haptic->pdata->hap_en_gpio, 0);
+ pwm_disable(haptic->pwm);
+ haptic->vibrate = false;
+ wake_unlock(&haptic->wklock);
+ }
+}
+
+static void isa1200_enable(struct timed_output_dev *dev, int timeout)
+{
+ int ret;
+ struct isa1200_data *haptic = container_of(dev, struct isa1200_data,
+ dev);
+
+ mutex_lock(&haptic->lock);
+ hrtimer_cancel(&haptic->timer);
+ pr_debug("timeout is %d msec\n", timeout);
+ if (timeout > 0) {
+ ret = isa1200_vibrate_on(haptic);
+ if (ret)
+ goto vibrate_error;
+ if (timeout > haptic->pdata->max_timeout)
+ timeout = haptic->pdata->max_timeout;
+ hrtimer_start(&haptic->timer,
+ ns_to_ktime((u64)timeout * NSEC_PER_MSEC),
+ HRTIMER_MODE_REL);
+ } else {
+ isa1200_vibrate_off(haptic);
+ }
+
+vibrate_error:
+ mutex_unlock(&haptic->lock);
+}
+
+static int isa1200_get_time(struct timed_output_dev *dev)
+{
+ struct isa1200_data *haptic = container_of(dev, struct isa1200_data,
+ dev);
+
+ if (hrtimer_active(&haptic->timer)) {
+ ktime_t r = hrtimer_get_remaining(&haptic->timer);
+ return ktime_to_ms(r);
+ }
+ return 0;
+}
+
+static enum hrtimer_restart isa1200_timer_func(struct hrtimer *timer)
+{
+ struct isa1200_data *haptic = container_of(timer, struct isa1200_data,
+ timer);
+ pr_debug("vibrate is %d\n", haptic->vibrate);
+ /* no lock required below, as isa1200_enable cancels timer first */
+ isa1200_vibrate_off(haptic);
+ return HRTIMER_NORESTART;
+}
+
+static int __devinit isa1200_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct isa1200_data *haptic;
+ struct isa1200_platform_data *pdata;
+ int ret;
+ pr_debug("\n");
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+ pr_err("no support for i2c write byte data\n");
+ return -ENOSYS;
+ }
+
+ pdata = client->dev.platform_data;
+ if (!pdata) {
+ pr_err("no platform data\n");
+ return -EINVAL;
+ }
+
+ haptic = kmalloc(sizeof(*haptic), GFP_KERNEL);
+ if (!haptic)
+ return -ENOMEM;
+ haptic->client = client;
+ haptic->pdata = pdata;
+ haptic->vibrate = false;
+
+ ret = gpio_request_one(pdata->hap_en_gpio, GPIOF_OUT_INIT_LOW,
+ "haptic_gpio");
+ if (ret) {
+ printk(KERN_ERR "%s: gpio %d request failed with error %d\n",
+ __func__, pdata->hap_en_gpio, ret);
+ goto enable_gpio_fail;
+ }
+
+ haptic->pwm = pwm_request(pdata->pwm_ch, id->name);
+ if (IS_ERR(haptic->pwm)) {
+ pr_err("pwm request failed\n");
+ ret = PTR_ERR(haptic->pwm);
+ goto pwm_req_fail;
+ }
+
+ mutex_init(&haptic->lock);
+ wake_lock_init(&haptic->wklock, WAKE_LOCK_SUSPEND, "vibrator");
+
+ hrtimer_init(&haptic->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ haptic->timer.function = isa1200_timer_func;
+
+ i2c_set_clientdata(client, haptic);
+
+ /* register with timed output class */
+ haptic->dev.name = "vibrator";
+ haptic->dev.get_time = isa1200_get_time;
+ haptic->dev.enable = isa1200_enable;
+ ret = timed_output_dev_register(&haptic->dev);
+ if (ret < 0) {
+ pr_err("timed output register failed %d\n", ret);
+ goto setup_fail;
+ }
+ pr_debug("%s registered\n", id->name);
+ return 0;
+
+setup_fail:
+ mutex_destroy(&haptic->lock);
+ wake_lock_destroy(&haptic->wklock);
+ pwm_free(haptic->pwm);
+pwm_req_fail:
+ gpio_free(pdata->hap_en_gpio);
+enable_gpio_fail:
+ kfree(haptic);
+ return ret;
+}
+
+static int __devexit isa1200_remove(struct i2c_client *client)
+{
+ struct isa1200_data *haptic = i2c_get_clientdata(client);
+ gpio_set_value(haptic->pdata->hap_en_gpio, 0);
+ timed_output_dev_unregister(&haptic->dev);
+ hrtimer_cancel(&haptic->timer);
+ mutex_destroy(&haptic->lock);
+ wake_lock_destroy(&haptic->wklock);
+ pwm_free(haptic->pwm);
+ gpio_free(haptic->pdata->hap_en_gpio);
+ kfree(haptic);
+ return 0;
+}
+
+static const struct i2c_device_id isa1200_id[] = {
+ {"isa1200", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, isa1200_id);
+
+static struct i2c_driver isa1200_driver = {
+ .driver = {
+ .name = "isa1200",
+ .owner = THIS_MODULE,
+ },
+ .probe = isa1200_probe,
+ .remove = __devexit_p(isa1200_remove),
+ .id_table = isa1200_id,
+};
+
+module_i2c_driver(isa1200_driver);
+
+MODULE_AUTHOR("Vishnudev Ramakrishnan <vramakri@sta.samsung.com>");
+MODULE_DESCRIPTION("ISA1200 Haptic Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/misc/sec_jack.c b/drivers/misc/sec_jack.c
new file mode 100755
index 0000000..4514dea
--- /dev/null
+++ b/drivers/misc/sec_jack.c
@@ -0,0 +1,505 @@
+/* drivers/misc/sec_jack.c
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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.
+ */
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/switch.h>
+#include <linux/input.h>
+#include <linux/timer.h>
+#include <linux/wakelock.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/gpio_event.h>
+#include <linux/sec_jack.h>
+
+#define MAX_ZONE_LIMIT 10
+#define SEND_KEY_CHECK_TIME_MS 30 /* 30ms */
+#define DET_CHECK_TIME_MS 200 /* 200ms */
+#define WAKE_LOCK_TIME (HZ * 5) /* 5 sec */
+
+struct sec_jack_info {
+ struct sec_jack_platform_data *pdata;
+ struct delayed_work jack_detect_work;
+ struct work_struct buttons_work;
+ struct work_struct detect_work;
+ struct workqueue_struct *queue;
+ struct input_dev *input_dev;
+ struct wake_lock det_wake_lock;
+ struct sec_jack_zone *zone;
+ struct input_handler handler;
+ struct input_handle handle;
+ struct input_device_id ids;
+ int det_irq;
+ int dev_id;
+ int pressed;
+ int pressed_code;
+ struct platform_device *send_key_dev;
+ unsigned int cur_jack_type;
+};
+
+/* with some modifications like moving all the gpio structs inside
+ * the platform data and getting the name for the switch and
+ * gpio_event from the platform data, the driver could support more than
+ * one headset jack, but currently user space is looking only for
+ * one key file and switch for a headset so it'd be overkill and
+ * untestable so we limit to one instantiation for now.
+ */
+static atomic_t instantiated = ATOMIC_INIT(0);
+
+/* sysfs name HeadsetObserver.java looks for to track headset state
+ */
+struct switch_dev switch_jack_detection = {
+ .name = "h2w",
+};
+
+static struct gpio_event_direct_entry sec_jack_key_map[] = {
+ {
+ .code = KEY_UNKNOWN,
+ },
+};
+
+static struct gpio_event_input_info sec_jack_key_info = {
+ .info.func = gpio_event_input_func,
+ .info.no_suspend = true,
+ .type = EV_KEY,
+ .debounce_time.tv64 = SEND_KEY_CHECK_TIME_MS * NSEC_PER_MSEC,
+ .keymap = sec_jack_key_map,
+ .keymap_size = ARRAY_SIZE(sec_jack_key_map)
+};
+
+static struct gpio_event_info *sec_jack_input_info[] = {
+ &sec_jack_key_info.info,
+};
+
+static struct gpio_event_platform_data sec_jack_input_data = {
+ .name = "sec_jack",
+ .info = sec_jack_input_info,
+ .info_count = ARRAY_SIZE(sec_jack_input_info),
+};
+
+/* gpio_input driver does not support to read adc value.
+ * We use input filter to support 3-buttons of headset
+ * without changing gpio_input driver.
+ */
+static bool sec_jack_buttons_filter(struct input_handle *handle,
+ unsigned int type, unsigned int code,
+ int value)
+{
+ struct sec_jack_info *hi = handle->handler->private;
+
+ if (type != EV_KEY || code != KEY_UNKNOWN)
+ return false;
+
+ hi->pressed = value;
+
+ /* This is called in timer handler of gpio_input driver.
+ * We use workqueue to read adc value.
+ */
+ queue_work(hi->queue, &hi->buttons_work);
+
+ return true;
+}
+
+static int sec_jack_buttons_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct sec_jack_info *hi;
+ struct sec_jack_platform_data *pdata;
+ struct sec_jack_buttons_zone *btn_zones;
+ int err;
+ int i;
+
+ /* bind input_handler to input device related to only sec_jack */
+ if (dev->name != sec_jack_input_data.name)
+ return -ENODEV;
+
+ hi = handler->private;
+ pdata = hi->pdata;
+ btn_zones = pdata->buttons_zones;
+
+ hi->input_dev = dev;
+ hi->handle.dev = dev;
+ hi->handle.handler = handler;
+ hi->handle.open = 0;
+ hi->handle.name = "sec_jack_buttons";
+
+ err = input_register_handle(&hi->handle);
+ if (err) {
+ pr_err("%s: Failed to register sec_jack buttons handle, "
+ "error %d\n", __func__, err);
+ goto err_register_handle;
+ }
+
+ err = input_open_device(&hi->handle);
+ if (err) {
+ pr_err("%s: Failed to open input device, error %d\n",
+ __func__, err);
+ goto err_open_device;
+ }
+
+ for (i = 0; i < pdata->num_buttons_zones; i++)
+ input_set_capability(dev, EV_KEY, btn_zones[i].code);
+
+ return 0;
+
+ err_open_device:
+ input_unregister_handle(&hi->handle);
+ err_register_handle:
+
+ return err;
+}
+
+static void sec_jack_buttons_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+}
+
+static void sec_jack_set_type(struct sec_jack_info *hi, int jack_type)
+{
+ struct sec_jack_platform_data *pdata = hi->pdata;
+
+ /* this can happen during slow inserts where we think we identified
+ * the type but then we get another interrupt and do it again
+ */
+ if (jack_type == hi->cur_jack_type) {
+ if (jack_type != SEC_HEADSET_4POLE)
+ pdata->set_micbias_state(false);
+ return;
+ }
+
+ if (jack_type == SEC_HEADSET_4POLE) {
+ /* for a 4 pole headset, enable detection of send/end key */
+ if (hi->send_key_dev == NULL)
+ /* enable to get events again */
+ hi->send_key_dev = platform_device_register_data(NULL,
+ GPIO_EVENT_DEV_NAME,
+ hi->dev_id,
+ &sec_jack_input_data,
+ sizeof(sec_jack_input_data));
+ } else {
+ /* for all other jacks, disable send/end key detection */
+ if (hi->send_key_dev != NULL) {
+ /* disable to prevent false events on next insert */
+ platform_device_unregister(hi->send_key_dev);
+ hi->send_key_dev = NULL;
+ }
+ /* micbias is left enabled for 4pole and disabled otherwise */
+ pdata->set_micbias_state(false);
+ }
+
+ hi->cur_jack_type = jack_type;
+ pr_info("%s : jack_type = %d\n", __func__, jack_type);
+
+ /* prevent suspend to allow user space to respond to switch */
+ wake_lock_timeout(&hi->det_wake_lock, WAKE_LOCK_TIME);
+
+ switch_set_state(&switch_jack_detection, jack_type);
+}
+
+static void handle_jack_not_inserted(struct sec_jack_info *hi)
+{
+ sec_jack_set_type(hi, SEC_JACK_NO_DEVICE);
+ hi->pdata->set_micbias_state(false);
+}
+
+static void determine_jack_type(struct sec_jack_info *hi)
+{
+ struct sec_jack_zone *zones = hi->pdata->zones;
+ int size = hi->pdata->num_zones;
+ int count[MAX_ZONE_LIMIT] = {0};
+ int adc;
+ int i;
+ unsigned npolarity = !hi->pdata->det_active_high;
+
+ while (gpio_get_value(hi->pdata->det_gpio) ^ npolarity) {
+ adc = hi->pdata->get_adc_value();
+ pr_debug("%s: adc = %d\n", __func__, adc);
+
+ /* determine the type of headset based on the
+ * adc value. An adc value can fall in various
+ * ranges or zones. Within some ranges, the type
+ * can be returned immediately. Within others, the
+ * value is considered unstable and we need to sample
+ * a few more types (up to the limit determined by
+ * the range) before we return the type for that range.
+ */
+ for (i = 0; i < size; i++) {
+ if (adc <= zones[i].adc_high) {
+ if (++count[i] > zones[i].check_count) {
+ sec_jack_set_type(hi,
+ zones[i].jack_type);
+ return;
+ }
+ msleep(zones[i].delay_ms);
+ break;
+ }
+ }
+ }
+ /* jack removed before detection complete */
+ pr_debug("%s : jack removed before detection complete\n", __func__);
+ handle_jack_not_inserted(hi);
+}
+
+/* thread run whenever the headset detect state changes (either insertion
+ * or removal).
+ */
+static irqreturn_t sec_jack_detect_irq(int irq, void *dev_id)
+{
+ struct sec_jack_info *hi = dev_id;
+
+ queue_work(hi->queue, &hi->detect_work);
+
+ return IRQ_HANDLED;
+}
+
+void sec_jack_detect_work(struct work_struct *work)
+{
+ struct sec_jack_info *hi =
+ container_of(work, struct sec_jack_info, detect_work);
+ struct sec_jack_platform_data *pdata = hi->pdata;
+ int time_left_ms = DET_CHECK_TIME_MS;
+ unsigned npolarity = !hi->pdata->det_active_high;
+
+ /* set mic bias to enable adc */
+ pdata->set_micbias_state(true);
+
+ /* debounce headset jack. don't try to determine the type of
+ * headset until the detect state is true for a while.
+ */
+ while (time_left_ms > 0) {
+ if (!(gpio_get_value(hi->pdata->det_gpio) ^ npolarity)) {
+ /* jack not detected. */
+ handle_jack_not_inserted(hi);
+ return;
+ }
+ msleep(10);
+ time_left_ms -= 10;
+ }
+ /* jack presence was detected the whole time, figure out which type */
+ determine_jack_type(hi);
+}
+
+/* thread run whenever the button of headset is pressed or released */
+void sec_jack_buttons_work(struct work_struct *work)
+{
+ struct sec_jack_info *hi =
+ container_of(work, struct sec_jack_info, buttons_work);
+ struct sec_jack_platform_data *pdata = hi->pdata;
+ struct sec_jack_buttons_zone *btn_zones = pdata->buttons_zones;
+ int adc;
+ int i;
+
+ /* when button is released */
+ if (hi->pressed == 0) {
+ input_report_key(hi->input_dev, hi->pressed_code, 0);
+ input_sync(hi->input_dev);
+ pr_debug("%s: keycode=%d, is released\n", __func__,
+ hi->pressed_code);
+ return;
+ }
+
+ /* when button is pressed */
+ adc = pdata->get_adc_value();
+
+ for (i = 0; i < pdata->num_buttons_zones; i++)
+ if (adc >= btn_zones[i].adc_low &&
+ adc <= btn_zones[i].adc_high) {
+ hi->pressed_code = btn_zones[i].code;
+ input_report_key(hi->input_dev, btn_zones[i].code, 1);
+ input_sync(hi->input_dev);
+ pr_debug("%s: keycode=%d, is pressed\n", __func__,
+ btn_zones[i].code);
+ return;
+ }
+
+ pr_warn("%s: key is skipped. ADC value is %d\n", __func__, adc);
+}
+
+static int sec_jack_probe(struct platform_device *pdev)
+{
+ struct sec_jack_info *hi;
+ struct sec_jack_platform_data *pdata = pdev->dev.platform_data;
+ int ret;
+
+ pr_info("%s : Registering jack driver\n", __func__);
+ if (!pdata) {
+ pr_err("%s : pdata is NULL.\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!pdata->get_adc_value || !pdata->zones ||
+ !pdata->set_micbias_state || pdata->num_zones > MAX_ZONE_LIMIT) {
+ pr_err("%s : need to check pdata\n", __func__);
+ return -ENODEV;
+ }
+
+ if (atomic_xchg(&instantiated, 1)) {
+ pr_err("%s : already instantiated, can only have one\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ sec_jack_key_map[0].gpio = pdata->send_end_gpio;
+
+ hi = kzalloc(sizeof(struct sec_jack_info), GFP_KERNEL);
+ if (hi == NULL) {
+ pr_err("%s : Failed to allocate memory.\n", __func__);
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ hi->pdata = pdata;
+
+ /* make the id of our gpi_event device the same as our platform device,
+ * which makes it the responsiblity of the board file to make sure
+ * it is unique relative to other gpio_event devices
+ */
+ hi->dev_id = pdev->id;
+
+ ret = gpio_request(pdata->det_gpio, "ear_jack_detect");
+ if (ret) {
+ pr_err("%s : gpio_request failed for %d\n",
+ __func__, pdata->det_gpio);
+ goto err_gpio_request;
+ }
+
+ ret = switch_dev_register(&switch_jack_detection);
+ if (ret < 0) {
+ pr_err("%s : Failed to register switch device\n", __func__);
+ goto err_switch_dev_register;
+ }
+
+ wake_lock_init(&hi->det_wake_lock, WAKE_LOCK_SUSPEND, "sec_jack_det");
+
+ INIT_WORK(&hi->buttons_work, sec_jack_buttons_work);
+ INIT_WORK(&hi->detect_work, sec_jack_detect_work);
+ hi->queue = create_freezable_workqueue("sec_jack_wq");
+ if (hi->queue == NULL) {
+ ret = -ENOMEM;
+ pr_err("%s: Failed to create workqueue\n", __func__);
+ goto err_create_wq_failed;
+ }
+ queue_work(hi->queue, &hi->detect_work);
+
+ hi->det_irq = gpio_to_irq(pdata->det_gpio);
+
+ set_bit(EV_KEY, hi->ids.evbit);
+ hi->ids.flags = INPUT_DEVICE_ID_MATCH_EVBIT;
+ hi->handler.filter = sec_jack_buttons_filter;
+ hi->handler.connect = sec_jack_buttons_connect;
+ hi->handler.disconnect = sec_jack_buttons_disconnect;
+ hi->handler.name = "sec_jack_buttons";
+ hi->handler.id_table = &hi->ids;
+ hi->handler.private = hi;
+
+ ret = input_register_handler(&hi->handler);
+ if (ret) {
+ pr_err("%s : Failed to register_handler\n", __func__);
+ goto err_register_input_handler;
+ }
+ ret = request_irq(hi->det_irq, sec_jack_detect_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT, "sec_headset_detect", hi);
+ if (ret) {
+ pr_err("%s : Failed to request_irq.\n", __func__);
+ goto err_request_detect_irq;
+ }
+
+ /* to handle insert/removal when we're sleeping in a call */
+ ret = enable_irq_wake(hi->det_irq);
+ if (ret) {
+ pr_err("%s : Failed to enable_irq_wake.\n", __func__);
+ goto err_enable_irq_wake;
+ }
+
+ dev_set_drvdata(&pdev->dev, hi);
+
+ return 0;
+
+err_enable_irq_wake:
+ free_irq(hi->det_irq, hi);
+err_request_detect_irq:
+ input_unregister_handler(&hi->handler);
+err_register_input_handler:
+ destroy_workqueue(hi->queue);
+err_create_wq_failed:
+ wake_lock_destroy(&hi->det_wake_lock);
+ switch_dev_unregister(&switch_jack_detection);
+err_switch_dev_register:
+ gpio_free(pdata->det_gpio);
+err_gpio_request:
+ kfree(hi);
+err_kzalloc:
+ atomic_set(&instantiated, 0);
+
+ return ret;
+}
+
+static int sec_jack_remove(struct platform_device *pdev)
+{
+
+ struct sec_jack_info *hi = dev_get_drvdata(&pdev->dev);
+
+ pr_info("%s :\n", __func__);
+ disable_irq_wake(hi->det_irq);
+ free_irq(hi->det_irq, hi);
+ destroy_workqueue(hi->queue);
+ if (hi->send_key_dev) {
+ platform_device_unregister(hi->send_key_dev);
+ hi->send_key_dev = NULL;
+ }
+ input_unregister_handler(&hi->handler);
+ wake_lock_destroy(&hi->det_wake_lock);
+ switch_dev_unregister(&switch_jack_detection);
+ gpio_free(hi->pdata->det_gpio);
+ kfree(hi);
+ atomic_set(&instantiated, 0);
+
+ return 0;
+}
+
+static struct platform_driver sec_jack_driver = {
+ .probe = sec_jack_probe,
+ .remove = sec_jack_remove,
+ .driver = {
+ .name = "sec_jack",
+ .owner = THIS_MODULE,
+ },
+};
+static int __init sec_jack_init(void)
+{
+ return platform_driver_register(&sec_jack_driver);
+}
+
+static void __exit sec_jack_exit(void)
+{
+ platform_driver_unregister(&sec_jack_driver);
+}
+
+module_init(sec_jack_init);
+module_exit(sec_jack_exit);
+
+MODULE_AUTHOR("ms17.kim@samsung.com");
+MODULE_DESCRIPTION("Samsung Electronics Corp Ear-Jack detection driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/stmpe811-adc.c b/drivers/misc/stmpe811-adc.c
new file mode 100644
index 0000000..14b14d7
--- /dev/null
+++ b/drivers/misc/stmpe811-adc.c
@@ -0,0 +1,285 @@
+/*
+ * stmpe811-adc.c
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * SangYoung Son <hello.son@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 and
+ * only 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/fs.h>
+#include <linux/gpio.h>
+#include <linux/platform_data/stmpe811-adc.h>
+
+#define STMPE811_CHIP_ID 0x00
+#define STMPE811_ID_VER 0x02
+#define STMPE811_SYS_CTRL1 0x03
+#define STMPE811_SYS_CTRL2 0x04
+#define STMPE811_INT_CTRL 0x09
+#define STMPE811_INT_EN 0x0A
+#define STMPE811_INT_STA 0x0B
+#define STMPE811_ADC_INT_EN 0x0E
+#define STMPE811_ADC_INT_STA 0x0F
+#define STMPE811_ADC_CTRL1 0x20
+#define STMPE811_ADC_CTRL2 0x21
+#define STMPE811_ADC_CAPT 0x22
+#define STMPE811_ADC_DATA_CH0 0x30
+#define STMPE811_ADC_DATA_CH1 0x32
+#define STMPE811_ADC_DATA_CH2 0x34
+#define STMPE811_ADC_DATA_CH3 0x36
+#define STMPE811_ADC_DATA_CH4 0x38
+#define STMPE811_ADC_DATA_CH5 0x3A
+#define STMPE811_ADC_DATA_CH6 0x3C
+#define STMPE811_ADC_DATA_CH7 0x3E
+#define STMPE811_GPIO_AF 0x17
+#define STMPE811_TSC_CTRL 0x40
+
+static struct i2c_client *stmpe811_adc_i2c_client;
+
+struct stmpe811_adc_data {
+ struct i2c_client *client;
+ struct stmpe811_platform_data *pdata;
+ struct stmpe811_callbacks callbacks;
+
+ struct mutex adc_lock;
+};
+
+static int stmpe811_i2c_read(struct i2c_client *client, u8 reg, u8 *data,
+ u8 length)
+{
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(client, reg, length, data);
+ if (ret != length) {
+ dev_err(&client->dev, "%s: err %d, reg: 0x%02x\n", __func__,
+ ret, reg);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int stmpe811_write_register(struct i2c_client *client, u8 reg,
+ u16 w_data)
+{
+ int ret;
+
+ ret = i2c_smbus_write_word_data(client, reg, w_data);
+ if (ret < 0) {
+ dev_err(&client->dev, "%s: err %d, reg: 0x%02x\n", __func__,
+ ret, reg);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stmpe811_get_adc_data(u8 channel)
+{
+ struct i2c_client *client = stmpe811_adc_i2c_client;
+ u8 data[2];
+ u16 w_data;
+ u8 data_channel_addr;
+ int ret;
+
+ ret = stmpe811_write_register(client, STMPE811_ADC_CAPT,
+ (1 << channel));
+ if (ret < 0)
+ return ret;
+
+ msleep(20);
+ data_channel_addr = STMPE811_ADC_DATA_CH0 + (channel * 2);
+ ret = stmpe811_i2c_read(client, data_channel_addr, data, 2);
+ if (ret < 0)
+ return ret;
+
+ w_data = ((data[0]<<8) | data[1]) & 0x0FFF;
+ pr_debug("%s: STMPE811_ADC_DATA_CH%d(0x%x, %d)\n", __func__,
+ channel, w_data, w_data);
+
+ return w_data;
+}
+
+static int stmpe811_reg_init(struct stmpe811_adc_data *adc_data)
+{
+ struct i2c_client *client = adc_data->client;
+ int ret;
+
+ /* clock control: only adc on */
+ ret = stmpe811_write_register(client, STMPE811_SYS_CTRL2, 0x0E);
+ if (ret < 0)
+ goto reg_init_error;
+
+ /* interrupt enable: disable interrupt */
+ ret = stmpe811_write_register(client, STMPE811_INT_EN, 0x00);
+ if (ret < 0)
+ goto reg_init_error;
+
+ /* adc control: 64 sample time, 12bit adc, internel referance*/
+ ret = stmpe811_write_register(client, STMPE811_ADC_CTRL1, 0x38);
+ if (ret < 0)
+ goto reg_init_error;
+
+ /* adc control: 1.625MHz typ */
+ ret = stmpe811_write_register(client, STMPE811_ADC_CTRL2, 0x03);
+ if (ret < 0)
+ goto reg_init_error;
+
+ /* alt func: use for adc */
+ ret = stmpe811_write_register(client, STMPE811_GPIO_AF, 0x00);
+ if (ret < 0)
+ goto reg_init_error;
+
+ /* ts control: tsc disable */
+ ret = stmpe811_write_register(client, STMPE811_TSC_CTRL, 0x00);
+ if (ret < 0)
+ goto reg_init_error;
+
+ return 0;
+
+reg_init_error:
+ dev_err(&client->dev, "%s: reg init error: %d\n", __func__, ret);
+ return ret;
+}
+
+static const struct file_operations stmpe811_fops = {
+ .owner = THIS_MODULE,
+};
+
+static struct miscdevice stmpe811_adc_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "sec_adc",
+ .fops = &stmpe811_fops,
+};
+
+static int __devinit stmpe811_adc_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct stmpe811_platform_data *pdata = client->dev.platform_data;
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct stmpe811_adc_data *adc_data;
+ int ret;
+ u8 i2c_data[2];
+ u8 rev[2];
+
+ if (pdata == NULL) {
+ dev_err(&client->dev, "%s: no pdata\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ adc_data = kzalloc(sizeof(*adc_data), GFP_KERNEL);
+ if (!adc_data)
+ return -ENOMEM;
+
+ adc_data->client = client;
+ adc_data->pdata = pdata;
+ adc_data->callbacks.get_adc_data = stmpe811_get_adc_data;
+ if (pdata->register_cb)
+ pdata->register_cb(&adc_data->callbacks);
+
+ i2c_set_clientdata(client, adc_data);
+ stmpe811_adc_i2c_client = client;
+
+ ret = misc_register(&stmpe811_adc_device);
+ if (ret)
+ goto misc_register_fail;
+
+ mutex_init(&adc_data->adc_lock);
+
+ /* initialize adc registers */
+ ret = stmpe811_reg_init(adc_data);
+ if (ret < 0)
+ goto reg_init_error;
+
+ /* TODO: ADC_INT setting */
+
+ ret = stmpe811_i2c_read(client, STMPE811_CHIP_ID, i2c_data, 2);
+ if (ret < 0)
+ goto reg_init_error;
+ /* read revision number, 0x01 for es, 0x03 for final silicon */
+ ret = stmpe811_i2c_read(client, STMPE811_ID_VER, rev, 2);
+ if (ret < 0)
+ goto reg_init_error;
+
+ dev_info(&client->dev, "stmpe811 adc (id 0x%x rev 0x%x)\n",
+ ((i2c_data[0]<<8) | i2c_data[1]), rev[0]);
+ return 0;
+
+reg_init_error:
+ mutex_destroy(&adc_data->adc_lock);
+ misc_deregister(&stmpe811_adc_device);
+misc_register_fail:
+ if (pdata->register_cb)
+ pdata->register_cb(NULL);
+
+ dev_err(&client->dev, "stmpe811 probe fail: %d\n", ret);
+ kfree(adc_data);
+ return ret;
+}
+
+static int __devexit stmpe811_adc_i2c_remove(struct i2c_client *client)
+{
+ struct stmpe811_adc_data *adc = i2c_get_clientdata(client);
+
+ misc_deregister(&stmpe811_adc_device);
+ mutex_destroy(&adc->adc_lock);
+ kfree(adc);
+
+ return 0;
+}
+
+static const struct i2c_device_id stmpe811_adc_device_id[] = {
+ {"stmpe811-adc", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, stmpe811_adc_device_id);
+
+static struct i2c_driver stmpe811_adc_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "stmpe811-adc",
+ },
+ .probe = stmpe811_adc_i2c_probe,
+ .remove = stmpe811_adc_i2c_remove,
+ .id_table = stmpe811_adc_device_id,
+};
+
+static int __init stmpe811_adc_init(void)
+{
+ return i2c_add_driver(&stmpe811_adc_i2c_driver);
+}
+
+static void __exit stmpe811_adc_exit(void)
+{
+ i2c_del_driver(&stmpe811_adc_i2c_driver);
+}
+
+module_init(stmpe811_adc_init);
+module_exit(stmpe811_adc_exit);
+
+MODULE_AUTHOR("SangYoung Son <hello.son@samsung.com>");
+MODULE_DESCRIPTION("stmpe811 adc driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index 7d06985..117b98a 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -58,13 +58,6 @@
#define INAND_CMD38_ARG_SECTRIM1 0x81
#define INAND_CMD38_ARG_SECTRIM2 0x88
-#define mmc_req_rel_wr(req) (((req->cmd_flags & REQ_FUA) || \
- (req->cmd_flags & REQ_META)) && \
- (rq_data_dir(req) == WRITE))
-#define PACKED_CMD_VER 0x01
-#define PACKED_CMD_RD 0x01
-#define PACKED_CMD_WR 0x02
-
static DEFINE_MUTEX(block_mutex);
/*
@@ -105,7 +98,6 @@
#define MMC_BLK_WRITE BIT(1)
#define MMC_BLK_DISCARD BIT(2)
#define MMC_BLK_SECDISCARD BIT(3)
-#define MMC_BLK_WR_HDR BIT(4)
/*
* Only set in main mmc_blk_data associated
@@ -131,24 +123,9 @@
MMC_BLK_NOMEDIUM,
};
-enum {
- MMC_PACKED_N_IDX = -1,
- MMC_PACKED_N_ZERO,
- MMC_PACKED_N_SINGLE,
-};
-
module_param(perdev_minors, int, 0444);
MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device");
-static inline void mmc_blk_clear_packed(struct mmc_queue_req *mqrq)
-{
- mqrq->packed_cmd = MMC_PACKED_NONE;
- mqrq->packed_num = MMC_PACKED_N_ZERO;
- mqrq->packed_sg_flag = MMC_PACKED_NONE_SG;
- mqrq->mmc_active.__mrq = NULL;
- mqrq->mmc_active.__cond = false;
-}
-
static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk)
{
struct mmc_blk_data *md;
@@ -1072,8 +1049,7 @@
* kind. If it was a write, we may have transitioned to
* program mode, which we have to wait for it to complete.
*/
- if ((!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) ||
- (mq_mrq->packed_cmd == MMC_PACKED_WR_HDR)) {
+ if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) {
u32 status;
do {
int err = get_card_status(card, &status, 5);
@@ -1098,8 +1074,7 @@
(unsigned)blk_rq_sectors(req),
brq->cmd.resp[0], brq->stop.resp[0]);
- if (rq_data_dir(req) == READ &&
- mq_mrq->packed_cmd != MMC_PACKED_WR_HDR) {
+ if (rq_data_dir(req) == READ) {
if (ecc_err)
return MMC_BLK_ECC_ERR;
return MMC_BLK_DATA_ERR;
@@ -1111,61 +1086,12 @@
if (!brq->data.bytes_xfered)
return MMC_BLK_RETRY;
- if (mq_mrq->packed_cmd != MMC_PACKED_NONE) {
- if (unlikely(brq->data.blocks << 9 != brq->data.bytes_xfered))
- return MMC_BLK_PARTIAL;
- else
- return MMC_BLK_SUCCESS;
- }
-
if (blk_rq_bytes(req) != brq->data.bytes_xfered)
return MMC_BLK_PARTIAL;
return MMC_BLK_SUCCESS;
}
-static int mmc_blk_packed_err_check(struct mmc_card *card,
- struct mmc_async_req *areq)
-{
- struct mmc_queue_req *mq_rq = container_of(areq, struct mmc_queue_req,
- mmc_active);
- struct request *req = mq_rq->req;
- int err, check, status;
- u8 ext_csd[512];
-
- mq_rq->packed_retries--;
- check = mmc_blk_err_check(card, areq);
- err = get_card_status(card, &status, 0);
- if (err) {
- pr_err("%s: error %d sending status command\n",
- req->rq_disk->disk_name, err);
- return MMC_BLK_ABORT;
- }
-
- if (status & R1_EXP_EVENT) {
- err = mmc_send_ext_csd(card, ext_csd);
- if (err) {
- pr_err("%s: error %d sending ext_csd\n",
- req->rq_disk->disk_name, err);
- return MMC_BLK_ABORT;
- }
-
- if ((ext_csd[EXT_CSD_EXP_EVENTS_STATUS] &
- EXT_CSD_PACKED_FAILURE) &&
- (ext_csd[EXT_CSD_PACKED_CMD_STATUS] &
- EXT_CSD_PACKED_GENERIC_ERROR)) {
- if (ext_csd[EXT_CSD_PACKED_CMD_STATUS] &
- EXT_CSD_PACKED_INDEXED_ERROR) {
- mq_rq->packed_fail_idx =
- ext_csd[EXT_CSD_PACKED_FAILURE_INDEX] - 1;
- return MMC_BLK_PARTIAL;
- }
- }
- }
-
- return check;
-}
-
static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
struct mmc_card *card,
int disable_multi,
@@ -1320,274 +1246,10 @@
mmc_queue_bounce_pre(mqrq);
}
-static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req)
-{
- struct request_queue *q = mq->queue;
- struct mmc_card *card = mq->card;
- struct request *cur = req, *next = NULL;
- struct mmc_blk_data *md = mq->data;
- bool en_rel_wr = card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN;
- unsigned int req_sectors = 0, phys_segments = 0;
- unsigned int max_blk_count, max_phys_segs;
- u8 put_back = 0;
- u8 max_packed_rw = 0;
- u8 reqs = 0;
-
- mmc_blk_clear_packed(mq->mqrq_cur);
-
- if (!(md->flags & MMC_BLK_CMD23) ||
- !card->ext_csd.packed_event_en)
- goto no_packed;
-
- if ((rq_data_dir(cur) == WRITE) &&
- (card->host->caps2 & MMC_CAP2_PACKED_WR))
- max_packed_rw = card->ext_csd.max_packed_writes;
- else if ((rq_data_dir(cur) == READ) &&
- (card->host->caps2 & MMC_CAP2_PACKED_RD))
- max_packed_rw = card->ext_csd.max_packed_reads;
-
- if (max_packed_rw == 0)
- goto no_packed;
-
- if (mmc_req_rel_wr(cur) &&
- (md->flags & MMC_BLK_REL_WR) &&
- !en_rel_wr)
- goto no_packed;
-
- max_blk_count = min(card->host->max_blk_count,
- card->host->max_req_size >> 9);
- if (unlikely(max_blk_count > 0xffff))
- max_blk_count = 0xffff;
-
- max_phys_segs = queue_max_segments(q);
- req_sectors += blk_rq_sectors(cur);
- phys_segments += cur->nr_phys_segments;
-
- if (rq_data_dir(cur) == WRITE) {
- req_sectors++;
- phys_segments++;
- }
-
- while (reqs < max_packed_rw - 1) {
- spin_lock_irq(q->queue_lock);
- next = blk_fetch_request(q);
- spin_unlock_irq(q->queue_lock);
- if (!next)
- break;
-
- if (next->cmd_flags & REQ_DISCARD ||
- next->cmd_flags & REQ_FLUSH) {
- put_back = 1;
- break;
- }
-
- if (rq_data_dir(cur) != rq_data_dir(next)) {
- put_back = 1;
- break;
- }
-
- if (mmc_req_rel_wr(next) &&
- (md->flags & MMC_BLK_REL_WR) &&
- !en_rel_wr) {
- put_back = 1;
- break;
- }
-
- req_sectors += blk_rq_sectors(next);
- if (req_sectors > max_blk_count) {
- put_back = 1;
- break;
- }
-
- phys_segments += next->nr_phys_segments;
- if (phys_segments > max_phys_segs) {
- put_back = 1;
- break;
- }
-
- list_add_tail(&next->queuelist, &mq->mqrq_cur->packed_list);
- cur = next;
- reqs++;
- }
-
- if (put_back) {
- spin_lock_irq(q->queue_lock);
- blk_requeue_request(q, next);
- spin_unlock_irq(q->queue_lock);
- }
-
- if (reqs > 0) {
- list_add(&req->queuelist, &mq->mqrq_cur->packed_list);
- mq->mqrq_cur->packed_num = ++reqs;
- mq->mqrq_cur->packed_retries = reqs;
- return reqs;
- }
-
-no_packed:
- mmc_blk_clear_packed(mq->mqrq_cur);
- return 0;
-}
-
-static void mmc_blk_packed_rrq_prep(struct mmc_queue_req *mqrq,
- struct mmc_card *card,
- struct mmc_queue *mq)
-{
- struct mmc_queue_req *__mqrq = mq->mqrq_hdr;
- struct mmc_blk_request *brq = &mqrq->brq;
- struct request *req = mqrq->req;
-
- mqrq->packed_cmd = MMC_PACKED_READ;
- mqrq->packed_sg_flag = MMC_PACKED_RD_SG;
-
- memset(brq, 0, sizeof(struct mmc_blk_request));
- brq->mrq.cmd = &brq->cmd;
- brq->mrq.data = &brq->data;
- brq->mrq.stop = &brq->stop;
-
- brq->cmd.opcode = MMC_READ_MULTIPLE_BLOCK;
- brq->cmd.arg = blk_rq_pos(req);
- if (!mmc_card_blockaddr(card))
- brq->cmd.arg <<= 9;
- brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
- brq->data.blksz = 512;
- brq->data.blocks = mqrq->packed_blocks;
- brq->data.flags |= MMC_DATA_READ;
-
- brq->stop.opcode = MMC_STOP_TRANSMISSION;
- brq->stop.arg = 0;
- brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
-
- mmc_set_data_timeout(&brq->data, card);
-
- brq->data.sg = mqrq->sg;
- brq->data.sg_len = mmc_queue_map_sg(mq, mqrq);
-
- mqrq->mmc_active.mrq = &brq->mrq;
- mqrq->mmc_active.__mrq = NULL;
- mqrq->mmc_active.__cond = true;
- mqrq->mmc_active.err_check = mmc_blk_packed_err_check;
- __mqrq->mmc_active.__mrq = &brq->mrq;
-}
-
-static void mmc_blk_packed_hdr_wrq_prep(struct mmc_queue_req *mqrq,
- struct mmc_card *card,
- struct mmc_queue *mq)
-{
- struct mmc_queue_req *__mqrq;
- struct mmc_blk_request *brq;
- struct request *req = mqrq->req;
- struct request *prq;
- struct mmc_blk_data *md = mq->data;
- bool do_rel_wr, do_data_tag;
- u32 *packed_cmd_hdr;
- u8 i = 1;
-
- if (rq_data_dir(req) == READ) {
- __mqrq = mq->mqrq_hdr;
- __mqrq->packed_cmd = MMC_PACKED_WR_HDR;
- __mqrq->packed_sg_flag = MMC_PACKED_HDR_SG;
- __mqrq->req = mqrq->req;
- } else {
- __mqrq = mqrq;
- __mqrq->packed_cmd = MMC_PACKED_WRITE;
- __mqrq->packed_sg_flag = MMC_PACKED_WR_SG;
- }
-
- brq = &__mqrq->brq;
- packed_cmd_hdr = __mqrq->packed_cmd_hdr;
-
- mqrq->packed_blocks = 0;
- mqrq->packed_fail_idx = MMC_PACKED_N_IDX;
-
- memset(packed_cmd_hdr, 0, sizeof(__mqrq->packed_cmd_hdr));
- packed_cmd_hdr[0] = (mqrq->packed_num << 16) |
- (((rq_data_dir(req) == READ) ?
- PACKED_CMD_RD : PACKED_CMD_WR) << 8) |
- PACKED_CMD_VER;
-
- /*
- * Argument for each entry of packed group
- */
- list_for_each_entry(prq, &mqrq->packed_list, queuelist) {
- do_rel_wr = mmc_req_rel_wr(prq) && (md->flags & MMC_BLK_REL_WR);
- do_data_tag = (card->ext_csd.data_tag_unit_size) &&
- (prq->cmd_flags & REQ_META) &&
- (rq_data_dir(prq) == WRITE) &&
- ((brq->data.blocks * brq->data.blksz) >=
- card->ext_csd.data_tag_unit_size);
- /* Argument of CMD23 */
- packed_cmd_hdr[(i * 2)] =
- (do_rel_wr ? MMC_CMD23_ARG_REL_WR : 0) |
- (do_data_tag ? MMC_CMD23_ARG_TAG_REQ : 0) |
- blk_rq_sectors(prq);
- /* Argument of CMD18 or CMD25 */
- packed_cmd_hdr[((i * 2)) + 1] =
- mmc_card_blockaddr(card) ?
- blk_rq_pos(prq) : blk_rq_pos(prq) << 9;
- mqrq->packed_blocks += blk_rq_sectors(prq);
- i++;
- }
-
- memset(brq, 0, sizeof(struct mmc_blk_request));
- brq->mrq.cmd = &brq->cmd;
- brq->mrq.data = &brq->data;
- brq->mrq.sbc = &brq->sbc;
- brq->mrq.stop = &brq->stop;
-
- brq->sbc.opcode = MMC_SET_BLOCK_COUNT;
- brq->sbc.arg = MMC_CMD23_ARG_PACKED |
- ((rq_data_dir(req) == READ) ? 1 : mqrq->packed_blocks + 1);
- brq->sbc.flags = MMC_RSP_R1 | MMC_CMD_AC;
-
- brq->cmd.opcode = MMC_WRITE_MULTIPLE_BLOCK;
- brq->cmd.arg = blk_rq_pos(req);
- if (!mmc_card_blockaddr(card))
- brq->cmd.arg <<= 9;
- brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
-
- brq->data.blksz = 512;
- /*
- * Write separately the packd command header only for packed read.
- * In case of packed write, header is sent with blocks of data.
- */
- brq->data.blocks = (rq_data_dir(req) == READ) ?
- 1 : mqrq->packed_blocks + 1;
- brq->data.flags |= MMC_DATA_WRITE;
-
- brq->stop.opcode = MMC_STOP_TRANSMISSION;
- brq->stop.arg = 0;
- brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
-
- mmc_set_data_timeout(&brq->data, card);
-
- brq->data.sg = __mqrq->sg;
- brq->data.sg_len = mmc_queue_map_sg(mq, __mqrq);
-
- __mqrq->mmc_active.mrq = &brq->mrq;
-
- if (rq_data_dir(req) == READ)
- __mqrq->mmc_active.err_check = mmc_blk_err_check;
- else
- __mqrq->mmc_active.err_check = mmc_blk_packed_err_check;
-
- mmc_queue_bounce_pre(__mqrq);
-
- /*
- * In case of packed read, header is split with data.
- * Prepare the data ahead for packed read.
- * This will preserve the asynchronos transfer.
- */
- if (rq_data_dir(req) == READ)
- mmc_blk_packed_rrq_prep(mqrq, card, mq);
-}
-
static int mmc_blk_cmd_err(struct mmc_blk_data *md, struct mmc_card *card,
struct mmc_blk_request *brq, struct request *req,
int ret)
{
- struct mmc_queue_req *mq_rq;
- mq_rq = container_of(brq, struct mmc_queue_req, brq);
-
/*
* If this is an SD card and we're writing, we can first
* mark the known good sectors as ok.
@@ -1606,132 +1268,11 @@
spin_unlock_irq(&md->lock);
}
} else {
- if (mq_rq->packed_cmd == MMC_PACKED_NONE) {
- spin_lock_irq(&md->lock);
- ret = __blk_end_request(req, 0, brq->data.bytes_xfered);
- spin_unlock_irq(&md->lock);
- }
- }
- return ret;
-}
-
-static int mmc_blk_end_packed_req(struct mmc_queue *mq,
- struct mmc_queue_req *mq_rq)
-{
- struct mmc_blk_data *md = mq->data;
- struct request *prq;
- int idx = mq_rq->packed_fail_idx, i = 0;
- int ret = 0;
-
- while (!list_empty(&mq_rq->packed_list)) {
- prq = list_entry_rq(mq_rq->packed_list.next);
- if (idx == i) {
- /* retry from error index */
- mq_rq->packed_num -= idx;
- mq_rq->req = prq;
- ret = 1;
-
- if (mq_rq->packed_num == MMC_PACKED_N_SINGLE) {
- list_del_init(&prq->queuelist);
- mmc_blk_clear_packed(mq_rq);
- }
- return ret;
- }
- list_del_init(&prq->queuelist);
spin_lock_irq(&md->lock);
- __blk_end_request(prq, 0, blk_rq_bytes(prq));
- spin_unlock_irq(&md->lock);
- i++;
- }
-
- mmc_blk_clear_packed(mq_rq);
- return ret;
-}
-
-static int mmc_blk_chk_hdr_err(struct mmc_queue *mq, int status)
-{
- struct mmc_blk_data *md = mq->data;
- struct mmc_card *card = md->queue.card;
- int type = MMC_BLK_WR_HDR, err = 0;
-
- switch (status) {
- case MMC_BLK_PARTIAL:
- case MMC_BLK_RETRY:
- err = 0;
- break;
- case MMC_BLK_CMD_ERR:
- case MMC_BLK_ABORT:
- case MMC_BLK_DATA_ERR:
- case MMC_BLK_ECC_ERR:
- err = mmc_blk_reset(md, card->host, type);
- if (!err)
- mmc_blk_reset_success(md, type);
- break;
- }
-
- return err;
-}
-
-static int mmc_blk_issue_packed_rd(struct mmc_queue *mq,
- struct mmc_queue_req *mq_rq)
-{
- struct mmc_blk_data *md = mq->data;
- struct mmc_card *card = md->queue.card;
- int status, ret = -EIO, retry = 2;
-
- do {
- mmc_start_req(card->host, &mq_rq->mmc_active, &status);
- if (!status) {
- ret = 0;
- break;
- }
-
- ret = mmc_blk_chk_hdr_err(mq, status);
- if (ret)
- break;
- mmc_blk_packed_hdr_wrq_prep(mq_rq, card, mq);
- mmc_start_req(card->host, &mq->mqrq_hdr->mmc_active, NULL);
- } while (retry-- > 0);
-
- return ret;
-}
-
-static void mmc_blk_abort_packed_req(struct mmc_queue *mq,
- struct mmc_queue_req *mq_rq)
-{
- struct mmc_blk_data *md = mq->data;
- struct request *prq;
-
- while (!list_empty(&mq_rq->packed_list)) {
- prq = list_entry_rq(mq_rq->packed_list.next);
- list_del_init(&prq->queuelist);
- spin_lock_irq(&md->lock);
- __blk_end_request(prq, -EIO, blk_rq_bytes(prq));
+ ret = __blk_end_request(req, 0, brq->data.bytes_xfered);
spin_unlock_irq(&md->lock);
}
-
- mmc_blk_clear_packed(mq_rq);
-}
-
-static void mmc_blk_revert_packed_req(struct mmc_queue *mq,
- struct mmc_queue_req *mq_rq)
-{
- struct request *prq;
- struct request_queue *q = mq->queue;
-
- while (!list_empty(&mq_rq->packed_list)) {
- prq = list_entry_rq(mq_rq->packed_list.prev);
- if (prq->queuelist.prev != &mq_rq->packed_list) {
- list_del_init(&prq->queuelist);
- spin_lock_irq(q->queue_lock);
- blk_requeue_request(mq->queue, prq);
- spin_unlock_irq(q->queue_lock);
- } else {
- list_del_init(&prq->queuelist);
- }
- }
-
- mmc_blk_clear_packed(mq_rq);
+ return ret;
}
static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
@@ -1744,37 +1285,19 @@
struct mmc_queue_req *mq_rq;
struct request *req;
struct mmc_async_req *areq;
- const u8 packed_num = 2;
- u8 reqs = 0;
if (!rqc && !mq->mqrq_prev->req)
return 0;
- if (rqc)
- reqs = mmc_blk_prep_packed_list(mq, rqc);
-
do {
if (rqc) {
- if (reqs >= packed_num)
- mmc_blk_packed_hdr_wrq_prep(mq->mqrq_cur,
- card, mq);
- else
- mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq);
-
- if (mq->mqrq_cur->packed_cmd == MMC_PACKED_READ)
- areq = &mq->mqrq_hdr->mmc_active;
- else
- areq = &mq->mqrq_cur->mmc_active;
+ mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq);
+ areq = &mq->mqrq_cur->mmc_active;
} else
areq = NULL;
-
areq = mmc_start_req(card->host, areq, (int *) &status);
- if (!areq) {
- if (mq->mqrq_cur->packed_cmd == MMC_PACKED_READ)
- goto snd_packed_rd;
- else
- return 0;
- }
+ if (!areq)
+ return 0;
mq_rq = container_of(areq, struct mmc_queue_req, mmc_active);
brq = &mq_rq->brq;
@@ -1789,16 +1312,10 @@
* A block was successfully transferred.
*/
mmc_blk_reset_success(md, type);
-
- if (mq_rq->packed_cmd != MMC_PACKED_NONE) {
- ret = mmc_blk_end_packed_req(mq, mq_rq);
- break;
- } else {
- spin_lock_irq(&md->lock);
- ret = __blk_end_request(req, 0,
+ spin_lock_irq(&md->lock);
+ ret = __blk_end_request(req, 0,
brq->data.bytes_xfered);
- spin_unlock_irq(&md->lock);
- }
+ spin_unlock_irq(&md->lock);
/*
* If the blk_end_request function returns non-zero even
* though all data has been transferred and no errors
@@ -1831,8 +1348,7 @@
err = mmc_blk_reset(md, card->host, type);
if (!err)
break;
- if (err == -ENODEV ||
- mq_rq->packed_cmd != MMC_PACKED_NONE)
+ if (err == -ENODEV)
goto cmd_abort;
/* Fall through */
}
@@ -1861,60 +1377,27 @@
}
if (ret) {
- if (mq_rq->packed_cmd == MMC_PACKED_NONE) {
- /*
- * In case of a incomplete request
- * prepare it again and resend.
- */
- mmc_blk_rw_rq_prep(mq_rq, card,
- disable_multi, mq);
- mmc_start_req(card->host,
- &mq_rq->mmc_active, NULL);
- } else {
- if (!mq_rq->packed_retries)
- goto cmd_abort;
- mmc_blk_packed_hdr_wrq_prep(mq_rq, card, mq);
- if (mq_rq->packed_cmd == MMC_PACKED_READ) {
- mmc_start_req(card->host,
- &mq->mqrq_hdr->mmc_active,
- NULL);
- if (mmc_blk_issue_packed_rd(mq, mq_rq))
- goto cmd_abort;
- } else
- mmc_start_req(card->host,
- &mq_rq->mmc_active, NULL);
- }
+ /*
+ * In case of a incomplete request
+ * prepare it again and resend.
+ */
+ mmc_blk_rw_rq_prep(mq_rq, card, disable_multi, mq);
+ mmc_start_req(card->host, &mq_rq->mmc_active, NULL);
}
} while (ret);
-snd_packed_rd:
- if (mq->mqrq_cur->packed_cmd == MMC_PACKED_READ) {
- if (mmc_blk_issue_packed_rd(mq, mq->mqrq_cur))
- goto start_new_req;
- }
return 1;
cmd_abort:
- if (mq_rq->packed_cmd == MMC_PACKED_NONE) {
- spin_lock_irq(&md->lock);
- if (mmc_card_removed(card))
- req->cmd_flags |= REQ_QUIET;
- while (ret)
- ret = __blk_end_request(req, -EIO,
- blk_rq_cur_bytes(req));
- spin_unlock_irq(&md->lock);
- } else {
- mmc_blk_abort_packed_req(mq, mq_rq);
- }
+ spin_lock_irq(&md->lock);
+ if (mmc_card_removed(card))
+ req->cmd_flags |= REQ_QUIET;
+ while (ret)
+ ret = __blk_end_request(req, -EIO, blk_rq_cur_bytes(req));
+ spin_unlock_irq(&md->lock);
start_new_req:
if (rqc) {
- /*
- * If current request is packed, it needs to put back.
- */
- if (mq->mqrq_cur->packed_cmd != MMC_PACKED_NONE)
- mmc_blk_revert_packed_req(mq, mq->mqrq_cur);
-
mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq);
mmc_start_req(card->host, &mq->mqrq_cur->mmc_active, NULL);
}
diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c
index b3a0896..996f8e3 100644
--- a/drivers/mmc/card/queue.c
+++ b/drivers/mmc/card/queue.c
@@ -166,7 +166,6 @@
int ret;
struct mmc_queue_req *mqrq_cur = &mq->mqrq[0];
struct mmc_queue_req *mqrq_prev = &mq->mqrq[1];
- struct mmc_queue_req *mqrq_hdr = &mq->mqrq[2];
if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
limit = *mmc_dev(host)->dma_mask;
@@ -178,15 +177,8 @@
memset(&mq->mqrq_cur, 0, sizeof(mq->mqrq_cur));
memset(&mq->mqrq_prev, 0, sizeof(mq->mqrq_prev));
- memset(&mq->mqrq_hdr, 0, sizeof(mq->mqrq_hdr));
-
- INIT_LIST_HEAD(&mqrq_cur->packed_list);
- INIT_LIST_HEAD(&mqrq_prev->packed_list);
- INIT_LIST_HEAD(&mqrq_hdr->packed_list);
-
mq->mqrq_cur = mqrq_cur;
mq->mqrq_prev = mqrq_prev;
- mq->mqrq_hdr = mqrq_hdr;
mq->queue->queuedata = mq;
blk_queue_prep_rq(mq->queue, mmc_prep_request);
@@ -222,21 +214,9 @@
kfree(mqrq_cur->bounce_buf);
mqrq_cur->bounce_buf = NULL;
}
-
- mqrq_hdr->bounce_buf = kmalloc(bouncesz, GFP_KERNEL);
- if (!mqrq_hdr->bounce_buf) {
- printk(KERN_WARNING "%s: unable to "
- "allocate bounce hdr buffer\n",
- mmc_card_name(card));
- kfree(mqrq_cur->bounce_buf);
- mqrq_cur->bounce_buf = NULL;
- kfree(mqrq_prev->bounce_buf);
- mqrq_prev->bounce_buf = NULL;
- }
}
- if (mqrq_cur->bounce_buf && mqrq_prev->bounce_buf &&
- mqrq_hdr->bounce_buf) {
+ if (mqrq_cur->bounce_buf && mqrq_prev->bounce_buf) {
blk_queue_bounce_limit(mq->queue, BLK_BOUNCE_ANY);
blk_queue_max_hw_sectors(mq->queue, bouncesz / 512);
blk_queue_max_segments(mq->queue, bouncesz / 512);
@@ -259,21 +239,11 @@
mmc_alloc_sg(bouncesz / 512, &ret);
if (ret)
goto cleanup_queue;
-
- mqrq_hdr->sg = mmc_alloc_sg(1, &ret);
- if (ret)
- goto cleanup_queue;
-
- mqrq_hdr->bounce_sg =
- mmc_alloc_sg(bouncesz / 512, &ret);
- if (ret)
- goto cleanup_queue;
}
}
#endif
- if (!mqrq_cur->bounce_buf && !mqrq_prev->bounce_buf &&
- !mqrq_hdr->bounce_buf) {
+ if (!mqrq_cur->bounce_buf && !mqrq_prev->bounce_buf) {
blk_queue_bounce_limit(mq->queue, limit);
blk_queue_max_hw_sectors(mq->queue,
min(host->max_blk_count, host->max_req_size / 512));
@@ -284,11 +254,8 @@
if (ret)
goto cleanup_queue;
- mqrq_prev->sg = mmc_alloc_sg(host->max_segs, &ret);
- if (ret)
- goto cleanup_queue;
- mqrq_hdr->sg = mmc_alloc_sg(host->max_segs, &ret);
+ mqrq_prev->sg = mmc_alloc_sg(host->max_segs, &ret);
if (ret)
goto cleanup_queue;
}
@@ -309,8 +276,6 @@
mqrq_cur->bounce_sg = NULL;
kfree(mqrq_prev->bounce_sg);
mqrq_prev->bounce_sg = NULL;
- kfree(mqrq_hdr->bounce_sg);
- mqrq_hdr->bounce_sg = NULL;
cleanup_queue:
kfree(mqrq_cur->sg);
@@ -323,11 +288,6 @@
kfree(mqrq_prev->bounce_buf);
mqrq_prev->bounce_buf = NULL;
- kfree(mqrq_hdr->sg);
- mqrq_hdr->sg = NULL;
- kfree(mqrq_hdr->bounce_buf);
- mqrq_hdr->bounce_buf = NULL;
-
blk_cleanup_queue(mq->queue);
return ret;
}
@@ -338,7 +298,6 @@
unsigned long flags;
struct mmc_queue_req *mqrq_cur = mq->mqrq_cur;
struct mmc_queue_req *mqrq_prev = mq->mqrq_prev;
- struct mmc_queue_req *mqrq_hdr = mq->mqrq_hdr;
/* Make sure the queue isn't suspended, as that will deadlock */
mmc_queue_resume(mq);
@@ -370,15 +329,6 @@
kfree(mqrq_prev->bounce_buf);
mqrq_prev->bounce_buf = NULL;
- kfree(mqrq_hdr->bounce_sg);
- mqrq_prev->bounce_sg = NULL;
-
- kfree(mqrq_hdr->sg);
- mqrq_prev->sg = NULL;
-
- kfree(mqrq_hdr->bounce_buf);
- mqrq_prev->bounce_buf = NULL;
-
mq->card = NULL;
}
EXPORT_SYMBOL(mmc_cleanup_queue);
@@ -427,37 +377,6 @@
}
}
-static unsigned int mmc_queue_packed_map_sg(struct mmc_queue *mq,
- struct mmc_queue_req *mqrq,
- struct scatterlist *sg)
-{
- struct scatterlist *__sg;
- unsigned int sg_len = 0;
- struct request *req;
- enum mmc_packed_sg_flag sg_flag = mqrq->packed_sg_flag;
-
- if (sg_flag == MMC_PACKED_HDR_SG || sg_flag == MMC_PACKED_WR_SG) {
- __sg = sg;
- sg_set_buf(__sg, mqrq->packed_cmd_hdr,
- sizeof(mqrq->packed_cmd_hdr));
- sg_len++;
- if (sg_flag == MMC_PACKED_HDR_SG) {
- sg_mark_end(__sg);
- return sg_len;
- }
- __sg->page_link &= ~0x02;
- }
-
- __sg = sg + sg_len;
- list_for_each_entry(req, &mqrq->packed_list, queuelist) {
- sg_len += blk_rq_map_sg(mq->queue, req, __sg);
- __sg = sg + (sg_len - 1);
- (__sg++)->page_link &= ~0x02;
- }
- sg_mark_end(sg + (sg_len - 1));
- return sg_len;
-}
-
/*
* Prepare the sg list(s) to be handed of to the host driver
*/
@@ -466,22 +385,14 @@
unsigned int sg_len;
size_t buflen;
struct scatterlist *sg;
- enum mmc_packed_sg_flag sg_flag = mqrq->packed_sg_flag;
int i;
- if (!mqrq->bounce_buf) {
- if (sg_flag != MMC_PACKED_NONE_SG)
- return mmc_queue_packed_map_sg(mq, mqrq, mqrq->sg);
- else
- return blk_rq_map_sg(mq->queue, mqrq->req, mqrq->sg);
- }
+ if (!mqrq->bounce_buf)
+ return blk_rq_map_sg(mq->queue, mqrq->req, mqrq->sg);
BUG_ON(!mqrq->bounce_sg);
- if (sg_flag != MMC_PACKED_NONE_SG)
- sg_len = mmc_queue_packed_map_sg(mq, mqrq, mqrq->bounce_sg);
- else
- sg_len = blk_rq_map_sg(mq->queue, mqrq->req, mqrq->bounce_sg);
+ sg_len = blk_rq_map_sg(mq->queue, mqrq->req, mqrq->bounce_sg);
mqrq->bounce_sg_len = sg_len;
@@ -503,8 +414,7 @@
if (!mqrq->bounce_buf)
return;
- if (rq_data_dir(mqrq->req) != WRITE &&
- mqrq->packed_cmd != MMC_PACKED_WR_HDR)
+ if (rq_data_dir(mqrq->req) != WRITE)
return;
sg_copy_to_buffer(mqrq->bounce_sg, mqrq->bounce_sg_len,
diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h
index bbd8e4c..d2a1eb4 100644
--- a/drivers/mmc/card/queue.h
+++ b/drivers/mmc/card/queue.h
@@ -12,36 +12,14 @@
struct mmc_data data;
};
-enum mmc_packed_cmd {
- MMC_PACKED_NONE = 0,
- MMC_PACKED_WR_HDR,
- MMC_PACKED_WRITE,
- MMC_PACKED_READ,
-};
-
-enum mmc_packed_sg_flag {
- MMC_PACKED_NONE_SG = 0,
- MMC_PACKED_HDR_SG,
- MMC_PACKED_WR_SG,
- MMC_PACKED_RD_SG,
-};
-
struct mmc_queue_req {
struct request *req;
struct mmc_blk_request brq;
struct scatterlist *sg;
- enum mmc_packed_sg_flag packed_sg_flag;
char *bounce_buf;
struct scatterlist *bounce_sg;
unsigned int bounce_sg_len;
struct mmc_async_req mmc_active;
- struct list_head packed_list;
- u32 packed_cmd_hdr[128];
- unsigned int packed_blocks;
- enum mmc_packed_cmd packed_cmd;
- int packed_retries;
- int packed_fail_idx;
- u8 packed_num;
};
struct mmc_queue {
@@ -52,10 +30,9 @@
int (*issue_fn)(struct mmc_queue *, struct request *);
void *data;
struct request_queue *queue;
- struct mmc_queue_req mqrq[3];
+ struct mmc_queue_req mqrq[2];
struct mmc_queue_req *mqrq_cur;
struct mmc_queue_req *mqrq_prev;
- struct mmc_queue_req *mqrq_hdr;
};
extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *,
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 9b77227..7b0f4a8 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -348,12 +348,8 @@
struct mmc_async_req *data = host->areq;
/* Prepare a new request */
- if (areq && !areq->__cond) {
+ if (areq)
mmc_pre_req(host, areq->mrq, !host->areq);
- if (areq->__mrq) {
- mmc_pre_req(host, areq->__mrq, 0);
- }
- }
if (host->areq) {
mmc_wait_for_req_done(host, host->areq->mrq);
@@ -367,11 +363,8 @@
mmc_post_req(host, host->areq->mrq, 0);
/* Cancel a prepared request if it was not started. */
- if ((err || start_err) && areq) {
- mmc_post_req(host, areq->mrq, -EINVAL);
- if (areq->__mrq)
- mmc_post_req(host, areq->__mrq, -EINVAL);
- }
+ if ((err || start_err) && areq)
+ mmc_post_req(host, areq->mrq, -EINVAL);
if (err)
host->areq = NULL;
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index aafc946..2a9b7b1 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -516,11 +516,6 @@
} else {
card->ext_csd.data_tag_unit_size = 0;
}
-
- card->ext_csd.max_packed_writes =
- ext_csd[EXT_CSD_MAX_PACKED_WRITES];
- card->ext_csd.max_packed_reads =
- ext_csd[EXT_CSD_MAX_PACKED_READS];
}
out:
@@ -1257,26 +1252,6 @@
}
}
- if ((host->caps2 & MMC_CAP2_PACKED_WR &&
- card->ext_csd.max_packed_writes > 0) ||
- (host->caps2 & MMC_CAP2_PACKED_RD &&
- card->ext_csd.max_packed_reads > 0)) {
- err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
- EXT_CSD_EXP_EVENTS_CTRL,
- EXT_CSD_PACKED_EVENT_EN,
- card->ext_csd.generic_cmd6_time);
- if (err && err != -EBADMSG)
- goto free_card;
- if (err) {
- pr_warning("%s: Enabling packed event failed\n",
- mmc_hostname(card->host));
- card->ext_csd.packed_event_en = 0;
- err = 0;
- } else {
- card->ext_csd.packed_event_en = 1;
- }
- }
-
if (!oldcard)
host->card = card;
diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
index 2a2fed8..69370f4 100644
--- a/drivers/mmc/core/mmc_ops.c
+++ b/drivers/mmc/core/mmc_ops.c
@@ -335,7 +335,6 @@
return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD,
ext_csd, 512);
}
-EXPORT_SYMBOL_GPL(mmc_send_ext_csd);
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp)
{
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig
index 5af95927..f39fafe 100644
--- a/drivers/nfc/Kconfig
+++ b/drivers/nfc/Kconfig
@@ -1,9 +1,17 @@
#
# Near Field Communication (NFC) devices
#
+menuconfig NFC_DEVICES
+ bool "Near Field Communication (NFC) devices"
+ default n
+ ---help---
+ You'll have to say Y if your computer contains an NFC device that
+ you want to use under Linux.
-menu "Near Field Communication (NFC) devices"
- depends on NFC
+ You can say N here if you don't have any Near Field Communication
+ devices connected to your computer.
+
+if NFC_DEVICES
config PN544_NFC
tristate "PN544 NFC driver"
@@ -38,4 +46,15 @@
Say Y here to compile support for Texas Instrument's NFC WiLink driver
into the kernel or say M to compile it as module.
-endmenu
+config BCM2079X_NFC_SPI
+ tristate "BCM2079X NFC driver for SPI interface"
+ depends on SPI
+ default n
+ help
+ Say yes if you want BCM2079X Near Field Communication driver.
+ This is for spi connected version. If unsure, say N here.
+
+ To compile this driver as a module, choose m here. The module will
+ be called bcm2079x-spi.
+
+endif # NFC_DEVICES
diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile
index ab99e85..4aa1a77 100644
--- a/drivers/nfc/Makefile
+++ b/drivers/nfc/Makefile
@@ -5,5 +5,5 @@
obj-$(CONFIG_PN544_NFC) += pn544.o
obj-$(CONFIG_NFC_PN533) += pn533.o
obj-$(CONFIG_NFC_WILINK) += nfcwilink.o
-
+obj-$(CONFIG_BCM2079X_NFC_SPI) += bcm2079x-spi.o
ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG
diff --git a/drivers/nfc/bcm2079x-spi.c b/drivers/nfc/bcm2079x-spi.c
new file mode 100755
index 0000000..929b8eb
--- /dev/null
+++ b/drivers/nfc/bcm2079x-spi.c
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2011 Broadcom Corporation.
+ *
+ * Author: Kevin Park <spark@broadcom.com>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/sched.h>
+#include <linux/spi/spi.h>
+#include <linux/irq.h>
+#include <linux/jiffies.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+#include <linux/nfc/bcm2079x.h>
+#include <linux/poll.h>
+#include <linux/version.h>
+
+#ifndef U16_TO_STREAM
+#define U16_TO_STREAM(p, var16) {*(p)++ = (u8)((var16)>>8); \
+ *(p)++ = (u8)(var16&0xFF); }
+#endif
+#ifndef U8_TO_STREAM
+#define U8_TO_STREAM(p, var8) {*(p)++ = (u8)(var8); }
+#endif
+
+#ifndef STREAM_TO_U16
+#define STREAM_TO_U16(var16, p) {(var16) = ((u16)(*((u8 *)p+1)) + \
+ ((u16)(*((u8 *)p) << 8))); }
+#endif
+
+#define STATE_HIGH 1
+#define STATE_LOW 0
+
+#define NFC_REQ_ACTIVE_STATE STATE_LOW
+
+#define MAX_BUFFER_SIZE 274
+
+struct bcm2079x_dev {
+ wait_queue_head_t read_wq;
+ struct mutex read_mutex;
+ struct spi_device *spi;
+ struct miscdevice bcm2079x_device;
+ unsigned int en_gpio;
+ unsigned int irq_gpio;
+ unsigned int wake_gpio;
+ bool irq_enabled;
+ spinlock_t irq_enabled_lock;
+ unsigned char cs_change;
+ unsigned int packet_size;
+ unsigned int spi_delay; // in ns
+};
+
+static int spi_write_cs_hint(struct spi_device *spi, const void *buf,
+ size_t len, char cs_hint)
+{
+ int ret;
+ struct spi_message m;
+ struct spi_transfer t = {
+ .tx_buf = buf,
+ .len = len,
+ .cs_change = cs_hint,
+ };
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+
+ ret = spi_sync(spi, &m);
+ return ret;
+}
+
+static size_t spi_read_espi(struct spi_device *spi, void *buf,
+ size_t size)
+{
+ int ret;
+ unsigned char req[2] = { 0x02, 0x00 };
+ unsigned char res[2] = { 0x00, };
+ unsigned short rx_len = 0;
+
+ struct spi_transfer t_c = {
+ .tx_buf = req,
+ .len = 2,
+ .cs_change = 1,
+ };
+ struct spi_transfer t_r = {
+ .rx_buf = res,
+ .len = 2,
+ .cs_change = 1,
+ };
+ struct spi_message m;
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t_c, &m);
+ spi_message_add_tail(&t_r, &m);
+ ret = spi_sync(spi, &m);
+
+ if (ret)
+ return -EIO;
+
+ STREAM_TO_U16(rx_len, res);
+
+ if (size < rx_len)
+ rx_len = size;
+
+ spi_message_init(&m);
+ t_r.rx_buf = buf;
+ t_r.len = rx_len;
+ t_r.cs_change = 0;
+ spi_message_add_tail(&t_r, &m);
+ ret = spi_sync(spi, &m);
+
+ if (ret)
+ return -EIO;
+ else
+ return rx_len;
+}
+
+static void bcmspinfc_disable_irq(struct bcm2079x_dev *bcm2079x_dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&bcm2079x_dev->irq_enabled_lock, flags);
+
+ if (bcm2079x_dev->irq_enabled) {
+ disable_irq_nosync(bcm2079x_dev->spi->irq);
+ bcm2079x_dev->irq_enabled = false;
+ }
+
+ spin_unlock_irqrestore(&bcm2079x_dev->irq_enabled_lock, flags);
+}
+
+static void bcmspinfc_enable_irq(struct bcm2079x_dev *bcm2079x_dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&bcm2079x_dev->irq_enabled_lock, flags);
+
+ if (!bcm2079x_dev->irq_enabled) {
+ bcm2079x_dev->irq_enabled = true;
+ enable_irq(bcm2079x_dev->spi->irq);
+ }
+
+ spin_unlock_irqrestore(&bcm2079x_dev->irq_enabled_lock, flags);
+}
+
+static irqreturn_t bcm2079x_dev_irq_handler(int irq, void *dev_id)
+{
+ struct bcm2079x_dev *bcm2079x_dev = dev_id;
+
+ if (gpio_get_value(bcm2079x_dev->irq_gpio) != NFC_REQ_ACTIVE_STATE)
+ return IRQ_HANDLED;
+
+ bcmspinfc_disable_irq(bcm2079x_dev);
+
+ /* Wake up waiting readers */
+ wake_up(&bcm2079x_dev->read_wq);
+
+ return IRQ_HANDLED;
+}
+
+static unsigned int bcm2079x_dev_poll(struct file *filp, poll_table *wait)
+{
+ struct bcm2079x_dev *bcm2079x_dev = filp->private_data;
+ unsigned int mask = 0;
+
+ poll_wait(filp, &bcm2079x_dev->read_wq, wait);
+
+ if (gpio_get_value(bcm2079x_dev->irq_gpio) == NFC_REQ_ACTIVE_STATE)
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+static ssize_t bcm2079x_dev_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct bcm2079x_dev *bcm2079x_dev = filp->private_data;
+ char p_rcv[MAX_BUFFER_SIZE];
+ int ret;
+ int packets;
+
+ ret = 0;
+ packets = 0;
+
+ if (count > MAX_BUFFER_SIZE)
+ count = MAX_BUFFER_SIZE;
+
+ if (bcm2079x_dev->packet_size > 0)
+ count = bcm2079x_dev->packet_size;
+
+ mutex_lock(&bcm2079x_dev->read_mutex);
+
+ /* Check the IRQ state after mutex delays */
+ if (gpio_get_value(bcm2079x_dev->irq_gpio) == NFC_REQ_ACTIVE_STATE)
+ ret = spi_read_espi(bcm2079x_dev->spi, p_rcv, MAX_BUFFER_SIZE);
+ else
+ ret = 0;
+
+ /* bcm2079x requires 1 SCK delay between transactions */
+ ndelay(bcm2079x_dev->spi_delay);
+
+ mutex_unlock(&bcm2079x_dev->read_mutex);
+
+ if (ret > 0 && copy_to_user(buf, &p_rcv, ret)) {
+ dev_err(&bcm2079x_dev->spi->dev,
+ "failed to copy to user space, rx_cnt = %d\n", ret);
+ ret = -EIO;
+ }
+
+ bcmspinfc_enable_irq(bcm2079x_dev);
+
+ return ret;
+}
+
+static ssize_t bcm2079x_dev_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct bcm2079x_dev *bcm2079x_dev = filp->private_data;
+ char txbuff[MAX_BUFFER_SIZE];
+ char *p = txbuff;
+ int ret;
+
+ if (count + 4 > MAX_BUFFER_SIZE) {
+ dev_err(&bcm2079x_dev->spi->dev, "out of max memory\n");
+ return -ENOMEM;
+ }
+
+ if (copy_from_user(&txbuff[4], buf, count)) {
+ dev_err(&bcm2079x_dev->spi->dev,
+ "failed to copy from user space\n");
+ return -EFAULT;
+ }
+
+ bcmspinfc_disable_irq(bcm2079x_dev);
+ /* Direct Write */
+ U8_TO_STREAM(p, 0x01);
+ /* Reserved */
+ U8_TO_STREAM(p, 0x00);
+ /* Length */
+ U16_TO_STREAM(p, count);
+
+ mutex_lock(&bcm2079x_dev->read_mutex);
+
+ /* Write data */
+ ret = spi_write_cs_hint(bcm2079x_dev->spi, txbuff, count + 4,
+ bcm2079x_dev->cs_change);
+
+ /* Restore cs_change hit */
+ bcm2079x_dev->cs_change = 0;
+
+ if (ret != 0) {
+ dev_err(&bcm2079x_dev->spi->dev, "write %d\n", ret);
+ ret = -EIO;
+ } else {
+ ret = count;
+ }
+
+ /* bcm2079x requires 1 SCK delay between transactions */
+ ndelay(bcm2079x_dev->spi_delay);
+
+ mutex_unlock(&bcm2079x_dev->read_mutex);
+ bcmspinfc_enable_irq(bcm2079x_dev);
+
+ return ret;
+}
+
+static int bcm2079x_dev_open(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct bcm2079x_dev *bcm2079x_dev = container_of(filp->private_data,
+ struct bcm2079x_dev,
+ bcm2079x_device);
+
+ filp->private_data = bcm2079x_dev;
+
+ return ret;
+}
+
+static long bcm2079x_dev_unlocked_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct bcm2079x_dev *bcm2079x_dev = filp->private_data;
+ long ret = 0;
+
+ switch (cmd) {
+ case BCMNFC_READ_FULL_PACKET:
+ bcm2079x_dev->packet_size = arg;
+ break;
+ case BCMNFC_POWER_CTL:
+ gpio_set_value(bcm2079x_dev->en_gpio, arg);
+ break;
+ case BCMNFC_WAKE_CTL:
+ if (bcm2079x_dev->wake_gpio < 0) {
+ bcm2079x_dev->cs_change = 1;
+ ret = 1;
+ }
+ gpio_set_value(bcm2079x_dev->wake_gpio, arg);
+ break;
+ default:
+ dev_err(&bcm2079x_dev->spi->dev,
+ "%s, unknown cmd (%x, %lx)\n", __func__, cmd, arg);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct file_operations bcm2079x_dev_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .poll = bcm2079x_dev_poll,
+ .read = bcm2079x_dev_read,
+ .write = bcm2079x_dev_write,
+ .open = bcm2079x_dev_open,
+ .unlocked_ioctl = bcm2079x_dev_unlocked_ioctl
+};
+
+static int bcmspinfc_probe(struct spi_device *spi)
+{
+ int ret;
+ struct bcm2079x_platform_data *platform_data;
+ struct bcm2079x_dev *bcm2079x_dev;
+ unsigned int spi_speed_hz = spi->max_speed_hz;
+
+ platform_data = spi->dev.platform_data;
+
+ dev_info(&spi->dev, "%s, probing bcmspinfc driver\n", __func__);
+
+ if (platform_data == NULL) {
+ dev_err(&spi->dev, "nfc probe fail\n");
+ ret = -ENODEV;
+ goto err_nodev;
+ }
+
+ ret = gpio_request(platform_data->irq_gpio, "nfc_spi_int");
+ if (ret) {
+ ret = -ENODEV;
+ goto err_nodev;
+ }
+
+ ret = gpio_request(platform_data->en_gpio, "nfc_en");
+ if (ret)
+ goto err_en;
+
+ /* Wake pin can be joined with SPI_CSN */
+ if (platform_data->wake_gpio > 0) {
+ ret = gpio_request(platform_data->wake_gpio, "nfc_wake");
+ if (ret) {
+ pr_err("%s : nfc_wake request error", __func__);
+ goto err_firm;
+ }
+ }
+
+ gpio_direction_output(platform_data->en_gpio, 0);
+
+ /* Wake pin can be joined with SPI_CSN */
+ if (platform_data->wake_gpio > 0)
+ gpio_direction_output(platform_data->wake_gpio, 0);
+
+ gpio_direction_input(platform_data->irq_gpio);
+ gpio_set_value(platform_data->en_gpio, 0);
+ gpio_set_value(platform_data->wake_gpio, 0);
+
+ bcm2079x_dev = kzalloc(sizeof(*bcm2079x_dev), GFP_KERNEL);
+ if (bcm2079x_dev == NULL) {
+ dev_err(&spi->dev,
+ "failed to allocate memory for module data\n");
+ ret = -ENOMEM;
+ goto err_exit;
+ }
+
+ bcm2079x_dev->wake_gpio = platform_data->wake_gpio;
+ bcm2079x_dev->irq_gpio = platform_data->irq_gpio;
+ bcm2079x_dev->en_gpio = platform_data->en_gpio;
+ bcm2079x_dev->spi = spi;
+ if (spi_speed_hz == 0)
+ bcm2079x_dev->spi_delay = 5000;
+ else
+ bcm2079x_dev->spi_delay = 1000000000 / spi_speed_hz;
+
+ /* init mutex and queues */
+ init_waitqueue_head(&bcm2079x_dev->read_wq);
+ mutex_init(&bcm2079x_dev->read_mutex);
+ spin_lock_init(&bcm2079x_dev->irq_enabled_lock);
+
+ bcm2079x_dev->bcm2079x_device.minor = MISC_DYNAMIC_MINOR;
+ bcm2079x_dev->bcm2079x_device.name = "bcm2079x";
+ bcm2079x_dev->bcm2079x_device.fops = &bcm2079x_dev_fops;
+
+ ret = misc_register(&bcm2079x_dev->bcm2079x_device);
+ if (ret) {
+ dev_err(&spi->dev, "misc_register failed\n");
+ goto err_misc_register;
+ }
+
+ /* request irq. the irq is set whenever the chip has data available
+ * for reading. it is cleared when all data has been read.
+ */
+ dev_info(&spi->dev, "requesting IRQ %d\n", spi->irq);
+ bcm2079x_dev->irq_enabled = true;
+ ret = request_irq(spi->irq, bcm2079x_dev_irq_handler,
+ IRQF_TRIGGER_FALLING, spi->modalias,
+ bcm2079x_dev);
+ if (ret) {
+ dev_err(&spi->dev, "request_irq failed\n");
+ goto err_request_irq_failed;
+ }
+ bcmspinfc_disable_irq(bcm2079x_dev);
+ spi_set_drvdata(spi, bcm2079x_dev);
+
+ bcm2079x_dev->packet_size = 0;
+ dev_info(&spi->dev,
+ "%s, probing bcmspinfc driver exited successfully\n",
+ __func__);
+
+ return 0;
+
+err_request_irq_failed:
+ misc_deregister(&bcm2079x_dev->bcm2079x_device);
+err_misc_register:
+ mutex_destroy(&bcm2079x_dev->read_mutex);
+ kfree(bcm2079x_dev);
+err_exit:
+ gpio_free(platform_data->wake_gpio);
+err_firm:
+ gpio_free(platform_data->en_gpio);
+err_en:
+ gpio_free(platform_data->irq_gpio);
+err_nodev:
+ return ret;
+}
+
+static int bcmspinfc_remove(struct spi_device *spi)
+{
+ struct bcm2079x_dev *bcm2079x_dev;
+
+ bcm2079x_dev = spi_get_drvdata(spi);
+
+ free_irq(spi->irq, bcm2079x_dev);
+ misc_deregister(&bcm2079x_dev->bcm2079x_device);
+ mutex_destroy(&bcm2079x_dev->read_mutex);
+ gpio_free(bcm2079x_dev->wake_gpio);
+ gpio_free(bcm2079x_dev->en_gpio);
+ gpio_free(bcm2079x_dev->irq_gpio);
+ kfree(bcm2079x_dev);
+
+ return 0;
+}
+
+static struct spi_driver bcmspinfc_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "bcm2079x-spi",
+ },
+ .probe = bcmspinfc_probe,
+ .remove = bcmspinfc_remove,
+};
+
+/*
+ * module load/unload record keeping
+ */
+
+static int __init bcm2079x_dev_init(void)
+{
+ return spi_register_driver(&bcmspinfc_driver);
+}
+module_init(bcm2079x_dev_init);
+
+static void __exit bcm2079x_dev_exit(void)
+{
+ spi_unregister_driver(&bcmspinfc_driver);
+}
+module_exit(bcm2079x_dev_exit);
+
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("NFC bcmspinfc driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 50adc1b..5b3bbbf 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -97,6 +97,17 @@
Say Y here to enable support for the DS2782/DS2786 standalone battery
gas-gauge.
+config FUELGAUGE_DS2784
+ tristate "MAXIM DS2784 Fuel Gauge"
+ select W1
+ select W1_SLAVE_DS2784
+ help
+ Say Y to enable support for the DS2784 fuel gauge chip.
+
+ This fuel gauge chip is used to monitor remaining battery
+ capacity, and possibly protect against charging errors, for
+ Li+ batteries.
+
config BATTERY_PMU
tristate "Apple PMU battery"
depends on PPC32 && ADB_PMU
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index c8b0352..147e6c1 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -45,3 +45,4 @@
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_BATTERY_ANDROID) += android_battery.o
+obj-$(CONFIG_FUELGAUGE_DS2784) += ds2784_fuelgauge.o
diff --git a/drivers/power/ds2784_fuelgauge.c b/drivers/power/ds2784_fuelgauge.c
new file mode 100644
index 0000000..bec4297
--- /dev/null
+++ b/drivers/power/ds2784_fuelgauge.c
@@ -0,0 +1,307 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+* Copyright (C) 2012 Samsung Electronics Co., Ltd.
+*
+* 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/spinlock.h>
+#include <linux/power_supply.h>
+#include <linux/debugfs.h>
+
+#include "../w1/w1.h"
+#include "../w1/slaves/w1_ds2784.h"
+
+struct fuelgauge_status {
+ int timestamp;
+
+ int voltage_uV; /* units of uV */
+ int current_uA; /* units of uA */
+ int charge_uAh;
+
+ short temp_C; /* units of 0.1 C */
+
+ u8 percentage; /* battery percentage */
+ u8 charge_source;
+ u8 status_reg;
+ u8 battery_full; /* battery full (don't charge) */
+
+ u8 cooldown; /* was overtemp */
+ u8 charge_mode;
+};
+
+struct ds2784_info {
+ struct device *dev;
+ struct device *w1_dev;
+ struct power_supply bat;
+ struct delayed_work work;
+ char raw[DS2784_DATA_SIZE];
+ struct fuelgauge_status status;
+ bool inited;
+ struct dentry *dentry;
+};
+
+static int ds2784_read(struct ds2784_info *di, char *buf, int addr,
+ size_t count)
+{
+ return w1_ds2784_read(di->w1_dev, buf, addr, count);
+}
+
+static int ds2784_get_soc(struct ds2784_info *di, int *soc)
+{
+ int ret;
+
+ ret = ds2784_read(di, di->raw + DS2784_REG_RARC, DS2784_REG_RARC, 1);
+
+ if (ret < 0)
+ return ret;
+
+ di->status.percentage = di->raw[DS2784_REG_RARC];
+ pr_debug("%s: level : %d\n", __func__, di->status.percentage);
+ *soc = di->status.percentage;
+ return 0;
+}
+
+static int ds2784_get_vcell(struct ds2784_info *di, int *vcell)
+{
+ short n;
+ int ret;
+
+ ret = ds2784_read(di, di->raw + DS2784_REG_VOLT_MSB,
+ DS2784_REG_VOLT_MSB, 2);
+
+ if (ret < 0)
+ return ret;
+
+ n = (((di->raw[DS2784_REG_VOLT_MSB] << 8) |
+ (di->raw[DS2784_REG_VOLT_LSB])) >> 5);
+ di->status.voltage_uV = n * 4886;
+ pr_debug("%s: voltage : %d\n", __func__, di->status.voltage_uV);
+ *vcell = di->status.voltage_uV;
+ return 0;
+}
+
+static int ds2784_get_current(struct ds2784_info *di, bool avg, int *ival)
+{
+ int reg = avg ? DS2784_REG_AVG_CURR_MSB : DS2784_REG_CURR_MSB;
+ short n;
+ int ret;
+
+ if (!di->raw[DS2784_REG_RSNSP]) {
+ ret = ds2784_read(di, di->raw + DS2784_REG_RSNSP,
+ DS2784_REG_RSNSP, 1);
+ if (ret < 0)
+ dev_err(di->dev, "error %d reading RSNSP\n", ret);
+ }
+
+ ret = ds2784_read(di, di->raw + reg, reg, 2);
+ if (ret < 0)
+ return ret;
+
+ n = ((di->raw[reg] << 8) | (di->raw[reg+1]));
+
+ *ival = (n * 15625) / 10000 * di->raw[DS2784_REG_RSNSP] / 1000;
+ return 0;
+}
+
+static int ds2784_get_current_now(struct ds2784_info *di, int *i_current)
+{
+ return ds2784_get_current(di, false, i_current);
+}
+
+static int ds2784_get_current_avg(struct ds2784_info *di, int *i_avg)
+{
+ return ds2784_get_current(di, true, i_avg);
+}
+
+static int ds2784_get_temperature(struct ds2784_info *di, int *temp_now)
+{
+ short n;
+ int ret;
+
+ ret = ds2784_read(di, di->raw + DS2784_REG_TEMP_MSB,
+ DS2784_REG_TEMP_MSB, 2);
+
+ if (ret < 0)
+ return ret;
+
+ n = (((di->raw[DS2784_REG_TEMP_MSB] << 8) |
+ (di->raw[DS2784_REG_TEMP_LSB])) >> 5);
+
+ if (di->raw[DS2784_REG_TEMP_MSB] & (1 << 7))
+ n |= 0xf800;
+
+ di->status.temp_C = (n * 10) / 8;
+ pr_debug("%s: temp : %d\n", __func__, di->status.temp_C);
+
+ *temp_now = di->status.temp_C;
+ return 0;
+}
+
+static int ds2784_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct ds2784_info *di = container_of(psy, struct ds2784_info, bat);
+
+ if (!di->inited)
+ return -ENODEV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = ds2784_get_vcell(di, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = ds2784_get_temperature(di, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = "DS2784";
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "Maxim/Dallas";
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = ds2784_get_current_now(di, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = ds2784_get_current_avg(di, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = ds2784_get_soc(di, &val->intval);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property ds2784_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int ds2784_debugfs_show(struct seq_file *s, void *unused)
+{
+ struct ds2784_info *di = s->private;
+ u8 reg;
+
+ ds2784_read(di, di->raw, 0x00, 0x1C);
+ ds2784_read(di, di->raw + 0x20, 0x20, 0x10);
+ ds2784_read(di, di->raw + 0x60, 0x60, 0x20);
+ ds2784_read(di, di->raw + 0xb0, 0xb0, 0x02);
+
+ for (reg = 0x0; reg <= 0xb1; reg++) {
+ if ((reg >= 0x1c && reg <= 0x1f) ||
+ (reg >= 0x38 && reg <= 0x5f) ||
+ (reg >= 0x90 && reg <= 0xaf))
+ continue;
+
+ if (!(reg & 0x7))
+ seq_printf(s, "\n0x%02x:", reg);
+
+ seq_printf(s, "\t0x%02x", di->raw[reg]);
+ }
+ seq_printf(s, "\n");
+ return 0;
+}
+
+static int ds2784_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ds2784_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations ds2784_debugfs_fops = {
+ .open = ds2784_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __devinit ds2784_probe(struct platform_device *pdev)
+{
+ struct ds2784_info *di;
+ int ret;
+
+ di = kzalloc(sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ pr_err("%s:failed to allocate memory for module data\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, di);
+ di->dev = &pdev->dev;
+ di->w1_dev = pdev->dev.parent;
+ di->bat.name = dev_name(&pdev->dev);
+ di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->bat.properties = ds2784_props;
+ di->bat.num_properties = ARRAY_SIZE(ds2784_props);
+ di->bat.get_property = ds2784_get_property;
+
+ ret = power_supply_register(&pdev->dev, &di->bat);
+ if (ret) {
+ dev_err(di->dev, "failed to register battery power supply\n");
+ kfree(di);
+ return ret;
+ }
+
+ di->dentry = debugfs_create_file("ds2784", S_IRUGO, NULL, di,
+ &ds2784_debugfs_fops);
+ di->inited = true;
+ return 0;
+}
+
+static int __devexit ds2784_remove(struct platform_device *pdev)
+{
+ struct ds2784_info *di = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&di->bat);
+ debugfs_remove(di->dentry);
+ kfree(di);
+ return 0;
+}
+
+static struct platform_driver ds2784_driver = {
+ .probe = ds2784_probe,
+ .remove = __devexit_p(ds2784_remove),
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ds2784-fuelgauge",
+ },
+};
+
+module_platform_driver(ds2784_driver);
+
+MODULE_AUTHOR("Samsung");
+MODULE_DESCRIPTION("ds2784 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c
index b20acfa..dade6bb 100644
--- a/drivers/power/power_supply_sysfs.c
+++ b/drivers/power/power_supply_sysfs.c
@@ -97,7 +97,8 @@
return sprintf(buf, "%s\n", technology_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL)
return sprintf(buf, "%s\n", capacity_level_text[value.intval]);
- else if (off == POWER_SUPPLY_PROP_TYPE)
+ else if (off == POWER_SUPPLY_PROP_TYPE ||
+ off == POWER_SUPPLY_PROP_REMOTE_TYPE)
return sprintf(buf, "%s\n", type_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_SCOPE)
return sprintf(buf, "%s\n", scope_text[value.intval]);
@@ -178,6 +179,10 @@
POWER_SUPPLY_ATTR(usb_hc),
POWER_SUPPLY_ATTR(usb_otg),
POWER_SUPPLY_ATTR(charge_enabled),
+ POWER_SUPPLY_ATTR(usb_inpriority),
+ POWER_SUPPLY_ATTR(auto_current_limit),
+ POWER_SUPPLY_ATTR(remote_type),
+ POWER_SUPPLY_ATTR(charger_detection),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(model_name),
POWER_SUPPLY_ATTR(manufacturer),
diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c
index fb56ec7..7f0fd6f 100644
--- a/drivers/power/smb347-charger.c
+++ b/drivers/power/smb347-charger.c
@@ -41,11 +41,13 @@
#define CFG_CURRENT_LIMIT_USB_MASK 0x0f
#define CFG_VARIOUS_FUNCTION 0x02
#define CFG_INPUT_SOURCE_PRIORITY BIT(2)
+#define CFG_AUTOMATIC_INPUT_CURRENT_LIMIT BIT(4)
#define CFG_FLOAT_VOLTAGE 0x03
#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0
#define CFG_FLOAT_VOLTAGE_MASK 0x3F
#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6
#define CFG_CHARGE_CONTROL 0x04
+#define CFG_AUTOMATIC_POWER_SOURCE_DETECTION BIT(2)
#define CFG_AUTOMATIC_RECHARGE_DISABLE BIT(7)
#define CFG_STAT 0x05
#define CFG_STAT_DISABLED BIT(5)
@@ -123,8 +125,27 @@
#define STAT_C_CHG_MASK 0x06
#define STAT_C_CHG_SHIFT 1
#define STAT_C_CHARGER_ERROR BIT(6)
+#define STAT_D 0x3e
+#define STAT_D_APSD_RESULT_MASK 0x7
+#define STAT_D_APSD_COMPLETE BIT(3)
#define STAT_E 0x3f
+/* APSD results */
+#define APSD_RESULT_NOT_RUN 0x0
+#define APSD_RESULT_CDP 0x1
+#define APSD_RESULT_DCP 0x2
+#define APSD_RESULT_OTHER_CHARGER 0x3
+#define APSD_RESULT_SDP 0x4
+#define APSD_RESULT_ACA 0x5
+
+static enum power_supply_type apsd_to_pst[STAT_D_APSD_RESULT_MASK] = {
+ [APSD_RESULT_CDP] = POWER_SUPPLY_TYPE_USB_CDP,
+ [APSD_RESULT_DCP] = POWER_SUPPLY_TYPE_USB_DCP,
+ [APSD_RESULT_SDP] = POWER_SUPPLY_TYPE_USB,
+ [APSD_RESULT_ACA] = POWER_SUPPLY_TYPE_USB_ACA,
+ [APSD_RESULT_OTHER_CHARGER] = POWER_SUPPLY_TYPE_MAINS,
+};
+
/**
* struct smb347_charger - smb347 charger instance
* @lock: protects concurrent access to online variables
@@ -146,6 +167,8 @@
struct power_supply battery;
bool mains_online;
bool usb_online;
+ enum power_supply_type usb_apsd_result;
+ bool usb_apsd_enabled;
bool charging_enabled;
unsigned int mains_current_limit;
bool usb_hc_mode;
@@ -231,9 +254,18 @@
static int smb347_read(struct smb347_charger *smb, u8 reg)
{
+ int t;
int ret;
- ret = i2c_smbus_read_byte_data(smb->client, reg);
+ for (t = 0; t < 20; t++) {
+ ret = i2c_smbus_read_byte_data(smb->client, reg);
+ if (ret >= 0)
+ break;
+ dev_dbg(&smb->client->dev, "%s: retry %d reg=0x%x ret=%d\n",
+ __func__, t, reg, ret);
+ msleep(20);
+ }
+
if (ret < 0)
dev_warn(&smb->client->dev, "failed to read reg 0x%x: %d\n",
reg, ret);
@@ -242,15 +274,46 @@
static int smb347_write(struct smb347_charger *smb, u8 reg, u8 val)
{
+ int t;
int ret;
- ret = i2c_smbus_write_byte_data(smb->client, reg, val);
+ for (t = 0; t < 20; t++) {
+ ret = i2c_smbus_write_byte_data(smb->client, reg, val);
+ if (ret >= 0)
+ break;
+ dev_dbg(&smb->client->dev, "%s: retry %d reg=0x%x ret=%d\n",
+ __func__, t, reg, ret);
+ msleep(20);
+ }
+
if (ret < 0)
dev_warn(&smb->client->dev, "failed to write reg 0x%x: %d\n",
reg, ret);
return ret;
}
+static int smb347_read_block_data(struct smb347_charger *smb, u8 reg,
+ u8 length, u8 *values)
+{
+ int t;
+ int ret;
+
+ for (t = 0; t < 20; t++) {
+ ret = i2c_smbus_read_i2c_block_data(smb->client, reg, length,
+ values);
+ if (ret >= 0)
+ break;
+ dev_dbg(&smb->client->dev, "%s: retry %d reg=0x%x ret=%d\n",
+ __func__, t, reg, ret);
+ msleep(20);
+ }
+
+ if (ret < 0)
+ dev_warn(&smb->client->dev,
+ "failed to block read reg 0x%x: %d\n", reg, ret);
+ return ret;
+}
+
/**
* smb347_update_status - updates the charging status
* @smb: pointer to smb347 charger instance
@@ -759,7 +822,7 @@
u8 irqstat[6];
irqreturn_t ret = IRQ_NONE;
- t = i2c_smbus_read_i2c_block_data(smb->client, IRQSTAT_A, 6, irqstat);
+ t = smb347_read_block_data(smb, IRQSTAT_A, 6, irqstat);
if (t < 0) {
dev_warn(&smb->client->dev,
"reading IRQSTAT registers failed\n");
@@ -1018,31 +1081,12 @@
{
struct smb347_charger *smb =
container_of(psy, struct smb347_charger, mains);
- int ret;
bool oldval;
switch (prop) {
case POWER_SUPPLY_PROP_ONLINE:
oldval = smb->mains_online;
-
smb->mains_online = val->intval;
-
- smb347_set_writable(smb, true);
-
- ret = smb347_read(smb, CMD_A);
- if (ret < 0)
- return -EINVAL;
-
- ret &= ~CMD_A_SUSPEND_ENABLED;
- if (val->intval)
- ret |= CMD_A_SUSPEND_ENABLED;
-
- ret = smb347_write(smb, CMD_A, ret);
-
- smb347_hw_init(smb);
-
- smb347_set_writable(smb, false);
-
if (smb->mains_online != oldval)
power_supply_changed(psy);
return 0;
@@ -1076,6 +1120,108 @@
POWER_SUPPLY_PROP_CURRENT_MAX,
};
+static int apsd_detect(struct smb347_charger *smb, bool wait)
+{
+ int i;
+ int tries;
+ int ret;
+
+ smb347_update_status(smb);
+ mutex_lock(&smb->lock);
+
+ /*
+ * If USBIN input disconnected then use UNKNOWN.
+ */
+
+ if (!smb->usb_online) {
+ smb->usb_apsd_result = POWER_SUPPLY_TYPE_UNKNOWN;
+ ret = 0;
+ goto unlock;
+ }
+
+ /*
+ * If APSD is now disabled then return cached result from previous
+ * run.
+ */
+
+ if (!smb->usb_apsd_enabled) {
+ ret = 0;
+ goto unlock;
+ }
+
+ tries = wait ? 8 : 1;
+
+ for (i = 1; i <= tries; i++) {
+ ret = smb347_read(smb, STAT_D);
+ if (ret < 0)
+ goto unlock;
+ if (ret & STAT_D_APSD_COMPLETE)
+ goto apsd_done;
+
+ if (i < tries)
+ msleep(100);
+ }
+
+ ret = 0;
+ goto unlock;
+
+apsd_done:
+ smb->usb_apsd_result = apsd_to_pst[ret & STAT_D_APSD_RESULT_MASK];
+ smb->usb.type = smb->usb_apsd_result;
+ ret = 0;
+
+unlock:
+ mutex_unlock(&smb->lock);
+ return ret;
+}
+
+static int __apsd_enable(struct smb347_charger *smb, bool enable)
+{
+ int ret = 0;
+ int chgc;
+
+ smb347_set_writable(smb, true);
+ ret = smb347_read(smb, CFG_CHARGE_CONTROL);
+ if (ret < 0)
+ goto writeoff;
+
+ chgc = ret;
+ if (enable)
+ chgc |= CFG_AUTOMATIC_POWER_SOURCE_DETECTION;
+ else
+ chgc &= ~CFG_AUTOMATIC_POWER_SOURCE_DETECTION;
+
+ ret = smb347_write(smb, CFG_CHARGE_CONTROL, chgc);
+ if (ret)
+ goto writeoff;
+ smb->usb_apsd_enabled = enable;
+ ret = 0;
+
+writeoff:
+ smb347_set_writable(smb, false);
+ return ret;
+}
+
+static int apsd_enable(struct smb347_charger *smb, bool enable)
+{
+ int ret = 0;
+
+ mutex_lock(&smb->lock);
+
+ if (smb->usb_apsd_enabled == enable)
+ goto skip;
+
+ ret = __apsd_enable(smb, enable);
+ mutex_unlock(&smb->lock);
+ if (!ret)
+ ret = apsd_detect(smb, true);
+ return ret;
+
+skip:
+ mutex_unlock(&smb->lock);
+ return ret;
+}
+
static int smb347_usb_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
@@ -1096,6 +1242,15 @@
val->intval = smb->usb_otg_enabled;
return 0;
+ case POWER_SUPPLY_PROP_REMOTE_TYPE:
+ apsd_detect(smb, false);
+ val->intval = smb->usb_apsd_result;
+ return 0;
+
+ case POWER_SUPPLY_PROP_CHARGER_DETECTION:
+ val->intval = smb->usb_apsd_enabled;
+ return 0;
+
default:
break;
}
@@ -1115,6 +1270,7 @@
case POWER_SUPPLY_PROP_ONLINE:
oldval = smb->usb_online;
smb->usb_online = val->intval;
+ apsd_detect(smb, true);
if (smb->usb_online != oldval)
power_supply_changed(psy);
@@ -1146,6 +1302,10 @@
break;
+ case POWER_SUPPLY_PROP_CHARGER_DETECTION:
+ ret = apsd_enable(smb, val->intval);
+ break;
+
default:
break;
}
@@ -1159,6 +1319,7 @@
switch (prop) {
case POWER_SUPPLY_PROP_USB_HC:
case POWER_SUPPLY_PROP_USB_OTG:
+ case POWER_SUPPLY_PROP_CHARGER_DETECTION:
return 1;
default:
break;
@@ -1171,6 +1332,8 @@
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_USB_HC,
POWER_SUPPLY_PROP_USB_OTG,
+ POWER_SUPPLY_PROP_REMOTE_TYPE,
+ POWER_SUPPLY_PROP_CHARGER_DETECTION,
};
static int smb347_battery_get_property(struct power_supply *psy,
@@ -1290,6 +1453,20 @@
val->strval = pdata->battery_info.name;
break;
+ case POWER_SUPPLY_PROP_USB_INPRIORITY:
+ ret = smb347_read(smb, CFG_VARIOUS_FUNCTION);
+ if (ret < 0)
+ return ret;
+ val->intval = ret & CFG_INPUT_SOURCE_PRIORITY ? 1 : 0;
+ break;
+
+ case POWER_SUPPLY_PROP_AUTO_CURRENT_LIMIT:
+ ret = smb347_read(smb, CFG_VARIOUS_FUNCTION);
+ if (ret < 0)
+ return ret;
+ val->intval = ret & CFG_AUTOMATIC_INPUT_CURRENT_LIMIT ? 1 : 0;
+ break;
+
default:
return -EINVAL;
}
@@ -1310,6 +1487,50 @@
ret = smb347_charging_set(smb, val->intval);
break;
+ case POWER_SUPPLY_PROP_USB_INPRIORITY:
+ smb347_set_writable(smb, true);
+
+ ret = smb347_read(smb, CMD_A);
+ if (ret < 0)
+ goto priority_fail;
+ ret |= CMD_A_SUSPEND_ENABLED;
+ if (val->intval)
+ ret &= ~CMD_A_SUSPEND_ENABLED;
+ ret = smb347_write(smb, CMD_A, ret);
+ if (ret < 0)
+ goto priority_fail;
+
+ ret = smb347_read(smb, CFG_VARIOUS_FUNCTION);
+ if (ret < 0)
+ goto priority_fail;
+ ret &= ~(CFG_INPUT_SOURCE_PRIORITY);
+ if (val->intval)
+ ret |= CFG_INPUT_SOURCE_PRIORITY;
+ ret = smb347_write(smb, CFG_VARIOUS_FUNCTION, ret);
+ smb347_hw_init(smb);
+ if (ret < 0)
+ goto priority_fail;
+ ret = 0;
+priority_fail:
+ smb347_set_writable(smb, false);
+ break;
+
+ case POWER_SUPPLY_PROP_AUTO_CURRENT_LIMIT:
+ smb347_set_writable(smb, true);
+ ret = smb347_read(smb, CFG_VARIOUS_FUNCTION);
+ if (ret < 0)
+ goto aicl_fail;
+ ret &= ~(CFG_AUTOMATIC_INPUT_CURRENT_LIMIT);
+ if (val->intval)
+ ret |= CFG_AUTOMATIC_INPUT_CURRENT_LIMIT;
+ ret = smb347_write(smb, CFG_VARIOUS_FUNCTION, ret);
+ if (ret < 0)
+ goto aicl_fail;
+ ret = 0;
+aicl_fail:
+ smb347_set_writable(smb, false);
+ break;
+
default:
break;
}
@@ -1322,6 +1543,8 @@
{
switch (prop) {
case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ case POWER_SUPPLY_PROP_USB_INPRIORITY:
+ case POWER_SUPPLY_PROP_AUTO_CURRENT_LIMIT:
return 1;
default:
break;
@@ -1341,6 +1564,8 @@
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_ENABLED,
POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_USB_INPRIORITY,
+ POWER_SUPPLY_PROP_AUTO_CURRENT_LIMIT,
};
static int smb347_debugfs_show(struct seq_file *s, void *data)
@@ -1454,6 +1679,9 @@
if (ret < 0)
return ret;
+ /* Initialize APSD disabled */
+ __apsd_enable(smb, false);
+
smb->mains.name = "smb347-mains";
smb->mains.type = POWER_SUPPLY_TYPE_MAINS;
smb->mains.get_property = smb347_mains_get_property;
@@ -1475,7 +1703,7 @@
smb->usb.num_supplicants = ARRAY_SIZE(battery);
smb->battery.name = "smb347-battery";
- smb->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ smb->battery.type = POWER_SUPPLY_TYPE_UNKNOWN;
smb->battery.get_property = smb347_battery_get_property;
smb->battery.set_property = smb347_battery_set_property;
smb->battery.property_is_writeable = smb347_battery_property_is_writeable;
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 36db5a4..12df856 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -149,6 +149,15 @@
regulator via I2C bus. The provided regulator is suitable
for PXA27x chips to control VCC_CORE and VCC_USIM voltages.
+config REGULATOR_MAX77686
+ tristate "Maxim 77686 regulator"
+ depends on MFD_MAX77686
+ help
+ This driver controls a Maxim 77686 regulator via I2C bus or 3 GPIO ports
+ to control BUCK2, BUCK3, BUCK4.
+ The provided regulator is suitable for Exynos-4 and Exynos-5
+ chips to control VARM, VINT voltages and the other LDOs.
+
config REGULATOR_MAX8649
tristate "Maxim 8649 voltage regulator"
depends on I2C
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 94b5274..f0ac072 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -24,6 +24,7 @@
obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o
obj-$(CONFIG_REGULATOR_LP3972) += lp3972.o
obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o
+obj-$(CONFIG_REGULATOR_MAX77686) += max77686.o
obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o
obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o
obj-$(CONFIG_REGULATOR_MAX8925) += max8925-regulator.o
diff --git a/drivers/regulator/max77686.c b/drivers/regulator/max77686.c
new file mode 100644
index 0000000..815db80
--- /dev/null
+++ b/drivers/regulator/max77686.c
@@ -0,0 +1,915 @@
+/*
+ * max77686.c - Regulator driver for the Maxim 77686
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Chiwoong Byun <woong.byun@smasung.com>
+ *
+ * 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 <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/mfd/max77686.h>
+#include <linux/mfd/max77686-private.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/slab.h>
+
+#define MAX77686_OPMODE_SHIFT 6
+#define MAX77686_OPMODE_BUCK234_SHIFT 4
+#define MAX77686_OPMODE_MASK 0x3
+
+#define MAX77686_DVS_VOL_COMP 50000
+
+#define MAX77686_CHIPREV_MASK 0x7
+#define MAX77686_VERSION_MASK 0x78
+
+enum MAX77686_CHIPREV {
+ CHIPREV_MAX77686_PASS1 = 0x1,
+ CHIPREV_MAX77686_PASS2 = 0x2,
+};
+
+enum MAX77686_VERSION {
+ VERSION_MAX77686,
+ VERSION_MAX77686A,
+};
+
+struct max77686_data {
+ struct device *dev;
+ struct max77686_dev *iodev;
+ int num_regulators;
+ struct regulator_dev **rdev;
+ int ramp_delay; /* in mV/us */
+ u8 version;
+ u8 chip_rev;
+
+ struct max77686_opmode_data *opmode_data;
+
+ bool buck2_gpiodvs;
+ bool buck3_gpiodvs;
+ bool buck4_gpiodvs;
+ u8 buck2_vol[8];
+ u8 buck3_vol[8];
+ u8 buck4_vol[8];
+ int buck234_gpios_dvs[3];
+ char *buck234_gpios_dvs_label[3];
+ int buck234_gpios_selb[3];
+ char *buck234_gpios_selb_label[3];
+ int buck234_gpioindex;
+ bool ignore_gpiodvs_side_effect;
+
+ u8 saved_states[MAX77686_REG_MAX];
+};
+
+static inline void max77686_set_gpio(struct max77686_data *max77686)
+{
+ int set3 = (max77686->buck234_gpioindex) & 0x1;
+ int set2 = ((max77686->buck234_gpioindex) >> 1) & 0x1;
+ int set1 = ((max77686->buck234_gpioindex) >> 2) & 0x1;
+
+ if (max77686->buck234_gpios_dvs[0])
+ gpio_set_value(max77686->buck234_gpios_dvs[0], set1);
+ if (max77686->buck234_gpios_dvs[1])
+ gpio_set_value(max77686->buck234_gpios_dvs[1], set2);
+ if (max77686->buck234_gpios_dvs[2])
+ gpio_set_value(max77686->buck234_gpios_dvs[2], set3);
+}
+
+struct voltage_map_desc {
+ int min;
+ int max;
+ int step;
+ unsigned int n_bits;
+};
+
+/* LDO3 ~ 5, 9 ~ 14, 16 ~ 26 (uV) */
+static struct voltage_map_desc ldo_voltage_map_desc = {
+ .min = 800000, .max = 3950000, .step = 50000, .n_bits = 6,
+};
+
+/* LDO1 ~ 2, 6 ~ 8, 15 (uV) */
+static struct voltage_map_desc ldo_low_voltage_map_desc = {
+ .min = 800000, .max = 2375000, .step = 25000, .n_bits = 6,
+};
+
+/* Buck2, 3, 4 (uV) */
+static struct voltage_map_desc buck_dvs_voltage_map_desc = {
+ .min = 600000, .max = 3787500, .step = 12500, .n_bits = 8,
+};
+
+/* Buck1, 5 ~ 9 (uV) */
+static struct voltage_map_desc buck_voltage_map_desc = {
+ .min = 750000, .max = 3900000, .step = 50000, .n_bits = 6,
+};
+
+/* Buck1 (uV) for MAX77686A */
+static struct voltage_map_desc buck1_voltage_map_desc = {
+ .min = 750000, .max = 1537500, .step = 12500, .n_bits = 6,
+};
+
+static struct voltage_map_desc *reg_voltage_map[] = {
+ [MAX77686_LDO1] = &ldo_low_voltage_map_desc,
+ [MAX77686_LDO2] = &ldo_low_voltage_map_desc,
+ [MAX77686_LDO3] = &ldo_voltage_map_desc,
+ [MAX77686_LDO4] = &ldo_voltage_map_desc,
+ [MAX77686_LDO5] = &ldo_voltage_map_desc,
+ [MAX77686_LDO6] = &ldo_low_voltage_map_desc,
+ [MAX77686_LDO7] = &ldo_low_voltage_map_desc,
+ [MAX77686_LDO8] = &ldo_low_voltage_map_desc,
+ [MAX77686_LDO9] = &ldo_voltage_map_desc,
+ [MAX77686_LDO10] = &ldo_voltage_map_desc,
+ [MAX77686_LDO11] = &ldo_voltage_map_desc,
+ [MAX77686_LDO12] = &ldo_voltage_map_desc,
+ [MAX77686_LDO13] = &ldo_voltage_map_desc,
+ [MAX77686_LDO14] = &ldo_voltage_map_desc,
+ [MAX77686_LDO15] = &ldo_low_voltage_map_desc,
+ [MAX77686_LDO16] = &ldo_voltage_map_desc,
+ [MAX77686_LDO17] = &ldo_voltage_map_desc,
+ [MAX77686_LDO18] = &ldo_voltage_map_desc,
+ [MAX77686_LDO19] = &ldo_voltage_map_desc,
+ [MAX77686_LDO20] = &ldo_voltage_map_desc,
+ [MAX77686_LDO21] = &ldo_voltage_map_desc,
+ [MAX77686_LDO22] = &ldo_voltage_map_desc,
+ [MAX77686_LDO23] = &ldo_voltage_map_desc,
+ [MAX77686_LDO24] = &ldo_voltage_map_desc,
+ [MAX77686_LDO25] = &ldo_voltage_map_desc,
+ [MAX77686_LDO26] = &ldo_voltage_map_desc,
+ [MAX77686_BUCK1] = &buck_voltage_map_desc,
+ [MAX77686_BUCK2] = &buck_dvs_voltage_map_desc,
+ [MAX77686_BUCK3] = &buck_dvs_voltage_map_desc,
+ [MAX77686_BUCK4] = &buck_dvs_voltage_map_desc,
+ [MAX77686_BUCK5] = &buck_voltage_map_desc,
+ [MAX77686_BUCK6] = &buck_voltage_map_desc,
+ [MAX77686_BUCK7] = &buck_voltage_map_desc,
+ [MAX77686_BUCK8] = &buck_voltage_map_desc,
+ [MAX77686_BUCK9] = &buck_voltage_map_desc,
+ [MAX77686_EN32KHZ_AP] = NULL,
+ [MAX77686_EN32KHZ_CP] = NULL,
+ [MAX77686_P32KH] = NULL,
+};
+
+static int max77686_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ const struct voltage_map_desc *desc;
+ int rid = rdev_get_id(rdev);
+ int val;
+
+ if (rid >= ARRAY_SIZE(reg_voltage_map) ||
+ rid < 0)
+ return -EINVAL;
+
+ desc = reg_voltage_map[rid];
+ if (desc == NULL)
+ return -EINVAL;
+
+ val = desc->min + desc->step * selector;
+ if (val > desc->max)
+ return -EINVAL;
+
+ return val;
+}
+
+/*
+ * TODO
+ * Reaction to the LP/Standby for each regulator should be defined by
+ * each consumer, not by the regulator device driver if it depends
+ * on which device is attached to which regulator. Here we are
+ * creating possible PM-wise regression with board changes.Also,
+ * we can do the same effect without creating issues with board
+ * changes by carefully defining .state_mem at bsp and suspend ops
+ * callbacks.
+ */
+unsigned int max77686_opmode_reg[][3] = {
+ /* LDO1 ... LDO26 */
+ /* {NORMAL, LP, STANDBY} */
+ {0x3, 0x2, 0x0}, /* LDO1 */
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x1}, /* LDO11 */
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0}, /* LDO21 */
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0},
+ {0x3, 0x2, 0x0},
+ /* BUCK1 ... BUCK9 */
+ {0x3, 0x0, 0x1}, /* BUCK1 */
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x1},
+ {0x3, 0x2, 0x1},
+ {0x3, 0x0, 0x0},
+ {0x3, 0x0, 0x0},
+ {0x3, 0x0, 0x0},
+ {0x3, 0x0, 0x0},
+ {0x3, 0x0, 0x0},
+ /* 32KHZ */
+ {0x1, 0x0, 0x0},
+ {0x1, 0x0, 0x0},
+ {0x1, 0x0, 0x0},
+};
+
+static int max77686_get_enable_register(struct regulator_dev *rdev,
+ int *reg, int *mask, int *pattern)
+{
+ unsigned int rid = rdev_get_id(rdev);
+ unsigned int mode;
+ struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+
+ if (rid >= ARRAY_SIZE(max77686_opmode_reg))
+ return -EINVAL;
+
+ mode = max77686->opmode_data[rid].mode;
+ pr_debug("%s: rid=%d, mode=%d, size=%d\n",
+ __func__, rid, mode, ARRAY_SIZE(max77686_opmode_reg));
+
+ if (max77686_opmode_reg[rid][mode] == 0x0)
+ WARN(1, "Not supported opmode\n");
+
+ switch (rid) {
+ case MAX77686_LDO1 ... MAX77686_LDO26:
+ *reg = MAX77686_REG_LDO1CTRL1 + (rid - MAX77686_LDO1);
+ *mask = MAX77686_OPMODE_MASK << MAX77686_OPMODE_SHIFT;
+ *pattern = max77686_opmode_reg[rid][mode]
+ << MAX77686_OPMODE_SHIFT;
+ break;
+ case MAX77686_BUCK1:
+ *reg = MAX77686_REG_BUCK1CTRL;
+ *mask = MAX77686_OPMODE_MASK;
+ *pattern = max77686_opmode_reg[rid][mode];
+ break;
+ case MAX77686_BUCK2:
+ case MAX77686_BUCK3:
+ case MAX77686_BUCK4:
+ *reg = MAX77686_REG_BUCK2CTRL1 + (rid - MAX77686_BUCK2)*10;
+ *mask = MAX77686_OPMODE_MASK << MAX77686_OPMODE_BUCK234_SHIFT;
+ *pattern = max77686_opmode_reg[rid][mode]
+ << MAX77686_OPMODE_BUCK234_SHIFT;
+ break;
+ case MAX77686_BUCK5 ... MAX77686_BUCK9:
+ *reg = MAX77686_REG_BUCK5CTRL + (rid - MAX77686_BUCK5) * 2;
+ *mask = MAX77686_OPMODE_MASK;
+ *pattern = max77686_opmode_reg[rid][mode];
+ break;
+ case MAX77686_EN32KHZ_AP ... MAX77686_P32KH:
+ *reg = MAX77686_REG_32KHZ;
+ *mask = 0x01 << (rid - MAX77686_EN32KHZ_AP);
+ *pattern = 0x01 << (rid - MAX77686_EN32KHZ_AP);
+ break;
+ default:
+ /* Not controllable or not exists */
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int max77686_reg_is_enabled(struct regulator_dev *rdev)
+{
+ struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max77686->iodev->i2c;
+ int ret, reg, mask, pattern;
+ u8 val;
+
+ ret = max77686_get_enable_register(rdev, ®, &mask, &pattern);
+ if (ret == -EINVAL)
+ return 1; /* "not controllable" */
+ else if (ret)
+ return ret;
+
+ ret = max77686_read_reg(i2c, reg, &val);
+ if (ret)
+ return ret;
+
+ pr_debug("%s: id=%d, ret=%d, val=%x, mask=%x, pattern=%x\n",
+ __func__, rdev_get_id(rdev), (val & mask) == pattern,
+ val, mask, pattern);
+
+ return (val & mask) == pattern;
+}
+
+static int max77686_reg_enable(struct regulator_dev *rdev)
+{
+ struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max77686->iodev->i2c;
+ int ret, reg, mask, pattern;
+
+ ret = max77686_get_enable_register(rdev, ®, &mask, &pattern);
+ if (ret)
+ return ret;
+
+ pr_debug("%s: id=%d, reg=%x, mask=%x, pattern=%x\n",
+ __func__, rdev_get_id(rdev), reg, mask, pattern);
+
+ return max77686_update_reg(i2c, reg, pattern, mask);
+}
+
+static int max77686_reg_disable(struct regulator_dev *rdev)
+{
+ struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max77686->iodev->i2c;
+ int ret, reg, mask, pattern;
+
+ ret = max77686_get_enable_register(rdev, ®, &mask, &pattern);
+ if (ret)
+ return ret;
+
+ pr_debug("%s: id=%d, reg=%x, mask=%x, pattern=%x\n",
+ __func__, rdev_get_id(rdev), reg, mask, pattern);
+
+ return max77686_update_reg(i2c, reg, ~mask, mask);
+}
+
+static int max77686_get_voltage_register(struct regulator_dev *rdev,
+ int *_reg, int *_shift, int *_mask)
+{
+ int rid = rdev_get_id(rdev);
+ int reg, shift = 0, mask = 0x3f;
+
+ switch (rid) {
+ case MAX77686_LDO1 ... MAX77686_LDO26:
+ reg = MAX77686_REG_LDO1CTRL1 + (rid - MAX77686_LDO1);
+ break;
+ case MAX77686_BUCK1:
+ reg = MAX77686_REG_BUCK1OUT;
+ break;
+ case MAX77686_BUCK2:
+ reg = MAX77686_REG_BUCK2DVS1;
+ mask = 0xff;
+ break;
+ case MAX77686_BUCK3:
+ reg = MAX77686_REG_BUCK3DVS1;
+ mask = 0xff;
+ break;
+ case MAX77686_BUCK4:
+ reg = MAX77686_REG_BUCK4DVS1;
+ mask = 0xff;
+ break;
+ case MAX77686_BUCK5 ... MAX77686_BUCK9:
+ reg = MAX77686_REG_BUCK5OUT + (rid - MAX77686_BUCK5) * 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *_reg = reg;
+ *_shift = shift;
+ *_mask = mask;
+
+ return 0;
+}
+
+static int max77686_get_voltage(struct regulator_dev *rdev)
+{
+ struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max77686->iodev->i2c;
+ int reg, shift, mask, ret;
+ int rid = rdev_get_id(rdev);
+ u8 val;
+
+ ret = max77686_get_voltage_register(rdev, ®, &shift, &mask);
+ if (ret)
+ return ret;
+
+ if ((rid == MAX77686_BUCK2 && max77686->buck2_gpiodvs) ||
+ (rid == MAX77686_BUCK3 && max77686->buck3_gpiodvs) ||
+ (rid == MAX77686_BUCK4 && max77686->buck4_gpiodvs))
+ reg += max77686->buck234_gpioindex;
+
+ ret = max77686_read_reg(i2c, reg, &val);
+ if (ret)
+ return ret;
+
+ val >>= shift;
+ val &= mask;
+
+ pr_debug("%s: id=%d, reg=%x, mask=%x, val=%x\n",
+ __func__, rid, reg, mask, val);
+
+ return max77686_list_voltage(rdev, val);
+}
+
+static inline int max77686_get_voltage_proper_val(
+ const struct voltage_map_desc *desc,
+ int min_vol, int max_vol)
+{
+ int i = 0;
+
+ if (desc == NULL)
+ return -EINVAL;
+
+ if (max_vol < desc->min || min_vol > desc->max)
+ return -EINVAL;
+
+ while (desc->min + desc->step * i < min_vol &&
+ desc->min + desc->step * i < desc->max)
+ i++;
+
+ if (desc->min + desc->step * i > max_vol)
+ return -EINVAL;
+
+ if (i >= (1 << desc->n_bits))
+ return -EINVAL;
+
+ return i;
+}
+
+static int max77686_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned *selector)
+{
+ struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max77686->iodev->i2c;
+ const struct voltage_map_desc *desc;
+ int rid = rdev_get_id(rdev);
+ int reg, shift = 0, mask, ret;
+ int i;
+ u8 org;
+
+ desc = reg_voltage_map[rid];
+
+ /* W/A code about voltage drop of PASS1 */
+ if (max77686->version < VERSION_MAX77686A &&
+ max77686->chip_rev <= CHIPREV_MAX77686_PASS1) {
+ if (rid >= MAX77686_BUCK1 && rid <= MAX77686_BUCK4) {
+ min_uV = min_uV + MAX77686_DVS_VOL_COMP;
+ max_uV = max_uV + MAX77686_DVS_VOL_COMP;
+ }
+ }
+
+ i = max77686_get_voltage_proper_val(desc, min_uV, max_uV);
+ if (i < 0)
+ return i;
+
+ ret = max77686_get_voltage_register(rdev, ®, &shift, &mask);
+ if (ret)
+ return ret;
+
+ /* TODO: If GPIO-DVS is being used, this won't work. */
+
+ max77686_read_reg(i2c, reg, &org);
+ org = (org & mask) >> shift;
+
+ pr_debug("%s: id=%d, reg=%x, mask=%x, org=%x, val=%x\n",
+ __func__, rdev_get_id(rdev), reg, mask, org, i);
+
+ ret = max77686_update_reg(i2c, reg, i << shift, mask << shift);
+ *selector = i;
+
+ switch (rid) {
+ case MAX77686_BUCK2 ... MAX77686_BUCK4:
+ if (org < i)
+ udelay(DIV_ROUND_UP(desc->step * (i - org),
+ max77686->ramp_delay * 1000));
+ break;
+ case MAX77686_BUCK1:
+ case MAX77686_BUCK5 ... MAX77686_BUCK9:
+ /* Unconditionally 100 mV/us */
+ if (org < i)
+ udelay(DIV_ROUND_UP(desc->step * (i - org), 100000));
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int max77686_reg_do_nothing(struct regulator_dev *rdev)
+{
+ return 0;
+}
+
+static struct regulator_ops max77686_ldo_ops = {
+ .list_voltage = max77686_list_voltage,
+ .is_enabled = max77686_reg_is_enabled,
+ .enable = max77686_reg_enable,
+ .disable = max77686_reg_disable,
+ .get_voltage = max77686_get_voltage,
+ .set_voltage = max77686_set_voltage,
+ /* TODO: set 0xC0 -> 0x40 with suspend_enable. for 0x0, ->0x0 */
+ .set_suspend_enable = max77686_reg_do_nothing,
+ /* LDO's ON(0xC0) means "ON at normal, OFF at suspend" */
+ .set_suspend_disable = max77686_reg_do_nothing,
+};
+
+static struct regulator_ops max77686_buck_ops = {
+ .list_voltage = max77686_list_voltage,
+ .is_enabled = max77686_reg_is_enabled,
+ .enable = max77686_reg_enable,
+ .disable = max77686_reg_disable,
+ .get_voltage = max77686_get_voltage,
+ .set_voltage = max77686_set_voltage,
+ /* Interpret suspend_enable as "keep on if it was enabled." */
+ .set_suspend_enable = max77686_reg_do_nothing,
+ .set_suspend_disable = max77686_reg_disable,
+};
+
+static struct regulator_ops max77686_fixedvolt_ops = {
+ .list_voltage = max77686_list_voltage,
+ .is_enabled = max77686_reg_is_enabled,
+ .enable = max77686_reg_enable,
+ .disable = max77686_reg_disable,
+ /* Interpret suspend_enable as "keep on if it was enabled." */
+ .set_suspend_enable = max77686_reg_do_nothing,
+ .set_suspend_disable = max77686_reg_disable,
+};
+
+#define regulator_desc_ldo(num) { \
+ .name = "LDO"#num, \
+ .id = MAX77686_LDO##num, \
+ .ops = &max77686_ldo_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+}
+#define regulator_desc_buck(num) { \
+ .name = "BUCK"#num, \
+ .id = MAX77686_BUCK##num, \
+ .ops = &max77686_buck_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+}
+
+static struct regulator_desc regulators[] = {
+ regulator_desc_ldo(1),
+ regulator_desc_ldo(2),
+ regulator_desc_ldo(3),
+ regulator_desc_ldo(4),
+ regulator_desc_ldo(5),
+ regulator_desc_ldo(6),
+ regulator_desc_ldo(7),
+ regulator_desc_ldo(8),
+ regulator_desc_ldo(9),
+ regulator_desc_ldo(10),
+ regulator_desc_ldo(11),
+ regulator_desc_ldo(12),
+ regulator_desc_ldo(13),
+ regulator_desc_ldo(14),
+ regulator_desc_ldo(15),
+ regulator_desc_ldo(16),
+ regulator_desc_ldo(17),
+ regulator_desc_ldo(18),
+ regulator_desc_ldo(19),
+ regulator_desc_ldo(20),
+ regulator_desc_ldo(21),
+ regulator_desc_ldo(22),
+ regulator_desc_ldo(23),
+ regulator_desc_ldo(24),
+ regulator_desc_ldo(25),
+ regulator_desc_ldo(26),
+ regulator_desc_buck(1),
+ regulator_desc_buck(2),
+ regulator_desc_buck(3),
+ regulator_desc_buck(4),
+ regulator_desc_buck(5),
+ regulator_desc_buck(6),
+ regulator_desc_buck(7),
+ regulator_desc_buck(8),
+ regulator_desc_buck(9),
+ {
+ .name = "EN32KHz AP",
+ .id = MAX77686_EN32KHZ_AP,
+ .ops = &max77686_fixedvolt_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ }, {
+ .name = "EN32KHz CP",
+ .id = MAX77686_EN32KHZ_CP,
+ .ops = &max77686_fixedvolt_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ }, {
+ .name = "EN32KHz PMIC",
+ .id = MAX77686_P32KH,
+ .ops = &max77686_fixedvolt_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int max77686_set_ramp_rate(struct i2c_client *i2c, int rate)
+{
+ int ramp_delay = 0;
+ u8 data = 0;
+
+ switch (rate) {
+ case MAX77686_RAMP_RATE_100MV:
+ ramp_delay = 100;
+ data = MAX77686_REG_RAMP_RATE_100MV;
+ break;
+ case MAX77686_RAMP_RATE_13MV:
+ ramp_delay = 14;
+ data = MAX77686_REG_RAMP_RATE_13MV;
+ break;
+ case MAX77686_RAMP_RATE_27MV:
+ ramp_delay = 28;
+ data = MAX77686_REG_RAMP_RATE_27MV;
+ break;
+ case MAX77686_RAMP_RATE_55MV:
+ ramp_delay = 55;
+ data = MAX77686_REG_RAMP_RATE_55MV;
+ break;
+ }
+
+ pr_debug("%s: ramp_delay=%d, data=0x%x\n", __func__,
+ ramp_delay, data);
+
+ max77686_update_reg(i2c, MAX77686_REG_BUCK2CTRL1, data, 0xC0);
+ max77686_update_reg(i2c, MAX77686_REG_BUCK3CTRL1, data, 0xC0);
+ max77686_update_reg(i2c, MAX77686_REG_BUCK4CTRL1, data, 0xC0);
+
+ return ramp_delay;
+}
+
+static __devinit void max77686_show_pwron_src(struct max77686_data *max77686)
+{
+ const char *src[] = {
+ "PWRON=High", "JIGONB=Low", "ACOKB=Low", "Manual Reset Event",
+ "ALARM1", "ALARM2", "SMPL Event", "WTSR Event"
+ };
+ struct i2c_client *i2c = max77686->iodev->i2c;
+ int i, ret;
+ u8 data = 0;
+
+ ret = max77686_read_reg(i2c, MAX77686_REG_PWRON, &data);
+ if (ret < 0) {
+ dev_err(max77686->dev, "%s: fail to read PWRON reg(%d)\n",
+ __func__, ret);
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(src); i++)
+ if (data & 1 << i)
+ dev_info(max77686->dev, "Power on triggered by %s\n",
+ src[i]);
+}
+
+static __devinit int max77686_pmic_probe(struct platform_device *pdev)
+{
+ struct max77686_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max77686_platform_data *pdata = dev_get_platdata(iodev->dev);
+ struct regulator_dev **rdev;
+ struct max77686_data *max77686;
+ struct i2c_client *i2c;
+ int i, ret, size;
+ u8 data = 0;
+
+ pr_debug("%s\n", __func__);
+
+ if (!pdata) {
+ dev_err(pdev->dev.parent, "No platform init data supplied.\n");
+ return -ENODEV;
+ }
+
+ max77686 = kzalloc(sizeof(struct max77686_data), GFP_KERNEL);
+ if (!max77686)
+ return -ENOMEM;
+
+ size = sizeof(struct regulator_dev *) * pdata->num_regulators;
+ max77686->rdev = kzalloc(size, GFP_KERNEL);
+ if (!max77686->rdev) {
+ kfree(max77686);
+ return -ENOMEM;
+ }
+
+ rdev = max77686->rdev;
+ max77686->dev = &pdev->dev;
+ max77686->iodev = iodev;
+ max77686->num_regulators = pdata->num_regulators;
+ platform_set_drvdata(pdev, max77686);
+ i2c = max77686->iodev->i2c;
+
+ max77686->opmode_data = pdata->opmode_data;
+ max77686->ramp_delay = max77686_set_ramp_rate(i2c, pdata->ramp_rate);
+
+ max77686_read_reg(i2c, MAX77686_REG_DEVICE_ID, &data);
+ max77686->version =
+ (data & MAX77686_VERSION_MASK) >> __ffs(MAX77686_VERSION_MASK);
+ max77686->chip_rev = data & MAX77686_CHIPREV_MASK;
+ pr_info("%s: Device ID=0x%02x, (version:%d, chip_rev:%d)\n", __func__,
+ data, max77686->version, max77686->chip_rev);
+
+ max77686_show_pwron_src(max77686);
+
+ /*
+ * TODO
+ * This disables GPIO-DVS. Later we may need to implement GPIO-DVS..
+ * or we do not?
+ */
+ max77686->buck2_gpiodvs = false;
+ max77686->buck3_gpiodvs = false;
+ max77686->buck4_gpiodvs = false;
+ for (i = 0; i < 3; i++) {
+ if (gpio_is_valid(pdata->buck234_gpio_dvs[i])) {
+ max77686->buck234_gpios_dvs_label[i] =
+ kasprintf(GFP_KERNEL, "MAX77686 DVS%d", i);
+ max77686->buck234_gpios_dvs[i] =
+ pdata->buck234_gpio_dvs[i];
+ gpio_request(pdata->buck234_gpio_dvs[i],
+ max77686->buck234_gpios_dvs_label[i]);
+ gpio_direction_output(pdata->buck234_gpio_dvs[i], 0);
+ } else {
+ dev_info(&pdev->dev, "GPIO MAX77686 DVS%d ignored (%d)\n",
+ i, pdata->buck234_gpio_dvs[i]);
+ }
+
+ if (gpio_is_valid(pdata->buck234_gpio_selb[i])) {
+ max77686->buck234_gpios_selb_label[i] =
+ kasprintf(GFP_KERNEL, "MAX77686 SELB%d", i);
+ max77686->buck234_gpios_selb[i] =
+ pdata->buck234_gpio_selb[i];
+ gpio_request(pdata->buck234_gpio_selb[i],
+ max77686->buck234_gpios_selb_label[i]);
+ gpio_direction_output(pdata->buck234_gpio_selb[i], 0);
+ } else {
+ dev_info(&pdev->dev, "GPIO MAX77686 SELB%d ignored (%d)\n",
+ i, pdata->buck234_gpio_selb[i]);
+ }
+ }
+ max77686->buck234_gpioindex = 0;
+
+ for (i = 0; i < 8; i++) {
+ ret = max77686_get_voltage_proper_val(
+ &buck_dvs_voltage_map_desc,
+ pdata->buck2_voltage[i],
+ pdata->buck2_voltage[i]
+ + buck_dvs_voltage_map_desc.step);
+ /* 1.1V as default for safety */
+ if (ret < 0)
+ max77686->buck2_vol[i] = 0x28;
+ else
+ max77686->buck2_vol[i] = ret;
+ max77686_write_reg(i2c, MAX77686_REG_BUCK2DVS1 + i,
+ max77686->buck2_vol[i]);
+
+ ret = max77686_get_voltage_proper_val(
+ &buck_dvs_voltage_map_desc,
+ pdata->buck3_voltage[i],
+ pdata->buck3_voltage[i]
+ + buck_dvs_voltage_map_desc.step);
+ /* 1.1V as default for safety */
+ if (ret < 0)
+ max77686->buck3_vol[i] = 0x28;
+ else
+ max77686->buck3_vol[i] = ret;
+ max77686_write_reg(i2c, MAX77686_REG_BUCK3DVS1 + i,
+ max77686->buck3_vol[i]);
+
+ ret = max77686_get_voltage_proper_val(
+ &buck_dvs_voltage_map_desc,
+ pdata->buck4_voltage[i],
+ pdata->buck4_voltage[i]
+ + buck_dvs_voltage_map_desc.step);
+ /* 1.1V as default for safety */
+ if (ret < 0)
+ max77686->buck4_vol[i] = 0x28;
+ else
+ max77686->buck4_vol[i] = ret;
+ max77686_write_reg(i2c, MAX77686_REG_BUCK4DVS1 + i,
+ max77686->buck4_vol[i]);
+ }
+
+ if (pdata->has_full_constraints)
+ regulator_has_full_constraints();
+
+ if (max77686->version > VERSION_MAX77686)
+ reg_voltage_map[MAX77686_BUCK1] = &buck1_voltage_map_desc;
+
+ for (i = 0; i < pdata->num_regulators; i++) {
+ const struct voltage_map_desc *desc;
+ int id = pdata->regulators[i].id;
+
+ desc = reg_voltage_map[id];
+ if (desc) {
+ regulators[id].n_voltages =
+ (desc->max - desc->min) / desc->step + 1;
+
+ pr_debug("%s: desc=%p, id=%d, n_vol=%d, "
+ "max=%d, min=%d, step=%d\n", __func__,
+ desc, id, regulators[id].n_voltages,
+ desc->max, desc->min, desc->step);
+ }
+
+ rdev[i] = regulator_register(®ulators[id], max77686->dev,
+ pdata->regulators[i].initdata, max77686, NULL);
+ if (IS_ERR(rdev[i])) {
+ ret = PTR_ERR(rdev[i]);
+ dev_err(max77686->dev, "regulator init failed for %d\n",
+ id);
+ rdev[i] = NULL;
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ for (i = 0; i < 3; i++) {
+ if (max77686->buck234_gpios_dvs[i])
+ gpio_free(max77686->buck234_gpios_dvs[i]);
+ if (max77686->buck234_gpios_dvs[i])
+ gpio_free(max77686->buck234_gpios_selb[i]);
+ kfree(max77686->buck234_gpios_dvs_label[i]);
+ kfree(max77686->buck234_gpios_selb_label[i]);
+ }
+
+ for (i = 0; i < max77686->num_regulators; i++)
+ if (rdev[i])
+ regulator_unregister(rdev[i]);
+
+ kfree(max77686->rdev);
+ kfree(max77686);
+
+ return ret;
+}
+
+static int __devexit max77686_pmic_remove(struct platform_device *pdev)
+{
+ struct max77686_data *max77686 = platform_get_drvdata(pdev);
+ struct regulator_dev **rdev = max77686->rdev;
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (max77686->buck234_gpios_dvs[i])
+ gpio_free(max77686->buck234_gpios_dvs[i]);
+ if (max77686->buck234_gpios_dvs[i])
+ gpio_free(max77686->buck234_gpios_selb[i]);
+ kfree(max77686->buck234_gpios_dvs_label[i]);
+ kfree(max77686->buck234_gpios_selb_label[i]);
+ }
+
+ for (i = 0; i < max77686->num_regulators; i++)
+ if (rdev[i])
+ regulator_unregister(rdev[i]);
+
+ kfree(max77686->rdev);
+ kfree(max77686);
+
+ return 0;
+}
+
+static const struct platform_device_id max77686_pmic_id[] = {
+ { "max77686-pmic", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(platform, max77686_pmic_id);
+
+static struct platform_driver max77686_pmic_driver = {
+ .driver = {
+ .name = "max77686-pmic",
+ .owner = THIS_MODULE,
+ },
+ .probe = max77686_pmic_probe,
+ .remove = __devexit_p(max77686_pmic_remove),
+ .id_table = max77686_pmic_id,
+};
+
+static int __init max77686_pmic_init(void)
+{
+ pr_debug("%s\n", __func__);
+
+ return platform_driver_register(&max77686_pmic_driver);
+}
+subsys_initcall(max77686_pmic_init);
+
+static void __exit max77686_pmic_cleanup(void)
+{
+ platform_driver_unregister(&max77686_pmic_driver);
+}
+module_exit(max77686_pmic_cleanup);
+
+MODULE_DESCRIPTION("MAXIM 77686 Regulator Driver");
+MODULE_AUTHOR("Chiwoong Byun <woong.byun@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 8c8377d..a6092a6 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -213,6 +213,16 @@
This driver can also be built as a module. If so, the module
will be called rtc-max8998.
+config RTC_DRV_MAX77686
+ tristate "Maxim MAX77686"
+ depends on MFD_MAX77686
+ help
+ If you say yes here you will get support for the
+ RTC of Maxim MAX77686 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-max77686.
+
config RTC_DRV_RS5C372
tristate "Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A"
help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 727ae77..e82b9c1 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -65,6 +65,7 @@
obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o
obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o
+obj-$(CONFIG_RTC_DRV_MAX77686) += rtc-max77686.o
obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o
obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o
diff --git a/drivers/rtc/rtc-max77686.c b/drivers/rtc/rtc-max77686.c
new file mode 100644
index 0000000..e90636a
--- /dev/null
+++ b/drivers/rtc/rtc-max77686.c
@@ -0,0 +1,591 @@
+/*
+ * RTC driver for Maxim MAX77686
+ *
+ * Copyright (c) 2012 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 as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/rtc.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/max77686.h>
+#include <linux/mfd/max77686-private.h>
+
+/* RTCINTM Register */
+#define RTCA1M_SHIFT 1
+#define RTCA1M_MASK (1 << RTCA1M_SHIFT)
+/* RTCCNTL Register */
+#define BCD_EN_SHIFT 0
+#define BCD_EN_MASK (1 << BCD_EN_SHIFT)
+#define MODEL24_SHIFT 1
+#define MODEL24_MASK (1 << MODEL24_SHIFT)
+/* RTCUPDATE0 Register */
+#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)
+/* RTCHOUR 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)
+
+/* PMIC STATUS1 register */
+#define STATUS1_JIGONB_MASK BIT(1)
+/* PMIC STATUS2 register */
+#define STATUS2_RTCA1_MASK BIT(2)
+
+#define MAX77686_RTC_UPDATE_DELAY 16
+
+#define WTSR_TIMER_BITS(v) (((v) << WTSRT_SHIFT) & WTSRT_MASK)
+#define SMPL_TIMER_BITS(v) (((v) << SMPLT_SHIFT) & SMPLT_MASK)
+
+/* RTC Counter Register offsets */
+enum rtc_cnt_reg_offset {
+ RTC_SEC = 0,
+ RTC_MIN,
+ RTC_HOUR,
+ RTC_WEEKDAY,
+ RTC_MONTH,
+ RTC_YEAR,
+ RTC_DATE,
+ NR_RTC_CNT_REGS,
+};
+
+struct max77686_rtc_info {
+ struct device *dev;
+ struct max77686_dev *max77686;
+ struct i2c_client *rtc;
+ struct rtc_device *rtc_dev;
+ struct mutex lock;
+ int irq;
+ bool use_irq;
+ bool wtsr_en;
+ bool alarm_enabled;
+ u8 update0_reg;
+};
+
+enum MAX77686_RTC_OP {
+ MAX77686_RTC_WRITE,
+ MAX77686_RTC_READ,
+};
+
+
+static void max77686_rtc_data_to_tm(u8 *data, struct rtc_time *tm)
+{
+ tm->tm_sec = data[RTC_SEC] & 0x7f;
+ tm->tm_min = data[RTC_MIN] & 0x7f;
+ tm->tm_hour = data[RTC_HOUR] & 0x1f;
+ tm->tm_wday = __fls(data[RTC_WEEKDAY] & 0x7f);
+ tm->tm_mday = data[RTC_DATE] & 0x1f;
+ tm->tm_mon = (data[RTC_MONTH] & 0x0f) - 1;
+ tm->tm_year = (data[RTC_YEAR] & 0x7f) + 100;
+ tm->tm_yday = 0;
+ tm->tm_isdst = 0;
+}
+
+static int max77686_rtc_tm_to_data(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: MAX77686 RTC cannot handle the year %d\n",
+ __func__, 1900 + tm->tm_year);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max77686_rtc_update(struct max77686_rtc_info *info,
+ enum MAX77686_RTC_OP op)
+{
+ u8 data;
+ int ret;
+
+ if (!info || !info->rtc) {
+ pr_err("%s: Invalid argument\n", __func__);
+ return -EINVAL;
+ }
+
+ switch (op) {
+ case MAX77686_RTC_WRITE:
+ data = RTC_UDR_MASK;
+ break;
+ case MAX77686_RTC_READ:
+ data = RTC_RBUDR_MASK;
+ break;
+ default:
+ dev_err(info->dev, "%s: invalid op(%d)\n", __func__, op);
+ return -EINVAL;
+ }
+
+ data |= info->update0_reg;
+
+ /* NOTES about UDF and RBUDF(RTCUPDATE1(0x05) register):
+ * If the user read RTCUPDATE1 register when RBUDF or UDF bit of the
+ * register was set to 1 at the same time, the value read from the
+ * register could be 0 and RBUDF or UDF bit would be cleared.
+ * The user should wait for 16msec before initiating new read/write
+ * operation and RTCUPDATE1 register will be erased from the datasheet.
+ */
+ ret = max77686_write_reg(info->rtc, MAX77686_RTC_UPDATE0, data);
+ if (ret < 0)
+ dev_err(info->dev,
+ "%s: fail to write update0 reg(ret=%d, data=0x%x)\n",
+ __func__, ret, data);
+ else
+ msleep(MAX77686_RTC_UPDATE_DELAY);
+
+ return ret;
+}
+
+static int max77686_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[NR_RTC_CNT_REGS];
+ int ret;
+
+ mutex_lock(&info->lock);
+ ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_read(info->rtc, MAX77686_RTC_SEC, NR_RTC_CNT_REGS,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read time reg(%d)\n", __func__,
+ ret);
+ goto out;
+ }
+
+ dev_dbg(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(0x%02x)\n",
+ __func__, data[RTC_YEAR] + 2000, data[RTC_MONTH],
+ data[RTC_DATE], data[RTC_HOUR], data[RTC_MIN],
+ data[RTC_SEC], data[RTC_WEEKDAY]);
+
+ max77686_rtc_data_to_tm(data, tm);
+ ret = rtc_valid_tm(tm);
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int max77686_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[NR_RTC_CNT_REGS];
+ int ret;
+
+ ret = max77686_rtc_tm_to_data(tm, data);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(0x%02x)\n",
+ __func__, data[RTC_YEAR] + 2000, data[RTC_MONTH],
+ data[RTC_DATE], data[RTC_HOUR], data[RTC_MIN],
+ data[RTC_SEC], data[RTC_WEEKDAY]);
+
+ mutex_lock(&info->lock);
+ ret = max77686_bulk_write(info->rtc, MAX77686_RTC_SEC, NR_RTC_CNT_REGS,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write time reg(%d)\n", __func__,
+ ret);
+ goto out;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int max77686_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[NR_RTC_CNT_REGS], val;
+ int ret;
+
+ mutex_lock(&info->lock);
+ ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_read(info->rtc, MAX77686_ALARM1_SEC,
+ NR_RTC_CNT_REGS, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s:%d fail to read alarm reg(%d)\n",
+ __func__, __LINE__, ret);
+ goto out;
+ }
+
+ max77686_rtc_data_to_tm(data, &alrm->time);
+
+ dev_dbg(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(%d)\n", __func__,
+ alrm->time.tm_year + 1900, alrm->time.tm_mon + 1,
+ alrm->time.tm_mday, alrm->time.tm_hour,
+ alrm->time.tm_min, alrm->time.tm_sec,
+ alrm->time.tm_wday);
+
+ alrm->enabled = info->alarm_enabled;
+ alrm->pending = 0;
+ ret = max77686_read_reg(info->max77686->i2c, MAX77686_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 & STATUS2_RTCA1_MASK)
+ alrm->pending = 1;
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int max77686_rtc_set_alarm_enable(struct max77686_rtc_info *info,
+ bool enabled)
+{
+ if (!info->use_irq)
+ return -EPERM;
+
+ if (enabled && !info->alarm_enabled) {
+ info->alarm_enabled = true;
+ enable_irq(info->irq);
+ } else if (!enabled && info->alarm_enabled) {
+ info->alarm_enabled = false;
+ disable_irq(info->irq);
+ }
+ return 0;
+}
+
+static int max77686_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[NR_RTC_CNT_REGS];
+ int ret, i;
+
+ mutex_lock(&info->lock);
+ ret = max77686_rtc_tm_to_data(&alrm->time, data);
+ if (ret < 0)
+ goto out;
+
+ dev_dbg(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(0x%02x)\n",
+ __func__, data[RTC_YEAR] + 2000, data[RTC_MONTH],
+ data[RTC_DATE], data[RTC_HOUR], data[RTC_MIN],
+ data[RTC_SEC], data[RTC_WEEKDAY]);
+
+ for (i = 0; i < NR_RTC_CNT_REGS; i++)
+ data[i] |= ALARM_ENABLE_MASK;
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_ALARM1_SEC,
+ NR_RTC_CNT_REGS, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_rtc_set_alarm_enable(info, alrm->enabled);
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int max77686_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&info->lock);
+ ret = max77686_rtc_set_alarm_enable(info, enabled);
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static irqreturn_t max77686_rtc_alarm_irq(int irq, void *data)
+{
+ struct max77686_rtc_info *info = data;
+
+ if (!info->rtc_dev)
+ return IRQ_HANDLED;
+
+ 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 max77686_rtc_ops = {
+ .read_time = max77686_rtc_read_time,
+ .set_time = max77686_rtc_set_time,
+ .read_alarm = max77686_rtc_read_alarm,
+ .set_alarm = max77686_rtc_set_alarm,
+ .alarm_irq_enable = max77686_rtc_alarm_irq_enable,
+};
+
+static bool max77686_is_jigonb_low(struct max77686_rtc_info *info)
+{
+ int ret;
+ u8 val;
+
+ ret = max77686_read_reg(info->max77686->i2c, MAX77686_REG_STATUS1,
+ &val);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read status1 reg(%d)\n",
+ __func__, ret);
+ return false;
+ }
+
+ return !(val & STATUS1_JIGONB_MASK);
+}
+
+static void __devinit
+max77686_rtc_enable_wtsr_smpl(struct max77686_rtc_info *info,
+ struct max77686_platform_data *pdata)
+{
+ u8 val;
+ int ret;
+
+ if (pdata->wtsr_smpl->check_jigon && max77686_is_jigonb_low(info))
+ pdata->wtsr_smpl->smpl_en = false;
+
+ val = (pdata->wtsr_smpl->wtsr_en << WTSR_EN_SHIFT)
+ | (pdata->wtsr_smpl->smpl_en << SMPL_EN_SHIFT)
+ | WTSR_TIMER_BITS(pdata->wtsr_smpl->wtsr_timer_val)
+ | SMPL_TIMER_BITS(pdata->wtsr_smpl->smpl_timer_val);
+
+ dev_info(info->dev, "%s: WTSR: %s, SMPL: %s\n", __func__,
+ pdata->wtsr_smpl->wtsr_en ? "enable" : "disable",
+ pdata->wtsr_smpl->smpl_en ? "enable" : "disable");
+
+ ret = max77686_write_reg(info->rtc, MAX77686_WTSR_SMPL_CNTL, val);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write WTSR/SMPL reg(%d)\n",
+ __func__, ret);
+ return;
+ }
+ info->wtsr_en = pdata->wtsr_smpl->wtsr_en;
+
+ max77686_rtc_update(info, MAX77686_RTC_WRITE);
+}
+
+static void max77686_rtc_disable_wtsr(struct max77686_rtc_info *info)
+{
+ int ret;
+
+ dev_info(info->dev, "%s: disable WTSR\n", __func__);
+ max77686_rtc_update(info, MAX77686_RTC_READ);
+ ret = max77686_update_reg(info->rtc, MAX77686_WTSR_SMPL_CNTL, 0,
+ WTSR_EN_MASK);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to update WTSR reg(%d)\n",
+ __func__, ret);
+ return;
+ }
+ max77686_rtc_update(info, MAX77686_RTC_WRITE);
+}
+
+static int __devinit max77686_rtc_init_reg(struct max77686_rtc_info *info,
+ struct max77686_platform_data *pdata)
+{
+ u8 data[2], update0, cntl;
+ int ret;
+
+ max77686_rtc_update(info, MAX77686_RTC_READ);
+
+ ret = max77686_read_reg(info->rtc, MAX77686_RTC_CONTROL, &cntl);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read control reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = max77686_read_reg(info->rtc, MAX77686_RTC_UPDATE0, &update0);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read update0 reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ info->update0_reg = update0 & ~(RTC_UDR_MASK | RTC_RBUDR_MASK);
+
+ /* If the value of CONTROL register is 0, RTC registers were reset */
+ if (cntl == MODEL24_MASK)
+ return 0;
+
+ /* Set RTC control register : Binary mode, 24hour mode */
+ data[0] = BCD_EN_MASK | MODEL24_MASK;
+ data[1] = MODEL24_MASK;
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_RTC_CONTROLM, 2, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write controlm reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+ if (ret < 0)
+ return ret;
+
+ if (pdata->init_time) {
+ dev_info(info->dev, "%s: initialize RTC time\n", __func__);
+ ret = max77686_rtc_set_time(info->dev, pdata->init_time);
+ }
+ return ret;
+}
+
+static int __devinit max77686_rtc_probe(struct platform_device *pdev)
+{
+ struct max77686_dev *max77686 = dev_get_drvdata(pdev->dev.parent);
+ struct max77686_platform_data *pdata = dev_get_platdata(max77686->dev);
+ struct max77686_rtc_info *info;
+ int ret;
+
+ if (!pdata) {
+ dev_err(pdev->dev.parent, "RTC: No platform data supplied.\n");
+ return -ENODEV;
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ mutex_init(&info->lock);
+ info->dev = &pdev->dev;
+ info->max77686 = max77686;
+ info->rtc = max77686->rtc;
+ info->irq = max77686->irq_base + MAX77686_RTCIRQ_RTCA1;
+
+ platform_set_drvdata(pdev, info);
+
+ ret = max77686_rtc_init_reg(info, pdata);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to initialize RTC reg:%d\n", ret);
+ goto err_init_reg;
+ }
+
+ if (pdata->wtsr_smpl)
+ max77686_rtc_enable_wtsr_smpl(info, pdata);
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ ret = request_threaded_irq(info->irq, NULL, max77686_rtc_alarm_irq, 0,
+ "rtc-alarm0", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
+ info->irq, ret);
+ goto err_request_irq;
+ }
+ disable_irq(info->irq);
+ disable_irq(info->irq);
+ info->use_irq = true;
+
+ info->rtc_dev = rtc_device_register("max77686-rtc", &pdev->dev,
+ &max77686_rtc_ops, THIS_MODULE);
+ if (IS_ERR(info->rtc_dev)) {
+ ret = PTR_ERR(info->rtc_dev);
+ dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+ if (!ret)
+ ret = -EINVAL;
+ goto err_rtc_dev_register;
+ }
+ enable_irq(info->irq);
+ return 0;
+
+err_rtc_dev_register:
+ enable_irq(info->irq);
+ enable_irq(info->irq);
+ free_irq(info->irq, info);
+err_request_irq:
+err_init_reg:
+ kfree(info);
+ return ret;
+}
+
+static int __devexit max77686_rtc_remove(struct platform_device *pdev)
+{
+ struct max77686_rtc_info *info = platform_get_drvdata(pdev);
+
+ if (!info->alarm_enabled)
+ enable_irq(info->irq);
+
+ free_irq(info->irq, info);
+ rtc_device_unregister(info->rtc_dev);
+ kfree(info);
+
+ return 0;
+}
+
+static void max77686_rtc_shutdown(struct platform_device *pdev)
+{
+ struct max77686_rtc_info *info = platform_get_drvdata(pdev);
+
+ if (info->wtsr_en)
+ max77686_rtc_disable_wtsr(info);
+}
+
+static const struct platform_device_id rtc_id[] = {
+ {"max77686-rtc", 0},
+ {},
+};
+
+static struct platform_driver max77686_rtc_driver = {
+ .driver = {
+ .name = "max77686-rtc",
+ .owner = THIS_MODULE,
+ },
+ .probe = max77686_rtc_probe,
+ .remove = __devexit_p(max77686_rtc_remove),
+ .shutdown = max77686_rtc_shutdown,
+ .id_table = rtc_id,
+};
+
+static int __init max77686_rtc_init(void)
+{
+ return platform_driver_register(&max77686_rtc_driver);
+}
+
+module_init(max77686_rtc_init);
+
+static void __exit max77686_rtc_exit(void)
+{
+ platform_driver_unregister(&max77686_rtc_driver);
+}
+
+module_exit(max77686_rtc_exit);
+
+MODULE_DESCRIPTION("Maxim MAX77686 RTC driver");
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/iio/Kconfig b/drivers/staging/iio/Kconfig
index fe1586718..9d36c53 100644
--- a/drivers/staging/iio/Kconfig
+++ b/drivers/staging/iio/Kconfig
@@ -75,6 +75,7 @@
source "drivers/staging/iio/light/Kconfig"
source "drivers/staging/iio/magnetometer/Kconfig"
source "drivers/staging/iio/meter/Kconfig"
+source "drivers/staging/iio/pressure/Kconfig"
source "drivers/staging/iio/resolver/Kconfig"
source "drivers/staging/iio/trigger/Kconfig"
diff --git a/drivers/staging/iio/Makefile b/drivers/staging/iio/Makefile
index 5075291..c4d27cc 100644
--- a/drivers/staging/iio/Makefile
+++ b/drivers/staging/iio/Makefile
@@ -31,5 +31,6 @@
obj-y += light/
obj-y += magnetometer/
obj-y += meter/
+obj-y += pressure/
obj-y += resolver/
obj-y += trigger/
diff --git a/drivers/staging/iio/imu/Kconfig b/drivers/staging/iio/imu/Kconfig
index 2c2f47d..e2fa886 100644
--- a/drivers/staging/iio/imu/Kconfig
+++ b/drivers/staging/iio/imu/Kconfig
@@ -14,4 +14,5 @@
adis16365, adis16400 and adis16405 triaxial inertial sensors
(adis16400 series also have magnetometers).
+source "drivers/staging/iio/imu/mpu/Kconfig"
endmenu
diff --git a/drivers/staging/iio/imu/Makefile b/drivers/staging/iio/imu/Makefile
index 3400a13..9c471fd 100644
--- a/drivers/staging/iio/imu/Makefile
+++ b/drivers/staging/iio/imu/Makefile
@@ -5,3 +5,5 @@
adis16400-y := adis16400_core.o
adis16400-$(CONFIG_IIO_BUFFER) += adis16400_ring.o adis16400_trigger.o
obj-$(CONFIG_ADIS16400) += adis16400.o
+
+obj-y += mpu/
diff --git a/drivers/staging/iio/imu/mpu/Kconfig b/drivers/staging/iio/imu/mpu/Kconfig
new file mode 100644
index 0000000..fe29936
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/Kconfig
@@ -0,0 +1,31 @@
+#
+# inv-mpu-iio driver for Invensense MPU devices and combos
+#
+
+config INV_MPU_IIO
+ tristate "Invensense MPU devices"
+ depends on I2C && SYSFS && IIO && IIO_KFIFO_BUF && IIO_TRIGGER && !INV_MPU
+ default n
+ help
+ This driver supports the Invensense MPU devices.
+ This includes MPU6050/MPU3050/MPU9150/ITG3500.
+ This driver can be built as a module. The module will be called
+ inv-mpu-iio.
+
+config INV_IIO_MPU3050_ACCEL_SLAVE_BMA250
+ bool "Invense MPU3050 slave accelerometer device for bma250"
+ depends on INV_MPU_IIO
+ default n
+ help
+ This is slave device enable MPU3050 accelerometer slave device.
+ Right now, it is only bma250. For other acceleromter device,
+ it can be added to this menu if the proper interface is filled.
+ There are some interface function to be defined.
+
+config INV_TESTING
+ boolean "Invensense MPU Testing Information"
+ depends on INV_MPU_IIO
+ default n
+ help
+ This flag enables display of additional testing information from the
+ Invensense MPU IIO driver
diff --git a/drivers/staging/iio/imu/mpu/Makefile b/drivers/staging/iio/imu/mpu/Makefile
new file mode 100644
index 0000000..6ce3240
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/Makefile
@@ -0,0 +1,19 @@
+#
+# Makefile for Invensense inv-mpu-iio device.
+#
+
+obj-$(CONFIG_INV_MPU_IIO) += inv-mpu-iio.o
+
+inv-mpu-iio-objs := inv_mpu_core.o
+inv-mpu-iio-objs += inv_mpu_ring.o
+inv-mpu-iio-objs += inv_mpu_trigger.o
+inv-mpu-iio-objs += inv_mpu_misc.o
+inv-mpu-iio-objs += inv_mpu3050_iio.o
+inv-mpu-iio-objs += dmpDefaultMPU6050.o
+
+# the Bosch BMA250 driver is added to the inv-mpu device driver because it
+# must be connected to an MPU3050 device on the secondary slave bus.
+ifeq ($(CONFIG_INV_IIO_MPU3050_ACCEL_SLAVE_BMA250), y)
+inv-mpu-iio-objs += inv_slave_bma250.o
+endif
+
diff --git a/drivers/staging/iio/imu/mpu/README b/drivers/staging/iio/imu/mpu/README
new file mode 100644
index 0000000..a896fb9
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/README
@@ -0,0 +1,500 @@
+Kernel driver inv-mpu-iio
+Author: Invensense <http://invensense.com>
+
+Table of Contents:
+==================
+- Description
+- Integrating the Driver in the Linux Kernel
+- Board and Platform Data
+ - Interrupt Pin
+ - Platform Data
+- Board File Modifications for Secondary I2C Configuration
+ - MPU-6050 + AKM8963 on the secondary I2C interface
+ - MPU-6500 + AKM8963 on the secondary I2C interface
+ - MPU-9150
+ - MPU-3050 + BMA250 on the secondary I2C interface
+- Board File Modifications for Invensense Devices
+ - MPU-3050
+ - ITG-3500
+ - MPU-6050
+ - MPU-6500
+ - MPU-9150
+- IIO Subsystem
+ - Communicating with the Driver in Userspace
+ - ITG-3500
+ - MPU-6050 and MPU-6500
+ - MPU-9150
+ - MPU-3050 + BMA250 on the secondary I2C interface
+- Low Power Accelerometer Mode
+- DMP Event
+- Streaming Data to an Userspace Application
+- Recommended Sysfs Entry Setup Sequence
+ - With DMP Firmware
+ - Without DMP Firmware
+- Test Applications
+ - Running Test Applications with MPU-9150/MPU-6050/MPU-6500
+ - Running Test Applications with MPU-3050/ITG-3500
+
+
+
+Description
+===========
+This document describes how to install the Invensense device driver into a
+Linux kernel. The Invensense driver currently supports the following sensors:
+
+- ITG-3500
+- MPU-6050
+- MPU-9150
+- MPU-6500
+- MPU-3050
+
+The slave address of each device is either 0x68 or 0x69, depending on the AD0 pin
+value of the device. Please refer to the appropriate product specification document
+for further information regarding the AD0 pin. The driver supports both addresses.
+
+The following files are included in this package:
+- Kconfig
+- Makefile
+- inv_mpu_core.c
+- inv_mpu_misc.c
+- inv_mpu_trigger.c
+- inv_mpu3050_iio.c
+- inv_mpu_iio.h
+- inv_mpu_ring.c
+- inv_slave_bma250.c
+- dmpDefaultMPU6050.c
+- dmpkey.h
+- dmpmap.h
+- mpu.h
+
+
+Integrating the Driver in the Linux Kernel
+==========================================
+Please add the files as follows:
+- Add mpu.h to "kernel/include/linux".
+- Add all other files to drivers/staging/iio/imu/mpu
+(another directory is acceptable, but this is the recommended destination)
+
+In order to see the driver in menuconfig when building the kernel, please
+make modifications as shown below:
+
+ modify "drivers/staging/iio/imu/Kconfig" like
+ source "drivers/staging/iio/imu/mpu/Kconfig"
+
+ modify "drivers/staging/iio/imu/Makefile"
+ obj-y += mpu/
+
+
+Board and Platform Data
+=======================
+In order to recognize the Invensense device on the I2C bus, the board file must be modified.
+The i2c_board_info instance must be defined as shown below.
+
+Interrupt Pin
+-------------
+The hardcoded value of 140 corresponds to the GPIO input pin connected to the Invensense device's interrupt pin.
+This pin will most likely be different for your platform, and the value should be changed accordingly.
+
+Platform Data
+-------------
+The platform data (orientation matrix and secondary bus configurations) must be modified as show below, according
+to your particular platform configuration.
+
+Please note that the MPU-9150 it is treated as a MPU-6050 with AKM8975 on the device's secondary I2C interface. Thus the secondary I2C address must be provided.
+
+
+Board File Modifications for Secondary I2C Configuration
+========================================================
+For the Panda Board, the board file can be found at arch/arm/mach-omap2/board-omap4panda.c.
+Please modify the pertinent baord file in your system according to the examples shown below:
+
+MPU-6050 + AKM8963 on the secondary I2C interface
+-------------------------------------------------
+static struct mpu_platform_data gyro_platform_data = {
+ .int_config = 0x10,
+ .level_shifter = 0,
+ .orientation = { -1, 0, 0,
+ 0, 1, 0,
+ 0, 0, -1 },
+ .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS,
+ .sec_slave_id = COMPASS_ID_AK8963,
+ .secondary_i2c_addr = 0x0E
+};
+
+MPU-6500 + AKM8963 on the secondary I2C interface
+-------------------------------------------------
+static struct mpu_platform_data gyro_platform_data = {
+ .int_config = 0x10,
+ .level_shifter = 0,
+ .orientation = { -1, 0, 0,
+ 0, 1, 0,
+ 0, 0, -1 },
+ .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS,
+ .sec_slave_id = COMPASS_ID_AK8963,
+ .secondary_i2c_addr = 0x0E
+};
+
+MPU-9150
+--------
+For MPU-9150, please provide the following secondary I2C bus information.
+
+static struct mpu_platform_data gyro_platform_data = {
+ .int_config = 0x10,
+ .level_shifter = 0,
+ .orientation = { -1, 0, 0,
+ 0, 1, 0,
+ 0, 0, -1 },
+ .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS,
+ .sec_slave_id = COMPASS_ID_AK8975,
+ .secondary_i2c_addr = 0x0E
+};
+
+MPU-3050 + BMA250 on the secondary I2C interface
+------------------------------------------------
+For BMA250 on the secondary I2C bus, please provide the following information.
+
+static struct mpu_platform_data gyro_platform_data = {
+ .int_config = 0x10,
+ .level_shifter = 0,
+ .orientation = { -1, 0, 0,
+ 0, 1, 0,
+ 0, 0, -1 },
+ .sec_slave_type = SECONDARY_SLAVE_TYPE_ACCEL,
+ .sec_slave_id = ACCEL_ID_BMA250,
+ .secondary_i2c_addr = 0x18,
+};
+
+
+Board File Modifications for Invensense Devices
+===============================================
+For Invensense devices, please provide the i2c init data as shown in the examples below.
+
+In the _i2c_init function, the device is registered in the following manner:
+
+ arch/arm/mach-omap2/board-omap4panda.c
+ in static int __init omap4_panda_i2c_init(void)
+ omap_register_i2c_bus(4, 400, single_chip_board_info, ARRAY_SIZE(single_chip_board_info));
+
+MPU-3050
+--------
+static struct i2c_board_info __initdata single_chip_board_info[] = {
+ {
+ I2C_BOARD_INFO("mpu3050", 0x68),
+ .irq = (IH_GPIO_BASE + MPUIRQ_GPIO),
+ .platform_data = &gyro_platform_data,
+ },
+};
+
+ITG-3050
+--------
+static struct i2c_board_info __initdata single_chip_board_info[] = {
+ {
+ I2C_BOARD_INFO("itg3500", 0x68),
+ .irq = (IH_GPIO_BASE + MPUIRQ_GPIO),
+ .platform_data = &gyro_platform_data,
+ },
+};
+
+MPU6050
+-------
+static struct i2c_board_info __initdata single_chip_board_info[] = {
+ {
+ I2C_BOARD_INFO("mpu6050", 0x68),
+ .irq = (IH_GPIO_BASE + MPUIRQ_GPIO),
+ .platform_data = &gyro_platform_data,
+ },
+};
+
+MPU6500
+-------
+static struct i2c_board_info __initdata single_chip_board_info[] = {
+ {
+ I2C_BOARD_INFO("mpu6500", 0x68),
+ .irq = (IH_GPIO_BASE + MPUIRQ_GPIO),
+ .platform_data = &gyro_platform_data,
+ },
+};
+
+MPU9150
+-------
+arch/arm/mach-omap2/board-omap4panda.c
+static struct i2c_board_info __initdata single_chip_board_info[] = {
+ {
+ I2C_BOARD_INFO("mpu9150", 0x68),
+ .irq = (IH_GPIO_BASE + MPUIRQ_GPIO),
+ .platform_data = &gyro_platform_data,
+ },
+};
+
+
+IIO subsystem
+=============
+A successful installation will create the following two new directories under /sys/bus/iio/devices:
+
+ - iio:device0
+ - trigger0
+
+Also, a new file, "iio:device0", will be created in the /dev/ diretory.
+(if you have more than one IIO device, the file will be named "iio:deviceX", where X is a number)
+
+
+Communicating with the Driver in Userspace
+------------------------------------------
+The driver generates several files in sysfs upon installation.
+These files are used to communicate with the driver. The files can be found
+at /sys/bus/iio/devices/iio:device0 (or ../iio:deviceX as shown above).
+
+A brief description of the pertinent files for each Invensense device is shown below:
+
+ITG-3500
+--------
+temperature (Read-only)
+--Read temperature data directly from the temperature register.
+
+sampling_frequency (Read/write)
+--Configure the ADC sampling rate and FIFO output rate.
+
+sampling_frequency_available(read-only)
+--show commonly used frequency
+
+clock_source (Read-only)
+--Check which clock-source is used by the chip.
+
+power_state (Read/write)
+--turn on/off the power supply
+
+self_test (read-only)
+--read this entry trigger self test. The return value is D.
+D is the success/fail.
+For different chip, the result is different for success/fail.
+1 means success 0 means fail. The LSB of D is for gyro; the bit
+next to LSB of D is for accel. The bit 2 of D is for compass result.
+
+key (read-only)
+--show the key value of this driver. Used by MPL.
+
+gyro_matrix (read-only)
+--show the orientation matrix obtained from the board file.
+
+MPU-6050 and MPU-6500
+---------------------
+MPU-6050 and MPU-6500 have all sysfs files belonging to ITG-3500 (shown above).
+In addition, it has the files below:
+
+gyro_enable (read/write)
+--enable/disable gyro functionality. Affects raw_gyro. Turning this off this will
+shut down gyro and save power.
+
+accl_enable (read/write)
+--enable/disable accelerometer functionality. Affects raw_accl.
+Turning this off this will shut down accel and save power.
+
+firmware_loaded (read/write)
+--Flag indicating the whether firmware is loaded or not in the DMP engine.
+0 means no firmware loaded. 1 means firmware is already loaded . This
+flag can only be written as 0. It internally updates to 1.
+
+dmp_on(read/write)
+--This entry controls whether to run DMP or not.
+Write 1 to enable DMP and write 0 to disable dmp.
+Please note that firmware_loaded must be 1 in order to enable DMP.
+
+dmp_int_on(read/write)
+--This entry controls whether dmp interrupt is on/off.
+Please note that firmware_loaded must be 1.
+Also, we'd like to remind you that it is sometimes advantageous to
+turn interrupts off while the DMP is running.
+
+dmp_output_rate
+--control dmp output rate when dmp is on.
+
+dmp_event_int_on(read/write)
+--This entry controls whether dmp event interrupt is on/off.
+Please note that turning this on will turn off the data interrupt.
+Interrupts will be generated only when events occur.
+
+This is useful for saving power when the system is waiting for a special event to wake up.
+
+dmp_firmware (write only binary file)
+--DMP firmware code is loaded into this entry.
+If loading is successful, the firmware_loaded flag will be updated to 1.
+In order to load new firmware, the firmware_loaded flag must be first set to 0.
+
+lpa_mode(read-write)
+--Low power accelerometer mode
+
+lpa_freq(read-write)
+--Low power acceleromter frequency.
+
+accel_matrix
+--orientation matrix for accelerometer.
+
+flick_lower,
+flick_upper,
+flick_counter,
+flick_message_on,
+flick_int_on,
+flick_axis,
+--Flick related entries
+
+pedometer_time
+pedometer_steps,
+--Pedometer related entries
+
+event_flick
+event_tap
+event_orientation
+event_display_orientation
+--Event related entries.
+Please poll these entries to read their values. Direct reads will yield meaningless results.
+Further details are provided in the DMP Events section of this README.
+
+tap_on
+--Controls tap function of DMP
+
+tap_time
+tap_min_count
+tap_threshold
+--Tap related entries. Controls various parameters of tap function.
+
+orientation_on
+--Turn on/off orientation function of DMP.
+
+display_orientation_on
+--Turn on/off display orientation function of DMP.
+
+quaternion_on
+--Turn on/off quaterniion data output. DMP is required for this feature.
+
+MPU-9150
+--------
+MPU-9150 has all of MPU-6050's entries. It also has two additional entries, described below.
+
+compass_enable (read/write)
+--Enables compass function.
+
+compass_matrix (read-only)
+--Compass orientation matrix
+
+MPU-3050 with BMA250 on secondary I2C interface
+-----------------------------------------------
+MPU-3050 with BMA250 on the secondary I2C interface has ever ITG-3500 entry.
+It also has two additional entries, shown below:
+
+accl_matrix
+
+accl_enable
+
+
+Low Power Accelerometer Mode
+============================
+Lower power accelerometer mode is a special mode that is only available
+in MPU-6050, MPU-6500, and MPU-9150.
+
+Only accelerometer is functional in this mode.
+"gyro_enable" and "compass_enable" must be zero. "dmp_on" must be zero.
+
+Low power acceleromter mode has two entries: lpa_mode and lpa_freq
+
+To run low power accel mode, set lpa_mode to 1.
+
+For MPU6050/MPU9150:
+Set lpa_freq to 0~3, which correspond to 1.25Hz, 5Hz, 20Hz, 40Hz.
+
+For MPU6500:
+Set lpa_freq to 0~11, which correspond to 0.3125Hz to 640Hz.
+
+
+DMP Event
+=========
+A DMP Event is an event that is output by the DMP unit within the Invensense device (MPU).
+Only the MPU-6050, MPU-6500, and MPU-9150 feature the DMP.
+
+
+There are four sysfs entries for DMP events:
+
+- event_flick
+- event_tap
+- event_orientation
+- event_display_orientation
+
+These four events must be polled before reading.
+
+The proper method to poll sysfs is as follows:
+1. open file.
+2. dummy read.
+3. poll.
+4. once the poll passed, use fopen and fread to read the sysfs entry.
+5. interpret the data.
+
+
+Streaming Data to an Userspace Application
+==========================================
+When streaming data to an userspace application, we recommend that you access
+gyro/accel/compass data via /dev/iio:device0.
+
+Please follow the steps below to read data at a constant rate from the driver:
+
+1. Write a 1 to power_state to turn on the chip. This is the default setting
+ after installing the driver.
+2. Write the desired output rate to fifo_rate.
+3. Write 1 to enable to turn on the event.
+4. Read /dev/iio:device0 to get a string of gyro/accel/compass data.
+5. Parse this string to obtain each gyro/accel/compass element.
+6. If dmp firmware code is loaded, use "dmp_on" to enable/disable dmp.
+7. If compass is enabled, the output will contain compass data.
+
+
+Recommended Sysfs Entry Setup Senquence
+=======================================
+
+Without DMP Firmware
+--------------------
+1. Set "power_state" to 1,
+2. Set the scale and fifo rate values according to your needs.
+3. Set gyro_enable, accel_enable, and compass_enable according to your needs. For example:
+- If you only want gyro data, set accel_enable to 0 (and compass_enable to 0, if applicable).
+- If you only want accel data, set gyro_enable to 0 (and compass_enable to 0, if applicable).
+- If you only want compass data, set gyro_enable to 0 and accel_enable to 0.
+4. Set "enable" to 1.
+5. You will now get the output that you want.
+
+With DMP Firmware
+-----------------
+1. Set "power_state" to 1.
+2. Write "0" to firmware_loaded if it is not zero already.
+3. Load firmware into "dmp_firmware" as a whole. Don't split the DMP firmware image.
+4. Make sure firmware_loaded is 1 after loading the DMP image.
+5. Make appropriate configurations as shown above in the "without DMP firmware" case.
+6. Set dmp_on to 1.
+7. Set "enable" to 1.
+
+Please note that the enable function uses the enable entry under
+"/sys/bus/iio/devices/iio:device0/buffer"
+
+Test Applications
+=================
+Test applications are located under ARTHROPOD/trunk/software/simple_apps/mpu_iio
+
+Running Test Applications with MPU-9150/MPU-6050/MPU-6500
+---------------------------------------------------------
+To run test applications with MPU-9150, MPU-6050, or MPU-6500 devices,
+please use the following commands:
+
+1. For orientation/tap/flick/display orientation events:
+
+ mpu_iio -c 10 -l 3 -p
+
+2. For printing data normally:
+
+ mpu_iio -c 10 -l 3 -r
+
+Running Test Applications with MPU-3050/ITG-3500
+------------------------------------------------
+To run test applications with MPU-3050 or ITG-3500 devices,
+please use the following command:
+
+1. For printing data normally:
+ mpu_iio -c 10 -l 3 -r
+
+Please use mpu_iio.c and iio_utils.h as example code for your development purposes.
\ No newline at end of file
diff --git a/drivers/staging/iio/imu/mpu/dmpDefaultMPU6050.c b/drivers/staging/iio/imu/mpu/dmpDefaultMPU6050.c
new file mode 100644
index 0000000..1620778
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/dmpDefaultMPU6050.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2012 Invensense, 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.
+ *
+ */
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file dmpDefaultMPU6050.c
+ * @brief dmp Default data
+ * @details This file is part of invensense mpu driver code
+ *
+ */
+
+#include "dmpKey.h"
+#include "dmpmap.h"
+
+#define CFG_27 (2745)
+#define CFG_20 (2078)
+#define CFG_23 (2748)
+#define CFG_FIFO_ON_EVENT (2694)
+#define CFG_ORIENT_IRQ_1 (2533)
+#define CGNOTICE_INTR (2636)
+#define X_GRT_Y_TMP (1318)
+#define CFG_DR_INT (1029)
+#define CFG_AUTH (1035)
+#define FCFG_1 (1062)
+#define SKIP_X_GRT_Y_TMP (1319)
+#define SKIP_END_COMPARE (1395)
+#define FCFG_3 (1110)
+#define FCFG_2 (1066)
+#define END_COMPARE_Y_X_TMP2 (1415)
+#define CFG_DISPLAY_ORIENT_INT (1706)
+#define FCFG_7 (1076)
+#define FCFG_6 (1128)
+#define NO_ORIENT_INTERRUPT (1725)
+#define CFG_8 (2723)
+#define CFG_15 (2731)
+#define CFG_16 (2749)
+#define END_COMPARE_Y_X_TMP (1367)
+#define CFG_6 (2756)
+#define END_ORIENT_1 (1709)
+#define END_COMPARE_Y_X (1444)
+#define CFG_LP_QUAT (2717)
+#define END_ORIENT (1738)
+#define CFG_FLICK_IN (2589)
+#define CFG_7 (1221)
+#define CFG_MOTION_BIAS (1224)
+#define X_GRT_Y (1368)
+#define TEMPLABEL (2178)
+#define NOT_TIME_MINUS_1 (1528)
+#define END_COMPARE_Y_X_TMP3 (1394)
+#define X_GRT_Y_TMP2 (1339)
+
+#define D_0_22 (22+512)
+#define D_0_24 (24+512)
+
+#define D_0_36 (36)
+#define D_0_52 (52)
+#define D_0_96 (96)
+#define D_0_104 (104)
+#define D_0_108 (108)
+#define D_0_163 (163)
+#define D_0_188 (188)
+#define D_0_192 (192)
+#define D_0_224 (224)
+#define D_0_228 (228)
+#define D_0_232 (232)
+#define D_0_236 (236)
+
+#define D_1_2 (256 + 2)
+#define D_1_4 (256 + 4)
+#define D_1_8 (256 + 8)
+#define D_1_10 (256 + 10)
+#define D_1_24 (256 + 24)
+#define D_1_28 (256 + 28)
+#define D_1_36 (256 + 36)
+#define D_1_40 (256 + 40)
+#define D_1_44 (256 + 44)
+#define D_1_72 (256 + 72)
+#define D_1_74 (256 + 74)
+#define D_1_79 (256 + 79)
+#define D_1_88 (256 + 88)
+#define D_1_90 (256 + 90)
+#define D_1_92 (256 + 92)
+#define D_1_96 (256 + 96)
+#define D_1_98 (256 + 98)
+#define D_1_106 (256 + 106)
+#define D_1_108 (256 + 108)
+#define D_1_112 (256 + 112)
+#define D_1_128 (256 + 144)
+#define D_1_152 (256 + 12)
+#define D_1_160 (256 + 160)
+#define D_1_176 (256 + 176)
+#define D_1_178 (256 + 178)
+#define D_1_218 (256 + 218)
+#define D_1_232 (256 + 232)
+#define D_1_236 (256 + 236)
+#define D_1_240 (256 + 240)
+#define D_1_244 (256 + 244)
+#define D_1_250 (256 + 250)
+#define D_1_252 (256 + 252)
+#define D_2_12 (512 + 12)
+#define D_2_96 (512 + 96)
+#define D_2_108 (512 + 108)
+#define D_2_208 (512 + 208)
+#define D_2_224 (512 + 224)
+#define D_2_236 (512 + 236)
+#define D_2_244 (512 + 244)
+#define D_2_248 (512 + 248)
+#define D_2_252 (512 + 252)
+
+#define CPASS_BIAS_X (35 * 16 + 4)
+#define CPASS_BIAS_Y (35 * 16 + 8)
+#define CPASS_BIAS_Z (35 * 16 + 12)
+#define CPASS_MTX_00 (36 * 16)
+#define CPASS_MTX_01 (36 * 16 + 4)
+#define CPASS_MTX_02 (36 * 16 + 8)
+#define CPASS_MTX_10 (36 * 16 + 12)
+#define CPASS_MTX_11 (37 * 16)
+#define CPASS_MTX_12 (37 * 16 + 4)
+#define CPASS_MTX_20 (37 * 16 + 8)
+#define CPASS_MTX_21 (37 * 16 + 12)
+#define CPASS_MTX_22 (43 * 16 + 12)
+#define D_ACT0 (40 * 16)
+#define D_ACSX (40 * 16 + 4)
+#define D_ACSY (40 * 16 + 8)
+#define D_ACSZ (40 * 16 + 12)
+
+#define FLICK_MSG (45 * 16 + 4)
+#define FLICK_COUNTER (45 * 16 + 8)
+#define FLICK_LOWER (45 * 16 + 12)
+#define FLICK_UPPER (46 * 16 + 12)
+
+#define D_AUTH_OUT (992)
+#define D_AUTH_IN (996)
+#define D_AUTH_A (1000)
+#define D_AUTH_B (1004)
+
+#define D_PEDSTD_BP_B (768 + 0x1C)
+#define D_PEDSTD_HP_A (768 + 0x78)
+#define D_PEDSTD_HP_B (768 + 0x7C)
+#define D_PEDSTD_BP_A4 (768 + 0x40)
+#define D_PEDSTD_BP_A3 (768 + 0x44)
+#define D_PEDSTD_BP_A2 (768 + 0x48)
+#define D_PEDSTD_BP_A1 (768 + 0x4C)
+#define D_PEDSTD_INT_THRSH (768 + 0x68)
+#define D_PEDSTD_CLIP (768 + 0x6C)
+#define D_PEDSTD_SB (768 + 0x28)
+#define D_PEDSTD_SB_TIME (768 + 0x2C)
+#define D_PEDSTD_PEAKTHRSH (768 + 0x98)
+#define D_PEDSTD_TIML (768 + 0x2A)
+#define D_PEDSTD_TIMH (768 + 0x2E)
+#define D_PEDSTD_PEAK (768 + 0X94)
+#define D_PEDSTD_STEPCTR (768 + 0x60)
+#define D_PEDSTD_TIMECTR (964)
+#define D_PEDSTD_DECI (768 + 0xA0)
+
+#define D_HOST_NO_MOT (976)
+
+static const struct tKeyLabel dmpTConfig[] = {
+ {KEY_CFG_27, CFG_27},
+ {KEY_CFG_20, CFG_20},
+ {KEY_CFG_23, CFG_23},
+ {KEY_CFG_FIFO_ON_EVENT, CFG_FIFO_ON_EVENT},
+ {KEY_CFG_ORIENT_IRQ_1, CFG_ORIENT_IRQ_1},
+ {KEY_CGNOTICE_INTR, CGNOTICE_INTR},
+ {KEY_X_GRT_Y_TMP, X_GRT_Y_TMP},
+ {KEY_CFG_DR_INT, CFG_DR_INT},
+ {KEY_CFG_AUTH, CFG_AUTH},
+ {KEY_FCFG_1, FCFG_1},
+ {KEY_SKIP_X_GRT_Y_TMP, SKIP_X_GRT_Y_TMP},
+ {KEY_SKIP_END_COMPARE, SKIP_END_COMPARE},
+ {KEY_FCFG_3, FCFG_3},
+ {KEY_FCFG_2, FCFG_2},
+ {KEY_END_COMPARE_Y_X_TMP2, END_COMPARE_Y_X_TMP2},
+ {KEY_CFG_DISPLAY_ORIENT_INT, CFG_DISPLAY_ORIENT_INT},
+ {KEY_FCFG_7, FCFG_7},
+ {KEY_FCFG_6, FCFG_6},
+ {KEY_NO_ORIENT_INTERRUPT, NO_ORIENT_INTERRUPT},
+ {KEY_CFG_8, CFG_8},
+ {KEY_CFG_15, CFG_15},
+ {KEY_CFG_16, CFG_16},
+ {KEY_END_COMPARE_Y_X_TMP, END_COMPARE_Y_X_TMP},
+ {KEY_CFG_6, CFG_6},
+ {KEY_END_ORIENT_1, END_ORIENT_1},
+ {KEY_END_COMPARE_Y_X, END_COMPARE_Y_X},
+ {KEY_CFG_LP_QUAT, CFG_LP_QUAT},
+ {KEY_END_ORIENT, END_ORIENT},
+ {KEY_CFG_FLICK_IN, CFG_FLICK_IN},
+ {KEY_CFG_7, CFG_7},
+ {KEY_CFG_MOTION_BIAS, CFG_MOTION_BIAS},
+ {KEY_X_GRT_Y, X_GRT_Y},
+ {KEY_TEMPLABEL, TEMPLABEL},
+ {KEY_NOT_TIME_MINUS_1, NOT_TIME_MINUS_1},
+ {KEY_END_COMPARE_Y_X_TMP3, END_COMPARE_Y_X_TMP3},
+ {KEY_X_GRT_Y_TMP2, X_GRT_Y_TMP2},
+ {KEY_D_0_22, D_0_22},
+ {KEY_D_0_96, D_0_96},
+ {KEY_D_0_104, D_0_104},
+ {KEY_D_0_108, D_0_108},
+ {KEY_D_1_36, D_1_36},
+ {KEY_D_1_40, D_1_40},
+ {KEY_D_1_44, D_1_44},
+ {KEY_D_1_72, D_1_72},
+ {KEY_D_1_74, D_1_74},
+ {KEY_D_1_79, D_1_79},
+ {KEY_D_1_88, D_1_88},
+ {KEY_D_1_90, D_1_90},
+ {KEY_D_1_92, D_1_92},
+ {KEY_D_1_160, D_1_160},
+ {KEY_D_1_176, D_1_176},
+ {KEY_D_1_218, D_1_218},
+ {KEY_D_1_232, D_1_232},
+ {KEY_D_1_250, D_1_250},
+ {KEY_DMP_TAPW_MIN, DMP_TAPW_MIN},
+ {KEY_DMP_TAP_THR_X, DMP_TAP_THX},
+ {KEY_DMP_TAP_THR_Y, DMP_TAP_THY},
+ {KEY_DMP_TAP_THR_Z, DMP_TAP_THZ},
+ {KEY_DMP_SH_TH_Y, DMP_SH_TH_Y},
+ {KEY_DMP_SH_TH_X, DMP_SH_TH_X},
+ {KEY_DMP_SH_TH_Z, DMP_SH_TH_Z},
+ {KEY_DMP_ORIENT, DMP_ORIENT},
+ {KEY_D_AUTH_OUT, D_AUTH_OUT},
+ {KEY_D_AUTH_IN, D_AUTH_IN},
+ {KEY_D_AUTH_A, D_AUTH_A},
+ {KEY_D_AUTH_B, D_AUTH_B},
+ {KEY_CPASS_BIAS_X, CPASS_BIAS_X},
+ {KEY_CPASS_BIAS_Y, CPASS_BIAS_Y},
+ {KEY_CPASS_BIAS_Z, CPASS_BIAS_Z},
+ {KEY_CPASS_MTX_00, CPASS_MTX_00},
+ {KEY_CPASS_MTX_01, CPASS_MTX_01},
+ {KEY_CPASS_MTX_02, CPASS_MTX_02},
+ {KEY_CPASS_MTX_10, CPASS_MTX_10},
+ {KEY_CPASS_MTX_11, CPASS_MTX_11},
+ {KEY_CPASS_MTX_12, CPASS_MTX_12},
+ {KEY_CPASS_MTX_20, CPASS_MTX_20},
+ {KEY_CPASS_MTX_21, CPASS_MTX_21},
+ {KEY_CPASS_MTX_22, CPASS_MTX_22},
+ {KEY_D_ACT0, D_ACT0},
+ {KEY_D_ACSX, D_ACSX},
+ {KEY_D_ACSY, D_ACSY},
+ {KEY_D_ACSZ, D_ACSZ},
+ {KEY_FLICK_MSG, FLICK_MSG},
+ {KEY_FLICK_COUNTER, FLICK_COUNTER},
+ {KEY_FLICK_LOWER, FLICK_LOWER},
+ {KEY_FLICK_UPPER, FLICK_UPPER},
+ {KEY_D_PEDSTD_BP_B, D_PEDSTD_BP_B},
+ {KEY_D_PEDSTD_HP_A, D_PEDSTD_HP_A},
+ {KEY_D_PEDSTD_HP_B, D_PEDSTD_HP_B},
+ {KEY_D_PEDSTD_BP_A4, D_PEDSTD_BP_A4},
+ {KEY_D_PEDSTD_BP_A3, D_PEDSTD_BP_A3},
+ {KEY_D_PEDSTD_BP_A2, D_PEDSTD_BP_A2},
+ {KEY_D_PEDSTD_BP_A1, D_PEDSTD_BP_A1},
+ {KEY_D_PEDSTD_INT_THRSH, D_PEDSTD_INT_THRSH},
+ {KEY_D_PEDSTD_CLIP, D_PEDSTD_CLIP},
+ {KEY_D_PEDSTD_SB, D_PEDSTD_SB},
+ {KEY_D_PEDSTD_SB_TIME, D_PEDSTD_SB_TIME},
+ {KEY_D_PEDSTD_PEAKTHRSH, D_PEDSTD_PEAKTHRSH},
+ {KEY_D_PEDSTD_TIML, D_PEDSTD_TIML},
+ {KEY_D_PEDSTD_TIMH, D_PEDSTD_TIMH},
+ {KEY_D_PEDSTD_PEAK, D_PEDSTD_PEAK},
+ {KEY_D_PEDSTD_STEPCTR, D_PEDSTD_STEPCTR},
+ {KEY_D_PEDSTD_TIMECTR, D_PEDSTD_TIMECTR},
+ {KEY_D_PEDSTD_DECI, D_PEDSTD_DECI},
+ {KEY_D_HOST_NO_MOT, D_HOST_NO_MOT}
+};
+#define NUM_LOCAL_KEYS (sizeof(dmpTConfig)/sizeof(dmpTConfig[0]))
+
+static struct tKeyLabel keys[NUM_KEYS];
+
+unsigned short inv_dmp_get_address(unsigned short key)
+{
+ static int isSorted;
+ if (!isSorted) {
+ int kk;
+ for (kk = 0; kk < NUM_KEYS; ++kk) {
+ keys[kk].addr = 0xffff;
+ keys[kk].key = kk;
+ }
+ for (kk = 0; kk < NUM_LOCAL_KEYS; ++kk)
+ keys[dmpTConfig[kk].key].addr = dmpTConfig[kk].addr;
+ isSorted = 1;
+ }
+ if (key >= NUM_KEYS)
+ return 0xffff;
+ return keys[key].addr;
+}
+/**
+ * @}
+ */
diff --git a/drivers/staging/iio/imu/mpu/dmpKey.h b/drivers/staging/iio/imu/mpu/dmpKey.h
new file mode 100644
index 0000000..d19b15a
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/dmpKey.h
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2012 Invensense, 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.
+ *
+ */
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file dmpKey.h
+ * @brief dmp Key definition
+ * @details This file is part of invensense mpu driver code
+ *
+ */
+
+#ifndef DMPKEY_H__
+#define DMPKEY_H__
+
+#define KEY_CFG_25 (0)
+#define KEY_CFG_24 (KEY_CFG_25 + 1)
+#define KEY_CFG_26 (KEY_CFG_24 + 1)
+#define KEY_CFG_27 (KEY_CFG_26 + 1)
+#define KEY_CFG_21 (KEY_CFG_27 + 1)
+#define KEY_CFG_20 (KEY_CFG_21 + 1)
+#define KEY_CFG_TAP4 (KEY_CFG_20 + 1)
+#define KEY_CFG_TAP5 (KEY_CFG_TAP4 + 1)
+#define KEY_CFG_TAP6 (KEY_CFG_TAP5 + 1)
+#define KEY_CFG_TAP7 (KEY_CFG_TAP6 + 1)
+#define KEY_CFG_TAP0 (KEY_CFG_TAP7 + 1)
+#define KEY_CFG_TAP1 (KEY_CFG_TAP0 + 1)
+#define KEY_CFG_TAP2 (KEY_CFG_TAP1 + 1)
+#define KEY_CFG_TAP3 (KEY_CFG_TAP2 + 1)
+#define KEY_CFG_TAP_QUANTIZE (KEY_CFG_TAP3 + 1)
+#define KEY_CFG_TAP_JERK (KEY_CFG_TAP_QUANTIZE + 1)
+#define KEY_CFG_DR_INT (KEY_CFG_TAP_JERK + 1)
+#define KEY_CFG_AUTH (KEY_CFG_DR_INT + 1)
+#define KEY_CFG_TAP_SAVE_ACCB (KEY_CFG_AUTH + 1)
+#define KEY_CFG_TAP_CLEAR_STICKY (KEY_CFG_TAP_SAVE_ACCB + 1)
+#define KEY_CFG_FIFO_ON_EVENT (KEY_CFG_TAP_CLEAR_STICKY + 1)
+#define KEY_FCFG_ACCEL_INPUT (KEY_CFG_FIFO_ON_EVENT + 1)
+#define KEY_FCFG_ACCEL_INIT (KEY_FCFG_ACCEL_INPUT + 1)
+#define KEY_CFG_23 (KEY_FCFG_ACCEL_INIT + 1)
+#define KEY_FCFG_1 (KEY_CFG_23 + 1)
+#define KEY_FCFG_3 (KEY_FCFG_1 + 1)
+#define KEY_FCFG_2 (KEY_FCFG_3 + 1)
+#define KEY_CFG_3D (KEY_FCFG_2 + 1)
+#define KEY_CFG_3B (KEY_CFG_3D + 1)
+#define KEY_CFG_3C (KEY_CFG_3B + 1)
+#define KEY_FCFG_5 (KEY_CFG_3C + 1)
+#define KEY_FCFG_4 (KEY_FCFG_5 + 1)
+#define KEY_FCFG_7 (KEY_FCFG_4 + 1)
+#define KEY_FCFG_FSCALE (KEY_FCFG_7 + 1)
+#define KEY_FCFG_AZ (KEY_FCFG_FSCALE + 1)
+#define KEY_FCFG_6 (KEY_FCFG_AZ + 1)
+#define KEY_FCFG_LSB4 (KEY_FCFG_6 + 1)
+#define KEY_CFG_12 (KEY_FCFG_LSB4 + 1)
+#define KEY_CFG_14 (KEY_CFG_12 + 1)
+#define KEY_CFG_15 (KEY_CFG_14 + 1)
+#define KEY_CFG_16 (KEY_CFG_15 + 1)
+#define KEY_CFG_18 (KEY_CFG_16 + 1)
+#define KEY_CFG_6 (KEY_CFG_18 + 1)
+#define KEY_CFG_7 (KEY_CFG_6 + 1)
+#define KEY_CFG_4 (KEY_CFG_7 + 1)
+#define KEY_CFG_5 (KEY_CFG_4 + 1)
+#define KEY_CFG_2 (KEY_CFG_5 + 1)
+#define KEY_CFG_3 (KEY_CFG_2 + 1)
+#define KEY_CFG_1 (KEY_CFG_3 + 1)
+#define KEY_CFG_EXTERNAL (KEY_CFG_1 + 1)
+#define KEY_CFG_8 (KEY_CFG_EXTERNAL + 1)
+#define KEY_CFG_9 (KEY_CFG_8 + 1)
+#define KEY_CFG_ORIENT_3 (KEY_CFG_9 + 1)
+#define KEY_CFG_ORIENT_2 (KEY_CFG_ORIENT_3 + 1)
+#define KEY_CFG_ORIENT_1 (KEY_CFG_ORIENT_2 + 1)
+#define KEY_CFG_GYRO_SOURCE (KEY_CFG_ORIENT_1 + 1)
+#define KEY_CFG_ORIENT_IRQ_1 (KEY_CFG_GYRO_SOURCE + 1)
+#define KEY_CFG_ORIENT_IRQ_2 (KEY_CFG_ORIENT_IRQ_1 + 1)
+#define KEY_CFG_ORIENT_IRQ_3 (KEY_CFG_ORIENT_IRQ_2 + 1)
+#define KEY_FCFG_MAG_VAL (KEY_CFG_ORIENT_IRQ_3 + 1)
+#define KEY_FCFG_MAG_MOV (KEY_FCFG_MAG_VAL + 1)
+#define KEY_CFG_LP_QUAT (KEY_FCFG_MAG_MOV + 1)
+
+/* MPU6050 keys */
+#define KEY_CFG_ACCEL_FILTER (KEY_CFG_LP_QUAT + 1)
+#define KEY_CFG_MOTION_BIAS (KEY_CFG_ACCEL_FILTER + 1)
+#define KEY_TEMPLABEL (KEY_CFG_MOTION_BIAS + 1)
+
+#define KEY_D_0_22 (KEY_TEMPLABEL + 1)
+#define KEY_D_0_24 (KEY_D_0_22 + 1)
+#define KEY_D_0_36 (KEY_D_0_24 + 1)
+#define KEY_D_0_52 (KEY_D_0_36 + 1)
+#define KEY_D_0_96 (KEY_D_0_52 + 1)
+#define KEY_D_0_104 (KEY_D_0_96 + 1)
+#define KEY_D_0_108 (KEY_D_0_104 + 1)
+#define KEY_D_0_163 (KEY_D_0_108 + 1)
+#define KEY_D_0_188 (KEY_D_0_163 + 1)
+#define KEY_D_0_192 (KEY_D_0_188 + 1)
+#define KEY_D_0_224 (KEY_D_0_192 + 1)
+#define KEY_D_0_228 (KEY_D_0_224 + 1)
+#define KEY_D_0_232 (KEY_D_0_228 + 1)
+#define KEY_D_0_236 (KEY_D_0_232 + 1)
+
+#define KEY_DMP_PREVPTAT (KEY_D_0_236 + 1)
+#define KEY_D_1_2 (KEY_DMP_PREVPTAT + 1)
+#define KEY_D_1_4 (KEY_D_1_2 + 1)
+#define KEY_D_1_8 (KEY_D_1_4 + 1)
+#define KEY_D_1_10 (KEY_D_1_8 + 1)
+#define KEY_D_1_24 (KEY_D_1_10 + 1)
+#define KEY_D_1_28 (KEY_D_1_24 + 1)
+#define KEY_D_1_36 (KEY_D_1_28 + 1)
+#define KEY_D_1_40 (KEY_D_1_36 + 1)
+#define KEY_D_1_44 (KEY_D_1_40 + 1)
+#define KEY_D_1_72 (KEY_D_1_44 + 1)
+#define KEY_D_1_74 (KEY_D_1_72 + 1)
+#define KEY_D_1_79 (KEY_D_1_74 + 1)
+#define KEY_D_1_88 (KEY_D_1_79 + 1)
+#define KEY_D_1_90 (KEY_D_1_88 + 1)
+#define KEY_D_1_92 (KEY_D_1_90 + 1)
+#define KEY_D_1_96 (KEY_D_1_92 + 1)
+#define KEY_D_1_98 (KEY_D_1_96 + 1)
+#define KEY_D_1_100 (KEY_D_1_98 + 1)
+#define KEY_D_1_106 (KEY_D_1_100 + 1)
+#define KEY_D_1_108 (KEY_D_1_106 + 1)
+#define KEY_D_1_112 (KEY_D_1_108 + 1)
+#define KEY_D_1_128 (KEY_D_1_112 + 1)
+#define KEY_D_1_152 (KEY_D_1_128 + 1)
+#define KEY_D_1_160 (KEY_D_1_152 + 1)
+#define KEY_D_1_168 (KEY_D_1_160 + 1)
+#define KEY_D_1_175 (KEY_D_1_168 + 1)
+#define KEY_D_1_176 (KEY_D_1_175 + 1)
+#define KEY_D_1_178 (KEY_D_1_176 + 1)
+#define KEY_D_1_179 (KEY_D_1_178 + 1)
+#define KEY_D_1_218 (KEY_D_1_179 + 1)
+#define KEY_D_1_232 (KEY_D_1_218 + 1)
+#define KEY_D_1_236 (KEY_D_1_232 + 1)
+#define KEY_D_1_240 (KEY_D_1_236 + 1)
+#define KEY_D_1_244 (KEY_D_1_240 + 1)
+#define KEY_D_1_250 (KEY_D_1_244 + 1)
+#define KEY_D_1_252 (KEY_D_1_250 + 1)
+#define KEY_D_2_12 (KEY_D_1_252 + 1)
+#define KEY_D_2_96 (KEY_D_2_12 + 1)
+#define KEY_D_2_108 (KEY_D_2_96 + 1)
+#define KEY_D_2_208 (KEY_D_2_108 + 1)
+#define KEY_FLICK_MSG (KEY_D_2_208 + 1)
+#define KEY_FLICK_COUNTER (KEY_FLICK_MSG + 1)
+#define KEY_FLICK_LOWER (KEY_FLICK_COUNTER + 1)
+#define KEY_CFG_FLICK_IN (KEY_FLICK_LOWER + 1)
+#define KEY_FLICK_UPPER (KEY_CFG_FLICK_IN + 1)
+#define KEY_CGNOTICE_INTR (KEY_FLICK_UPPER + 1)
+#define KEY_D_2_224 (KEY_CGNOTICE_INTR + 1)
+#define KEY_D_2_244 (KEY_D_2_224 + 1)
+#define KEY_D_2_248 (KEY_D_2_244 + 1)
+#define KEY_D_2_252 (KEY_D_2_248 + 1)
+
+#define KEY_D_GYRO_BIAS_X (KEY_D_2_252 + 1)
+#define KEY_D_GYRO_BIAS_Y (KEY_D_GYRO_BIAS_X + 1)
+#define KEY_D_GYRO_BIAS_Z (KEY_D_GYRO_BIAS_Y + 1)
+#define KEY_D_GYRO_ENABLE (KEY_D_GYRO_BIAS_Z + 1)
+#define KEY_D_ACCEL_ENABLE (KEY_D_GYRO_ENABLE + 1)
+#define KEY_D_QUAT_ENABLE (KEY_D_ACCEL_ENABLE + 1)
+#define KEY_D_CR_TIME_G (KEY_D_QUAT_ENABLE + 1)
+#define KEY_D_CR_TIME_A (KEY_D_CR_TIME_G + 1)
+#define KEY_D_CR_TIME_Q (KEY_D_CR_TIME_A + 1)
+#define KEY_D_CS_TAX (KEY_D_CR_TIME_Q + 1)
+#define KEY_D_CS_TAY (KEY_D_CS_TAX + 1)
+#define KEY_D_CS_TAZ (KEY_D_CS_TAY + 1)
+
+#define KEY_D_CS_TGX (KEY_D_CS_TAZ + 1)
+#define KEY_D_CS_TGY (KEY_D_CS_TGX + 1)
+#define KEY_D_CS_TGZ (KEY_D_CS_TGY + 1)
+#define KEY_D_CS_TQ0 (KEY_D_CS_TGZ + 1)
+#define KEY_D_CS_TQ1 (KEY_D_CS_TQ0 + 1)
+#define KEY_D_CS_TQ2 (KEY_D_CS_TQ1 + 1)
+#define KEY_D_CS_TQ3 (KEY_D_CS_TQ2 + 1)
+
+/* Compass keys */
+#define KEY_CPASS_BIAS_X (KEY_D_CS_TQ3 + 1)
+#define KEY_CPASS_BIAS_Y (KEY_CPASS_BIAS_X + 1)
+#define KEY_CPASS_BIAS_Z (KEY_CPASS_BIAS_Y + 1)
+#define KEY_CPASS_MTX_00 (KEY_CPASS_BIAS_Z + 1)
+#define KEY_CPASS_MTX_01 (KEY_CPASS_MTX_00 + 1)
+#define KEY_CPASS_MTX_02 (KEY_CPASS_MTX_01 + 1)
+#define KEY_CPASS_MTX_10 (KEY_CPASS_MTX_02 + 1)
+#define KEY_CPASS_MTX_11 (KEY_CPASS_MTX_10 + 1)
+#define KEY_CPASS_MTX_12 (KEY_CPASS_MTX_11 + 1)
+#define KEY_CPASS_MTX_20 (KEY_CPASS_MTX_12 + 1)
+#define KEY_CPASS_MTX_21 (KEY_CPASS_MTX_20 + 1)
+#define KEY_CPASS_MTX_22 (KEY_CPASS_MTX_21 + 1)
+
+/* Gesture Keys */
+#define KEY_DMP_TAPW_MIN (KEY_CPASS_MTX_22 + 1)
+#define KEY_DMP_TAP_THR_X (KEY_DMP_TAPW_MIN + 1)
+#define KEY_DMP_TAP_THR_Y (KEY_DMP_TAP_THR_X + 1)
+#define KEY_DMP_TAP_THR_Z (KEY_DMP_TAP_THR_Y + 1)
+#define KEY_DMP_SH_TH_Y (KEY_DMP_TAP_THR_Z + 1)
+#define KEY_DMP_SH_TH_X (KEY_DMP_SH_TH_Y + 1)
+#define KEY_DMP_SH_TH_Z (KEY_DMP_SH_TH_X + 1)
+#define KEY_DMP_ORIENT (KEY_DMP_SH_TH_Z + 1)
+#define KEY_D_ACT0 (KEY_DMP_ORIENT + 1)
+#define KEY_D_ACSX (KEY_D_ACT0 + 1)
+#define KEY_D_ACSY (KEY_D_ACSX + 1)
+#define KEY_D_ACSZ (KEY_D_ACSY + 1)
+
+#define KEY_X_GRT_Y_TMP (KEY_D_ACSZ + 1)
+#define KEY_SKIP_X_GRT_Y_TMP (KEY_X_GRT_Y_TMP + 1)
+#define KEY_SKIP_END_COMPARE (KEY_SKIP_X_GRT_Y_TMP + 1)
+#define KEY_END_COMPARE_Y_X_TMP2 (KEY_SKIP_END_COMPARE + 1)
+#define KEY_CFG_DISPLAY_ORIENT_INT (KEY_END_COMPARE_Y_X_TMP2 + 1)
+#define KEY_NO_ORIENT_INTERRUPT (KEY_CFG_DISPLAY_ORIENT_INT + 1)
+#define KEY_END_COMPARE_Y_X_TMP (KEY_NO_ORIENT_INTERRUPT + 1)
+#define KEY_END_ORIENT_1 (KEY_END_COMPARE_Y_X_TMP + 1)
+#define KEY_END_COMPARE_Y_X (KEY_END_ORIENT_1 + 1)
+#define KEY_END_ORIENT (KEY_END_COMPARE_Y_X + 1)
+#define KEY_X_GRT_Y (KEY_END_ORIENT + 1)
+#define KEY_NOT_TIME_MINUS_1 (KEY_X_GRT_Y + 1)
+#define KEY_END_COMPARE_Y_X_TMP3 (KEY_NOT_TIME_MINUS_1 + 1)
+#define KEY_X_GRT_Y_TMP2 (KEY_END_COMPARE_Y_X_TMP3 + 1)
+
+/* Authenticate Keys */
+#define KEY_D_AUTH_OUT (KEY_X_GRT_Y_TMP2 + 1)
+#define KEY_D_AUTH_IN (KEY_D_AUTH_OUT + 1)
+#define KEY_D_AUTH_A (KEY_D_AUTH_IN + 1)
+#define KEY_D_AUTH_B (KEY_D_AUTH_A + 1)
+
+/* Pedometer standalone only keys */
+#define KEY_D_PEDSTD_BP_B (KEY_D_AUTH_B + 1)
+#define KEY_D_PEDSTD_HP_A (KEY_D_PEDSTD_BP_B + 1)
+#define KEY_D_PEDSTD_HP_B (KEY_D_PEDSTD_HP_A + 1)
+#define KEY_D_PEDSTD_BP_A4 (KEY_D_PEDSTD_HP_B + 1)
+#define KEY_D_PEDSTD_BP_A3 (KEY_D_PEDSTD_BP_A4 + 1)
+#define KEY_D_PEDSTD_BP_A2 (KEY_D_PEDSTD_BP_A3 + 1)
+#define KEY_D_PEDSTD_BP_A1 (KEY_D_PEDSTD_BP_A2 + 1)
+#define KEY_D_PEDSTD_INT_THRSH (KEY_D_PEDSTD_BP_A1 + 1)
+#define KEY_D_PEDSTD_CLIP (KEY_D_PEDSTD_INT_THRSH + 1)
+#define KEY_D_PEDSTD_SB (KEY_D_PEDSTD_CLIP + 1)
+#define KEY_D_PEDSTD_SB_TIME (KEY_D_PEDSTD_SB + 1)
+#define KEY_D_PEDSTD_PEAKTHRSH (KEY_D_PEDSTD_SB_TIME + 1)
+#define KEY_D_PEDSTD_TIML (KEY_D_PEDSTD_PEAKTHRSH + 1)
+#define KEY_D_PEDSTD_TIMH (KEY_D_PEDSTD_TIML + 1)
+#define KEY_D_PEDSTD_PEAK (KEY_D_PEDSTD_TIMH + 1)
+#define KEY_D_PEDSTD_TIMECTR (KEY_D_PEDSTD_PEAK + 1)
+#define KEY_D_PEDSTD_STEPCTR (KEY_D_PEDSTD_TIMECTR + 1)
+#define KEY_D_PEDSTD_WALKTIME (KEY_D_PEDSTD_STEPCTR + 1)
+#define KEY_D_PEDSTD_DECI (KEY_D_PEDSTD_WALKTIME + 1)
+
+/*Host Based No Motion*/
+#define KEY_D_HOST_NO_MOT (KEY_D_PEDSTD_DECI + 1)
+
+/* EIS keys */
+#define KEY_P_EIS_FIFO_FOOTER (KEY_D_HOST_NO_MOT + 1)
+#define KEY_P_EIS_FIFO_YSHIFT (KEY_P_EIS_FIFO_FOOTER + 1)
+#define KEY_P_EIS_DATA_RATE (KEY_P_EIS_FIFO_YSHIFT + 1)
+#define KEY_P_EIS_FIFO_XSHIFT (KEY_P_EIS_DATA_RATE + 1)
+#define KEY_P_EIS_FIFO_SYNC (KEY_P_EIS_FIFO_XSHIFT + 1)
+#define KEY_P_EIS_FIFO_ZSHIFT (KEY_P_EIS_FIFO_SYNC + 1)
+#define KEY_P_EIS_FIFO_READY (KEY_P_EIS_FIFO_ZSHIFT + 1)
+#define KEY_DMP_FOOTER (KEY_P_EIS_FIFO_READY + 1)
+#define KEY_DMP_INTX_HC (KEY_DMP_FOOTER + 1)
+#define KEY_DMP_INTX_PH (KEY_DMP_INTX_HC + 1)
+#define KEY_DMP_INTX_SH (KEY_DMP_INTX_PH + 1)
+#define KEY_DMP_AINV_SH (KEY_DMP_INTX_SH + 1)
+#define KEY_DMP_A_INV_XH (KEY_DMP_AINV_SH + 1)
+#define KEY_DMP_AINV_PH (KEY_DMP_A_INV_XH + 1)
+#define KEY_DMP_CTHX_H (KEY_DMP_AINV_PH + 1)
+#define KEY_DMP_CTHY_H (KEY_DMP_CTHX_H + 1)
+#define KEY_DMP_CTHZ_H (KEY_DMP_CTHY_H + 1)
+#define KEY_DMP_NCTHX_H (KEY_DMP_CTHZ_H + 1)
+#define KEY_DMP_NCTHY_H (KEY_DMP_NCTHX_H + 1)
+#define KEY_DMP_NCTHZ_H (KEY_DMP_NCTHY_H + 1)
+#define KEY_DMP_CTSQ_XH (KEY_DMP_NCTHZ_H + 1)
+#define KEY_DMP_CTSQ_YH (KEY_DMP_CTSQ_XH + 1)
+#define KEY_DMP_CTSQ_ZH (KEY_DMP_CTSQ_YH + 1)
+#define KEY_DMP_INTX_H (KEY_DMP_CTSQ_ZH + 1)
+#define KEY_DMP_INTY_H (KEY_DMP_INTX_H + 1)
+#define KEY_DMP_INTZ_H (KEY_DMP_INTY_H + 1)
+#define KEY_DMP_HPX_H (KEY_DMP_INTZ_H + 1)
+#define KEY_DMP_HPY_H (KEY_DMP_HPX_H + 1)
+#define KEY_DMP_HPZ_H (KEY_DMP_HPY_H + 1)
+
+/* Stream keys */
+#define KEY_STREAM_P_GYRO_Z (KEY_DMP_HPZ_H + 1)
+#define KEY_STREAM_P_GYRO_Y (KEY_STREAM_P_GYRO_Z + 1)
+#define KEY_STREAM_P_GYRO_X (KEY_STREAM_P_GYRO_Y + 1)
+#define KEY_STREAM_P_TEMP (KEY_STREAM_P_GYRO_X + 1)
+#define KEY_STREAM_P_AUX_Y (KEY_STREAM_P_TEMP + 1)
+#define KEY_STREAM_P_AUX_X (KEY_STREAM_P_AUX_Y + 1)
+#define KEY_STREAM_P_AUX_Z (KEY_STREAM_P_AUX_X + 1)
+#define KEY_STREAM_P_ACCEL_Y (KEY_STREAM_P_AUX_Z + 1)
+#define KEY_STREAM_P_ACCEL_X (KEY_STREAM_P_ACCEL_Y + 1)
+#define KEY_STREAM_P_FOOTER (KEY_STREAM_P_ACCEL_X + 1)
+#define KEY_STREAM_P_ACCEL_Z (KEY_STREAM_P_FOOTER + 1)
+
+#define NUM_KEYS (KEY_STREAM_P_ACCEL_Z + 1)
+
+struct tKeyLabel {
+ unsigned short key;
+ unsigned short addr;
+};
+
+#define DINA0A 0x0a
+#define DINA22 0x22
+#define DINA42 0x42
+#define DINA5A 0x5a
+
+#define DINA06 0x06
+#define DINA0E 0x0e
+#define DINA16 0x16
+#define DINA1E 0x1e
+#define DINA26 0x26
+#define DINA2E 0x2e
+#define DINA36 0x36
+#define DINA3E 0x3e
+#define DINA46 0x46
+#define DINA4E 0x4e
+#define DINA56 0x56
+#define DINA5E 0x5e
+#define DINA66 0x66
+#define DINA6E 0x6e
+#define DINA76 0x76
+#define DINA7E 0x7e
+
+#define DINA00 0x00
+#define DINA08 0x08
+#define DINA10 0x10
+#define DINA18 0x18
+#define DINA20 0x20
+#define DINA28 0x28
+#define DINA30 0x30
+#define DINA38 0x38
+#define DINA40 0x40
+#define DINA48 0x48
+#define DINA50 0x50
+#define DINA58 0x58
+#define DINA60 0x60
+#define DINA68 0x68
+#define DINA70 0x70
+#define DINA78 0x78
+
+#define DINA04 0x04
+#define DINA0C 0x0c
+#define DINA14 0x14
+#define DINA1C 0x1C
+#define DINA24 0x24
+#define DINA2C 0x2c
+#define DINA34 0x34
+#define DINA3C 0x3c
+#define DINA44 0x44
+#define DINA4C 0x4c
+#define DINA54 0x54
+#define DINA5C 0x5c
+#define DINA64 0x64
+#define DINA6C 0x6c
+#define DINA74 0x74
+#define DINA7C 0x7c
+
+#define DINA01 0x01
+#define DINA09 0x09
+#define DINA11 0x11
+#define DINA19 0x19
+#define DINA21 0x21
+#define DINA29 0x29
+#define DINA31 0x31
+#define DINA39 0x39
+#define DINA41 0x41
+#define DINA49 0x49
+#define DINA51 0x51
+#define DINA59 0x59
+#define DINA61 0x61
+#define DINA69 0x69
+#define DINA71 0x71
+#define DINA79 0x79
+
+#define DINA25 0x25
+#define DINA2D 0x2d
+#define DINA35 0x35
+#define DINA3D 0x3d
+#define DINA4D 0x4d
+#define DINA55 0x55
+#define DINA5D 0x5D
+#define DINA6D 0x6d
+#define DINA75 0x75
+#define DINA7D 0x7d
+
+#define DINADC 0xdc
+#define DINAF2 0xf2
+#define DINAAB 0xab
+#define DINAAA 0xaa
+#define DINAF1 0xf1
+#define DINADF 0xdf
+#define DINADA 0xda
+#define DINAB1 0xb1
+#define DINAB9 0xb9
+#define DINAF3 0xf3
+#define DINA8B 0x8b
+#define DINAA3 0xa3
+#define DINA91 0x91
+#define DINAB6 0xb6
+#define DINAB4 0xb4
+
+
+#define DINC00 0x00
+#define DINC01 0x01
+#define DINC02 0x02
+#define DINC03 0x03
+#define DINC08 0x08
+#define DINC09 0x09
+#define DINC0A 0x0a
+#define DINC0B 0x0b
+#define DINC10 0x10
+#define DINC11 0x11
+#define DINC12 0x12
+#define DINC13 0x13
+#define DINC18 0x18
+#define DINC19 0x19
+#define DINC1A 0x1a
+#define DINC1B 0x1b
+
+#define DINC20 0x20
+#define DINC21 0x21
+#define DINC22 0x22
+#define DINC23 0x23
+#define DINC28 0x28
+#define DINC29 0x29
+#define DINC2A 0x2a
+#define DINC2B 0x2b
+#define DINC30 0x30
+#define DINC31 0x31
+#define DINC32 0x32
+#define DINC33 0x33
+#define DINC38 0x38
+#define DINC39 0x39
+#define DINC3A 0x3a
+#define DINC3B 0x3b
+
+#define DINC40 0x40
+#define DINC41 0x41
+#define DINC42 0x42
+#define DINC43 0x43
+#define DINC48 0x48
+#define DINC49 0x49
+#define DINC4A 0x4a
+#define DINC4B 0x4b
+#define DINC50 0x50
+#define DINC51 0x51
+#define DINC52 0x52
+#define DINC53 0x53
+#define DINC58 0x58
+#define DINC59 0x59
+#define DINC5A 0x5a
+#define DINC5B 0x5b
+
+#define DINC60 0x60
+#define DINC61 0x61
+#define DINC62 0x62
+#define DINC63 0x63
+#define DINC68 0x68
+#define DINC69 0x69
+#define DINC6A 0x6a
+#define DINC6B 0x6b
+#define DINC70 0x70
+#define DINC71 0x71
+#define DINC72 0x72
+#define DINC73 0x73
+#define DINC78 0x78
+#define DINC79 0x79
+#define DINC7A 0x7a
+#define DINC7B 0x7b
+#define DIND40 0x40
+#define DINA80 0x80
+#define DINA90 0x90
+#define DINAA0 0xa0
+#define DINAC9 0xc9
+#define DINACB 0xcb
+#define DINACD 0xcd
+#define DINACF 0xcf
+#define DINAC8 0xc8
+#define DINACA 0xca
+#define DINACC 0xcc
+#define DINACE 0xce
+#define DINAD8 0xd8
+#define DINADD 0xdd
+#define DINAF8 0xf0
+#define DINAFE 0xfe
+
+#define DINBF8 0xf8
+#define DINAC0 0xb0
+#define DINAC1 0xb1
+#define DINAC2 0xb4
+#define DINAC3 0xb5
+#define DINAC4 0xb8
+#define DINAC5 0xb9
+#define DINBC0 0xc0
+#define DINBC2 0xc2
+#define DINBC4 0xc4
+#define DINBC6 0xc6
+
+#endif
diff --git a/drivers/staging/iio/imu/mpu/dmpmap.h b/drivers/staging/iio/imu/mpu/dmpmap.h
new file mode 100644
index 0000000..28f59af
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/dmpmap.h
@@ -0,0 +1,283 @@
+/*
+* Copyright (C) 2012 Invensense, 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file dmpmap.h
+ * @brief dmp map definition
+ * @details This file is part of invensense mpu driver code
+ *
+ */
+#ifndef DMPMAP_H
+#define DMPMAP_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define DMP_PTAT 0
+#define DMP_XGYR 2
+#define DMP_YGYR 4
+#define DMP_ZGYR 6
+#define DMP_XACC 8
+#define DMP_YACC 10
+#define DMP_ZACC 12
+#define DMP_ADC1 14
+#define DMP_ADC2 16
+#define DMP_ADC3 18
+#define DMP_BIASUNC 20
+#define DMP_FIFORT 22
+#define DMP_INVGSFH 24
+#define DMP_INVGSFL 26
+#define DMP_1H 28
+#define DMP_1L 30
+#define DMP_BLPFSTCH 32
+#define DMP_BLPFSTCL 34
+#define DMP_BLPFSXH 36
+#define DMP_BLPFSXL 38
+#define DMP_BLPFSYH 40
+#define DMP_BLPFSYL 42
+#define DMP_BLPFSZH 44
+#define DMP_BLPFSZL 46
+#define DMP_BLPFMTC 48
+#define DMP_SMC 50
+#define DMP_BLPFMXH 52
+#define DMP_BLPFMXL 54
+#define DMP_BLPFMYH 56
+#define DMP_BLPFMYL 58
+#define DMP_BLPFMZH 60
+#define DMP_BLPFMZL 62
+#define DMP_BLPFC 64
+#define DMP_SMCTH 66
+#define DMP_0H2 68
+#define DMP_0L2 70
+#define DMP_BERR2H 72
+#define DMP_BERR2L 74
+#define DMP_BERR2NH 76
+#define DMP_SMCINC 78
+#define DMP_ANGVBXH 80
+#define DMP_ANGVBXL 82
+#define DMP_ANGVBYH 84
+#define DMP_ANGVBYL 86
+#define DMP_ANGVBZH 88
+#define DMP_ANGVBZL 90
+#define DMP_BERR1H 92
+#define DMP_BERR1L 94
+#define DMP_ATCH 96
+#define DMP_BIASUNCSF 98
+#define DMP_ACT2H 100
+#define DMP_ACT2L 102
+#define DMP_GSFH 104
+#define DMP_GSFL 106
+#define DMP_GH 108
+#define DMP_GL 110
+#define DMP_0_5H 112
+#define DMP_0_5L 114
+#define DMP_0_0H 116
+#define DMP_0_0L 118
+#define DMP_1_0H 120
+#define DMP_1_0L 122
+#define DMP_1_5H 124
+#define DMP_1_5L 126
+#define DMP_TMP1AH 128
+#define DMP_TMP1AL 130
+#define DMP_TMP2AH 132
+#define DMP_TMP2AL 134
+#define DMP_TMP3AH 136
+#define DMP_TMP3AL 138
+#define DMP_TMP4AH 140
+#define DMP_TMP4AL 142
+#define DMP_XACCW 144
+#define DMP_TMP5 146
+#define DMP_XACCB 148
+#define DMP_TMP8 150
+#define DMP_YACCB 152
+#define DMP_TMP9 154
+#define DMP_ZACCB 156
+#define DMP_TMP10 158
+#define DMP_DZH 160
+#define DMP_DZL 162
+#define DMP_XGCH 164
+#define DMP_XGCL 166
+#define DMP_YGCH 168
+#define DMP_YGCL 170
+#define DMP_ZGCH 172
+#define DMP_ZGCL 174
+#define DMP_YACCW 176
+#define DMP_TMP7 178
+#define DMP_AFB1H 180
+#define DMP_AFB1L 182
+#define DMP_AFB2H 184
+#define DMP_AFB2L 186
+#define DMP_MAGFBH 188
+#define DMP_MAGFBL 190
+#define DMP_QT1H 192
+#define DMP_QT1L 194
+#define DMP_QT2H 196
+#define DMP_QT2L 198
+#define DMP_QT3H 200
+#define DMP_QT3L 202
+#define DMP_QT4H 204
+#define DMP_QT4L 206
+#define DMP_CTRL1H 208
+#define DMP_CTRL1L 210
+#define DMP_CTRL2H 212
+#define DMP_CTRL2L 214
+#define DMP_CTRL3H 216
+#define DMP_CTRL3L 218
+#define DMP_CTRL4H 220
+#define DMP_CTRL4L 222
+#define DMP_CTRLS1 224
+#define DMP_CTRLSF1 226
+#define DMP_CTRLS2 228
+#define DMP_CTRLSF2 230
+#define DMP_CTRLS3 232
+#define DMP_CTRLSFNLL 234
+#define DMP_CTRLS4 236
+#define DMP_CTRLSFNL2 238
+#define DMP_CTRLSFNL 240
+#define DMP_TMP30 242
+#define DMP_CTRLSFJT 244
+#define DMP_TMP31 246
+#define DMP_TMP11 248
+#define DMP_CTRLSF2_2 250
+#define DMP_TMP12 252
+#define DMP_CTRLSF1_2 254
+#define DMP_PREVPTAT 256
+#define DMP_ACCZB 258
+#define DMP_ACCXB 264
+#define DMP_ACCYB 266
+#define DMP_1HB 272
+#define DMP_1LB 274
+#define DMP_0H 276
+#define DMP_0L 278
+#define DMP_ASR22H 280
+#define DMP_ASR22L 282
+#define DMP_ASR6H 284
+#define DMP_ASR6L 286
+#define DMP_TMP13 288
+#define DMP_TMP14 290
+#define DMP_FINTXH 292
+#define DMP_FINTXL 294
+#define DMP_FINTYH 296
+#define DMP_FINTYL 298
+#define DMP_FINTZH 300
+#define DMP_FINTZL 302
+#define DMP_TMP1BH 304
+#define DMP_TMP1BL 306
+#define DMP_TMP2BH 308
+#define DMP_TMP2BL 310
+#define DMP_TMP3BH 312
+#define DMP_TMP3BL 314
+#define DMP_TMP4BH 316
+#define DMP_TMP4BL 318
+#define DMP_STXG 320
+#define DMP_ZCTXG 322
+#define DMP_STYG 324
+#define DMP_ZCTYG 326
+#define DMP_STZG 328
+#define DMP_ZCTZG 330
+#define DMP_CTRLSFJT2 332
+#define DMP_CTRLSFJTCNT 334
+#define DMP_PVXG 336
+#define DMP_TMP15 338
+#define DMP_PVYG 340
+#define DMP_TMP16 342
+#define DMP_PVZG 344
+#define DMP_TMP17 346
+#define DMP_MNMFLAGH 352
+#define DMP_MNMFLAGL 354
+#define DMP_MNMTMH 356
+#define DMP_MNMTML 358
+#define DMP_MNMTMTHRH 360
+#define DMP_MNMTMTHRL 362
+#define DMP_MNMTHRH 364
+#define DMP_MNMTHRL 366
+#define DMP_ACCQD4H 368
+#define DMP_ACCQD4L 370
+#define DMP_ACCQD5H 372
+#define DMP_ACCQD5L 374
+#define DMP_ACCQD6H 376
+#define DMP_ACCQD6L 378
+#define DMP_ACCQD7H 380
+#define DMP_ACCQD7L 382
+#define DMP_ACCQD0H 384
+#define DMP_ACCQD0L 386
+#define DMP_ACCQD1H 388
+#define DMP_ACCQD1L 390
+#define DMP_ACCQD2H 392
+#define DMP_ACCQD2L 394
+#define DMP_ACCQD3H 396
+#define DMP_ACCQD3L 398
+#define DMP_XN2H 400
+#define DMP_XN2L 402
+#define DMP_XN1H 404
+#define DMP_XN1L 406
+#define DMP_YN2H 408
+#define DMP_YN2L 410
+#define DMP_YN1H 412
+#define DMP_YN1L 414
+#define DMP_YH 416
+#define DMP_YL 418
+#define DMP_B0H 420
+#define DMP_B0L 422
+#define DMP_A1H 424
+#define DMP_A1L 426
+#define DMP_A2H 428
+#define DMP_A2L 430
+#define DMP_SEM1 432
+#define DMP_FIFOCNT 434
+#define DMP_SH_TH_X 436
+#define DMP_PACKET 438
+#define DMP_SH_TH_Y 440
+#define DMP_FOOTER 442
+#define DMP_SH_TH_Z 444
+#define DMP_TEMP29 448
+#define DMP_TEMP30 450
+#define DMP_XACCB_PRE 452
+#define DMP_XACCB_PREL 454
+#define DMP_YACCB_PRE 456
+#define DMP_YACCB_PREL 458
+#define DMP_ZACCB_PRE 460
+#define DMP_ZACCB_PREL 462
+#define DMP_TMP22 464
+#define DMP_TAP_TIMER 466
+#define DMP_TAP_THX 468
+#define DMP_TAP_THY 472
+#define DMP_TAP_THZ 476
+#define DMP_TAPW_MIN 478
+#define DMP_TMP25 480
+#define DMP_TMP26 482
+#define DMP_TMP27 484
+#define DMP_TMP28 486
+#define DMP_ORIENT 488
+#define DMP_THRSH 490
+#define DMP_ENDIANH 492
+#define DMP_ENDIANL 494
+#define DMP_BLPFNMTCH 496
+#define DMP_BLPFNMTCL 498
+#define DMP_BLPFNMXH 500
+#define DMP_BLPFNMXL 502
+#define DMP_BLPFNMYH 504
+#define DMP_BLPFNMYL 506
+#define DMP_BLPFNMZH 508
+#define DMP_BLPFNMZL 510
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/drivers/staging/iio/imu/mpu/inv_mpu3050_iio.c b/drivers/staging/iio/imu/mpu/inv_mpu3050_iio.c
new file mode 100644
index 0000000..df12d78
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/inv_mpu3050_iio.c
@@ -0,0 +1,295 @@
+/*
+* Copyright (C) 2012 Invensense, 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_mpu3050_iio.c
+ * @brief A sysfs device driver for Invensense devices
+ * @details This file is part of invensense mpu driver code
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+
+#include "inv_mpu_iio.h"
+#define MPU3050_NACK_MIN_TIME (2 * 1000)
+#define MPU3050_NACK_MAX_TIME (3 * 1000)
+
+#define MPU3050_ONE_MPU_TIME 20
+#define MPU3050_BOGUS_ADDR 0x7F
+int __attribute__((weak)) inv_register_mpu3050_slave(struct inv_mpu_iio_s *st)
+{
+ return 0;
+}
+
+int set_3050_bypass(struct inv_mpu_iio_s *st, bool enable)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ unsigned char b;
+
+ reg = &st->reg;
+ result = inv_i2c_read(st, reg->user_ctrl, 1, &b);
+ if (result)
+ return result;
+ if (((b & BIT_3050_AUX_IF_EN) == 0) && enable)
+ return 0;
+ if ((b & BIT_3050_AUX_IF_EN) && (enable == 0))
+ return 0;
+ b &= ~BIT_3050_AUX_IF_EN;
+ if (!enable) {
+ b |= BIT_3050_AUX_IF_EN;
+ result = inv_i2c_single_write(st, reg->user_ctrl, b);
+ return result;
+ } else {
+ /* Coming out of I2C is tricky due to several erratta. Do not
+ * modify this algorithm
+ */
+ /*
+ * 1) wait for the right time and send the command to change
+ * the aux i2c slave address to an invalid address that will
+ * get nack'ed
+ *
+ * 0x00 is broadcast. 0x7F is unlikely to be used by any aux.
+ */
+ result = inv_i2c_single_write(st, REG_3050_SLAVE_ADDR,
+ MPU3050_BOGUS_ADDR);
+ if (result)
+ return result;
+ /*
+ * 2) wait enough time for a nack to occur, then go into
+ * bypass mode:
+ */
+ usleep_range(MPU3050_NACK_MIN_TIME, MPU3050_NACK_MAX_TIME);
+ result = inv_i2c_single_write(st, reg->user_ctrl, b);
+ if (result)
+ return result;
+ /*
+ * 3) wait for up to one MPU cycle then restore the slave
+ * address
+ */
+ msleep(MPU3050_ONE_MPU_TIME);
+
+ result = inv_i2c_single_write(st, REG_3050_SLAVE_ADDR,
+ st->plat_data.secondary_i2c_addr);
+ if (result)
+ return result;
+ result = inv_i2c_single_write(st, reg->user_ctrl, b);
+ if (result)
+ return result;
+ usleep_range(MPU3050_NACK_MIN_TIME, MPU3050_NACK_MAX_TIME);
+ }
+ return 0;
+}
+
+void inv_setup_reg_mpu3050(struct inv_reg_map_s *reg)
+{
+ reg->fifo_en = REG_3050_FIFO_EN;
+ reg->sample_rate_div = REG_3050_SAMPLE_RATE_DIV;
+ reg->lpf = REG_3050_LPF;
+ reg->fifo_count_h = REG_3050_FIFO_COUNT_H;
+ reg->fifo_r_w = REG_3050_FIFO_R_W;
+ reg->user_ctrl = REG_3050_USER_CTRL;
+ reg->pwr_mgmt_1 = REG_3050_PWR_MGMT_1;
+ reg->raw_gyro = REG_3050_RAW_GYRO;
+ reg->raw_accl = REG_3050_AUX_XOUT_H;
+ reg->temperature = REG_3050_TEMPERATURE;
+ reg->int_enable = REG_3050_INT_ENABLE;
+ reg->int_status = REG_3050_INT_STATUS;
+}
+
+int inv_switch_3050_gyro_engine(struct inv_mpu_iio_s *st, bool en)
+{
+ struct inv_reg_map_s *reg;
+ unsigned char data, p;
+ int result;
+ reg = &st->reg;
+ if (en) {
+ data = INV_CLK_PLL;
+ p = (BITS_3050_POWER1 | data);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p);
+ if (result)
+ return result;
+ p = (BITS_3050_POWER2 | data);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p);
+ if (result)
+ return result;
+ p = data;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p);
+ msleep(SENSOR_UP_TIME);
+ } else {
+ p = BITS_3050_GYRO_STANDBY;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p);
+ }
+
+ return result;
+}
+
+int inv_switch_3050_accl_engine(struct inv_mpu_iio_s *st, bool en)
+{
+ int result;
+ if (NULL == st->mpu_slave)
+ return -EPERM;
+ if (en)
+ result = st->mpu_slave->resume(st);
+ else
+ result = st->mpu_slave->suspend(st);
+
+ return result;
+}
+
+/**
+ * inv_init_config_mpu3050() - Initialize hardware, disable FIFO.
+ * @st: Device driver instance.
+ * Initial configuration:
+ * FSR: +/- 2000DPS
+ * DLPF: 42Hz
+ * FIFO rate: 50Hz
+ * Clock source: Gyro PLL
+ */
+int inv_init_config_mpu3050(struct iio_dev *indio_dev)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ unsigned char data;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+
+ if (st->chip_config.is_asleep)
+ return -EPERM;
+ /*reading AUX VDDIO register */
+ result = inv_i2c_read(st, REG_3050_AUX_VDDIO, 1, &data);
+ if (result)
+ return result;
+ data &= ~BIT_3050_VDDIO;
+ if (st->plat_data.level_shifter)
+ data |= BIT_3050_VDDIO;
+ result = inv_i2c_single_write(st, REG_3050_AUX_VDDIO, data);
+ if (result)
+ return result;
+
+ reg = &st->reg;
+ result = set_inv_enable(indio_dev, false);
+ if (result)
+ return result;
+ /*2000dps full scale range*/
+ result = inv_i2c_single_write(st, reg->lpf,
+ (INV_FSR_2000DPS << GYRO_CONFIG_FSR_SHIFT)
+ | INV_FILTER_42HZ);
+ if (result)
+ return result;
+ st->chip_config.fsr = INV_FSR_2000DPS;
+ st->chip_config.lpf = INV_FILTER_42HZ;
+ result = inv_i2c_single_write(st, reg->sample_rate_div,
+ ONE_K_HZ/INIT_FIFO_RATE - 1);
+ if (result)
+ return result;
+ st->chip_config.fifo_rate = INIT_FIFO_RATE;
+ st->irq_dur_ns = INIT_DUR_TIME;
+ st->chip_config.prog_start_addr = DMP_START_ADDR;
+ st->chip_config.gyro_enable = 1;
+ st->chip_config.gyro_fifo_enable = 1;
+ if ((SECONDARY_SLAVE_TYPE_ACCEL == st->plat_data.sec_slave_type) &&
+ st->mpu_slave) {
+ result = st->mpu_slave->setup(st);
+ if (result)
+ return result;
+ result = st->mpu_slave->set_fs(st, INV_FS_02G);
+ if (result)
+ return result;
+ result = st->mpu_slave->set_lpf(st, INIT_FIFO_RATE);
+ if (result)
+ return result;
+ st->chip_config.accl_enable = 1;
+ st->chip_config.accl_fifo_enable = 1;
+ }
+
+ return 0;
+}
+
+/**
+ * set_power_mpu3050() - set power of mpu3050.
+ * @st: Device driver instance.
+ * @power_on: on/off
+ */
+int set_power_mpu3050(struct inv_mpu_iio_s *st, bool power_on)
+{
+ struct inv_reg_map_s *reg;
+ unsigned char data, p;
+ int result;
+ reg = &st->reg;
+ if (power_on) {
+ data = 0;
+ } else {
+ if (st->mpu_slave) {
+ result = st->mpu_slave->suspend(st);
+ if (result)
+ return result;
+ }
+ data = BIT_SLEEP;
+ }
+ if (st->chip_config.gyro_enable) {
+ p = (BITS_3050_POWER1 | INV_CLK_PLL);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p);
+ if (result)
+ return result;
+
+ p = (BITS_3050_POWER2 | INV_CLK_PLL);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p);
+ if (result)
+ return result;
+
+ p = INV_CLK_PLL;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p);
+ if (result)
+ return result;
+
+ st->chip_config.clk_src = INV_CLK_PLL;
+ } else {
+ data |= (BITS_3050_GYRO_STANDBY | INV_CLK_INTERNAL);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data);
+ if (result)
+ return result;
+ st->chip_config.clk_src = INV_CLK_INTERNAL;
+ }
+ if (power_on) {
+ msleep(POWER_UP_TIME);
+ if (st->mpu_slave) {
+ result = st->mpu_slave->resume(st);
+ if (result)
+ return result;
+ }
+ }
+ st->chip_config.is_asleep = !power_on;
+
+ return 0;
+}
+/**
+ * @}
+ */
+
diff --git a/drivers/staging/iio/imu/mpu/inv_mpu_core.c b/drivers/staging/iio/imu/mpu/inv_mpu_core.c
new file mode 100644
index 0000000..e1e3138
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/inv_mpu_core.c
@@ -0,0 +1,2038 @@
+/*
+* Copyright (C) 2012 Invensense, 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_mpu_core.c
+ * @brief A sysfs device driver for Invensense devices
+ * @details This driver currently works for the MPU3050/MPU6050/MPU9150
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+#include "inv_mpu_iio.h"
+#include "../../sysfs.h"
+
+s64 get_time_ns(void)
+{
+ struct timespec ts;
+ ktime_get_ts(&ts);
+ return timespec_to_ns(&ts);
+}
+
+static const short AKM8975_ST_Lower[3] = {-100, -100, -1000};
+static const short AKM8975_ST_Upper[3] = {100, 100, -300};
+
+static const short AKM8972_ST_Lower[3] = {-50, -50, -500};
+static const short AKM8972_ST_Upper[3] = {50, 50, -100};
+
+static const short AKM8963_ST_Lower[3] = {-200, -200, -3200};
+static const short AKM8963_ST_Upper[3] = {200, 200, -800};
+
+static const struct inv_hw_s hw_info[INV_NUM_PARTS] = {
+ {119, "ITG3500"},
+ { 63, "MPU3050"},
+ {117, "MPU6050"},
+ {118, "MPU9150"},
+ {119, "MPU6500"},
+};
+
+static void inv_setup_reg(struct inv_reg_map_s *reg)
+{
+ reg->sample_rate_div = REG_SAMPLE_RATE_DIV;
+ reg->lpf = REG_CONFIG;
+ reg->bank_sel = REG_BANK_SEL;
+ reg->user_ctrl = REG_USER_CTRL;
+ reg->fifo_en = REG_FIFO_EN;
+ reg->gyro_config = REG_GYRO_CONFIG;
+ reg->accl_config = REG_ACCEL_CONFIG;
+ reg->fifo_count_h = REG_FIFO_COUNT_H;
+ reg->fifo_r_w = REG_FIFO_R_W;
+ reg->raw_gyro = REG_RAW_GYRO;
+ reg->raw_accl = REG_RAW_ACCEL;
+ reg->temperature = REG_TEMPERATURE;
+ reg->int_enable = REG_INT_ENABLE;
+ reg->int_status = REG_INT_STATUS;
+ reg->pwr_mgmt_1 = REG_PWR_MGMT_1;
+ reg->pwr_mgmt_2 = REG_PWR_MGMT_2;
+ reg->mem_start_addr = REG_MEM_START_ADDR;
+ reg->mem_r_w = REG_MEM_RW;
+ reg->prgm_strt_addrh = REG_PRGM_STRT_ADDRH;
+};
+
+/**
+ * inv_i2c_read() - Read one or more bytes from the device registers.
+ * @st: Device driver instance.
+ * @reg: First device register to be read from.
+ * @length: Number of bytes to read.
+ * @data: Data read from device.
+ * NOTE:This is not re-implementation of i2c_smbus_read because i2c
+ * address could be specified in this case. We could have two different
+ * i2c address due to secondary i2c interface.
+ */
+int inv_i2c_read_base(struct inv_mpu_iio_s *st, unsigned short i2c_addr,
+ u8 reg, unsigned short length, u8 *data)
+{
+ struct i2c_msg msgs[2];
+ int res;
+
+ if (!data)
+ return -EINVAL;
+
+ msgs[0].addr = i2c_addr;
+ msgs[0].flags = 0; /* write */
+ msgs[0].buf = ®
+ msgs[0].len = 1;
+
+ msgs[1].addr = i2c_addr;
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].buf = data;
+ msgs[1].len = length;
+
+ pr_debug("%s RD%02X%02X%02X\n", st->hw->name, i2c_addr, reg, length);
+ res = i2c_transfer(st->sl_handle, msgs, 2);
+ if (res < 2) {
+ if (res >= 0)
+ res = -EIO;
+ return res;
+ } else {
+#ifdef CONFIG_INV_TESTING
+ st->i2c_writecount += 3; /* addr + reg + addr */
+ st->i2c_readcount += length; /* addr + data */
+#endif
+ return 0;
+ }
+}
+
+/**
+ * inv_i2c_single_write() - Write a byte to a device register.
+ * @st: Device driver instance.
+ * @reg: Device register to be written to.
+ * @data: Byte to write to device.
+ * NOTE:This is not re-implementation of i2c_smbus_write because i2c
+ * address could be specified in this case. We could have two different
+ * i2c address due to secondary i2c interface.
+ */
+int inv_i2c_single_write_base(struct inv_mpu_iio_s *st,
+ unsigned short i2c_addr, u8 reg, u8 data)
+{
+ u8 tmp[2];
+ struct i2c_msg msg;
+ int res;
+
+ tmp[0] = reg;
+ tmp[1] = data;
+
+ msg.addr = i2c_addr;
+ msg.flags = 0; /* write */
+ msg.buf = tmp;
+ msg.len = 2;
+
+ pr_debug("%s WS%02X%02X%02X\n", st->hw->name, i2c_addr, reg, data);
+ res = i2c_transfer(st->sl_handle, &msg, 1);
+ if (res < 1) {
+ if (res == 0)
+ res = -EIO;
+ return res;
+ } else {
+#ifdef CONFIG_INV_TESTING
+ st->i2c_writecount += 3; /* addr + reg + data */
+#endif
+ return 0;
+ }
+}
+
+static int set_power_itg(struct inv_mpu_iio_s *st, bool power_on)
+{
+ struct inv_reg_map_s *reg;
+ u8 data;
+ int result;
+
+ reg = &st->reg;
+ if (power_on)
+ data = 0;
+ else
+ data = BIT_SLEEP;
+ if (st->chip_config.lpa_mode)
+ data |= BIT_CYCLE;
+ if (st->chip_config.gyro_enable) {
+ result = inv_i2c_single_write(st,
+ reg->pwr_mgmt_1, data | INV_CLK_PLL);
+ if (result)
+ return result;
+ st->chip_config.clk_src = INV_CLK_PLL;
+ } else {
+ result = inv_i2c_single_write(st,
+ reg->pwr_mgmt_1, data | INV_CLK_INTERNAL);
+ if (result)
+ return result;
+ st->chip_config.clk_src = INV_CLK_INTERNAL;
+ }
+
+ if (power_on) {
+ msleep(POWER_UP_TIME);
+ data = 0;
+ if (!st->chip_config.accl_enable)
+ data |= BIT_PWR_ACCL_STBY;
+ if (!st->chip_config.gyro_enable)
+ data |= BIT_PWR_GYRO_STBY;
+ if (INV_MPU6500 != st->chip_type)
+ data |= (st->chip_config.lpa_freq << LPA_FREQ_SHIFT);
+
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_2, data);
+ if (result) {
+ inv_i2c_single_write(st, reg->pwr_mgmt_1, BIT_SLEEP);
+ return result;
+ }
+ msleep(SENSOR_UP_TIME);
+ }
+ st->chip_config.is_asleep = !power_on;
+
+ return 0;
+}
+
+/**
+ * inv_init_config() - Initialize hardware, disable FIFO.
+ * @indio_dev: Device driver instance.
+ * Initial configuration:
+ * FSR: +/- 2000DPS
+ * DLPF: 42Hz
+ * FIFO rate: 50Hz
+ * Clock source: Gyro PLL
+ */
+static int inv_init_config(struct iio_dev *indio_dev)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+
+ if (st->chip_config.is_asleep)
+ return -EPERM;
+ reg = &st->reg;
+ result = set_inv_enable(indio_dev, false);
+ if (result)
+ return result;
+
+ result = inv_i2c_single_write(st, reg->gyro_config,
+ INV_FSR_2000DPS << GYRO_CONFIG_FSR_SHIFT);
+ if (result)
+ return result;
+ st->chip_config.fsr = INV_FSR_2000DPS;
+
+ result = inv_i2c_single_write(st, reg->lpf, INV_FILTER_42HZ);
+ if (result)
+ return result;
+ st->chip_config.lpf = INV_FILTER_42HZ;
+
+ result = inv_i2c_single_write(st, reg->sample_rate_div,
+ ONE_K_HZ / INIT_FIFO_RATE - 1);
+ if (result)
+ return result;
+ st->chip_config.fifo_rate = INIT_FIFO_RATE;
+ st->irq_dur_ns = INIT_DUR_TIME;
+ st->chip_config.prog_start_addr = DMP_START_ADDR;
+ st->chip_config.gyro_enable = 1;
+ st->chip_config.gyro_fifo_enable = 1;
+ st->chip_config.dmp_output_rate = INIT_DMP_OUTPUT_RATE;
+ if (INV_ITG3500 != st->chip_type) {
+ st->chip_config.accl_enable = 1;
+ st->chip_config.accl_fifo_enable = 1;
+ st->chip_config.accl_fs = INV_FS_02G;
+ result = inv_i2c_single_write(st, reg->accl_config,
+ (INV_FS_02G << ACCL_CONFIG_FSR_SHIFT));
+ if (result)
+ return result;
+ st->tap.time = INIT_TAP_TIME;
+ st->tap.thresh = INIT_TAP_THRESHOLD;
+ st->tap.min_count = INIT_TAP_MIN_COUNT;
+ }
+
+ return 0;
+}
+
+/**
+ * inv_compass_scale_show() - show compass scale.
+ */
+static int inv_compass_scale_show(struct inv_mpu_iio_s *st, int *scale)
+{
+ if (COMPASS_ID_AK8975 == st->plat_data.sec_slave_id)
+ *scale = DATA_AKM8975_SCALE;
+ else if (COMPASS_ID_AK8972 == st->plat_data.sec_slave_id)
+ *scale = DATA_AKM8972_SCALE;
+ else if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id)
+ if (st->compass_scale)
+ *scale = DATA_AKM8963_SCALE1;
+ else
+ *scale = DATA_AKM8963_SCALE0;
+ else
+ return -EINVAL;
+
+ return IIO_VAL_INT;
+}
+
+/**
+ * mpu_read_raw() - read raw method.
+ */
+static int mpu_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ int result;
+ if (st->chip_config.is_asleep)
+ return -EINVAL;
+ switch (mask) {
+ case 0:
+ switch (chan->type) {
+ case IIO_ANGL_VEL:
+ *val = st->raw_gyro[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ case IIO_ACCEL:
+ *val = st->raw_accel[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ case IIO_MAGN:
+ *val = st->raw_compass[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ case IIO_QUATERNION:
+ if (IIO_MOD_R == chan->channel2)
+ *val = st->raw_quaternion[0];
+ else
+ *val = st->raw_quaternion[chan->channel2 -
+ IIO_MOD_X + 1];
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ return -EINVAL;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ANGL_VEL:
+ {
+ const s16 gyro_scale_6050[] = {250, 500, 1000, 2000};
+ const s16 gyro_scale_6500[] = {250, 1000, 2000, 4000};
+ if (INV_MPU6500 == st->chip_type)
+ *val = gyro_scale_6500[st->chip_config.fsr];
+ else
+ *val = gyro_scale_6050[st->chip_config.fsr];
+ return IIO_VAL_INT;
+ }
+ case IIO_ACCEL:
+ {
+ const s16 accel_scale[] = {2, 4, 8, 16};
+ *val = accel_scale[st->chip_config.accl_fs];
+ return IIO_VAL_INT;
+ }
+ case IIO_MAGN:
+ return inv_compass_scale_show(st, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_CALIBBIAS:
+ /* return bias=0 for both accel and gyro for MPU6500;
+ self test not supported yet */
+ if (INV_MPU6500 == st->chip_type) {
+ *val = 0;
+ return IIO_VAL_INT;
+ }
+ if (st->chip_config.self_test_run_once == 0) {
+ result = inv_do_test(st, 0, st->gyro_bias,
+ st->accel_bias);
+ /* Reset Accel and Gyro full scale range
+ back to default value */
+ inv_recover_setting(st);
+ if (result)
+ return result;
+ st->chip_config.self_test_run_once = 1;
+ }
+
+ switch (chan->type) {
+ case IIO_ANGL_VEL:
+ *val = st->gyro_bias[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ case IIO_ACCEL:
+ *val = st->accel_bias[chan->channel2 - IIO_MOD_X] *
+ st->chip_info.multi;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * inv_write_fsr() - Configure the gyro's scale range.
+ */
+static int inv_write_fsr(struct inv_mpu_iio_s *st, int fsr)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ reg = &st->reg;
+ if ((fsr < 0) || (fsr > MAX_GYRO_FS_PARAM))
+ return -EINVAL;
+ if (fsr == st->chip_config.fsr)
+ return 0;
+
+ if (INV_MPU3050 == st->chip_type)
+ result = inv_i2c_single_write(st, reg->lpf,
+ (fsr << GYRO_CONFIG_FSR_SHIFT) | st->chip_config.lpf);
+ else
+ result = inv_i2c_single_write(st, reg->gyro_config,
+ fsr << GYRO_CONFIG_FSR_SHIFT);
+
+ if (result)
+ return result;
+ st->chip_config.fsr = fsr;
+
+ return 0;
+}
+
+/**
+ * inv_write_accel_fs() - Configure the accelerometer's scale range.
+ */
+static int inv_write_accel_fs(struct inv_mpu_iio_s *st, int fs)
+{
+ int result;
+ struct inv_reg_map_s *reg;
+ reg = &st->reg;
+
+ if (fs < 0 || fs > MAX_ACCL_FS_PARAM)
+ return -EINVAL;
+ if (fs == st->chip_config.accl_fs)
+ return 0;
+ if (INV_MPU3050 == st->chip_type)
+ result = st->mpu_slave->set_fs(st, fs);
+ else
+ result = inv_i2c_single_write(st, reg->accl_config,
+ (fs << ACCL_CONFIG_FSR_SHIFT));
+ if (result)
+ return result;
+
+ st->chip_config.accl_fs = fs;
+
+ return 0;
+}
+
+/**
+ * inv_write_compass_scale() - Configure the compass's scale range.
+ */
+static int inv_write_compass_scale(struct inv_mpu_iio_s *st, int data)
+{
+ char d, en;
+ int result;
+ if (COMPASS_ID_AK8963 != st->plat_data.sec_slave_id)
+ return 0;
+ en = !!data;
+ if (st->compass_scale == en)
+ return 0;
+ d = (DATA_AKM_MODE_SM | (st->compass_scale << AKM8963_SCALE_SHIFT));
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_DO, d);
+ if (result)
+ return result;
+ st->compass_scale = en;
+
+ return 0;
+}
+
+static inline int check_enable(struct inv_mpu_iio_s *st)
+{
+ return st->chip_config.is_asleep | st->chip_config.enable;
+}
+
+/**
+ * mpu_write_raw() - write raw method.
+ */
+static int mpu_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask) {
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ if (check_enable(st))
+ return -EPERM;
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ANGL_VEL:
+ return inv_write_fsr(st, val);
+ case IIO_ACCEL:
+ return inv_write_accel_fs(st, val);
+ case IIO_MAGN:
+ return inv_write_compass_scale(st, val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * inv_set_lpf() - set low pass filer based on fifo rate.
+ */
+static int inv_set_lpf(struct inv_mpu_iio_s *st, int rate)
+{
+ const short hz[] = {188, 98, 42, 20, 10, 5};
+ const int d[] = {INV_FILTER_188HZ, INV_FILTER_98HZ,
+ INV_FILTER_42HZ, INV_FILTER_20HZ,
+ INV_FILTER_10HZ, INV_FILTER_5HZ};
+ int i, h, data, result;
+ struct inv_reg_map_s *reg;
+ reg = &st->reg;
+ h = (rate >> 1);
+ i = 0;
+ while ((h < hz[i]) && (i < ARRAY_SIZE(d) - 1))
+ i++;
+ data = d[i];
+ if (INV_MPU3050 == st->chip_type) {
+ if (st->mpu_slave != NULL) {
+ result = st->mpu_slave->set_lpf(st, rate);
+ if (result)
+ return result;
+ }
+ result = inv_i2c_single_write(st, reg->lpf, data |
+ (st->chip_config.fsr << GYRO_CONFIG_FSR_SHIFT));
+ } else {
+ result = inv_i2c_single_write(st, reg->lpf, data);
+ }
+ if (result)
+ return result;
+ st->chip_config.lpf = data;
+
+ return 0;
+}
+
+/**
+ * inv_fifo_rate_store() - Set fifo rate.
+ */
+static ssize_t inv_fifo_rate_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long fifo_rate;
+ u8 data;
+ int result;
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ struct inv_reg_map_s *reg;
+ reg = &st->reg;
+
+ if (check_enable(st))
+ return -EPERM;
+ if (kstrtoul(buf, 10, &fifo_rate))
+ return -EINVAL;
+ if ((fifo_rate < MIN_FIFO_RATE) || (fifo_rate > MAX_FIFO_RATE))
+ return -EINVAL;
+ if (fifo_rate == st->chip_config.fifo_rate)
+ return count;
+ if (st->chip_config.has_compass) {
+ st->compass_divider = COMPASS_RATE_SCALE * fifo_rate /
+ ONE_K_HZ;
+ if (st->compass_divider > 0)
+ st->compass_divider -= 1;
+ st->compass_counter = 0;
+ }
+ data = ONE_K_HZ / fifo_rate - 1;
+ result = inv_i2c_single_write(st, reg->sample_rate_div, data);
+ if (result)
+ return result;
+ st->chip_config.fifo_rate = fifo_rate;
+ result = inv_set_lpf(st, fifo_rate);
+ if (result)
+ return result;
+ st->irq_dur_ns = (data + 1) * NSEC_PER_MSEC;
+
+ return count;
+}
+
+/**
+ * inv_fifo_rate_show() - Get the current sampling rate.
+ */
+static ssize_t inv_fifo_rate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+
+ return sprintf(buf, "%d\n", st->chip_config.fifo_rate);
+}
+
+/**
+ * inv_power_state_store() - Turn device on/off.
+ */
+static ssize_t inv_power_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int result;
+ unsigned long power_state;
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ if (kstrtoul(buf, 10, &power_state))
+ return -EINVAL;
+ if ((!power_state) == st->chip_config.is_asleep)
+ return count;
+ result = st->set_power_state(st, power_state);
+
+ return count;
+}
+
+/**
+ * inv_reg_dump_show() - Register dump for testing.
+ */
+static ssize_t inv_reg_dump_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ii;
+ char data;
+ ssize_t bytes_printed = 0;
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+
+ for (ii = 0; ii < st->hw->num_reg; ii++) {
+ /* don't read fifo r/w register */
+ if (ii == st->reg.fifo_r_w)
+ data = 0;
+ else
+ inv_i2c_read(st, ii, 1, &data);
+ bytes_printed += sprintf(buf + bytes_printed, "%#2x: %#2x\n",
+ ii, data);
+ }
+
+ return bytes_printed;
+}
+
+static inline int write_be32_key_to_mem(struct inv_mpu_iio_s *st,
+ u32 data, int key)
+{
+ cpu_to_be32s(&data);
+ return mem_w_key(key, 4, (u8 *)&data);
+}
+
+/**
+ * inv_dmp_attr_store() - calling this function will store current
+ * dmp parameter settings
+ */
+static ssize_t inv_dmp_attr_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ int result, data;
+ char d[4];
+ if (st->chip_config.is_asleep | (!st->chip_config.firmware_loaded))
+ return -EPERM;
+ result = kstrtoint(buf, 10, &data);
+ if (result)
+ return result;
+ switch (this_attr->address) {
+ case ATTR_DMP_FLICK_LOWER:
+ result = write_be32_key_to_mem(st, data, KEY_FLICK_LOWER);
+ if (result)
+ return result;
+ st->flick.lower = data;
+ break;
+ case ATTR_DMP_FLICK_UPPER:
+ result = write_be32_key_to_mem(st, data, KEY_FLICK_UPPER);
+ if (result)
+ return result;
+ st->flick.upper = data;
+ break;
+ case ATTR_DMP_FLICK_COUNTER:
+ result = write_be32_key_to_mem(st, data, KEY_FLICK_COUNTER);
+ if (result)
+ return result;
+ st->flick.counter = data;
+ break;
+ case ATTR_DMP_FLICK_INT_ON:
+ if (data)
+ /* Use interrupt*/
+ d[0] = DIND40+4;
+ else
+ d[0] = DINAA0+8;
+ result = mem_w_key(KEY_CGNOTICE_INTR, 1, d);
+ if (result)
+ return result;
+ st->chip_config.flick_int_on = !!data;
+ break;
+ case ATTR_DMP_FLICK_AXIS:
+ if (data == 0)
+ d[0] = DINBC2;
+ else if (data == 2)
+ d[2] = DINBC6;
+ else
+ d[0] = DINBC4;
+ result = mem_w_key(KEY_CFG_FLICK_IN, 1, d);
+ if (result)
+ return result;
+ st->flick.axis = data;
+ break;
+ case ATTR_DMP_FLICK_MSG_ON:
+ if (data)
+ data = DATA_MSG_ON;
+ result = write_be32_key_to_mem(st, data, KEY_FLICK_MSG);
+ if (result)
+ return result;
+ st->flick.msg_on = data;
+ break;
+ case ATTR_DMP_PEDOMETER_STEPS:
+ result = write_be32_key_to_mem(st, data, KEY_D_PEDSTD_STEPCTR);
+ if (result)
+ return result;
+ break;
+ case ATTR_DMP_PEDOMETER_TIME:
+ result = write_be32_key_to_mem(st, data, KEY_D_PEDSTD_TIMECTR);
+ if (result)
+ return result;
+ break;
+ case ATTR_DMP_TAP_THRESHOLD: {
+ const char ax[] = {INV_TAP_AXIS_X, INV_TAP_AXIS_Y,
+ INV_TAP_AXIS_Z};
+ int i;
+ if (data < 0 || data > USHRT_MAX)
+ return -EINVAL;
+ for (i = 0; i < ARRAY_SIZE(ax); i++) {
+ result = inv_set_tap_threshold_dmp(st, ax[i], data);
+ if (result)
+ return result;
+ }
+ st->tap.thresh = data;
+ break;
+ }
+ case ATTR_DMP_TAP_MIN_COUNT:
+ if (data < 0 || data > USHRT_MAX)
+ return -EINVAL;
+ result = inv_set_min_taps_dmp(st, data);
+ if (result)
+ return result;
+ st->tap.min_count = data;
+ break;
+ case ATTR_DMP_TAP_ON:
+ result = inv_enable_tap_dmp(st, !!data);
+ if (result)
+ return result;
+ st->chip_config.tap_on = !!data;
+ break;
+ case ATTR_DMP_TAP_TIME:
+ if (data < 0 || data > USHRT_MAX)
+ return -EINVAL;
+ result = inv_set_tap_time_dmp(st, data);
+ if (result)
+ return result;
+ st->tap.time = data;
+ break;
+ case ATTR_DMP_ON:
+ st->chip_config.dmp_on = !!data;
+ break;
+ case ATTR_DMP_INT_ON:
+ st->chip_config.dmp_int_on = !!data;
+ break;
+ case ATTR_DMP_EVENT_INT_ON:
+ result = inv_set_interrupt_on_gesture_event(st, !!data);
+ if (result)
+ return result;
+ st->chip_config.dmp_event_int_on = !!data;
+ break;
+ case ATTR_DMP_OUTPUT_RATE:
+ if (data <= 0 || data > USHRT_MAX)
+ return -EINVAL;
+ result = inv_set_fifo_rate(st, data);
+ if (result)
+ return result;
+ if (st->chip_config.has_compass) {
+ st->compass_dmp_divider = COMPASS_RATE_SCALE * data /
+ ONE_K_HZ;
+ if (st->compass_dmp_divider > 0)
+ st->compass_dmp_divider -= 1;
+ }
+ st->chip_config.dmp_output_rate = data;
+ break;
+ case ATTR_DMP_ORIENTATION_ON:
+ result = inv_enable_orientation_dmp(st, !!data);
+ if (result)
+ return result;
+ st->chip_config.orientation_on = !!data;
+ break;
+ case ATTR_DMP_DISPLAY_ORIENTATION_ON:
+ result = inv_set_display_orient_interrupt_dmp(st, !!data);
+ if (result)
+ return result;
+ st->chip_config.display_orient_on = !!data;
+ break;
+ default:
+ return -EPERM;
+ }
+
+ return count;
+}
+
+/**
+ * inv_attr_show() - calling this function will show current
+ * dmp parameters.
+ */
+static ssize_t inv_attr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ char d[4];
+ int result, data;
+ signed char *m;
+ u8 *key;
+ int i;
+
+ switch (this_attr->address) {
+ case ATTR_DMP_FLICK_LOWER:
+ return sprintf(buf, "%d\n", st->flick.lower);
+ case ATTR_DMP_FLICK_UPPER:
+ return sprintf(buf, "%d\n", st->flick.upper);
+ case ATTR_DMP_FLICK_COUNTER:
+ return sprintf(buf, "%d\n", st->flick.counter);
+ case ATTR_DMP_FLICK_INT_ON:
+ return sprintf(buf, "%d\n", st->chip_config.flick_int_on);
+ case ATTR_DMP_FLICK_AXIS:
+ return sprintf(buf, "%d\n", st->flick.axis);
+ case ATTR_DMP_FLICK_MSG_ON:
+ return sprintf(buf, "%d\n", st->flick.msg_on);
+ case ATTR_DMP_PEDOMETER_STEPS:
+ result = mpu_memory_read(st->sl_handle, st->i2c_addr,
+ inv_dmp_get_address(KEY_D_PEDSTD_STEPCTR), 4, d);
+ if (result)
+ return result;
+ data = be32_to_cpup((int *)d);
+ return sprintf(buf, "%d\n", data);
+ case ATTR_DMP_PEDOMETER_TIME:
+ result = mpu_memory_read(st->sl_handle, st->i2c_addr,
+ inv_dmp_get_address(KEY_D_PEDSTD_TIMECTR), 4, d);
+ if (result)
+ return result;
+ data = be32_to_cpup((int *)d);
+ return sprintf(buf, "%d\n", data * MS_PER_DMP_TICK);
+ case ATTR_DMP_TAP_THRESHOLD:
+ return sprintf(buf, "%d\n", st->tap.thresh);
+ case ATTR_DMP_TAP_MIN_COUNT:
+ return sprintf(buf, "%d\n", st->tap.min_count);
+ case ATTR_DMP_TAP_ON:
+ return sprintf(buf, "%d\n", st->chip_config.tap_on);
+ case ATTR_DMP_TAP_TIME:
+ return sprintf(buf, "%d\n", st->tap.time);
+ case ATTR_DMP_ON:
+ return sprintf(buf, "%d\n", st->chip_config.dmp_on);
+ case ATTR_DMP_INT_ON:
+ return sprintf(buf, "%d\n", st->chip_config.dmp_int_on);
+ case ATTR_DMP_EVENT_INT_ON:
+ return sprintf(buf, "%d\n", st->chip_config.dmp_event_int_on);
+ case ATTR_DMP_OUTPUT_RATE:
+ return sprintf(buf, "%d\n", st->chip_config.dmp_output_rate);
+ case ATTR_DMP_ORIENTATION_ON:
+ return sprintf(buf, "%d\n", st->chip_config.orientation_on);
+ case ATTR_DMP_QUATERNION_ON:
+ return sprintf(buf, "%d\n", st->chip_config.quaternion_on);
+ case ATTR_DMP_DISPLAY_ORIENTATION_ON:
+ return sprintf(buf, "%d\n",
+ st->chip_config.display_orient_on);
+ case ATTR_LPA_MODE:
+ return sprintf(buf, "%d\n", st->chip_config.lpa_mode);
+ case ATTR_LPA_FREQ:{
+ const char *f[] = {"1.25", "5", "20", "40"};
+ const char *f_6500[] = {"0.3125", "0.625", "1.25",
+ "2.5", "5", "10", "20", "40",
+ "80", "160", "320", "640"};
+ if (INV_MPU6500 == st->chip_type)
+ return sprintf(buf, "%s\n",
+ f_6500[st->chip_config.lpa_freq]);
+ else
+ return sprintf(buf, "%s\n",
+ f[st->chip_config.lpa_freq]);
+ }
+ case ATTR_CLK_SRC:
+ if (INV_CLK_INTERNAL == st->chip_config.clk_src)
+ return sprintf(buf, "INTERNAL\n");
+ else if (INV_CLK_PLL == st->chip_config.clk_src)
+ return sprintf(buf, "Gyro PLL\n");
+ else
+ return -EPERM;
+ case ATTR_SELF_TEST:
+ if (INV_MPU3050 == st->chip_type)
+ result = 1;
+ else if (INV_MPU6500 == st->chip_type)
+ result = inv_hw_self_test_6500(st);
+ else
+ result = inv_hw_self_test(st);
+ return sprintf(buf, "%d\n", result);
+ case ATTR_KEY:
+ key = st->plat_data.key;
+ result = 0;
+ for (i = 0; i < 16; i++)
+ result += sprintf(buf + result, "%02x", key[i]);
+ result += sprintf(buf + result, "\n");
+ return result;
+
+ case ATTR_GYRO_MATRIX:
+ m = st->plat_data.orientation;
+ return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
+ case ATTR_ACCL_MATRIX:
+ if (st->plat_data.sec_slave_type == SECONDARY_SLAVE_TYPE_ACCEL)
+ m = st->plat_data.secondary_orientation;
+ else
+ m = st->plat_data.orientation;
+ return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
+ case ATTR_COMPASS_MATRIX:
+ if (st->plat_data.sec_slave_type ==
+ SECONDARY_SLAVE_TYPE_COMPASS)
+ m = st->plat_data.secondary_orientation;
+ else
+ return -ENODEV;
+ return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
+ case ATTR_GYRO_ENABLE:
+ return sprintf(buf, "%d\n", st->chip_config.gyro_enable);
+ case ATTR_ACCL_ENABLE:
+ return sprintf(buf, "%d\n", st->chip_config.accl_enable);
+ case ATTR_COMPASS_ENABLE:
+ return sprintf(buf, "%d\n", st->chip_config.compass_enable);
+ case ATTR_POWER_STATE:
+ return sprintf(buf, "%d\n", !st->chip_config.is_asleep);
+ case ATTR_FIRMWARE_LOADED:
+ return sprintf(buf, "%d\n", st->chip_config.firmware_loaded);
+#ifdef CONFIG_INV_TESTING
+ case ATTR_I2C_COUNTERS:
+ return scnprintf(buf, PAGE_SIZE, "%ld.%ld %ld %ld\n",
+ jiffies / HZ, jiffies % HZ, st->i2c_readcount,
+ st->i2c_writecount);
+ case ATTR_REG_WRITE:
+ return sprintf(buf, "1\n");
+#endif
+ default:
+ return -EPERM;
+ }
+}
+
+/**
+ * inv_dmp_flick_show() - calling this function will show flick event.
+ * This event must use poll.
+ */
+static ssize_t inv_dmp_flick_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "1\n");
+}
+
+/**
+ * inv_dmp_orient_show() - calling this function will show orientation
+ * This event must use poll.
+ */
+static ssize_t inv_dmp_orient_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ return sprintf(buf, "%d\n", st->orient_data);
+}
+
+/**
+ * inv_dmp_display_orient_show() - calling this function will
+ * show orientation This event must use poll.
+ */
+static ssize_t inv_dmp_display_orient_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ return sprintf(buf, "%d\n", st->display_orient_data);
+}
+
+/**
+ * inv_dmp_tap_show() - calling this function will show tap
+ * This event must use poll.
+ */
+static ssize_t inv_dmp_tap_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ return sprintf(buf, "%d\n", st->tap_data);
+}
+
+/**
+ * inv_temperature_show() - Read temperature data directly from registers.
+ */
+static ssize_t inv_temperature_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ struct inv_reg_map_s *reg;
+ int result;
+ short temp;
+ long scale_t;
+ u8 data[2];
+ reg = &st->reg;
+
+ if (st->chip_config.is_asleep)
+ return -EPERM;
+ result = inv_i2c_read(st, reg->temperature, 2, data);
+ if (result) {
+ pr_err("Could not read temperature register.\n");
+ return result;
+ }
+ temp = (signed short)(be16_to_cpup((short *)&data[0]));
+
+ if (INV_MPU3050 == st->chip_type)
+ scale_t = MPU3050_TEMP_OFFSET +
+ inv_q30_mult((int)temp << MPU_TEMP_SHIFT,
+ MPU3050_TEMP_SCALE);
+ else
+ scale_t = MPU6050_TEMP_OFFSET +
+ inv_q30_mult((int)temp << MPU_TEMP_SHIFT,
+ MPU6050_TEMP_SCALE);
+#ifdef CONFIG_INV_TESTING
+ st->i2c_tempreads++;
+#endif
+ return sprintf(buf, "%ld %lld\n", scale_t, get_time_ns());
+}
+
+/**
+ * inv_firmware_loaded() - calling this function will change
+ * firmware load
+ */
+static int inv_firmware_loaded(struct inv_mpu_iio_s *st, int data)
+{
+ if (data)
+ return -EINVAL;
+ st->chip_config.firmware_loaded = 0;
+ st->chip_config.dmp_on = 0;
+ st->chip_config.quaternion_on = 0;
+
+ return 0;
+}
+
+/**
+ * inv_quaternion_on() - calling this function will store
+ * current quaternion on
+ */
+static int inv_quaternion_on(struct inv_mpu_iio_s *st,
+ struct iio_buffer *ring, bool en)
+{
+ unsigned int result;
+ result = inv_send_quaternion(st, en);
+ if (result)
+ return result;
+ st->chip_config.quaternion_on = en;
+ if (!en) {
+ clear_bit(INV_MPU_SCAN_QUAT_R, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_QUAT_X, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_QUAT_Y, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_QUAT_Z, ring->scan_mask);
+ }
+
+ return 0;
+}
+
+/**
+ * inv_lpa_mode() - store current low power mode settings
+ */
+static int inv_lpa_mode(struct inv_mpu_iio_s *st, int lpa_mode)
+{
+ unsigned long result;
+ u8 d;
+ struct inv_reg_map_s *reg;
+
+ reg = &st->reg;
+ result = inv_i2c_read(st, reg->pwr_mgmt_1, 1, &d);
+ if (result)
+ return result;
+ if (lpa_mode)
+ d |= BIT_CYCLE;
+ else
+ d &= ~BIT_CYCLE;
+
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, d);
+ if (result)
+ return result;
+ if (INV_MPU6500 == st->chip_type) {
+ result = inv_i2c_single_write(st, REG_6500_ACCEL_CONFIG2,
+ BIT_ACCEL_FCHOCIE_B);
+ if (result)
+ return result;
+ }
+ st->chip_config.lpa_mode = !!lpa_mode;
+
+ return 0;
+}
+
+/**
+ * inv_lpa_freq() - store current low power frequency setting.
+ */
+static int inv_lpa_freq(struct inv_mpu_iio_s *st, int lpa_freq)
+{
+ unsigned long result;
+ u8 d;
+ struct inv_reg_map_s *reg;
+
+ if (INV_MPU6500 == st->chip_type) {
+ if (lpa_freq > MAX_6500_LPA_FREQ_PARAM)
+ return -EINVAL;
+ result = inv_i2c_single_write(st, REG_6500_LP_ACCEL_ODR,
+ lpa_freq);
+ if (result)
+ return result;
+ } else {
+ if (lpa_freq > MAX_LPA_FREQ_PARAM)
+ return -EINVAL;
+ reg = &st->reg;
+ result = inv_i2c_read(st, reg->pwr_mgmt_2, 1, &d);
+ if (result)
+ return result;
+ d &= ~BIT_LPA_FREQ;
+ d |= (u8)(lpa_freq << LPA_FREQ_SHIFT);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_2, d);
+ if (result)
+ return result;
+ }
+ st->chip_config.lpa_freq = lpa_freq;
+
+ return 0;
+}
+
+static int inv_switch_engine(struct inv_mpu_iio_s *st, bool en, u32 mask)
+{
+ struct inv_reg_map_s *reg;
+ u8 data;
+ int result;
+ reg = &st->reg;
+ result = inv_i2c_read(st, reg->pwr_mgmt_2, 1, &data);
+ if (result)
+ return result;
+ if (en)
+ data &= (~mask);
+ else
+ data |= mask;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_2, data);
+ if (result)
+ return result;
+ if (en)
+ msleep(SENSOR_UP_TIME);
+
+ return 0;
+
+}
+static int inv_switch_gyro_engine(struct inv_mpu_iio_s *st, bool en)
+{
+ return inv_switch_engine(st, en, BIT_PWR_GYRO_STBY);
+}
+
+static int inv_switch_accl_engine(struct inv_mpu_iio_s *st, bool en)
+{
+ return inv_switch_engine(st, en, BIT_PWR_ACCL_STBY);
+}
+
+/**
+ * inv_gyro_enable() - Enable/disable gyro.
+ */
+static int inv_gyro_enable(struct inv_mpu_iio_s *st,
+ struct iio_buffer *ring, bool en)
+{
+ int result;
+ if (en == st->chip_config.gyro_enable)
+ return 0;
+ result = st->switch_gyro_engine(st, en);
+ if (result)
+ return result;
+ if (en)
+ st->chip_config.clk_src = INV_CLK_PLL;
+ else
+ st->chip_config.clk_src = INV_CLK_INTERNAL;
+
+ if (!en) {
+ st->chip_config.gyro_fifo_enable = 0;
+ clear_bit(INV_MPU_SCAN_GYRO_X, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_GYRO_Y, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_GYRO_Z, ring->scan_mask);
+ }
+ st->chip_config.gyro_enable = en;
+
+ return 0;
+}
+
+/**
+ * inv_accl_enable() - Enable/disable accl.
+ */
+static ssize_t inv_accl_enable(struct inv_mpu_iio_s *st,
+ struct iio_buffer *ring, bool en)
+{
+ int result;
+ if (en == st->chip_config.accl_enable)
+ return 0;
+ result = st->switch_accl_engine(st, en);
+ if (result)
+ return result;
+ st->chip_config.accl_enable = en;
+ if (!en) {
+ st->chip_config.accl_fifo_enable = 0;
+ clear_bit(INV_MPU_SCAN_ACCL_X, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_ACCL_Y, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_ACCL_Z, ring->scan_mask);
+ }
+
+ return 0;
+}
+
+/**
+ * inv_compass_enable() - calling this function will store compass
+ * enable
+ */
+static ssize_t inv_compass_enable(struct inv_mpu_iio_s *st,
+ struct iio_buffer *ring, bool en)
+{
+ if (en == st->chip_config.compass_enable)
+ return 0;
+ st->chip_config.compass_enable = en;
+ if (!en) {
+ st->chip_config.compass_fifo_enable = 0;
+ clear_bit(INV_MPU_SCAN_MAGN_X, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_MAGN_Y, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_MAGN_Z, ring->scan_mask);
+ }
+
+ return 0;
+}
+
+/**
+ * inv_attr_store() - calling this function will store current
+ * non-dmp parameter settings
+ */
+static ssize_t inv_attr_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ int data;
+ int result;
+ if (check_enable(st))
+ return -EPERM;
+ result = kstrtoint(buf, 10, &data);
+ if (result)
+ return -EINVAL;
+
+ switch (this_attr->address) {
+ case ATTR_GYRO_ENABLE:
+ result = inv_gyro_enable(st, ring, !!data);
+ break;
+ case ATTR_ACCL_ENABLE:
+ result = inv_accl_enable(st, ring, !!data);
+ break;
+ case ATTR_COMPASS_ENABLE:
+ result = inv_compass_enable(st, ring, !!data);
+ break;
+ case ATTR_DMP_QUATERNION_ON:
+ if (!st->chip_config.firmware_loaded)
+ return -EPERM;
+ result = inv_quaternion_on(st, ring, !!data);
+ break;
+ case ATTR_LPA_FREQ:
+ result = inv_lpa_freq(st, data);
+ break;
+ case ATTR_LPA_MODE:
+ result = inv_lpa_mode(st, data);
+ break;
+ case ATTR_FIRMWARE_LOADED:
+ result = inv_firmware_loaded(st, data);
+ break;
+ default:
+ return -EINVAL;
+ };
+ if (result)
+ return result;
+
+ return count;
+}
+
+#ifdef CONFIG_INV_TESTING
+/**
+ * inv_reg_write_store() - register write command for testing.
+ * Format: WSRRDD, where RR is the register in hex,
+ * and DD is the data in hex.
+ */
+static ssize_t inv_reg_write_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ unsigned int result;
+ u8 wreg, wval;
+ int temp;
+ char local_buf[10];
+
+ if ((buf[0] != 'W' && buf[0] != 'w') ||
+ (buf[1] != 'S' && buf[1] != 's'))
+ return -EINVAL;
+ if (strlen(buf) < 6)
+ return -EINVAL;
+
+ strncpy(local_buf, buf, 7);
+ local_buf[6] = 0;
+ result = sscanf(&local_buf[4], "%x", &temp);
+ if (result == 0)
+ return -EINVAL;
+ wval = temp;
+ local_buf[4] = 0;
+ sscanf(&local_buf[2], "%x", &temp);
+ if (result == 0)
+ return -EINVAL;
+ wreg = temp;
+
+ result = inv_i2c_single_write(st, wreg, wval);
+ if (result)
+ return result;
+
+ return count;
+}
+#endif /* CONFIG_INV_TESTING */
+
+#define INV_MPU_CHAN(_type, _channel2, _index) \
+ { \
+ .type = _type, \
+ .modified = 1, \
+ .channel2 = _channel2, \
+ .info_mask = (IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \
+ IIO_CHAN_INFO_SCALE_SHARED_BIT), \
+ .scan_index = _index, \
+ .scan_type = IIO_ST('s', 16, 16, 0) \
+ }
+
+#define INV_MPU_QUATERNION_CHAN(_channel2, _index) \
+ { \
+ .type = IIO_QUATERNION, \
+ .modified = 1, \
+ .channel2 = _channel2, \
+ .scan_index = _index, \
+ .scan_type = IIO_ST('s', 32, 32, 0) \
+ }
+
+#define INV_MPU_MAGN_CHAN(_channel2, _index) \
+ { \
+ .type = IIO_MAGN, \
+ .modified = 1, \
+ .channel2 = _channel2, \
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \
+ .scan_index = _index, \
+ .scan_type = IIO_ST('s', 16, 16, 0) \
+ }
+
+static const struct iio_chan_spec inv_mpu_channels[] = {
+ IIO_CHAN_SOFT_TIMESTAMP(INV_MPU_SCAN_TIMESTAMP),
+
+ INV_MPU_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU_SCAN_GYRO_X),
+ INV_MPU_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU_SCAN_GYRO_Y),
+ INV_MPU_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU_SCAN_GYRO_Z),
+
+ INV_MPU_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU_SCAN_ACCL_X),
+ INV_MPU_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU_SCAN_ACCL_Y),
+ INV_MPU_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU_SCAN_ACCL_Z),
+
+ INV_MPU_QUATERNION_CHAN(IIO_MOD_R, INV_MPU_SCAN_QUAT_R),
+ INV_MPU_QUATERNION_CHAN(IIO_MOD_X, INV_MPU_SCAN_QUAT_X),
+ INV_MPU_QUATERNION_CHAN(IIO_MOD_Y, INV_MPU_SCAN_QUAT_Y),
+ INV_MPU_QUATERNION_CHAN(IIO_MOD_Z, INV_MPU_SCAN_QUAT_Z),
+
+ INV_MPU_MAGN_CHAN(IIO_MOD_X, INV_MPU_SCAN_MAGN_X),
+ INV_MPU_MAGN_CHAN(IIO_MOD_Y, INV_MPU_SCAN_MAGN_Y),
+ INV_MPU_MAGN_CHAN(IIO_MOD_Z, INV_MPU_SCAN_MAGN_Z),
+};
+
+/*constant IIO attribute */
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("10 20 50 100 200 500");
+static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, inv_fifo_rate_show,
+ inv_fifo_rate_store);
+static DEVICE_ATTR(temperature, S_IRUGO, inv_temperature_show, NULL);
+static IIO_DEVICE_ATTR(clock_source, S_IRUGO, inv_attr_show, NULL,
+ ATTR_CLK_SRC);
+static IIO_DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_power_state_store, ATTR_POWER_STATE);
+static IIO_DEVICE_ATTR(firmware_loaded, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_FIRMWARE_LOADED);
+static IIO_DEVICE_ATTR(lpa_mode, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_LPA_MODE);
+static IIO_DEVICE_ATTR(lpa_freq, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_LPA_FREQ);
+static DEVICE_ATTR(reg_dump, S_IRUGO, inv_reg_dump_show, NULL);
+static IIO_DEVICE_ATTR(self_test, S_IRUGO, inv_attr_show, NULL,
+ ATTR_SELF_TEST);
+static IIO_DEVICE_ATTR(key, S_IRUGO, inv_attr_show, NULL, ATTR_KEY);
+static IIO_DEVICE_ATTR(gyro_matrix, S_IRUGO, inv_attr_show, NULL,
+ ATTR_GYRO_MATRIX);
+static IIO_DEVICE_ATTR(accl_matrix, S_IRUGO, inv_attr_show, NULL,
+ ATTR_ACCL_MATRIX);
+static IIO_DEVICE_ATTR(compass_matrix, S_IRUGO, inv_attr_show, NULL,
+ ATTR_COMPASS_MATRIX);
+static IIO_DEVICE_ATTR(flick_lower, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_FLICK_LOWER);
+static IIO_DEVICE_ATTR(flick_upper, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_FLICK_UPPER);
+static IIO_DEVICE_ATTR(flick_counter, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_FLICK_COUNTER);
+static IIO_DEVICE_ATTR(flick_message_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_FLICK_MSG_ON);
+static IIO_DEVICE_ATTR(flick_int_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_FLICK_INT_ON);
+static IIO_DEVICE_ATTR(flick_axis, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_FLICK_AXIS);
+static IIO_DEVICE_ATTR(dmp_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_ON);
+static IIO_DEVICE_ATTR(dmp_int_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_INT_ON);
+static IIO_DEVICE_ATTR(dmp_event_int_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_EVENT_INT_ON);
+static IIO_DEVICE_ATTR(dmp_output_rate, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_OUTPUT_RATE);
+static IIO_DEVICE_ATTR(orientation_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_ORIENTATION_ON);
+static IIO_DEVICE_ATTR(quaternion_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_DMP_QUATERNION_ON);
+static IIO_DEVICE_ATTR(display_orientation_on, S_IRUGO | S_IWUSR,
+ inv_attr_show, inv_dmp_attr_store, ATTR_DMP_DISPLAY_ORIENTATION_ON);
+static IIO_DEVICE_ATTR(tap_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_TAP_ON);
+static IIO_DEVICE_ATTR(tap_time, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_TAP_TIME);
+static IIO_DEVICE_ATTR(tap_min_count, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_TAP_MIN_COUNT);
+static IIO_DEVICE_ATTR(tap_threshold, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_TAP_THRESHOLD);
+static IIO_DEVICE_ATTR(pedometer_time, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_PEDOMETER_TIME);
+static IIO_DEVICE_ATTR(pedometer_steps, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_PEDOMETER_STEPS);
+static DEVICE_ATTR(event_flick, S_IRUGO, inv_dmp_flick_show, NULL);
+static DEVICE_ATTR(event_orientation, S_IRUGO, inv_dmp_orient_show, NULL);
+static DEVICE_ATTR(event_tap, S_IRUGO, inv_dmp_tap_show, NULL);
+static DEVICE_ATTR(event_display_orientation, S_IRUGO,
+ inv_dmp_display_orient_show, NULL);
+static IIO_DEVICE_ATTR(gyro_enable, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_GYRO_ENABLE);
+static IIO_DEVICE_ATTR(accl_enable, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_ACCL_ENABLE);
+static IIO_DEVICE_ATTR(compass_enable, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_COMPASS_ENABLE);
+#ifdef CONFIG_INV_TESTING
+static IIO_DEVICE_ATTR(i2c_counters, S_IRUGO, inv_attr_show, NULL,
+ ATTR_I2C_COUNTERS);
+static IIO_DEVICE_ATTR(reg_write, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_reg_write_store, ATTR_REG_WRITE);
+#endif
+
+static const struct attribute *inv_gyro_attributes[] = {
+ &iio_dev_attr_gyro_enable.dev_attr.attr,
+ &dev_attr_temperature.attr,
+ &iio_dev_attr_clock_source.dev_attr.attr,
+ &iio_dev_attr_power_state.dev_attr.attr,
+ &dev_attr_reg_dump.attr,
+ &iio_dev_attr_self_test.dev_attr.attr,
+ &iio_dev_attr_key.dev_attr.attr,
+ &iio_dev_attr_gyro_matrix.dev_attr.attr,
+#ifdef CONFIG_INV_TESTING
+ &iio_dev_attr_i2c_counters.dev_attr.attr,
+ &iio_dev_attr_reg_write.dev_attr.attr,
+#endif
+ &iio_dev_attr_sampling_frequency.dev_attr.attr,
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+};
+
+static const struct attribute *inv_mpu6050_attributes[] = {
+ &iio_dev_attr_accl_enable.dev_attr.attr,
+ &iio_dev_attr_accl_matrix.dev_attr.attr,
+ &iio_dev_attr_firmware_loaded.dev_attr.attr,
+ &iio_dev_attr_lpa_mode.dev_attr.attr,
+ &iio_dev_attr_lpa_freq.dev_attr.attr,
+ &iio_dev_attr_flick_lower.dev_attr.attr,
+ &iio_dev_attr_flick_upper.dev_attr.attr,
+ &iio_dev_attr_flick_counter.dev_attr.attr,
+ &iio_dev_attr_flick_message_on.dev_attr.attr,
+ &iio_dev_attr_flick_int_on.dev_attr.attr,
+ &iio_dev_attr_flick_axis.dev_attr.attr,
+ &iio_dev_attr_dmp_on.dev_attr.attr,
+ &iio_dev_attr_dmp_int_on.dev_attr.attr,
+ &iio_dev_attr_dmp_event_int_on.dev_attr.attr,
+ &iio_dev_attr_dmp_output_rate.dev_attr.attr,
+ &iio_dev_attr_orientation_on.dev_attr.attr,
+ &iio_dev_attr_quaternion_on.dev_attr.attr,
+ &iio_dev_attr_display_orientation_on.dev_attr.attr,
+ &iio_dev_attr_tap_on.dev_attr.attr,
+ &iio_dev_attr_tap_time.dev_attr.attr,
+ &iio_dev_attr_tap_min_count.dev_attr.attr,
+ &iio_dev_attr_tap_threshold.dev_attr.attr,
+ &iio_dev_attr_pedometer_time.dev_attr.attr,
+ &iio_dev_attr_pedometer_steps.dev_attr.attr,
+ &dev_attr_event_flick.attr,
+ &dev_attr_event_orientation.attr,
+ &dev_attr_event_display_orientation.attr,
+ &dev_attr_event_tap.attr,
+};
+
+static const struct attribute *inv_compass_attributes[] = {
+ &iio_dev_attr_compass_matrix.dev_attr.attr,
+ &iio_dev_attr_compass_enable.dev_attr.attr,
+};
+
+static const struct attribute *inv_mpu3050_attributes[] = {
+ &iio_dev_attr_accl_matrix.dev_attr.attr,
+ &iio_dev_attr_accl_enable.dev_attr.attr,
+};
+
+static struct attribute *inv_attributes[ARRAY_SIZE(inv_gyro_attributes) +
+ ARRAY_SIZE(inv_mpu6050_attributes) +
+ ARRAY_SIZE(inv_compass_attributes) + 1];
+
+static const struct attribute_group inv_attribute_group = {
+ .name = "mpu",
+ .attrs = inv_attributes
+};
+
+static const struct iio_info mpu_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = &mpu_read_raw,
+ .write_raw = &mpu_write_raw,
+ .attrs = &inv_attribute_group,
+};
+
+/**
+ * inv_setup_compass() - Configure compass.
+ */
+static int inv_setup_compass(struct inv_mpu_iio_s *st)
+{
+ int result;
+ u8 data[4];
+
+ result = inv_i2c_read(st, REG_YGOFFS_TC, 1, data);
+ if (result)
+ return result;
+ data[0] &= ~BIT_I2C_MST_VDDIO;
+ if (st->plat_data.level_shifter)
+ data[0] |= BIT_I2C_MST_VDDIO;
+ /*set up VDDIO register */
+ result = inv_i2c_single_write(st, REG_YGOFFS_TC, data[0]);
+ if (result)
+ return result;
+ /* set to bypass mode */
+ result = inv_i2c_single_write(st, REG_INT_PIN_CFG,
+ st->plat_data.int_config | BIT_BYPASS_EN);
+ if (result)
+ return result;
+ /*read secondary i2c ID register */
+ result = inv_secondary_read(REG_AKM_ID, 1, data);
+ if (result)
+ return result;
+ if (data[0] != DATA_AKM_ID)
+ return -ENXIO;
+ /*set AKM to Fuse ROM access mode */
+ result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_FR);
+ if (result)
+ return result;
+ result = inv_secondary_read(REG_AKM_SENSITIVITY, THREE_AXIS,
+ st->chip_info.compass_sens);
+ if (result)
+ return result;
+ /*revert to power down mode */
+ result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD);
+ if (result)
+ return result;
+ pr_debug("%s senx=%d, seny=%d, senz=%d\n",
+ st->hw->name,
+ st->chip_info.compass_sens[0],
+ st->chip_info.compass_sens[1],
+ st->chip_info.compass_sens[2]);
+ /*restore to non-bypass mode */
+ result = inv_i2c_single_write(st, REG_INT_PIN_CFG,
+ st->plat_data.int_config);
+ if (result)
+ return result;
+
+ /*setup master mode and master clock and ES bit*/
+ result = inv_i2c_single_write(st, REG_I2C_MST_CTRL, BIT_WAIT_FOR_ES);
+ if (result)
+ return result;
+ /* slave 0 is used to read data from compass */
+ /*read mode */
+ result = inv_i2c_single_write(st, REG_I2C_SLV0_ADDR, BIT_I2C_READ|
+ st->plat_data.secondary_i2c_addr);
+ if (result)
+ return result;
+ /* AKM status register address is 2 */
+ result = inv_i2c_single_write(st, REG_I2C_SLV0_REG, REG_AKM_STATUS);
+ if (result)
+ return result;
+ /* slave 0 is enabled at the beginning, read 8 bytes from here */
+ result = inv_i2c_single_write(st, REG_I2C_SLV0_CTRL, BIT_SLV_EN |
+ NUM_BYTES_COMPASS_SLAVE);
+ if (result)
+ return result;
+ /*slave 1 is used for AKM mode change only*/
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_ADDR,
+ st->plat_data.secondary_i2c_addr);
+ if (result)
+ return result;
+ /* AKM mode register address is 0x0A */
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_REG, REG_AKM_MODE);
+ if (result)
+ return result;
+ /* slave 1 is enabled, byte length is 1 */
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_CTRL, BIT_SLV_EN | 1);
+ if (result)
+ return result;
+ /* output data for slave 1 is fixed, single measure mode*/
+ st->compass_scale = 1;
+ if (COMPASS_ID_AK8975 == st->plat_data.sec_slave_id) {
+ st->compass_st_upper = AKM8975_ST_Upper;
+ st->compass_st_lower = AKM8975_ST_Lower;
+ data[0] = DATA_AKM_MODE_SM;
+ } else if (COMPASS_ID_AK8972 == st->plat_data.sec_slave_id) {
+ st->compass_st_upper = AKM8972_ST_Upper;
+ st->compass_st_lower = AKM8972_ST_Lower;
+ data[0] = DATA_AKM_MODE_SM;
+ } else if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id) {
+ st->compass_st_upper = AKM8963_ST_Upper;
+ st->compass_st_lower = AKM8963_ST_Lower;
+ data[0] = DATA_AKM_MODE_SM |
+ (st->compass_scale << AKM8963_SCALE_SHIFT);
+ }
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_DO, data[0]);
+ if (result)
+ return result;
+ /* slave 0 and 1 timer action is enabled every sample*/
+ result = inv_i2c_single_write(st, REG_I2C_MST_DELAY_CTRL,
+ BIT_SLV0_DLY_EN | BIT_SLV1_DLY_EN);
+ return result;
+}
+
+static void inv_setup_func_ptr(struct inv_mpu_iio_s *st)
+{
+ if (st->chip_type == INV_MPU3050) {
+ st->set_power_state = set_power_mpu3050;
+ st->switch_gyro_engine = inv_switch_3050_gyro_engine;
+ st->switch_accl_engine = inv_switch_3050_accl_engine;
+ st->init_config = inv_init_config_mpu3050;
+ st->setup_reg = inv_setup_reg_mpu3050;
+ } else {
+ st->set_power_state = set_power_itg;
+ st->switch_gyro_engine = inv_switch_gyro_engine;
+ st->switch_accl_engine = inv_switch_accl_engine;
+ st->init_config = inv_init_config;
+ st->setup_reg = inv_setup_reg;
+ }
+}
+
+/**
+ * inv_check_chip_type() - check and setup chip type.
+ */
+static int inv_check_chip_type(struct inv_mpu_iio_s *st,
+ const struct i2c_device_id *id)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ int t_ind;
+ if (!strcmp(id->name, "itg3500"))
+ st->chip_type = INV_ITG3500;
+ else if (!strcmp(id->name, "mpu3050"))
+ st->chip_type = INV_MPU3050;
+ else if (!strcmp(id->name, "mpu6050"))
+ st->chip_type = INV_MPU6050;
+ else if (!strcmp(id->name, "mpu9150"))
+ st->chip_type = INV_MPU9150;
+ else if (!strcmp(id->name, "mpu6500"))
+ st->chip_type = INV_MPU6500;
+ else
+ return -EPERM;
+ inv_setup_func_ptr(st);
+ st->hw = &hw_info[st->chip_type];
+ st->mpu_slave = NULL;
+ reg = &st->reg;
+ st->setup_reg(reg);
+ st->chip_config.gyro_enable = 1;
+ /* reset to make sure previous state are not there */
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, BIT_H_RESET);
+ if (result)
+ return result;
+ msleep(POWER_UP_TIME);
+ /* turn off and turn on power to ensure gyro engine is on */
+ result = st->set_power_state(st, false);
+ if (result)
+ return result;
+ result = st->set_power_state(st, true);
+ if (result)
+ return result;
+
+ switch (st->chip_type) {
+ case INV_ITG3500:
+ st->num_channels = INV_CHANNEL_NUM_GYRO;
+ break;
+ case INV_MPU6050:
+ case INV_MPU6500:
+ if (SECONDARY_SLAVE_TYPE_COMPASS ==
+ st->plat_data.sec_slave_type) {
+ st->chip_config.has_compass = 1;
+ st->num_channels =
+ INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION_MAGN;
+ } else {
+ st->chip_config.has_compass = 0;
+ st->num_channels =
+ INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION;
+ }
+ break;
+ case INV_MPU9150:
+ st->plat_data.sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS;
+ st->plat_data.sec_slave_id = COMPASS_ID_AK8975;
+ st->chip_config.has_compass = 1;
+ st->num_channels = INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION_MAGN;
+ break;
+ case INV_MPU3050:
+ if (SECONDARY_SLAVE_TYPE_ACCEL ==
+ st->plat_data.sec_slave_type) {
+ if (ACCEL_ID_BMA250 == st->plat_data.sec_slave_id)
+ inv_register_mpu3050_slave(st);
+ st->num_channels = INV_CHANNEL_NUM_GYRO_ACCL;
+ } else {
+ st->num_channels = INV_CHANNEL_NUM_GYRO;
+ }
+ break;
+ default:
+ result = st->set_power_state(st, false);
+ return -ENODEV;
+ }
+
+ switch (st->chip_type) {
+ case INV_MPU6050:
+ case INV_MPU9150:
+ result = inv_get_silicon_rev_mpu6050(st);
+ break;
+ case INV_MPU6500:
+ result = inv_get_silicon_rev_mpu6500(st);
+ break;
+ default:
+ result = 0;
+ break;
+ }
+ if (result) {
+ pr_err("read silicon rev error\n");
+ st->set_power_state(st, false);
+ return result;
+ }
+ if (st->chip_config.has_compass) {
+ result = inv_setup_compass(st);
+ if (result) {
+ pr_err("compass setup failed\n");
+ st->set_power_state(st, false);
+ return result;
+ }
+ }
+
+ t_ind = 0;
+ memcpy(&inv_attributes[t_ind], inv_gyro_attributes,
+ sizeof(inv_gyro_attributes));
+ t_ind += ARRAY_SIZE(inv_gyro_attributes);
+
+ if (INV_MPU3050 == st->chip_type && st->mpu_slave != NULL) {
+ memcpy(&inv_attributes[t_ind], inv_mpu3050_attributes,
+ sizeof(inv_mpu3050_attributes));
+ t_ind += ARRAY_SIZE(inv_mpu3050_attributes);
+ inv_attributes[t_ind] = NULL;
+ return 0;
+ }
+
+ if ((INV_MPU6050 == st->chip_type) ||
+ (INV_MPU9150 == st->chip_type) ||
+ (INV_MPU6500 == st->chip_type)) {
+ memcpy(&inv_attributes[t_ind], inv_mpu6050_attributes,
+ sizeof(inv_mpu6050_attributes));
+ t_ind += ARRAY_SIZE(inv_mpu6050_attributes);
+ }
+
+ if (st->chip_config.has_compass) {
+ memcpy(&inv_attributes[t_ind], inv_compass_attributes,
+ sizeof(inv_compass_attributes));
+ t_ind += ARRAY_SIZE(inv_compass_attributes);
+ }
+ inv_attributes[t_ind] = NULL;
+
+ return 0;
+}
+
+/**
+ * inv_create_dmp_sysfs() - create binary sysfs dmp entry.
+ */
+static const struct bin_attribute dmp_firmware = {
+ .attr = {
+ .name = "dmp_firmware",
+ .mode = S_IRUGO | S_IWUSR
+ },
+ .size = 4096,
+ .read = inv_dmp_firmware_read,
+ .write = inv_dmp_firmware_write,
+};
+
+static int inv_create_dmp_sysfs(struct iio_dev *ind)
+{
+ int result;
+ result = sysfs_create_bin_file(&ind->dev.kobj, &dmp_firmware);
+
+ return result;
+}
+
+/**
+ * inv_mpu_probe() - probe function.
+ */
+static int inv_mpu_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct inv_mpu_iio_s *st;
+ struct iio_dev *indio_dev;
+ int result;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ result = -ENOSYS;
+ pr_err("I2c function error\n");
+ goto out_no_free;
+ }
+ indio_dev = iio_allocate_device(sizeof(*st));
+ if (indio_dev == NULL) {
+ pr_err("memory allocation failed\n");
+ result = -ENOMEM;
+ goto out_no_free;
+ }
+ st = iio_priv(indio_dev);
+ st->client = client;
+ st->sl_handle = client->adapter;
+ st->i2c_addr = client->addr;
+ st->plat_data =
+ *(struct mpu_platform_data *)dev_get_platdata(&client->dev);
+ /* power is turned on inside check chip type*/
+ result = inv_check_chip_type(st, id);
+ if (result)
+ goto out_free;
+
+ result = st->init_config(indio_dev);
+ if (result) {
+ dev_err(&client->adapter->dev,
+ "Could not initialize device.\n");
+ goto out_free;
+ }
+ result = st->set_power_state(st, false);
+ if (result) {
+ dev_err(&client->adapter->dev,
+ "%s could not be turned off.\n", st->hw->name);
+ goto out_free;
+ }
+
+ /* Make state variables available to all _show and _store functions. */
+ i2c_set_clientdata(client, indio_dev);
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->name = id->name;
+ indio_dev->channels = inv_mpu_channels;
+ indio_dev->num_channels = st->num_channels;
+
+ indio_dev->info = &mpu_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->currentmode = INDIO_DIRECT_MODE;
+
+ result = inv_mpu_configure_ring(indio_dev);
+ if (result) {
+ pr_err("configure ring buffer fail\n");
+ goto out_free;
+ }
+ result = iio_buffer_register(indio_dev, indio_dev->channels,
+ indio_dev->num_channels);
+ if (result) {
+ pr_err("ring buffer register fail\n");
+ goto out_unreg_ring;
+ }
+ st->irq = client->irq;
+ result = inv_mpu_probe_trigger(indio_dev);
+ if (result) {
+ pr_err("trigger probe fail\n");
+ goto out_remove_ring;
+ }
+
+ result = iio_device_register(indio_dev);
+ if (result) {
+ pr_err("IIO device register fail\n");
+ goto out_remove_trigger;
+ }
+
+ if (INV_MPU6050 == st->chip_type || INV_MPU9150 == st->chip_type ||
+ INV_MPU6500 == st->chip_type) {
+ result = inv_create_dmp_sysfs(indio_dev);
+ if (result) {
+ pr_err("create dmp sysfs failed\n");
+ goto out_unreg_iio;
+ }
+ }
+
+ INIT_KFIFO(st->timestamps);
+ spin_lock_init(&st->time_stamp_lock);
+ dev_info(&client->adapter->dev, "%s is ready to go!\n", st->hw->name);
+
+ return 0;
+out_unreg_iio:
+ iio_device_unregister(indio_dev);
+out_remove_trigger:
+ if (indio_dev->modes & INDIO_BUFFER_TRIGGERED)
+ inv_mpu_remove_trigger(indio_dev);
+out_remove_ring:
+ iio_buffer_unregister(indio_dev);
+out_unreg_ring:
+ inv_mpu_unconfigure_ring(indio_dev);
+out_free:
+ iio_free_device(indio_dev);
+out_no_free:
+ dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+
+ return -EIO;
+}
+
+static void inv_mpu_shutdown(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct inv_reg_map_s *reg;
+ int result;
+
+ reg = &st->reg;
+
+ dev_dbg(&client->adapter->dev, "Shutting down %s...\n", st->hw->name);
+
+ /* reset to make sure previous state are not there */
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, BIT_H_RESET);
+ if (result)
+ dev_err(&client->adapter->dev, "Failed to reset %s\n", st->hw->name);
+ msleep(POWER_UP_TIME);
+ /* turn off power to ensure gyro engine is off */
+ result = st->set_power_state(st, false);
+ if (result)
+ dev_err(&client->adapter->dev, "Failed to turn off %s\n", st->hw->name);
+}
+
+/**
+ * inv_mpu_remove() - remove function.
+ */
+static int inv_mpu_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ kfifo_free(&st->timestamps);
+ iio_device_unregister(indio_dev);
+ if (indio_dev->modes & INDIO_BUFFER_TRIGGERED)
+ inv_mpu_remove_trigger(indio_dev);
+ iio_buffer_unregister(indio_dev);
+ inv_mpu_unconfigure_ring(indio_dev);
+ iio_free_device(indio_dev);
+
+ dev_info(&client->adapter->dev, "inv-mpu-iio module removed.\n");
+
+ return 0;
+}
+#ifdef CONFIG_PM
+
+static int inv_mpu_resume(struct device *dev)
+{
+ struct inv_mpu_iio_s *st =
+ iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ if (st->chip_config.was_asleep_on_suspend)
+ return 0;
+ return st->set_power_state(st, true);
+}
+
+static int inv_mpu_suspend(struct device *dev)
+{
+ struct inv_mpu_iio_s *st =
+ iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+ st->chip_config.was_asleep_on_suspend = st->chip_config.is_asleep;
+ return st->set_power_state(st, false);
+}
+static const struct dev_pm_ops inv_mpu_pmops = {
+ SET_SYSTEM_SLEEP_PM_OPS(inv_mpu_suspend, inv_mpu_resume)
+};
+#define INV_MPU_PMOPS (&inv_mpu_pmops)
+#else
+#define INV_MPU_PMOPS NULL
+#endif /* CONFIG_PM */
+
+static const unsigned short normal_i2c[] = { I2C_CLIENT_END };
+/* device id table is used to identify what device can be
+ * supported by this driver
+ */
+static const struct i2c_device_id inv_mpu_id[] = {
+ {"itg3500", INV_ITG3500},
+ {"mpu3050", INV_MPU3050},
+ {"mpu6050", INV_MPU6050},
+ {"mpu9150", INV_MPU9150},
+ {"mpu6500", INV_MPU6500},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, inv_mpu_id);
+
+static struct i2c_driver inv_mpu_driver = {
+ .class = I2C_CLASS_HWMON,
+ .probe = inv_mpu_probe,
+ .remove = inv_mpu_remove,
+ .shutdown = inv_mpu_shutdown,
+ .id_table = inv_mpu_id,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "inv-mpu-iio",
+ .pm = INV_MPU_PMOPS,
+ },
+ .address_list = normal_i2c,
+};
+
+static int __init inv_mpu_init(void)
+{
+ int result = i2c_add_driver(&inv_mpu_driver);
+ if (result) {
+ pr_err("failed\n");
+ return result;
+ }
+ return 0;
+}
+
+static void __exit inv_mpu_exit(void)
+{
+ i2c_del_driver(&inv_mpu_driver);
+}
+
+module_init(inv_mpu_init);
+module_exit(inv_mpu_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Invensense device driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("inv-mpu-iio");
+/**
+ * @}
+ */
diff --git a/drivers/staging/iio/imu/mpu/inv_mpu_iio.h b/drivers/staging/iio/imu/mpu/inv_mpu_iio.h
new file mode 100644
index 0000000..50169f8
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/inv_mpu_iio.h
@@ -0,0 +1,809 @@
+/*
+* Copyright (C) 2012 Invensense, 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_mpu_iio.h
+ * @brief Struct definitions for the Invensense mpu driver.
+ */
+
+#ifndef _INV_MPU_IIO_H_
+#define _INV_MPU_IIO_H_
+
+#include <linux/i2c.h>
+#include <linux/kfifo.h>
+#include <linux/miscdevice.h>
+#include <linux/input.h>
+#include <linux/spinlock.h>
+#include <linux/mpu.h>
+#include "../../iio.h"
+#include "../../buffer.h"
+#include "dmpKey.h"
+/**
+ * struct inv_reg_map_s - Notable slave registers.
+ * @sample_rate_div: Divider applied to gyro output rate.
+ * @lpf: Configures internal LPF.
+ * @bank_sel: Selects between memory banks.
+ * @user_ctrl: Enables/resets the FIFO.
+ * @fifo_en: Determines which data will appear in FIFO.
+ * @gyro_config: gyro config register.
+ * @accl_config: accel config register
+ * @fifo_count_h: Upper byte of FIFO count.
+ * @fifo_r_w: FIFO register.
+ * @raw_gyro Address of first gyro register.
+ * @raw_accl Address of first accel register.
+ * @temperature temperature register
+ * @int_enable: Interrupt enable register.
+ * @int_status: Interrupt flags.
+ * @pwr_mgmt_1: Controls chip's power state and clock source.
+ * @pwr_mgmt_2: Controls power state of individual sensors.
+ * @mem_start_addr: Address of first memory read.
+ * @mem_r_w: Access to memory.
+ * @prgm_strt_addrh firmware program start address register
+ */
+struct inv_reg_map_s {
+ u8 sample_rate_div;
+ u8 lpf;
+ u8 bank_sel;
+ u8 user_ctrl;
+ u8 fifo_en;
+ u8 gyro_config;
+ u8 accl_config;
+ u8 fifo_count_h;
+ u8 fifo_r_w;
+ u8 raw_gyro;
+ u8 raw_accl;
+ u8 temperature;
+ u8 int_enable;
+ u8 int_status;
+ u8 pwr_mgmt_1;
+ u8 pwr_mgmt_2;
+ u8 mem_start_addr;
+ u8 mem_r_w;
+ u8 prgm_strt_addrh;
+};
+/*device enum */
+enum inv_devices {
+ INV_ITG3500,
+ INV_MPU3050,
+ INV_MPU6050,
+ INV_MPU9150,
+ INV_MPU6500,
+ INV_NUM_PARTS
+};
+
+/**
+ * struct test_setup_t - set up parameters for self test.
+ * @gyro_sens: sensitity for gyro.
+ * @sample_rate: sample rate, i.e, fifo rate.
+ * @lpf: low pass filter.
+ * @fsr: full scale range.
+ * @accl_fs: accel full scale range.
+ * @accl_sens: accel sensitivity
+ */
+struct test_setup_t {
+ int gyro_sens;
+ int sample_rate;
+ int lpf;
+ int fsr;
+ int accl_fs;
+ u32 accl_sens[3];
+};
+
+/**
+ * struct inv_hw_s - Other important hardware information.
+ * @num_reg: Number of registers on device.
+ * @name: name of the chip
+ */
+struct inv_hw_s {
+ u8 num_reg;
+ u8 *name;
+};
+
+/**
+ * struct inv_chip_config_s - Cached chip configuration data.
+ * @fsr: Full scale range.
+ * @lpf: Digital low pass filter frequency.
+ * @clk_src: Clock source.
+ * @accl_fs: accel full scale range.
+ * @self_test_run_once flag for self test run ever.
+ * @has_footer: MPU3050 specific work around.
+ * @has_compass: has compass or not.
+ * @enable: master enable to enable output
+ * @accl_enable: enable accel functionality
+ * @accl_fifo_enable: enable accel data output
+ * @gyro_enable: enable gyro functionality
+ * @gyro_fifo_enable: enable gyro data output
+ * @compass_enable: enable compass
+ * @compass_fifo_enable: enable compass data output
+ * @is_asleep: 1 if chip is powered down.
+ * @was_asleep_on_suspend: 1 if chip was powered at time of sustpend.
+ * @dmp_on: dmp is on/off.
+ * @dmp_int_on: dmp interrupt on/off.
+ * @dmp_event_int_on: dmp event interrupt on/off.
+ * @orientation_on: dmp is on/off.
+ * @firmware_loaded: flag indicate firmware loaded or not.
+ * @lpa_mod: low power mode.
+ * @tap_on: tap on/off.
+ * @flick_int_on: flick interrupt on/off.
+ * @quaternion_on: send quaternion data on/off.
+ * @display_orient_on: display orientation on/off.
+ * @lpa_freq: low power frequency
+ * @prog_start_addr: firmware program start address.
+ * @dmp_output_rate: dmp output rate.
+ * @fifo_rate: FIFO update rate.
+ */
+struct inv_chip_config_s {
+ u32 fsr:2;
+ u32 lpf:3;
+ u32 clk_src:1;
+ u32 accl_fs:2;
+ u32 self_test_run_once:1;
+ u32 has_footer:1;
+ u32 has_compass:1;
+ u32 enable:1;
+ u32 accl_enable:1;
+ u32 accl_fifo_enable:1;
+ u32 gyro_enable:1;
+ u32 gyro_fifo_enable:1;
+ u32 compass_enable:1;
+ u32 compass_fifo_enable:1;
+ u32 is_asleep:1;
+ u32 was_asleep_on_suspend:1;
+ u32 dmp_on:1;
+ u32 dmp_int_on:1;
+ u32 dmp_event_int_on:1;
+ u32 orientation_on:1;
+ u32 firmware_loaded:1;
+ u32 lpa_mode:1;
+ u32 tap_on:1;
+ u32 flick_int_on:1;
+ u32 quaternion_on:1;
+ u32 display_orient_on:1;
+ u16 lpa_freq;
+ u16 prog_start_addr;
+ u16 fifo_rate;
+ u16 dmp_output_rate;
+};
+
+/**
+ * struct inv_chip_info_s - Chip related information.
+ * @product_id: Product id.
+ * @product_revision: Product revision.
+ * @silicon_revision: Silicon revision.
+ * @software_revision: software revision.
+ * @multi: accel specific multiplier.
+ * @compass_sens: compass sensitivity.
+ * @gyro_sens_trim: Gyro sensitivity trim factor.
+ * @accl_sens_trim: accel sensitivity trim factor.
+ */
+struct inv_chip_info_s {
+ u8 product_id;
+ u8 product_revision;
+ u8 silicon_revision;
+ u8 software_revision;
+ u8 multi;
+ u8 compass_sens[3];
+ unsigned long gyro_sens_trim;
+ unsigned long accl_sens_trim;
+};
+
+/**
+ * struct inv_flick_s structure to store flick data.
+ * @lower: lower bound of flick.
+ * @upper: upper bound of flick.
+ * @counter: counter of flick.
+ * @msg_on; message to carry flick
+ * @axis: axis of flick
+ */
+struct inv_flick_s {
+ int lower;
+ int upper;
+ int counter;
+ int msg_on;
+ int axis;
+};
+
+enum inv_channel_num {
+ INV_CHANNEL_NUM_GYRO = 4,
+ INV_CHANNEL_NUM_GYRO_ACCL = 7,
+ INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION = 11,
+ INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION_MAGN = 14,
+};
+
+/**
+ * struct inv_tap_s structure to store tap data.
+ * @min_count: minimum taps counted.
+ * @thresh: tap threshold.
+ * @time: tap time.
+ */
+struct inv_tap_s {
+ u16 min_count;
+ u16 thresh;
+ u16 time;
+};
+struct inv_mpu_slave;
+/**
+ * struct inv_mpu_iio_s - Driver state variables.
+ * @chip_config: Cached attribute information.
+ * @chip_info: Chip information from read-only registers.
+ * @trig; iio trigger.
+ * @flick: flick data structure
+ * @tap: tap data structure
+ * @reg: Map of important registers.
+ * @hw: Other hardware-specific information.
+ * @chip_type: chip type.
+ * @time_stamp_lock: spin lock to time stamp.
+ * @client: i2c client handle.
+ * @plat_data: platform data.
+ * @mpu_slave: mpu slave handle.
+ * int (*set_power_state)(struct inv_mpu_iio_s *, int on): function ptr
+ * int (*switch_gyro_engine)(struct inv_mpu_iio_s *, int on): function ptr
+ * int (*switch_accl_engine)(struct inv_mpu_iio_s *, int on): function ptr
+ * int (*init_config)(struct iio_dev *indio_dev): function ptr
+ * void (*setup_reg)(struct inv_reg_map_s *reg): function ptr
+ * @timestamps: kfifo queue to store time stamp.
+ * @compass_st_upper: compass self test upper limit.
+ * @compass_st_lower: compass self test lower limit.
+ * @irq: irq number store.
+ * @accel_bias: accel bias store.
+ * @gyro_bias: gyro bias store.
+ * @raw_gyro: raw gyro data.
+ * @raw_accel: raw accel data.
+ * @raw_compass: raw compass.
+ * @raw_quaternion raw quaternion data.
+ * @compass_scale: compass scale.
+ * @i2c_addr: i2c address.
+ * @compass_divider: slow down compass rate.
+ * @compass_dmp_divider: slow down compass rate for dmp.
+ * @compass_counter: slow down compass rate.
+ * @sample_divider: sample divider for dmp.
+ * @fifo_divider: fifo divider for dmp.
+ * @orient_data: orientation data.
+ * @display_orient_data:display orient data.
+ * @tap_data: tap data.
+ * @num_channels: number of channels for current chip.
+ * @sl_handle: Handle to I2C port.
+ * @irq_dur_ns: duration between each irq.
+ * @last_isr_time: last isr time.
+ */
+struct inv_mpu_iio_s {
+#define TIMESTAMP_FIFO_SIZE 16
+ struct inv_chip_config_s chip_config;
+ struct inv_chip_info_s chip_info;
+ struct iio_trigger *trig;
+ struct inv_flick_s flick;
+ struct inv_tap_s tap;
+ struct inv_reg_map_s reg;
+ const struct inv_hw_s *hw;
+ enum inv_devices chip_type;
+ spinlock_t time_stamp_lock;
+ struct i2c_client *client;
+ struct mpu_platform_data plat_data;
+ struct inv_mpu_slave *mpu_slave;
+ int (*set_power_state)(struct inv_mpu_iio_s *, bool on);
+ int (*switch_gyro_engine)(struct inv_mpu_iio_s *, bool on);
+ int (*switch_accl_engine)(struct inv_mpu_iio_s *, bool on);
+ int (*init_config)(struct iio_dev *indio_dev);
+ void (*setup_reg)(struct inv_reg_map_s *reg);
+ DECLARE_KFIFO(timestamps, long long, TIMESTAMP_FIFO_SIZE);
+ const short *compass_st_upper;
+ const short *compass_st_lower;
+ short irq;
+ int accel_bias[3];
+ int gyro_bias[3];
+ short raw_gyro[3];
+ short raw_accel[3];
+ short raw_compass[3];
+ int raw_quaternion[4];
+ u8 compass_scale;
+ u8 i2c_addr;
+ u8 compass_divider;
+ u8 compass_counter;
+ u8 compass_dmp_divider;
+ u8 sample_divider;
+ u8 fifo_divider;
+ u8 orient_data;
+ u8 display_orient_data;
+ u8 tap_data;
+ enum inv_channel_num num_channels;
+ void *sl_handle;
+ u32 irq_dur_ns;
+ u64 last_isr_time;
+#ifdef CONFIG_INV_TESTING
+ unsigned long i2c_readcount;
+ unsigned long i2c_writecount;
+#endif
+};
+
+/* produces an unique identifier for each device based on the
+ combination of product version and product revision */
+struct prod_rev_map_t {
+ u16 mpl_product_key;
+ u8 silicon_rev;
+ u16 gyro_trim;
+ u16 accel_trim;
+};
+
+/**
+ * struct inv_mpu_slave - MPU slave structure.
+ * @suspend: suspend operation.
+ * @resume: resume operation.
+ * @setup: setup chip. initialization.
+ * @combine_data: combine raw data into meaningful data.
+ * @get_mode: get current chip mode.
+ * @set_lpf set low pass filter.
+ * @set_fs set full scale
+ */
+struct inv_mpu_slave {
+ int (*suspend)(struct inv_mpu_iio_s *);
+ int (*resume)(struct inv_mpu_iio_s *);
+ int (*setup)(struct inv_mpu_iio_s *);
+ int (*combine_data)(u8 *in, short *out);
+ int (*get_mode)(void);
+ int (*set_lpf)(struct inv_mpu_iio_s *, int rate);
+ int (*set_fs)(struct inv_mpu_iio_s *, int fs);
+};
+
+/* AKM definitions */
+#define REG_AKM_ID 0x00
+#define REG_AKM_STATUS 0x02
+#define REG_AKM_MEASURE_DATA 0x03
+#define REG_AKM_MODE 0x0A
+#define REG_AKM_ST_CTRL 0x0C
+#define REG_AKM_SENSITIVITY 0x10
+#define REG_AKM8963_CNTL1 0x0A
+
+#define DATA_AKM_ID 0x48
+#define DATA_AKM_MODE_PD 0x00
+#define DATA_AKM_MODE_SM 0x01
+#define DATA_AKM_MODE_ST 0x08
+#define DATA_AKM_MODE_FR 0x0F
+#define DATA_AKM_SELF_TEST 0x40
+#define DATA_AKM_DRDY 0x01
+#define DATA_AKM8963_BIT 0x10
+#define DATA_AKM_STAT_MASK 0x0C
+
+#define DATA_AKM8975_SCALE (9830 * (1L << 15))
+#define DATA_AKM8972_SCALE (19661 * (1L << 15))
+#define DATA_AKM8963_SCALE0 (19661 * (1L << 15))
+#define DATA_AKM8963_SCALE1 (4915 * (1L << 15))
+#define AKM8963_SCALE_SHIFT 4
+#define NUM_BYTES_COMPASS_SLAVE 8
+
+/*register and associated bit definition*/
+#define REG_3050_FIFO_EN 0x12
+#define BITS_3050_ACCL_OUT 0x0E
+
+#define REG_3050_AUX_VDDIO 0x13
+#define BIT_3050_VDDIO 0x04
+
+#define REG_3050_SLAVE_ADDR 0x14
+#define REG_3050_SAMPLE_RATE_DIV 0x15
+#define REG_3050_LPF 0x16
+#define REG_3050_INT_ENABLE 0x17
+#define REG_3050_AUX_BST_ADDR 0x18
+#define REG_3050_INT_STATUS 0x1A
+#define REG_3050_TEMPERATURE 0x1B
+#define REG_3050_RAW_GYRO 0x1D
+#define REG_3050_AUX_XOUT_H 0x23
+#define REG_3050_FIFO_COUNT_H 0x3A
+#define REG_3050_FIFO_R_W 0x3C
+
+#define REG_3050_USER_CTRL 0x3D
+#define BIT_3050_AUX_IF_EN 0x20
+#define BIT_3050_FIFO_RST 0x02
+
+#define REG_3050_PWR_MGMT_1 0x3E
+#define BITS_3050_POWER1 0x30
+#define BITS_3050_POWER2 0x10
+#define BITS_3050_GYRO_STANDBY 0x38
+
+#define REG_3500_OTP 0x0
+
+#define REG_YGOFFS_TC 0x1
+#define BIT_I2C_MST_VDDIO 0x80
+
+#define REG_XA_OFFS_L_TC 0x7
+#define REG_PRODUCT_ID 0xC
+#define REG_ST_GCT_X 0xD
+#define REG_SAMPLE_RATE_DIV 0x19
+#define REG_CONFIG 0x1A
+
+#define REG_GYRO_CONFIG 0x1B
+#define BITS_SELF_TEST_EN 0xE0
+
+#define REG_ACCEL_CONFIG 0x1C
+
+#define REG_FIFO_EN 0x23
+#define BIT_ACCEL_OUT 0x08
+#define BITS_GYRO_OUT 0x70
+
+
+#define REG_I2C_MST_CTRL 0x24
+#define BIT_WAIT_FOR_ES 0x40
+
+#define REG_I2C_SLV0_ADDR 0x25
+#define BIT_I2C_READ 0x80
+
+#define REG_I2C_SLV0_REG 0x26
+
+#define REG_I2C_SLV0_CTRL 0x27
+#define BIT_SLV_EN 0x80
+
+#define REG_I2C_SLV1_ADDR 0x28
+#define REG_I2C_SLV1_REG 0x29
+#define REG_I2C_SLV1_CTRL 0x2A
+#define REG_I2C_SLV4_CTRL 0x34
+
+#define REG_INT_PIN_CFG 0x37
+#define BIT_BYPASS_EN 0x2
+
+#define REG_INT_ENABLE 0x38
+#define BIT_DATA_RDY_EN 0x01
+#define BIT_DMP_INT_EN 0x02
+
+#define REG_DMP_INT_STATUS 0x39
+#define REG_INT_STATUS 0x3A
+#define REG_RAW_ACCEL 0x3B
+#define REG_TEMPERATURE 0x41
+#define REG_RAW_GYRO 0x43
+#define REG_EXT_SENS_DATA_00 0x49
+#define REG_I2C_SLV1_DO 0x64
+
+#define REG_I2C_MST_DELAY_CTRL 0x67
+#define BIT_SLV0_DLY_EN 0x01
+#define BIT_SLV1_DLY_EN 0x02
+
+#define REG_USER_CTRL 0x6A
+#define BIT_FIFO_RST 0x04
+#define BIT_DMP_RST 0x08
+#define BIT_I2C_MST_EN 0x20
+#define BIT_FIFO_EN 0x40
+#define BIT_DMP_EN 0x80
+
+#define REG_PWR_MGMT_1 0x6B
+#define BIT_H_RESET 0x80
+#define BIT_SLEEP 0x40
+#define BIT_CYCLE 0x20
+
+#define REG_PWR_MGMT_2 0x6C
+#define BIT_PWR_ACCL_STBY 0x38
+#define BIT_PWR_GYRO_STBY 0x07
+#define BIT_LPA_FREQ 0xC0
+
+#define REG_BANK_SEL 0x6D
+#define REG_MEM_START_ADDR 0x6E
+#define REG_MEM_RW 0x6F
+#define REG_PRGM_STRT_ADDRH 0x70
+#define REG_FIFO_COUNT_H 0x72
+#define REG_FIFO_R_W 0x74
+#define REG_WHOAMI 0x75
+
+#define REG_6500_ACCEL_CONFIG2 0x1D
+#define BIT_ACCEL_FCHOCIE_B 0x08
+
+#define REG_6500_LP_ACCEL_ODR 0x1E
+
+/* data definitions */
+#define DMP_START_ADDR 0x400
+#define DMP_MASK_TAP 0x3f
+#define DMP_MASK_DIS_ORIEN 0xC0
+#define DMP_DIS_ORIEN_SHIFT 6
+
+#define BYTES_FOR_DMP 16
+#define QUATERNION_BYTES 16
+#define BYTES_PER_SENSOR 6
+#define MPU3050_FOOTER_SIZE 2
+#define FIFO_COUNT_BYTE 2
+#define FIFO_THRESHOLD 500
+#define POWER_UP_TIME 100
+#define SENSOR_UP_TIME 30
+#define MPU_MEM_BANK_SIZE 256
+#define MPU3050_TEMP_OFFSET 5383314L
+#define MPU3050_TEMP_SCALE 3834792L
+#define MPU6050_TEMP_OFFSET 2462307L
+#define MPU6050_TEMP_SCALE 2977653L
+#define MPU_TEMP_SHIFT 16
+#define LPA_FREQ_SHIFT 6
+#define COMPASS_RATE_SCALE 10
+#define MAX_GYRO_FS_PARAM 3
+#define MAX_ACCL_FS_PARAM 3
+#define MAX_LPA_FREQ_PARAM 3
+
+/*---- MPU6500 ----*/
+#define MAX_6500_LPA_FREQ_PARAM 11
+#define MPU6500_ID 0x70 /* unique WHOAMI */
+#define MPU6500_PRODUCT_REVISION 1
+
+/*---- MPU9250 ----*/
+#define MPU9250_ID 0x71 /* unique WHOAMI */
+
+#define THREE_AXIS 3
+#define GYRO_CONFIG_FSR_SHIFT 3
+#define ACCL_CONFIG_FSR_SHIFT 3
+#define GYRO_DPS_SCALE 250
+#define MEM_ADDR_PROD_REV 0x6
+#define SOFT_PROD_VER_BYTES 5
+#define CRC_FIRMWARE_SEED 0
+#define SELF_TEST_SUCCESS 1
+#define MS_PER_DMP_TICK 20
+
+/* init parameters */
+#define INIT_FIFO_RATE 50
+#define INIT_DMP_OUTPUT_RATE 25
+#define INIT_DUR_TIME ((1000 / INIT_FIFO_RATE) * 1000 * 1000)
+#define INIT_TAP_THRESHOLD 100
+#define INIT_TAP_TIME 100
+#define INIT_TAP_MIN_COUNT 2
+#define MPL_PROD_KEY(ver, rev) (ver * 100 + rev)
+#define NUM_OF_PROD_REVS (ARRAY_SIZE(prod_rev_map))
+/*---- MPU6050 Silicon Revisions ----*/
+#define MPU_SILICON_REV_A2 1 /* MPU6050A2 Device */
+#define MPU_SILICON_REV_B1 2 /* MPU6050B1 Device */
+
+#define BIT_PRFTCH_EN 0x40
+#define BIT_CFG_USER_BANK 0x20
+#define BITS_MEM_SEL 0x1f
+
+#define TIME_STAMP_TOR 5
+#define MAX_CATCH_UP 5
+#define DEFAULT_ACCL_TRIM 16384
+#define DEFAULT_GYRO_TRIM 131
+#define MAX_FIFO_RATE 1000
+#define MIN_FIFO_RATE 4
+#define ONE_K_HZ 1000
+
+/* flick related defines */
+#define DATA_INT 2097
+#define DATA_MSG_ON 262144
+#define FLICK_INT_STATUS 8
+
+/*tap related defines */
+#define INV_TAP 0x08
+#define INV_NUM_TAP_AXES 3
+
+#define INV_TAP_AXIS_X_POS 0x20
+#define INV_TAP_AXIS_X_NEG 0x10
+#define INV_TAP_AXIS_Y_POS 0x08
+#define INV_TAP_AXIS_Y_NEG 0x04
+#define INV_TAP_AXIS_Z_POS 0x02
+#define INV_TAP_AXIS_Z_NEG 0x01
+#define INV_TAP_ALL_DIRECTIONS 0x3f
+
+#define INV_TAP_AXIS_X 0x1
+#define INV_TAP_AXIS_Y 0x2
+#define INV_TAP_AXIS_Z 0x4
+
+#define INV_TAP_AXIS_ALL \
+ (INV_TAP_AXIS_X | \
+ INV_TAP_AXIS_Y | \
+ INV_TAP_AXIS_Z)
+
+#define INT_SRC_TAP 0x01
+#define INT_SRC_ORIENT 0x02
+#define INT_SRC_DISPLAY_ORIENT 0x08
+#define INT_SRC_SHAKE 0x10
+
+
+/*orientation related */
+#define INV_X_UP 0x01
+#define INV_X_DOWN 0x02
+#define INV_Y_UP 0x04
+#define INV_Y_DOWN 0x08
+#define INV_Z_UP 0x10
+#define INV_Z_DOWN 0x20
+#define INV_ORIENTATION_ALL 0x3F
+
+#define INV_ORIENTATION_FLIP 0x40
+#define INV_X_AXIS_INDEX 0x00
+#define INV_Y_AXIS_INDEX 0x01
+#define INV_Z_AXIS_INDEX 0x02
+
+#define INV_ELEMENT_1 0x0001
+#define INV_ELEMENT_2 0x0002
+#define INV_ELEMENT_3 0x0004
+#define INV_ELEMENT_4 0x0008
+#define INV_ELEMENT_5 0x0010
+#define INV_ELEMENT_6 0x0020
+#define INV_ELEMENT_7 0x0040
+#define INV_ELEMENT_8 0x0080
+#define INV_ALL 0xFFFF
+#define INV_ELEMENT_MASK 0x00FF
+#define INV_GYRO_ACC_MASK 0x007E
+/* scan element definition */
+enum inv_mpu_scan {
+ INV_MPU_SCAN_QUAT_R = 0,
+ INV_MPU_SCAN_QUAT_X,
+ INV_MPU_SCAN_QUAT_Y,
+ INV_MPU_SCAN_QUAT_Z,
+ INV_MPU_SCAN_GYRO_X,
+ INV_MPU_SCAN_GYRO_Y,
+ INV_MPU_SCAN_GYRO_Z,
+ INV_MPU_SCAN_ACCL_X,
+ INV_MPU_SCAN_ACCL_Y,
+ INV_MPU_SCAN_ACCL_Z,
+ INV_MPU_SCAN_MAGN_X,
+ INV_MPU_SCAN_MAGN_Y,
+ INV_MPU_SCAN_MAGN_Z,
+ INV_MPU_SCAN_TIMESTAMP,
+};
+
+enum inv_filter_e {
+ INV_FILTER_256HZ_NOLPF2 = 0,
+ INV_FILTER_188HZ,
+ INV_FILTER_98HZ,
+ INV_FILTER_42HZ,
+ INV_FILTER_20HZ,
+ INV_FILTER_10HZ,
+ INV_FILTER_5HZ,
+ INV_FILTER_2100HZ_NOLPF,
+ NUM_FILTER
+};
+
+enum inv_slave_mode {
+ INV_MODE_SUSPEND,
+ INV_MODE_NORMAL,
+};
+
+/*==== MPU6050B1 MEMORY ====*/
+enum MPU_MEMORY_BANKS {
+ MEM_RAM_BANK_0 = 0,
+ MEM_RAM_BANK_1,
+ MEM_RAM_BANK_2,
+ MEM_RAM_BANK_3,
+ MEM_RAM_BANK_4,
+ MEM_RAM_BANK_5,
+ MEM_RAM_BANK_6,
+ MEM_RAM_BANK_7,
+ MEM_RAM_BANK_8,
+ MEM_RAM_BANK_9,
+ MEM_RAM_BANK_10,
+ MEM_RAM_BANK_11,
+ MPU_MEM_NUM_RAM_BANKS,
+ MPU_MEM_OTP_BANK_0 = 16
+};
+
+/* IIO attribute address */
+enum MPU_IIO_ATTR_ADDR {
+ ATTR_DMP_FLICK_LOWER,
+ ATTR_DMP_FLICK_UPPER,
+ ATTR_DMP_FLICK_COUNTER,
+ ATTR_DMP_FLICK_INT_ON,
+ ATTR_DMP_FLICK_AXIS,
+ ATTR_DMP_FLICK_MSG_ON,
+ ATTR_DMP_PEDOMETER_STEPS,
+ ATTR_DMP_PEDOMETER_TIME,
+ ATTR_DMP_TAP_THRESHOLD,
+ ATTR_DMP_TAP_MIN_COUNT,
+ ATTR_DMP_TAP_ON,
+ ATTR_DMP_TAP_TIME,
+ ATTR_DMP_ON,
+ ATTR_DMP_INT_ON,
+ ATTR_DMP_EVENT_INT_ON,
+ ATTR_DMP_OUTPUT_RATE,
+ ATTR_DMP_ORIENTATION_ON,
+ ATTR_DMP_QUATERNION_ON,
+ ATTR_DMP_DISPLAY_ORIENTATION_ON,
+ ATTR_LPA_MODE,
+ ATTR_LPA_FREQ,
+ ATTR_CLK_SRC,
+ ATTR_SELF_TEST,
+ ATTR_KEY,
+ ATTR_GYRO_MATRIX,
+ ATTR_ACCL_MATRIX,
+ ATTR_COMPASS_MATRIX,
+ ATTR_GYRO_ENABLE,
+ ATTR_ACCL_ENABLE,
+ ATTR_COMPASS_ENABLE,
+ ATTR_POWER_STATE,
+ ATTR_FIRMWARE_LOADED,
+#ifdef CONFIG_INV_TESTING
+ ATTR_I2C_COUNTERS,
+ ATTR_REG_WRITE,
+#endif
+};
+
+enum inv_accl_fs_e {
+ INV_FS_02G = 0,
+ INV_FS_04G,
+ INV_FS_08G,
+ INV_FS_16G,
+ NUM_ACCL_FSR
+};
+
+enum inv_fsr_e {
+ INV_FSR_250DPS = 0,
+ INV_FSR_500DPS,
+ INV_FSR_1000DPS,
+ INV_FSR_2000DPS,
+ NUM_FSR
+};
+
+enum inv_clock_sel_e {
+ INV_CLK_INTERNAL = 0,
+ INV_CLK_PLL,
+ NUM_CLK
+};
+
+ssize_t inv_dmp_firmware_write(struct file *fp, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf, loff_t pos, size_t size);
+ssize_t inv_dmp_firmware_read(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count);
+
+int inv_mpu_configure_ring(struct iio_dev *indio_dev);
+int inv_mpu_probe_trigger(struct iio_dev *indio_dev);
+void inv_mpu_unconfigure_ring(struct iio_dev *indio_dev);
+void inv_mpu_remove_trigger(struct iio_dev *indio_dev);
+int inv_init_config_mpu3050(struct iio_dev *indio_dev);
+int inv_get_silicon_rev_mpu6050(struct inv_mpu_iio_s *st);
+int inv_get_silicon_rev_mpu6500(struct inv_mpu_iio_s *st);
+int set_3050_bypass(struct inv_mpu_iio_s *st, bool enable);
+int inv_register_mpu3050_slave(struct inv_mpu_iio_s *st);
+void inv_setup_reg_mpu3050(struct inv_reg_map_s *reg);
+int inv_switch_3050_gyro_engine(struct inv_mpu_iio_s *st, bool en);
+int inv_switch_3050_accl_engine(struct inv_mpu_iio_s *st, bool en);
+int set_power_mpu3050(struct inv_mpu_iio_s *st, bool power_on);
+int set_inv_enable(struct iio_dev *indio_dev, bool enable);
+int inv_set_interrupt_on_gesture_event(struct inv_mpu_iio_s *st, bool on);
+int inv_send_quaternion(struct inv_mpu_iio_s *st, bool on);
+int inv_set_display_orient_interrupt_dmp(struct inv_mpu_iio_s *st, bool on);
+int inv_enable_orientation_dmp(struct inv_mpu_iio_s *st, bool on);
+int inv_set_fifo_rate(struct inv_mpu_iio_s *st, u16 fifo_rate);
+u16 inv_dmp_get_address(u16 key);
+int inv_q30_mult(int a, int b);
+int inv_set_tap_threshold_dmp(struct inv_mpu_iio_s *st,
+ u32 axis, u16 threshold);
+int inv_set_min_taps_dmp(struct inv_mpu_iio_s *st, u16 min_taps);
+int inv_set_tap_time_dmp(struct inv_mpu_iio_s *st, u16 time);
+int inv_enable_tap_dmp(struct inv_mpu_iio_s *st, bool on);
+int inv_i2c_read_base(struct inv_mpu_iio_s *st, u16 i2c_addr,
+ u8 reg, u16 length, u8 *data);
+int inv_i2c_single_write_base(struct inv_mpu_iio_s *st,
+ u16 i2c_addr, u8 reg, u8 data);
+int inv_do_test(struct inv_mpu_iio_s *st, int self_test_flag,
+ int *gyro_result, int *accl_result);
+int mpu_memory_write(struct i2c_adapter *i2c_adap,
+ u8 mpu_addr,
+ u16 mem_addr,
+ u32 len, u8 const *data);
+int mpu_memory_read(struct i2c_adapter *i2c_adap,
+ u8 mpu_addr,
+ u16 mem_addr,
+ u32 len, u8 *data);
+int inv_hw_self_test(struct inv_mpu_iio_s *st);
+int inv_hw_self_test_6500(struct inv_mpu_iio_s *st);
+void inv_recover_setting(struct inv_mpu_iio_s *st);
+s64 get_time_ns(void);
+
+#define mem_w(a, b, c) mpu_memory_write(st->sl_handle,\
+ st->i2c_addr, a, b, c)
+#define mem_w_key(key, b, c) mpu_memory_write(st->sl_handle,\
+ st->i2c_addr, inv_dmp_get_address(key), b, c)
+#define inv_i2c_read(st, reg, len, data) \
+ inv_i2c_read_base(st, st->i2c_addr, reg, len, data)
+#define inv_i2c_single_write(st, reg, data) \
+ inv_i2c_single_write_base(st, st->i2c_addr, reg, data)
+#define inv_secondary_read(reg, len, data) \
+ inv_i2c_read_base(st, st->plat_data.secondary_i2c_addr, reg, len, data)
+#define inv_secondary_write(reg, data) \
+ inv_i2c_single_write_base(st, st->plat_data.secondary_i2c_addr, \
+ reg, data)
+#endif /* #ifndef _INV_MPU_IIO_H_ */
+
diff --git a/drivers/staging/iio/imu/mpu/inv_mpu_misc.c b/drivers/staging/iio/imu/mpu/inv_mpu_misc.c
new file mode 100644
index 0000000..dc0abf2
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/inv_mpu_misc.c
@@ -0,0 +1,1849 @@
+/*
+* Copyright (C) 2012 Invensense, 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_mpu_misc.c
+ * @brief A sysfs device driver for Invensense mpu.
+ * @details This file is part of invensense mpu driver code
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/crc32.h>
+
+#include "inv_mpu_iio.h"
+/* DMP defines */
+#define DMP_ORIENTATION_TIME 500
+#define DMP_ORIENTATION_ANGLE 60
+#define DMP_DEFAULT_FIFO_RATE 200
+#define DMP_TAP_SCALE (767603923 / 5)
+#define DMP_MULTI_SHIFT 30
+#define DMP_MULTI_TAP_TIME 500
+#define DMP_SHAKE_REJECT_THRESH 100
+#define DMP_SHAKE_REJECT_TIME 10
+#define DMP_SHAKE_REJECT_TIMEOUT 10
+#define DMP_ANGLE_SCALE 15
+#define DMP_PRECISION 1000
+#define DMP_MAX_DIVIDER 4
+#define DMP_MAX_MIN_TAPS 4
+uint32_t DMP_IMAGE_CRC_VALUES[] = { 0x6590dad6, 0xd37d4599 };
+#define DMP_IMAGE_SIZE 3065
+
+/*--- Test parameters defaults --- */
+#define DEF_OLDEST_SUPP_PROD_REV 8
+#define DEF_OLDEST_SUPP_SW_REV 2
+
+/* sample rate */
+#define DEF_SELFTEST_SAMPLE_RATE 0
+/* LPF parameter */
+#define DEF_SELFTEST_LPF_PARA 1
+/* full scale setting dps */
+#define DEF_SELFTEST_GYRO_FULL_SCALE (0 << 3)
+#define DEF_SELFTEST_ACCL_FULL_SCALE (2 << 3)
+#define DEF_SELFTEST_GYRO_SENS (32768 / 250)
+/* wait time before collecting data */
+#define DEF_GYRO_WAIT_TIME 50
+#define DEF_ST_STABLE_TIME 200
+#define DEF_GYRO_PACKET_THRESH DEF_GYRO_WAIT_TIME
+#define DEF_GYRO_THRESH 10
+#define DEF_GYRO_SCALE 131
+#define DEF_ST_PRECISION 1000
+#define DEF_ST_ACCL_FULL_SCALE 8000UL
+#define DEF_ST_SCALE (1L << 15)
+#define DEF_ST_TRY_TIMES 2
+#define DEF_ST_COMPASS_RESULT_SHIFT 2
+#define DEF_ST_ACCEL_RESULT_SHIFT 1
+
+#define DEF_ST_COMPASS_WAIT_MIN (10 * 1000)
+#define DEF_ST_COMPASS_WAIT_MAX (15 * 1000)
+#define DEF_ST_COMPASS_TRY_TIMES 10
+#define DEF_ST_COMPASS_8963_SHIFT 2
+
+#define X 0
+#define Y 1
+#define Z 2
+/*---- MPU6050 notable product revisions ----*/
+#define MPU_PRODUCT_KEY_B1_E1_5 105
+#define MPU_PRODUCT_KEY_B2_F1 431
+/* accelerometer Hw self test min and max bias shift (mg) */
+#define DEF_ACCEL_ST_SHIFT_MIN 300
+#define DEF_ACCEL_ST_SHIFT_MAX 950
+
+#define DEF_ACCEL_ST_SHIFT_DELTA 140
+#define DEF_GYRO_CT_SHIFT_DELTA 140
+/* gyroscope Coriolis self test min and max bias shift (dps) */
+#define DEF_GYRO_CT_SHIFT_MIN 10
+#define DEF_GYRO_CT_SHIFT_MAX 105
+
+static struct test_setup_t test_setup = {
+ .gyro_sens = DEF_SELFTEST_GYRO_SENS,
+ .sample_rate = DEF_SELFTEST_SAMPLE_RATE,
+ .lpf = DEF_SELFTEST_LPF_PARA,
+ .fsr = DEF_SELFTEST_GYRO_FULL_SCALE,
+ .accl_fs = DEF_SELFTEST_ACCL_FULL_SCALE
+};
+
+/* NOTE: product entries are in chronological order */
+static const struct prod_rev_map_t prod_rev_map[] = {
+ /* prod_ver = 0 */
+ {MPL_PROD_KEY(0, 1), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 2), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 3), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 4), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 5), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 6), MPU_SILICON_REV_A2, 131, 16384}, /* (A2/C2-1) */
+ /* prod_ver = 1, forced to 0 for MPU6050 A2 */
+ {MPL_PROD_KEY(0, 7), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 8), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 9), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 10), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 11), MPU_SILICON_REV_A2, 131, 16384}, /* (A2/D2-1) */
+ {MPL_PROD_KEY(0, 12), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 13), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 14), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 15), MPU_SILICON_REV_A2, 131, 16384},
+ {MPL_PROD_KEY(0, 27), MPU_SILICON_REV_A2, 131, 16384}, /* (A2/D4) */
+ /* prod_ver = 1 */
+ {MPL_PROD_KEY(1, 16), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-1) */
+ {MPL_PROD_KEY(1, 17), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-2) */
+ {MPL_PROD_KEY(1, 18), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-3) */
+ {MPL_PROD_KEY(1, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-4) */
+ {MPL_PROD_KEY(1, 20), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-5) */
+ {MPL_PROD_KEY(1, 28), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D4) */
+ {MPL_PROD_KEY(1, 1), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-1) */
+ {MPL_PROD_KEY(1, 2), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-2) */
+ {MPL_PROD_KEY(1, 3), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-3) */
+ {MPL_PROD_KEY(1, 4), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-4) */
+ {MPL_PROD_KEY(1, 5), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-5) */
+ {MPL_PROD_KEY(1, 6), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-6) */
+ /* prod_ver = 2 */
+ {MPL_PROD_KEY(2, 7), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-1) */
+ {MPL_PROD_KEY(2, 8), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-2) */
+ {MPL_PROD_KEY(2, 9), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-3) */
+ {MPL_PROD_KEY(2, 10), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-4) */
+ {MPL_PROD_KEY(2, 11), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-5) */
+ {MPL_PROD_KEY(2, 12), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-6) */
+ {MPL_PROD_KEY(2, 29), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/D4) */
+ /* prod_ver = 3 */
+ {MPL_PROD_KEY(3, 30), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E2) */
+ /* prod_ver = 4 */
+ {MPL_PROD_KEY(4, 31), MPU_SILICON_REV_B1, 131, 8192}, /* (B2/F1) */
+ {MPL_PROD_KEY(4, 1), MPU_SILICON_REV_B1, 131, 8192}, /* (B3/F1) */
+ {MPL_PROD_KEY(4, 3), MPU_SILICON_REV_B1, 131, 8192}, /* (B4/F1) */
+ /* prod_ver = 5 */
+ {MPL_PROD_KEY(5, 3), MPU_SILICON_REV_B1, 131, 16384}, /* (B4/F1) */
+ /* prod_ver = 6 */
+ {MPL_PROD_KEY(6, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B5/E2) */
+ /* prod_ver = 7 */
+ {MPL_PROD_KEY(7, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B5/E2) */
+ /* prod_ver = 8 */
+ {MPL_PROD_KEY(8, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B5/E2) */
+ /* prod_ver = 9 */
+ {MPL_PROD_KEY(9, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B5/E2) */
+ /* prod_ver = 10 */
+ {MPL_PROD_KEY(10, 19), MPU_SILICON_REV_B1, 131, 16384} /* (B5/E2) */
+};
+
+/*
+* List of product software revisions
+*
+* NOTE :
+* software revision 0 falls back to the old detection method
+* based off the product version and product revision per the
+* table above
+*/
+static const struct prod_rev_map_t sw_rev_map[] = {
+ {0, 0, 0, 0},
+ {1, MPU_SILICON_REV_B1, 131, 8192}, /* rev C */
+ {2, MPU_SILICON_REV_B1, 131, 16384} /* rev D */
+};
+
+static const int accl_st_tb[31] = {
+ 340, 351, 363, 375, 388, 401, 414, 428,
+ 443, 458, 473, 489, 506, 523, 541, 559,
+ 578, 597, 617, 638, 660, 682, 705, 729,
+ 753, 779, 805, 832, 860, 889, 919};
+static const int gyro_6050_st_tb[31] = {
+ 3275, 3425, 3583, 3748, 3920, 4100, 4289, 4486,
+ 4693, 4909, 5134, 5371, 5618, 5876, 6146, 6429,
+ 6725, 7034, 7358, 7696, 8050, 8421, 8808, 9213,
+ 9637, 10080, 10544, 11029, 11537, 12067, 12622};
+static const int gyro_3500_st_tb[255] = {
+ 2620, 2646, 2672, 2699, 2726, 2753, 2781, 2808,
+ 2837, 2865, 2894, 2923, 2952, 2981, 3011, 3041,
+ 3072, 3102, 3133, 3165, 3196, 3228, 3261, 3293,
+ 3326, 3359, 3393, 3427, 3461, 3496, 3531, 3566,
+ 3602, 3638, 3674, 3711, 3748, 3786, 3823, 3862,
+ 3900, 3939, 3979, 4019, 4059, 4099, 4140, 4182,
+ 4224, 4266, 4308, 4352, 4395, 4439, 4483, 4528,
+ 4574, 4619, 4665, 4712, 4759, 4807, 4855, 4903,
+ 4953, 5002, 5052, 5103, 5154, 5205, 5257, 5310,
+ 5363, 5417, 5471, 5525, 5581, 5636, 5693, 5750,
+ 5807, 5865, 5924, 5983, 6043, 6104, 6165, 6226,
+ 6289, 6351, 6415, 6479, 6544, 6609, 6675, 6742,
+ 6810, 6878, 6946, 7016, 7086, 7157, 7229, 7301,
+ 7374, 7448, 7522, 7597, 7673, 7750, 7828, 7906,
+ 7985, 8065, 8145, 8227, 8309, 8392, 8476, 8561,
+ 8647, 8733, 8820, 8909, 8998, 9088, 9178, 9270,
+ 9363, 9457, 9551, 9647, 9743, 9841, 9939, 10038,
+ 10139, 10240, 10343, 10446, 10550, 10656, 10763, 10870,
+ 10979, 11089, 11200, 11312, 11425, 11539, 11654, 11771,
+ 11889, 12008, 12128, 12249, 12371, 12495, 12620, 12746,
+ 12874, 13002, 13132, 13264, 13396, 13530, 13666, 13802,
+ 13940, 14080, 14221, 14363, 14506, 14652, 14798, 14946,
+ 15096, 15247, 15399, 15553, 15709, 15866, 16024, 16184,
+ 16346, 16510, 16675, 16842, 17010, 17180, 17352, 17526,
+ 17701, 17878, 18057, 18237, 18420, 18604, 18790, 18978,
+ 19167, 19359, 19553, 19748, 19946, 20145, 20347, 20550,
+ 20756, 20963, 21173, 21385, 21598, 21814, 22033, 22253,
+ 22475, 22700, 22927, 23156, 23388, 23622, 23858, 24097,
+ 24338, 24581, 24827, 25075, 25326, 25579, 25835, 26093,
+ 26354, 26618, 26884, 27153, 27424, 27699, 27976, 28255,
+ 28538, 28823, 29112, 29403, 29697, 29994, 30294, 30597,
+ 30903, 31212, 31524, 31839, 32157, 32479, 32804};
+
+int mpu_memory_write(struct i2c_adapter *i2c_adap,
+ u8 mpu_addr,
+ u16 mem_addr,
+ u32 len, u8 const *data)
+{
+ u8 bank[2];
+ u8 addr[2];
+ u8 buf[513];
+
+ struct i2c_msg msgs[3];
+ int res;
+
+ if (!data || !i2c_adap)
+ return -EINVAL;
+
+ if (len >= (sizeof(buf) - 1))
+ return -ENOMEM;
+
+ bank[0] = REG_BANK_SEL;
+ bank[1] = mem_addr >> 8;
+
+ addr[0] = REG_MEM_START_ADDR;
+ addr[1] = mem_addr & 0xFF;
+
+ buf[0] = REG_MEM_RW;
+ memcpy(buf + 1, data, len);
+
+ /* write message */
+ msgs[0].addr = mpu_addr;
+ msgs[0].flags = 0;
+ msgs[0].buf = bank;
+ msgs[0].len = sizeof(bank);
+
+ msgs[1].addr = mpu_addr;
+ msgs[1].flags = 0;
+ msgs[1].buf = addr;
+ msgs[1].len = sizeof(addr);
+
+ msgs[2].addr = mpu_addr;
+ msgs[2].flags = 0;
+ msgs[2].buf = (u8 *)buf;
+ msgs[2].len = len + 1;
+
+ res = i2c_transfer(i2c_adap, msgs, 3);
+ if (res != 3) {
+ if (res >= 0)
+ res = -EIO;
+ return res;
+ } else {
+ return 0;
+ }
+}
+
+int mpu_memory_read(struct i2c_adapter *i2c_adap,
+ u8 mpu_addr,
+ u16 mem_addr,
+ u32 len, u8 *data)
+{
+ u8 bank[2];
+ u8 addr[2];
+ u8 buf;
+
+ struct i2c_msg msgs[4];
+ int res;
+
+ if (!data || !i2c_adap)
+ return -EINVAL;
+
+ bank[0] = REG_BANK_SEL;
+ bank[1] = mem_addr >> 8;
+
+ addr[0] = REG_MEM_START_ADDR;
+ addr[1] = mem_addr & 0xFF;
+
+ buf = REG_MEM_RW;
+
+ /* write message */
+ msgs[0].addr = mpu_addr;
+ msgs[0].flags = 0;
+ msgs[0].buf = bank;
+ msgs[0].len = sizeof(bank);
+
+ msgs[1].addr = mpu_addr;
+ msgs[1].flags = 0;
+ msgs[1].buf = addr;
+ msgs[1].len = sizeof(addr);
+
+ msgs[2].addr = mpu_addr;
+ msgs[2].flags = 0;
+ msgs[2].buf = &buf;
+ msgs[2].len = 1;
+
+ msgs[3].addr = mpu_addr;
+ msgs[3].flags = I2C_M_RD;
+ msgs[3].buf = data;
+ msgs[3].len = len;
+
+ res = i2c_transfer(i2c_adap, msgs, 4);
+ if (res != 4) {
+ if (res >= 0)
+ res = -EIO;
+ return res;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * index_of_key()- Inverse lookup of the index of an MPL product key .
+ * @key: the MPL product indentifier also referred to as 'key'.
+ */
+static short index_of_key(u16 key)
+{
+ int i;
+ for (i = 0; i < NUM_OF_PROD_REVS; i++)
+ if (prod_rev_map[i].mpl_product_key == key)
+ return (short)i;
+ return -EINVAL;
+}
+
+int inv_get_silicon_rev_mpu6500(struct inv_mpu_iio_s *st)
+{
+ struct inv_chip_info_s *chip_info = &st->chip_info;
+ int result;
+ u8 whoami;
+
+ result = inv_i2c_read(st, REG_WHOAMI, 1, &whoami);
+ if (result)
+ return result;
+ if (whoami != MPU6500_ID && whoami != MPU9250_ID)
+ return -EINVAL;
+ /* these values are place holders and not real values */
+ chip_info->product_id = MPU6500_PRODUCT_REVISION;
+ chip_info->product_revision = MPU6500_PRODUCT_REVISION;
+ chip_info->silicon_revision = MPU6500_PRODUCT_REVISION;
+ chip_info->software_revision = MPU6500_PRODUCT_REVISION;
+ chip_info->gyro_sens_trim = DEFAULT_GYRO_TRIM;
+ chip_info->accl_sens_trim = DEFAULT_ACCL_TRIM;
+ chip_info->multi = 1;
+
+ return result;
+}
+
+int inv_get_silicon_rev_mpu6050(struct inv_mpu_iio_s *st)
+{
+ int result;
+ struct inv_reg_map_s *reg;
+ u8 prod_ver = 0x00, prod_rev = 0x00;
+ struct prod_rev_map_t *p_rev;
+ u8 bank =
+ (BIT_PRFTCH_EN | BIT_CFG_USER_BANK | MPU_MEM_OTP_BANK_0);
+ u16 mem_addr = ((bank << 8) | MEM_ADDR_PROD_REV);
+ u16 key;
+ u8 regs[5];
+ u16 sw_rev;
+ short index;
+ struct inv_chip_info_s *chip_info = &st->chip_info;
+ reg = &st->reg;
+
+ result = inv_i2c_read(st, REG_PRODUCT_ID, 1, &prod_ver);
+ if (result)
+ return result;
+ prod_ver &= 0xf;
+ /*memory read need more time after power up */
+ msleep(POWER_UP_TIME);
+ result = mpu_memory_read(st->sl_handle, st->i2c_addr, mem_addr,
+ 1, &prod_rev);
+ if (result)
+ return result;
+ prod_rev >>= 2;
+ /* clean the prefetch and cfg user bank bits */
+ result = inv_i2c_single_write(st, reg->bank_sel, 0);
+ if (result)
+ return result;
+ /* get the software-product version, read from XA_OFFS_L */
+ result = inv_i2c_read(st, REG_XA_OFFS_L_TC,
+ SOFT_PROD_VER_BYTES, regs);
+ if (result)
+ return result;
+
+ sw_rev = (regs[4] & 0x01) << 2 | /* 0x0b, bit 0 */
+ (regs[2] & 0x01) << 1 | /* 0x09, bit 0 */
+ (regs[0] & 0x01); /* 0x07, bit 0 */
+ /* if 0, use the product key to determine the type of part */
+ if (sw_rev == 0) {
+ key = MPL_PROD_KEY(prod_ver, prod_rev);
+ if (key == 0)
+ return -EINVAL;
+ index = index_of_key(key);
+ if (index < 0 || index >= NUM_OF_PROD_REVS)
+ return -EINVAL;
+ /* check MPL is compiled for this device */
+ if (prod_rev_map[index].silicon_rev != MPU_SILICON_REV_B1)
+ return -EINVAL;
+ p_rev = (struct prod_rev_map_t *)&prod_rev_map[index];
+ /* if valid, use the software product key */
+ } else if (sw_rev < ARRAY_SIZE(sw_rev_map)) {
+ p_rev = (struct prod_rev_map_t *)&sw_rev_map[sw_rev];
+ } else {
+ return -EINVAL;
+ }
+ chip_info->product_id = prod_ver;
+ chip_info->product_revision = prod_rev;
+ chip_info->silicon_revision = p_rev->silicon_rev;
+ chip_info->software_revision = sw_rev;
+ chip_info->gyro_sens_trim = p_rev->gyro_trim;
+ chip_info->accl_sens_trim = p_rev->accel_trim;
+ if (chip_info->accl_sens_trim == 0)
+ chip_info->accl_sens_trim = DEFAULT_ACCL_TRIM;
+ chip_info->multi = DEFAULT_ACCL_TRIM / chip_info->accl_sens_trim;
+ if (chip_info->multi != 1)
+ pr_info("multi is %d\n", chip_info->multi);
+ return result;
+}
+
+/**
+ * read_accel_hw_self_test_prod_shift()- read the accelerometer hardware
+ * self-test bias shift calculated
+ * during final production test and
+ * stored in chip non-volatile memory.
+ * @st: main data structure.
+ * @st_prod: A pointer to an array of 3 elements to hold the values
+ * for production hardware self-test bias shifts returned to the
+ * user.
+ */
+static int read_accel_hw_self_test_prod_shift(struct inv_mpu_iio_s *st,
+ int *st_prod)
+{
+ u8 regs[4];
+ u8 shift_code[3];
+ int result, i;
+ st_prod[0] = 0;
+ st_prod[1] = 0;
+ st_prod[2] = 0;
+ result = inv_i2c_read(st, REG_ST_GCT_X, ARRAY_SIZE(regs), regs);
+ if (result)
+ return result;
+ if ((0 == regs[0]) && (0 == regs[1]) &&
+ (0 == regs[2]) && (0 == regs[3]))
+ return -EINVAL;
+ shift_code[X] = ((regs[0] & 0xE0) >> 3) | ((regs[3] & 0x30) >> 4);
+ shift_code[Y] = ((regs[1] & 0xE0) >> 3) | ((regs[3] & 0x0C) >> 2);
+ shift_code[Z] = ((regs[2] & 0xE0) >> 3) | (regs[3] & 0x03);
+ for (i = 0; i < 3; i++) {
+ if (shift_code[i] != 0)
+ st_prod[i] = test_setup.accl_sens[i]*
+ accl_st_tb[shift_code[i] - 1];
+ }
+
+ return 0;
+}
+/**
+* inv_check_accl_self_test()- check accel self test. this function returns
+* zero as success. A non-zero return value
+* indicates failure in self test.
+* @*st: main data structure.
+* @*reg_avg: average value of normal test.
+* @*st_avg: average value of self test
+*/
+static int inv_check_accl_self_test(struct inv_mpu_iio_s *st,
+ int *reg_avg, int *st_avg){
+ int gravity, reg_z_avg, g_z_sign, fs, j, ret_val;
+ int tmp1;
+ int st_shift_prod[THREE_AXIS], st_shift_cust[THREE_AXIS];
+ int st_shift_ratio[THREE_AXIS];
+ if (st->chip_info.software_revision < DEF_OLDEST_SUPP_SW_REV &&
+ st->chip_info.product_revision < DEF_OLDEST_SUPP_PROD_REV)
+ return 0;
+ fs = DEF_ST_ACCL_FULL_SCALE; /* assume +/- 2 mg as typical */
+ g_z_sign = 1;
+ ret_val = 0;
+ test_setup.accl_sens[X] = (u32)(DEF_ST_SCALE *
+ DEF_ST_PRECISION / fs);
+ test_setup.accl_sens[Y] = (u32)(DEF_ST_SCALE *
+ DEF_ST_PRECISION / fs);
+ test_setup.accl_sens[Z] = (u32)(DEF_ST_SCALE *
+ DEF_ST_PRECISION / fs);
+
+ if (MPL_PROD_KEY(st->chip_info.product_id,
+ st->chip_info.product_revision) ==
+ MPU_PRODUCT_KEY_B1_E1_5) {
+ /* half sensitivity Z accelerometer parts */
+ test_setup.accl_sens[Z] /= 2;
+ } else {
+ /* half sensitivity X, Y, Z accelerometer parts */
+ test_setup.accl_sens[X] /= st->chip_info.multi;
+ test_setup.accl_sens[Y] /= st->chip_info.multi;
+ test_setup.accl_sens[Z] /= st->chip_info.multi;
+ }
+ gravity = test_setup.accl_sens[Z];
+ reg_z_avg = reg_avg[Z] - g_z_sign * gravity*DEF_ST_PRECISION;
+ read_accel_hw_self_test_prod_shift(st, st_shift_prod);
+ for (j = 0; j < 3; j++) {
+ st_shift_cust[j] = abs(reg_avg[j] - st_avg[j]);
+ if (st_shift_prod[j]) {
+ tmp1 = st_shift_prod[j]/DEF_ST_PRECISION;
+ st_shift_ratio[j] = st_shift_cust[j]/tmp1
+ - DEF_ST_PRECISION;
+ if (st_shift_ratio[j] > DEF_ACCEL_ST_SHIFT_DELTA)
+ ret_val |= 1 << j;
+ if (st_shift_ratio[j] < -DEF_ACCEL_ST_SHIFT_DELTA)
+ ret_val |= 1 << j;
+ } else {
+ if (st_shift_cust[j] <
+ DEF_ACCEL_ST_SHIFT_MIN*gravity)
+ ret_val |= 1 << j;
+ if (st_shift_cust[j] >
+ DEF_ACCEL_ST_SHIFT_MAX*gravity)
+ ret_val |= 1 << j;
+ }
+ }
+
+ return ret_val;
+}
+/**
+* inv_check_3500_gyro_self_test() check gyro self test. this function returns
+* zero as success. A non-zero return value
+* indicates failure in self test.
+* @*st: main data structure.
+* @*reg_avg: average value of normal test.
+* @*st_avg: average value of self test
+*/
+
+static int inv_check_3500_gyro_self_test(struct inv_mpu_iio_s *st,
+ int *reg_avg, int *st_avg){
+ int result;
+ int gst[3], ret_val;
+ int gst_otp[3], i;
+ u8 st_code[THREE_AXIS];
+ ret_val = 0;
+
+ for (i = 0; i < 3; i++)
+ gst[i] = st_avg[i] - reg_avg[i];
+ result = inv_i2c_read(st, REG_3500_OTP, THREE_AXIS, st_code);
+ if (result)
+ return result;
+ gst_otp[0] = 0;
+ gst_otp[1] = 0;
+ gst_otp[2] = 0;
+ for (i = 0; i < 3; i++) {
+ if (st_code[i] != 0)
+ gst_otp[i] = gyro_3500_st_tb[st_code[i] - 1];
+ }
+ for (i = 0; i < 3; i++) {
+ if (gst_otp[i] == 0) {
+ if (abs(gst[i]) * 4 < 60 * 2 * DEF_ST_PRECISION *
+ DEF_GYRO_SCALE)
+ ret_val |= (1 << i);
+ } else {
+ if (abs(gst[i]/gst_otp[i] - DEF_ST_PRECISION) >
+ DEF_GYRO_CT_SHIFT_DELTA)
+ ret_val |= (1 << i);
+ }
+ }
+ for (i = 0; i < 3; i++) {
+ if (abs(reg_avg[i]) * 4 > 20 * 2 *
+ DEF_ST_PRECISION*DEF_GYRO_SCALE)
+ ret_val |= (1 << i);
+ }
+
+ return ret_val;
+}
+
+/**
+* inv_check_6050_gyro_self_test() - check 6050 gyro self test. this function
+* returns zero as success. A non-zero return
+* value indicates failure in self test.
+* @*st: main data structure.
+* @*reg_avg: average value of normal test.
+* @*st_avg: average value of self test
+*/
+static int inv_check_6050_gyro_self_test(struct inv_mpu_iio_s *st,
+ int *reg_avg, int *st_avg){
+ int result;
+ int ret_val;
+ int ct_shift_prod[3], st_shift_cust[3], st_shift_ratio[3], i;
+ u8 regs[3];
+ if (st->chip_info.software_revision < DEF_OLDEST_SUPP_SW_REV &&
+ st->chip_info.product_revision < DEF_OLDEST_SUPP_PROD_REV)
+ return 0;
+
+ ret_val = 0;
+ result = inv_i2c_read(st, REG_ST_GCT_X, 3, regs);
+ if (result)
+ return result;
+ regs[X] &= 0x1f;
+ regs[Y] &= 0x1f;
+ regs[Z] &= 0x1f;
+
+ for (i = 0; i < 3; i++) {
+ if (regs[i] != 0)
+ ct_shift_prod[i] = gyro_6050_st_tb[regs[i] - 1];
+ else
+ ct_shift_prod[i] = 0;
+ }
+ for (i = 0; i < 3; i++) {
+ st_shift_cust[i] = abs(reg_avg[i] - st_avg[i]);
+ if (ct_shift_prod[i]) {
+ st_shift_ratio[i] = st_shift_cust[i] /
+ ct_shift_prod[i] - DEF_ST_PRECISION;
+ if (st_shift_ratio[i] > DEF_GYRO_CT_SHIFT_DELTA)
+ ret_val |= 1 << i;
+ if (st_shift_ratio[i] < -DEF_GYRO_CT_SHIFT_DELTA)
+ ret_val |= 1 << i;
+ } else {
+ if (st_shift_cust[i] < DEF_ST_PRECISION *
+ DEF_GYRO_CT_SHIFT_MIN * test_setup.gyro_sens)
+ ret_val |= 1 << i;
+ if (st_shift_cust[i] > DEF_ST_PRECISION *
+ DEF_GYRO_CT_SHIFT_MAX * test_setup.gyro_sens)
+ ret_val |= 1 << i;
+ }
+ }
+ for (i = 0; i < 3; i++) {
+ if (abs(reg_avg[i]) * 4 > 20 * 2 *
+ DEF_ST_PRECISION * DEF_GYRO_SCALE)
+ ret_val |= (1 << i);
+ }
+
+ return ret_val;
+}
+
+/**
+ * inv_do_test() - do the actual test of self testing
+ */
+int inv_do_test(struct inv_mpu_iio_s *st, int self_test_flag,
+ int *gyro_result, int *accl_result)
+{
+ struct inv_reg_map_s *reg;
+ int result, i, j, packet_size;
+ u8 data[BYTES_PER_SENSOR * 2], has_accl;
+ int fifo_count, packet_count, ind;
+
+ reg = &st->reg;
+ has_accl = (st->chip_type != INV_ITG3500);
+ packet_size = BYTES_PER_SENSOR*(1 + has_accl);
+
+ result = inv_i2c_single_write(st, reg->int_enable, 0);
+ if (result)
+ return result;
+ /* disable the sensor output to FIFO */
+ result = inv_i2c_single_write(st, reg->fifo_en, 0);
+ if (result)
+ return result;
+ /* disable fifo reading */
+ result = inv_i2c_single_write(st, reg->user_ctrl, 0);
+ if (result)
+ return result;
+ /* clear FIFO */
+ result = inv_i2c_single_write(st, reg->user_ctrl, BIT_FIFO_RST);
+ if (result)
+ return result;
+ /* setup parameters */
+ result = inv_i2c_single_write(st, reg->lpf, test_setup.lpf);
+ if (result)
+ return result;
+ result = inv_i2c_single_write(st, reg->sample_rate_div,
+ test_setup.sample_rate);
+ if (result)
+ return result;
+ result = inv_i2c_single_write(st, reg->gyro_config,
+ self_test_flag | test_setup.fsr);
+ if (result)
+ return result;
+ if (has_accl) {
+ result = inv_i2c_single_write(st, reg->accl_config,
+ self_test_flag | test_setup.accl_fs);
+ if (result)
+ return result;
+ }
+ /* wait for the output to get stable */
+ if (self_test_flag)
+ msleep(DEF_ST_STABLE_TIME);
+
+ /* enable FIFO reading */
+ result = inv_i2c_single_write(st, reg->user_ctrl, BIT_FIFO_EN);
+ if (result)
+ return result;
+ /* enable sensor output to FIFO */
+ result = inv_i2c_single_write(st, reg->fifo_en, BITS_GYRO_OUT
+ | (has_accl << 3));
+ if (result)
+ return result;
+ mdelay(DEF_GYRO_WAIT_TIME);
+ /* stop sending data to FIFO */
+ result = inv_i2c_single_write(st, reg->fifo_en, 0);
+ if (result)
+ return result;
+ result = inv_i2c_read(st, reg->fifo_count_h, FIFO_COUNT_BYTE, data);
+ if (result)
+ return result;
+ fifo_count = be16_to_cpup((__be16 *)(&data[0]));
+ packet_count = fifo_count / packet_size;
+ for (i = 0; i < 3; i++) {
+ gyro_result[i] = 0;
+ accl_result[i] = 0;
+ }
+ if (abs(packet_count - DEF_GYRO_PACKET_THRESH) > DEF_GYRO_THRESH)
+ return -EAGAIN;
+
+ for (i = 0; i < packet_count; i++) {
+ /* getting FIFO data */
+ result = inv_i2c_read(st, reg->fifo_r_w,
+ packet_size, data);
+ if (result)
+ return result;
+ ind = 0;
+ if (has_accl) {
+ for (j = 0; j < THREE_AXIS; j++)
+ accl_result[j] +=
+ (short)be16_to_cpup(
+ (__be16 *)(&data[ind + 2 * j]));
+ ind += BYTES_PER_SENSOR;
+ }
+ for (j = 0; j < THREE_AXIS; j++)
+ gyro_result[j] +=
+ (short)be16_to_cpup(
+ (__be16 *)(&data[ind + 2 * j]));
+ }
+
+ gyro_result[0] = gyro_result[0] * DEF_ST_PRECISION / packet_count;
+ gyro_result[1] = gyro_result[1] * DEF_ST_PRECISION / packet_count;
+ gyro_result[2] = gyro_result[2] * DEF_ST_PRECISION / packet_count;
+ if (has_accl) {
+ accl_result[0] =
+ accl_result[0] * DEF_ST_PRECISION / packet_count;
+ accl_result[1] =
+ accl_result[1] * DEF_ST_PRECISION / packet_count;
+ accl_result[2] =
+ accl_result[2] * DEF_ST_PRECISION / packet_count;
+ }
+
+ return 0;
+}
+
+/**
+ * inv_recover_setting() recover the old settings after everything is done
+ */
+
+void inv_recover_setting(struct inv_mpu_iio_s *st)
+{
+ struct inv_reg_map_s *reg;
+ int data;
+ struct iio_dev *indio = iio_priv_to_dev(st);
+
+ reg = &st->reg;
+ set_inv_enable(indio, st->chip_config.enable);
+ inv_i2c_single_write(st, reg->gyro_config,
+ st->chip_config.fsr << GYRO_CONFIG_FSR_SHIFT);
+ inv_i2c_single_write(st, reg->lpf, st->chip_config.lpf);
+ data = ONE_K_HZ/st->chip_config.fifo_rate - 1;
+ inv_i2c_single_write(st, reg->sample_rate_div, data);
+ if (INV_ITG3500 != st->chip_type) {
+ inv_i2c_single_write(st, reg->accl_config,
+ (st->chip_config.accl_fs <<
+ ACCL_CONFIG_FSR_SHIFT));
+ }
+ st->set_power_state(st, !st->chip_config.is_asleep);
+}
+
+static int inv_check_compass_self_test(struct inv_mpu_iio_s *st)
+{
+ int result;
+ u8 data[6];
+ u8 counter, cntl;
+ short x, y, z;
+ u8 *sens;
+ sens = st->chip_info.compass_sens;
+
+ /* set to bypass mode */
+ result = inv_i2c_single_write(st, REG_INT_PIN_CFG,
+ st->plat_data.int_config | BIT_BYPASS_EN);
+ if (result) {
+ result = inv_i2c_single_write(st, REG_INT_PIN_CFG,
+ st->plat_data.int_config);
+ return result;
+ }
+ /* set to power down mode */
+ result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD);
+ if (result)
+ goto AKM_fail;
+
+ /* write 1 to ASTC register */
+ result = inv_secondary_write(REG_AKM_ST_CTRL, DATA_AKM_SELF_TEST);
+ if (result)
+ goto AKM_fail;
+ /* set self test mode */
+ result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_ST);
+ if (result)
+ goto AKM_fail;
+ counter = DEF_ST_COMPASS_TRY_TIMES;
+ while (counter > 0) {
+ usleep_range(DEF_ST_COMPASS_WAIT_MIN, DEF_ST_COMPASS_WAIT_MAX);
+ result = inv_secondary_read(REG_AKM_STATUS, 1, data);
+ if (result)
+ goto AKM_fail;
+ if ((data[0] & DATA_AKM_DRDY) == 0)
+ counter--;
+ else
+ counter = 0;
+ }
+ if ((data[0] & DATA_AKM_DRDY) == 0) {
+ result = -EINVAL;
+ goto AKM_fail;
+ }
+ result = inv_secondary_read(REG_AKM_MEASURE_DATA,
+ BYTES_PER_SENSOR, data);
+ if (result)
+ goto AKM_fail;
+
+ x = le16_to_cpup((__le16 *)(&data[0]));
+ y = le16_to_cpup((__le16 *)(&data[2]));
+ z = le16_to_cpup((__le16 *)(&data[4]));
+ x = ((x * (sens[0] + 128)) >> 8);
+ y = ((y * (sens[1] + 128)) >> 8);
+ z = ((z * (sens[2] + 128)) >> 8);
+ if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id) {
+ result = inv_secondary_read(REG_AKM8963_CNTL1, 1, &cntl);
+ if (result)
+ goto AKM_fail;
+ if (0 == (cntl & DATA_AKM8963_BIT)) {
+ x <<= DEF_ST_COMPASS_8963_SHIFT;
+ y <<= DEF_ST_COMPASS_8963_SHIFT;
+ z <<= DEF_ST_COMPASS_8963_SHIFT;
+ }
+ }
+ result = -EINVAL;
+ if (x > st->compass_st_upper[X] || x < st->compass_st_lower[X])
+ goto AKM_fail;
+ if (y > st->compass_st_upper[Y] || y < st->compass_st_lower[Y])
+ goto AKM_fail;
+ if (z > st->compass_st_upper[Z] || z < st->compass_st_lower[Z])
+ goto AKM_fail;
+ result = 0;
+AKM_fail:
+ /*write 0 to ASTC register */
+ result |= inv_secondary_write(REG_AKM_ST_CTRL, 0);
+ /*set to power down mode */
+ result |= inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD);
+ /*restore to non-bypass mode */
+ result |= inv_i2c_single_write(st, REG_INT_PIN_CFG,
+ st->plat_data.int_config);
+ return result;
+}
+
+static int inv_power_up_self_test(struct inv_mpu_iio_s *st)
+{
+ int result;
+ result = inv_i2c_single_write(st, st->reg.pwr_mgmt_1, INV_CLK_PLL);
+ if (result)
+ return result;
+ msleep(POWER_UP_TIME);
+ result = inv_i2c_single_write(st, st->reg.pwr_mgmt_2, 0);
+ if (result)
+ return result;
+ msleep(SENSOR_UP_TIME);
+
+ return 0;
+}
+
+/**
+ * inv_hw_self_test() - main function to do hardware self test
+ */
+int inv_hw_self_test(struct inv_mpu_iio_s *st)
+{
+ int result;
+ int gyro_bias_st[THREE_AXIS], gyro_bias_regular[THREE_AXIS];
+ int accl_bias_st[THREE_AXIS], accl_bias_regular[THREE_AXIS];
+ int test_times;
+ char compass_result, accel_result, gyro_result;
+ if (st->chip_config.is_asleep ||
+ st->chip_config.lpa_mode ||
+ (!st->chip_config.gyro_enable) ||
+ (!st->chip_config.accl_enable)) {
+ result = inv_power_up_self_test(st);
+ if (result)
+ return result;
+ }
+ compass_result = 0;
+ accel_result = 0;
+ gyro_result = 0;
+ test_times = DEF_ST_TRY_TIMES;
+ while (test_times > 0) {
+ result = inv_do_test(st, 0, gyro_bias_regular,
+ accl_bias_regular);
+ if (result == -EAGAIN)
+ test_times--;
+ else
+ test_times = 0;
+ }
+ if (result)
+ goto test_fail;
+
+ test_times = DEF_ST_TRY_TIMES;
+ while (test_times > 0) {
+ result = inv_do_test(st, BITS_SELF_TEST_EN, gyro_bias_st,
+ accl_bias_st);
+ if (result == -EAGAIN)
+ test_times--;
+ else
+ break;
+ }
+ if (result)
+ goto test_fail;
+ if (st->chip_type == INV_ITG3500) {
+ gyro_result = !inv_check_3500_gyro_self_test(st,
+ gyro_bias_regular, gyro_bias_st);
+ } else {
+ if (st->chip_config.has_compass)
+ compass_result = !inv_check_compass_self_test(st);
+ accel_result = !inv_check_accl_self_test(st,
+ accl_bias_regular, accl_bias_st);
+ gyro_result = !inv_check_6050_gyro_self_test(st,
+ gyro_bias_regular, gyro_bias_st);
+ }
+test_fail:
+ inv_recover_setting(st);
+
+ return (compass_result << DEF_ST_COMPASS_RESULT_SHIFT) |
+ (accel_result << DEF_ST_ACCEL_RESULT_SHIFT) | gyro_result;
+}
+
+/**
+ * inv_hw_self_test_6500() - main function to do hardware self test for 6500
+ */
+int inv_hw_self_test_6500(struct inv_mpu_iio_s *st)
+{
+ int compass_result;
+ compass_result = !inv_check_compass_self_test(st);
+ return compass_result << DEF_ST_COMPASS_RESULT_SHIFT;
+}
+
+static int inv_load_firmware(struct inv_mpu_iio_s *st,
+ u8 *data, int size)
+{
+ int bank, write_size;
+ int result;
+ u16 memaddr;
+
+ /* Write and verify memory */
+ for (bank = 0; size > 0; bank++,
+ size -= write_size,
+ data += write_size) {
+ if (size > MPU_MEM_BANK_SIZE)
+ write_size = MPU_MEM_BANK_SIZE;
+ else
+ write_size = size;
+
+ memaddr = ((bank << 8) | 0x00);
+
+ result = mem_w(memaddr, write_size, data);
+ if (result)
+ return result;
+ }
+ return 0;
+}
+
+static int inv_verify_firmware(struct inv_mpu_iio_s *st,
+ u8 *data, int size)
+{
+ int bank, write_size;
+ int result;
+ u16 memaddr;
+ u8 firmware[MPU_MEM_BANK_SIZE];
+
+ /* Write and verify memory */
+ for (bank = 0; size > 0; bank++,
+ size -= write_size,
+ data += write_size) {
+ if (size > MPU_MEM_BANK_SIZE)
+ write_size = MPU_MEM_BANK_SIZE;
+ else
+ write_size = size;
+
+ memaddr = ((bank << 8) | 0x00);
+ result = mpu_memory_read(st->sl_handle,
+ st->i2c_addr, memaddr, write_size, firmware);
+ if (result)
+ return result;
+ if (0 != memcmp(firmware, data, write_size))
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int inv_set_fifo_div(struct inv_mpu_iio_s *st,
+ u16 fifoRate)
+{
+ u8 regs[2];
+ int result = 0;
+ /*For some reason DINAC4 is defined as 0xb8, but DINBC4 is not*/
+ const u8 regs_end[12] = {DINAFE, DINAF2, DINAAB, 0xc4,
+ DINAAA, DINAF1, DINADF, DINADF,
+ 0xbb, 0xaf, DINADF, DINADF};
+
+ regs[0] = (u8)((fifoRate >> 8) & 0xff);
+ regs[1] = (u8)(fifoRate & 0xff);
+ result = mem_w_key(KEY_D_0_22, ARRAY_SIZE(regs), regs);
+ if (result)
+ return result;
+
+ /*Modify the FIFO handler to reset the tap/orient interrupt flags*/
+ /* each time the FIFO handler runs*/
+ result = mem_w_key(KEY_CFG_6, ARRAY_SIZE(regs_end), regs_end);
+
+ return result;
+}
+
+int inv_send_quaternion(struct inv_mpu_iio_s *st, bool on)
+{
+ const u8 regs_on[] = {DINBC0, DINBC2,
+ DINBC4, DINBC6};
+ const u8 regs_off[] = {DINA80, DINA80,
+ DINA80, DINA80};
+ const u8 *regs;
+ u8 result;
+ if (on)
+ regs = regs_on;
+ else
+ regs = regs_off;
+ result = mem_w_key(KEY_CFG_LP_QUAT, ARRAY_SIZE(regs_on), regs);
+
+ return result;
+}
+
+int inv_set_display_orient_interrupt_dmp(struct inv_mpu_iio_s *st,
+ bool on)
+{
+ /*Turn on the display orientation interrupt in the DMP*/
+ int result;
+ u8 regs[] = {0xd8};
+
+ if (on)
+ regs[0] = 0xd9;
+ result = mem_w_key(KEY_CFG_DISPLAY_ORIENT_INT, 1, regs);
+ return result;
+}
+
+int inv_set_fifo_rate(struct inv_mpu_iio_s *st, u16 fifo_rate)
+{
+ u8 divider;
+ int result;
+
+ divider = (u8)(ONE_K_HZ / fifo_rate) - 1;
+ if (divider > DMP_MAX_DIVIDER) {
+ st->sample_divider = DMP_MAX_DIVIDER;
+ st->fifo_divider =
+ (u8)(DMP_DEFAULT_FIFO_RATE / fifo_rate) - 1;
+ } else {
+ st->sample_divider = divider;
+ st->fifo_divider = 0;
+ }
+
+ result = inv_set_fifo_div(st, st->fifo_divider);
+ return result;
+}
+
+static int inv_set_tap_interrupt_dmp(struct inv_mpu_iio_s *st,
+ u8 on)
+{
+ int result;
+ u8 regs[] = {0};
+
+ if (on)
+ regs[0] = 0xf8;
+ else
+ regs[0] = DINAD8;
+ result = mem_w_key(KEY_CFG_20, ARRAY_SIZE(regs), regs);
+ if (result)
+ return result;
+ return result;
+}
+
+static int inv_set_orientation_interrupt_dmp(struct inv_mpu_iio_s *st,
+ u8 on)
+{
+ int result;
+ u8 regs[2];
+ if (on) {
+ regs[0] = DINBF8;
+ regs[1] = DINBF8;
+ } else {
+ regs[0] = DINAD8;
+ regs[1] = DINAD8;
+ }
+ result = mem_w_key(KEY_CFG_ORIENT_IRQ_1, ARRAY_SIZE(regs), regs);
+ if (result)
+ return result;
+ return result;
+}
+
+int inv_set_tap_threshold_dmp(struct inv_mpu_iio_s *st,
+ u32 axis, u16 threshold)
+{
+ /* Sets the tap threshold in the dmp
+ Simultaneously sets secondary tap threshold to help correct the tap
+ direction for soft taps */
+ int result;
+ /* DMP Algorithm */
+ u8 data[2];
+ int sampleDivider;
+ int scaledThreshold;
+ u32 dmpThreshold;
+ u8 sample_div;
+ const u32 accel_sens = (0x20000000 / 0x00010000);
+
+ if ((axis & ~(INV_TAP_AXIS_ALL)) || (threshold > (1 << 15)))
+ return -EINVAL;
+ sample_div = st->sample_divider;
+
+ sampleDivider = (1 + sample_div);
+ /* Scale factor corresponds linearly using
+ * 0 : 0
+ * 25 : 0.0250 g/ms
+ * 50 : 0.0500 g/ms
+ * 100: 1.0000 g/ms
+ * 200: 2.0000 g/ms
+ * 400: 4.0000 g/ms
+ * 800: 8.0000 g/ms
+ */
+ /*multiply by 1000 to avoid floating point 1000/1000*/
+ scaledThreshold = threshold;
+ /* Convert to per sample */
+ scaledThreshold *= sampleDivider;
+
+ /* Scale to DMP 16 bit value */
+ if (accel_sens != 0)
+ dmpThreshold = (u32)(scaledThreshold * accel_sens);
+ else
+ return -EINVAL;
+ dmpThreshold = dmpThreshold / DMP_PRECISION;
+
+ data[0] = dmpThreshold >> 8;
+ data[1] = dmpThreshold & 0xFF;
+
+ /* MPL algorithm */
+ if (axis & INV_TAP_AXIS_X) {
+ result = mem_w_key(KEY_DMP_TAP_THR_X, ARRAY_SIZE(data), data);
+ if (result)
+ return result;
+
+ /*Also set additional threshold for correcting the direction
+ of taps that were very near the threshold. */
+ data[0] = (dmpThreshold * 3 / 4) >> 8;
+ data[1] = (dmpThreshold * 3 / 4) & 0xFF;
+ result = mem_w_key(KEY_D_1_36, ARRAY_SIZE(data), data);
+ if (result)
+ return result;
+ }
+ if (axis & INV_TAP_AXIS_Y) {
+ result = mem_w_key(KEY_DMP_TAP_THR_Y, 2, data);
+ if (result)
+ return result;
+ data[0] = (dmpThreshold * 3 / 4) >> 8;
+ data[1] = (dmpThreshold * 3 / 4) & 0xFF;
+
+ result = mem_w_key(KEY_D_1_40, ARRAY_SIZE(data), data);
+ if (result)
+ return result;
+ }
+ if (axis & INV_TAP_AXIS_Z) {
+ result = mem_w_key(KEY_DMP_TAP_THR_Z, ARRAY_SIZE(data), data);
+ if (result)
+ return result;
+ data[0] = (dmpThreshold * 3 / 4) >> 8;
+ data[1] = (dmpThreshold * 3 / 4) & 0xFF;
+
+ result = mem_w_key(KEY_D_1_44, ARRAY_SIZE(data), data);
+ if (result)
+ return result;
+ }
+ return 0;
+}
+
+static int inv_set_tap_axes_dmp(struct inv_mpu_iio_s *st,
+ u32 axes)
+{
+ /* Sets a mask in the DMP that indicates what tap events
+ should result in an interrupt */
+ u8 regs[4];
+ u8 result;
+
+ /* check if any spurious bit other the ones expected are set */
+ if (axes & (~(INV_TAP_ALL_DIRECTIONS)))
+ return -EINVAL;
+
+ regs[0] = (u8)axes;
+ result = mem_w_key(KEY_D_1_72, 1, regs);
+
+ return result;
+}
+
+int inv_set_min_taps_dmp(struct inv_mpu_iio_s *st,
+ u16 min_taps) {
+ /*Indicates the minimum number of consecutive taps required
+ before the DMP will generate an interrupt */
+ u8 regs[1];
+ u8 result;
+ /* check if any spurious bit other the ones expected are set */
+ if ((min_taps > DMP_MAX_MIN_TAPS) || (min_taps < 1))
+ return -EINVAL;
+ regs[0] = (u8)(min_taps-1);
+ result = mem_w_key(KEY_D_1_79, ARRAY_SIZE(regs), regs);
+
+ return result;
+}
+
+int inv_set_tap_time_dmp(struct inv_mpu_iio_s *st, u16 time)
+{
+ /* Determines how long after a tap the DMP requires before
+ another tap can be registered*/
+ int result;
+ /* DMP Algorithm */
+ u16 dmpTime;
+ u8 data[2];
+ u8 sampleDivider;
+
+ sampleDivider = st->sample_divider;
+ sampleDivider++;
+
+ /* 60 ms minimum time added */
+ dmpTime = ((time) / sampleDivider);
+ data[0] = dmpTime >> 8;
+ data[1] = dmpTime & 0xFF;
+
+ result = mem_w_key(KEY_DMP_TAPW_MIN, ARRAY_SIZE(data), data);
+
+ return result;
+}
+
+static int inv_set_multiple_tap_time_dmp(struct inv_mpu_iio_s *st,
+ u32 time)
+{
+ /*Determines how close together consecutive taps must occur
+ to be considered double/triple taps*/
+ int result;
+ /* DMP Algorithm */
+ u16 dmpTime;
+ u8 data[2];
+ u8 sampleDivider;
+
+ sampleDivider = st->sample_divider;
+ sampleDivider++;
+
+ /* 60 ms minimum time added */
+ dmpTime = ((time) / sampleDivider);
+ data[0] = dmpTime >> 8;
+ data[1] = dmpTime & 0xFF;
+ result = mem_w_key(KEY_D_1_218, ARRAY_SIZE(data), data);
+
+ return result;
+}
+
+int inv_q30_mult(int a, int b)
+{
+ u64 temp;
+ int result;
+ temp = (u64)a * b;
+ result = (int)(temp >> DMP_MULTI_SHIFT);
+
+ return result;
+}
+
+static u16 inv_row_2_scale(const signed char *row)
+{
+ u16 b;
+
+ if (row[0] > 0)
+ b = 0;
+ else if (row[0] < 0)
+ b = 4;
+ else if (row[1] > 0)
+ b = 1;
+ else if (row[1] < 0)
+ b = 5;
+ else if (row[2] > 0)
+ b = 2;
+ else if (row[2] < 0)
+ b = 6;
+ else
+ b = 7;
+
+ return b;
+}
+
+/** Converts an orientation matrix made up of 0,+1,and -1 to a scalar
+* representation.
+* @param[in] mtx Orientation matrix to convert to a scalar.
+* @return Description of orientation matrix. The lowest 2 bits (0 and 1)
+* represent the column the one is on for the
+* first row, with the bit number 2 being the sign. The next 2 bits
+* (3 and 4) represent
+* the column the one is on for the second row with bit number 5 being
+* the sign.
+* The next 2 bits (6 and 7) represent the column the one is on for the
+* third row with
+* bit number 8 being the sign. In binary the identity matrix would therefor
+* be: 010_001_000 or 0x88 in hex.
+*/
+static u16 inv_orientation_matrix_to_scaler(const signed char *mtx)
+{
+
+ u16 scalar;
+ scalar = inv_row_2_scale(mtx);
+ scalar |= inv_row_2_scale(mtx + 3) << 3;
+ scalar |= inv_row_2_scale(mtx + 6) << 6;
+
+ return scalar;
+}
+
+static int inv_gyro_dmp_cal(struct inv_mpu_iio_s *st)
+{
+ int inv_gyro_orient;
+ u8 regs[3];
+ int result;
+
+ u8 tmpD = DINA4C;
+ u8 tmpE = DINACD;
+ u8 tmpF = DINA6C;
+
+ inv_gyro_orient =
+ inv_orientation_matrix_to_scaler(st->plat_data.orientation);
+
+ if ((inv_gyro_orient & 3) == 0)
+ regs[0] = tmpD;
+ else if ((inv_gyro_orient & 3) == 1)
+ regs[0] = tmpE;
+ else if ((inv_gyro_orient & 3) == 2)
+ regs[0] = tmpF;
+ if ((inv_gyro_orient & 0x18) == 0)
+ regs[1] = tmpD;
+ else if ((inv_gyro_orient & 0x18) == 0x8)
+ regs[1] = tmpE;
+ else if ((inv_gyro_orient & 0x18) == 0x10)
+ regs[1] = tmpF;
+ if ((inv_gyro_orient & 0xc0) == 0)
+ regs[2] = tmpD;
+ else if ((inv_gyro_orient & 0xc0) == 0x40)
+ regs[2] = tmpE;
+ else if ((inv_gyro_orient & 0xc0) == 0x80)
+ regs[2] = tmpF;
+
+ result = mem_w_key(KEY_FCFG_1, 3, regs);
+ if (result)
+ return result;
+
+ if (inv_gyro_orient & 4)
+ regs[0] = DINA36 | 1;
+ else
+ regs[0] = DINA36;
+ if (inv_gyro_orient & 0x20)
+ regs[1] = DINA56 | 1;
+ else
+ regs[1] = DINA56;
+ if (inv_gyro_orient & 0x100)
+ regs[2] = DINA76 | 1;
+ else
+ regs[2] = DINA76;
+
+ result = mem_w_key(KEY_FCFG_3, ARRAY_SIZE(regs), regs);
+
+ return result;
+}
+
+static int inv_accel_dmp_cal(struct inv_mpu_iio_s *st)
+{
+ int inv_accel_orient;
+ int result;
+ u8 regs[3];
+ const u8 tmp[3] = { DINA0C, DINAC9, DINA2C };
+ inv_accel_orient =
+ inv_orientation_matrix_to_scaler(st->plat_data.orientation);
+
+ regs[0] = tmp[inv_accel_orient & 3];
+ regs[1] = tmp[(inv_accel_orient >> 3) & 3];
+ regs[2] = tmp[(inv_accel_orient >> 6) & 3];
+ result = mem_w_key(KEY_FCFG_2, 3, regs);
+ if (result)
+ return result;
+
+ regs[0] = DINA26;
+ regs[1] = DINA46;
+ regs[2] = DINA66;
+ if (inv_accel_orient & 4)
+ regs[0] |= 1;
+ if (inv_accel_orient & 0x20)
+ regs[1] |= 1;
+ if (inv_accel_orient & 0x100)
+ regs[2] |= 1;
+ result = mem_w_key(KEY_FCFG_7, ARRAY_SIZE(regs), regs);
+
+ return result;
+}
+
+static int inv_set_gyro_sf_dmp(struct inv_mpu_iio_s *st)
+{
+ /*The gyro threshold, in dps, above which taps will be rejected*/
+ int result, out;
+ /* DMP Algorithm */
+ u8 sampleDivider;
+ u8 *regs;
+ u32 gyro_sf;
+ const u32 gyro_sens = 0x03e80000;
+
+ sampleDivider = st->sample_divider;
+ gyro_sf = inv_q30_mult(gyro_sens,
+ (int)(DMP_TAP_SCALE * (sampleDivider + 1)));
+
+ out = cpu_to_be32p(&gyro_sf);
+ regs = (u8 *)&out;
+ result = mem_w_key(KEY_D_0_104, sizeof(out), regs);
+
+ return result;
+}
+
+static int inv_set_shake_reject_thresh_dmp(struct inv_mpu_iio_s *st,
+ int thresh)
+{ /*THIS FUNCTION FAILS MEM_W*/
+ /*The gyro threshold, in dps, above which taps will be rejected */
+ int result, out;
+ /* DMP Algorithm */
+ u8 sampleDivider;
+ int thresh_scaled;
+ u8 *regs;
+ u32 gyro_sf;
+ const u32 gyro_sens = 0x03e80000;
+ sampleDivider = st->sample_divider;
+ gyro_sf = inv_q30_mult(gyro_sens, (int)(DMP_TAP_SCALE *
+ (sampleDivider + 1)));
+ /* We're in units of DPS, convert it back to chip units*/
+ /*split the operation to aviod overflow of integer*/
+ thresh_scaled = gyro_sens / (1L << 16);
+ thresh_scaled = thresh_scaled / thresh;
+ thresh_scaled = gyro_sf / thresh_scaled;
+ out = cpu_to_be32p(&thresh_scaled);
+ regs = (u8 *)&out;
+
+ result = mem_w_key(KEY_D_1_92, sizeof(out), regs);
+ return result;
+}
+
+static int inv_set_shake_reject_time_dmp(struct inv_mpu_iio_s *st,
+ u32 time)
+{
+ /* How long a gyro axis must remain above its threshold
+ before taps are rejected */
+ int result;
+ /* DMP Algorithm */
+ u16 dmpTime;
+ u8 data[2];
+ u8 sampleDivider;
+
+ sampleDivider = st->sample_divider;
+ sampleDivider++;
+
+ /* 60 ms minimum time added */
+ dmpTime = ((time) / sampleDivider);
+ data[0] = dmpTime >> 8;
+ data[1] = dmpTime & 0xFF;
+
+ result = mem_w_key(KEY_D_1_88, ARRAY_SIZE(data), data);
+ return result;
+}
+
+static int inv_set_shake_reject_timeout_dmp(struct inv_mpu_iio_s *st,
+ u32 time)
+{
+ /*How long the gyros must remain below their threshold,
+ after taps have been rejected, before taps can be detected again*/
+ int result;
+ /* DMP Algorithm */
+ u16 dmpTime;
+ u8 data[2];
+ u8 sampleDivider;
+
+ sampleDivider = st->sample_divider;
+ sampleDivider++;
+
+ /* 60 ms minimum time added */
+ dmpTime = ((time) / sampleDivider);
+ data[0] = dmpTime >> 8;
+ data[1] = dmpTime & 0xFF;
+
+ result = mem_w_key(KEY_D_1_90, ARRAY_SIZE(data), data);
+ return result;
+}
+
+int inv_set_interrupt_on_gesture_event(struct inv_mpu_iio_s *st, bool on)
+{
+ u8 result;
+ const u8 regs_on[] = {DINADA, DINADA, DINAB1, DINAB9,
+ DINAF3, DINA8B, DINAA3, DINA91,
+ DINAB6, DINADA, DINAB4, DINADA};
+ const u8 regs_off[] = {0xd8, 0xd8, 0xb1, 0xb9, 0xf3, 0x8b,
+ 0xa3, 0x91, 0xb6, 0x09, 0xb4, 0xd9};
+ /*For some reason DINAC4 is defined as 0xb8,
+ but DINBC4 is not defined.*/
+ const u8 regs_end[] = {DINAFE, DINAF2, DINAAB, 0xc4,
+ DINAAA, DINAF1, DINADF, DINADF};
+ if (on)
+ /*Sets the DMP to send an interrupt and put a FIFO packet
+ in the FIFO if and only if a tap/orientation event
+ just occurred*/
+ result = mem_w_key(KEY_CFG_FIFO_ON_EVENT, ARRAY_SIZE(regs_on),
+ regs_on);
+ else
+ /*Sets the DMP to send an interrupt and put a FIFO packet
+ in the FIFO at the rate specified by the FIFO div.
+ see inv_set_fifo_div in hw_setup.c to set the FIFO div.*/
+ result = mem_w_key(KEY_CFG_FIFO_ON_EVENT, ARRAY_SIZE(regs_off),
+ regs_off);
+ if (result)
+ return result;
+
+ result = mem_w_key(KEY_CFG_6, ARRAY_SIZE(regs_end), regs_end);
+ return result;
+}
+
+/**
+ * inv_enable_tap_dmp() - calling this function will enable/disable tap function.
+ */
+int inv_enable_tap_dmp(struct inv_mpu_iio_s *st, bool on)
+{
+ int result;
+ result = inv_set_tap_interrupt_dmp(st, on);
+ if (result)
+ return result;
+ if (on) {
+ result = inv_set_tap_threshold_dmp(st, INV_TAP_AXIS_X,
+ st->tap.thresh);
+ if (result)
+ return result;
+ result = inv_set_tap_threshold_dmp(st, INV_TAP_AXIS_Y,
+ st->tap.thresh);
+ if (result)
+ return result;
+ result = inv_set_tap_threshold_dmp(st, INV_TAP_AXIS_Z,
+ st->tap.thresh);
+ if (result)
+ return result;
+ }
+
+ result = inv_set_tap_axes_dmp(st, INV_TAP_ALL_DIRECTIONS);
+ if (result)
+ return result;
+ result = inv_set_min_taps_dmp(st, st->tap.min_count);
+ if (result)
+ return result;
+
+ result = inv_set_tap_time_dmp(st, st->tap.time);
+ if (result)
+ return result;
+
+ result = inv_set_multiple_tap_time_dmp(st, DMP_MULTI_TAP_TIME);
+ if (result)
+ return result;
+
+ result = inv_set_gyro_sf_dmp(st);
+ if (result)
+ return result;
+
+ result = inv_set_shake_reject_thresh_dmp(st, DMP_SHAKE_REJECT_THRESH);
+ if (result)
+ return result;
+
+ result = inv_set_shake_reject_time_dmp(st, DMP_SHAKE_REJECT_TIME);
+ if (result)
+ return result;
+
+ result = inv_set_shake_reject_timeout_dmp(st,
+ DMP_SHAKE_REJECT_TIMEOUT);
+ return result;
+}
+
+static int inv_set_orientation_dmp(struct inv_mpu_iio_s *st,
+ int orientation)
+{
+ /*Set a mask in the DMP determining what orientations
+ will trigger interrupts*/
+ u8 regs[4];
+ u8 result;
+
+ /* check if any spurious bit other the ones expected are set */
+ if (orientation & (~(INV_ORIENTATION_ALL | INV_ORIENTATION_FLIP)))
+ return -EINVAL;
+
+ regs[0] = (u8)orientation;
+ result = mem_w_key(KEY_D_1_74, 1, regs);
+ return result;
+}
+
+static int inv_set_orientation_thresh_dmp(struct inv_mpu_iio_s *st,
+ int angle)
+{
+ /*Set an angle threshold in the DMP determining
+ when orientations change*/
+ u8 *regs;
+ u8 result;
+ u32 out;
+ u32 d;
+ const u32 threshold[] = {138952416, 268435455, 379625062,
+ 464943848, 518577479, 536870912};
+ /* The real calculation is
+ * threshold = (long)((1 << 29) * sin((angle * M_PI) / 180.));
+ * Here we have to use table lookup*/
+ d = angle / DMP_ANGLE_SCALE;
+ d -= 1;
+ if (d >= ARRAY_SIZE(threshold))
+ return -EPERM;
+ out = cpu_to_be32p(&threshold[d]);
+ regs = (u8 *)&out;
+
+ result = mem_w_key(KEY_D_1_232, sizeof(out), regs);
+ return result;
+}
+
+static int inv_set_orientation_time_dmp(struct inv_mpu_iio_s *st,
+ u32 time)
+{
+ /*Determines the stability time required before a
+ new orientation can be adopted */
+ u16 dmpTime;
+ u8 data[2];
+ u8 sampleDivider;
+ u8 result;
+ /* First check if we are allowed to call this function here */
+ sampleDivider = st->sample_divider;
+ sampleDivider++;
+ /* 60 ms minimum time added */
+ dmpTime = ((time) / sampleDivider);
+ data[0] = dmpTime >> 8;
+ data[1] = dmpTime & 0xFF;
+ result = mem_w_key(KEY_D_1_250, 2, data);
+
+ return result;
+}
+
+/**
+ * inv_enable_orientation_dmp() - calling this function will
+ * enable/disable orientation function.
+ */
+int inv_enable_orientation_dmp(struct inv_mpu_iio_s *st, bool on)
+{
+ int result;
+ result = inv_set_orientation_interrupt_dmp(st, on);
+ if (result)
+ return result;
+ result = inv_set_orientation_dmp(st, 0x40 | INV_ORIENTATION_ALL);
+ if (result)
+ return result;
+ result = inv_set_gyro_sf_dmp(st);
+ if (result)
+ return result;
+ result = inv_set_orientation_thresh_dmp(st, DMP_ORIENTATION_ANGLE);
+ if (result)
+ return result;
+ result = inv_set_orientation_time_dmp(st, DMP_ORIENTATION_TIME);
+ return result;
+}
+
+static int inv_send_sensor_data(struct inv_mpu_iio_s *st,
+ u16 elements)
+{
+ int result;
+ u8 regs[] = {DINAA0 + 3, DINAA0 + 3, DINAA0 + 3,
+ DINAA0 + 3, DINAA0 + 3, DINAA0 + 3,
+ DINAA0 + 3, DINAA0 + 3, DINAA0 + 3,
+ DINAA0 + 3};
+
+ if (elements & INV_ELEMENT_1)
+ regs[0] = DINACA;
+ if (elements & INV_ELEMENT_2)
+ regs[4] = DINBC4;
+ if (elements & INV_ELEMENT_3)
+ regs[5] = DINACC;
+ if (elements & INV_ELEMENT_4)
+ regs[6] = DINBC6;
+ if ((elements & INV_ELEMENT_5) || (elements & INV_ELEMENT_6) ||
+ (elements & INV_ELEMENT_7)) {
+ regs[1] = DINBC0;
+ regs[2] = DINAC8;
+ regs[3] = DINBC2;
+ }
+ result = mem_w_key(KEY_CFG_15, ARRAY_SIZE(regs), regs);
+ return result;
+}
+
+static int inv_send_interrupt_word(struct inv_mpu_iio_s *st)
+{
+ const u8 regs[] = { DINA20 };
+ u8 result;
+
+ result = mem_w_key(KEY_CFG_27, ARRAY_SIZE(regs), regs);
+ return result;
+}
+
+/**
+ * inv_dmp_firmware_write() - calling this function will load the firmware.
+ * This is the write function of file "dmp_firmware".
+ */
+ssize_t inv_dmp_firmware_write(struct file *fp, struct kobject *kobj,
+ struct bin_attribute *attr,
+ char *buf, loff_t pos, size_t size)
+{
+ u8 *firmware;
+ int result;
+ uint32_t crc;
+ int crc_idx;
+ struct inv_reg_map_s *reg;
+ struct iio_dev *indio_dev;
+ struct inv_mpu_iio_s *st;
+
+ indio_dev = dev_get_drvdata(container_of(kobj, struct device, kobj));
+ st = iio_priv(indio_dev);
+
+ if (st->chip_config.is_asleep || st->chip_config.firmware_loaded)
+ return -EINVAL;
+
+ reg = &st->reg;
+ if (DMP_IMAGE_SIZE != size) {
+ pr_err("wrong DMP image size\n");
+ return -EINVAL;
+ }
+
+ firmware = kmalloc(size, GFP_KERNEL);
+ if (!firmware)
+ return -ENOMEM;
+
+ memcpy(firmware, buf, size);
+ crc = crc32(CRC_FIRMWARE_SEED, firmware, size);
+ for (crc_idx = 0; crc_idx < ARRAY_SIZE(DMP_IMAGE_CRC_VALUES); crc_idx++)
+ if (DMP_IMAGE_CRC_VALUES[crc_idx] == crc)
+ break;
+ if (crc_idx >= ARRAY_SIZE(DMP_IMAGE_CRC_VALUES)) {
+ pr_err("firmware CRC error. Got 0x%08x\n", crc);
+ result = -EINVAL;
+ goto firmware_write_fail;
+ }
+
+ result = inv_load_firmware(st, firmware, size);
+ if (result)
+ goto firmware_write_fail;
+
+ result = inv_verify_firmware(st, firmware, size);
+ if (result)
+ goto firmware_write_fail;
+
+ result = inv_i2c_single_write(st, reg->prgm_strt_addrh,
+ st->chip_config.prog_start_addr >> 8);
+ if (result)
+ goto firmware_write_fail;
+ result = inv_i2c_single_write(st, reg->prgm_strt_addrh + 1,
+ st->chip_config.prog_start_addr & 0xff);
+ if (result)
+ goto firmware_write_fail;
+
+ result = inv_verify_firmware(st, firmware, size);
+ if (result)
+ goto firmware_write_fail;
+ result = inv_set_fifo_rate(st, DMP_DEFAULT_FIFO_RATE);
+ if (result)
+ goto firmware_write_fail;
+ result = inv_send_sensor_data(st, INV_GYRO_ACC_MASK);
+ if (result)
+ goto firmware_write_fail;
+ result = inv_send_interrupt_word(st);
+ if (result)
+ goto firmware_write_fail;
+ result = inv_gyro_dmp_cal(st);
+ if (result)
+ goto firmware_write_fail;
+ result = inv_accel_dmp_cal(st);
+ if (result)
+ goto firmware_write_fail;
+ st->chip_config.firmware_loaded = 1;
+ pr_info("firmware loaded CRC=0x%08x\n", DMP_IMAGE_CRC_VALUES[crc_idx]);
+ result = size;
+firmware_write_fail:
+ kfree(firmware);
+
+ return result;
+}
+
+ssize_t inv_dmp_firmware_read(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ int bank, write_size, size, data, result;
+ u16 memaddr;
+ struct iio_dev *indio_dev;
+ struct inv_mpu_iio_s *st;
+ size = count;
+
+ indio_dev = dev_get_drvdata(container_of(kobj, struct device, kobj));
+ st = iio_priv(indio_dev);
+ data = 0;
+ for (bank = 0; size > 0; bank++, size -= write_size,
+ data += write_size) {
+ if (size > MPU_MEM_BANK_SIZE)
+ write_size = MPU_MEM_BANK_SIZE;
+ else
+ write_size = size;
+
+ memaddr = (bank << 8);
+ result = mpu_memory_read(st->sl_handle,
+ st->i2c_addr, memaddr, write_size, &buf[data]);
+ if (result)
+ return result;
+ }
+
+ return 0;
+}
+/**
+ * @}
+ */
+
diff --git a/drivers/staging/iio/imu/mpu/inv_mpu_ring.c b/drivers/staging/iio/imu/mpu/inv_mpu_ring.c
new file mode 100644
index 0000000..62d2bb0
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/inv_mpu_ring.c
@@ -0,0 +1,830 @@
+/*
+* Copyright (C) 2012 Invensense, 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_mpu_ring.c
+ * @brief A sysfs device driver for Invensense gyroscopes.
+ * @details This file is part of inv mpu iio driver code
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include "inv_mpu_iio.h"
+#include "../../iio.h"
+#include "../../kfifo_buf.h"
+#include "../../trigger_consumer.h"
+#include "../../sysfs.h"
+
+static void inv_scan_query(struct iio_dev *indio_dev)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+
+ if (iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_GYRO_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_GYRO_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_GYRO_Z))
+ st->chip_config.gyro_fifo_enable = 1;
+ else
+ st->chip_config.gyro_fifo_enable = 0;
+
+ if (iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_ACCL_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_ACCL_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_ACCL_Z))
+ st->chip_config.accl_fifo_enable = 1;
+ else
+ st->chip_config.accl_fifo_enable = 0;
+
+ if (iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_MAGN_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_MAGN_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_MAGN_Z))
+ st->chip_config.compass_fifo_enable = 1;
+ else
+ st->chip_config.compass_fifo_enable = 0;
+}
+
+/**
+ * reset_fifo_mpu3050() - Reset FIFO related registers
+ * @st: Device driver instance.
+ */
+static int reset_fifo_mpu3050(struct iio_dev *indio_dev)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ unsigned char val, user_ctrl;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ reg = &st->reg;
+
+ inv_scan_query(indio_dev);
+ /* disable interrupt */
+ result = inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config);
+ if (result)
+ return result;
+ /* disable the sensor output to FIFO */
+ result = inv_i2c_single_write(st, reg->fifo_en, 0);
+ if (result)
+ goto reset_fifo_fail;
+ result = inv_i2c_read(st, reg->user_ctrl, 1, &user_ctrl);
+ if (result)
+ goto reset_fifo_fail;
+ /* disable fifo reading */
+ user_ctrl &= ~BIT_FIFO_EN;
+ st->chip_config.has_footer = 0;
+ /* reset fifo */
+ val = (BIT_3050_FIFO_RST | user_ctrl);
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+ st->last_isr_time = get_time_ns();
+ if (st->chip_config.dmp_on) {
+ /* enable interrupt when DMP is done */
+ result = inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config | BIT_DMP_INT_EN);
+ if (result)
+ return result;
+
+ result = inv_i2c_single_write(st, reg->user_ctrl,
+ BIT_FIFO_EN|user_ctrl);
+ if (result)
+ return result;
+ } else {
+ /* enable interrupt */
+ if (st->chip_config.accl_fifo_enable ||
+ st->chip_config.gyro_fifo_enable) {
+ result = inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config | BIT_DATA_RDY_EN);
+ if (result)
+ return result;
+ }
+ /* enable FIFO reading and I2C master interface*/
+ result = inv_i2c_single_write(st, reg->user_ctrl,
+ BIT_FIFO_EN | user_ctrl);
+ if (result)
+ return result;
+ /* enable sensor output to FIFO and FIFO footer*/
+ val = 1;
+ if (st->chip_config.accl_fifo_enable)
+ val |= BITS_3050_ACCL_OUT;
+ if (st->chip_config.gyro_fifo_enable)
+ val |= BITS_GYRO_OUT;
+ result = inv_i2c_single_write(st, reg->fifo_en, val);
+ if (result)
+ return result;
+ }
+
+ return 0;
+reset_fifo_fail:
+ if (st->chip_config.dmp_on)
+ val = BIT_DMP_INT_EN;
+ else
+ val = BIT_DATA_RDY_EN;
+ inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config | val);
+ pr_err("reset fifo failed\n");
+
+ return result;
+}
+
+/**
+ * reset_fifo_itg() - Reset FIFO related registers.
+ * @st: Device driver instance.
+ */
+static int reset_fifo_itg(struct iio_dev *indio_dev)
+{
+ struct inv_reg_map_s *reg;
+ int result, data;
+ unsigned char val;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ reg = &st->reg;
+
+ inv_scan_query(indio_dev);
+ /* disable interrupt */
+ result = inv_i2c_single_write(st, reg->int_enable, 0);
+ if (result) {
+ pr_err("int_enable write failed\n");
+ return result;
+ }
+ /* disable the sensor output to FIFO */
+ result = inv_i2c_single_write(st, reg->fifo_en, 0);
+ if (result)
+ goto reset_fifo_fail;
+ /* disable fifo reading */
+ result = inv_i2c_single_write(st, reg->user_ctrl, 0);
+ if (result)
+ goto reset_fifo_fail;
+
+ if (st->chip_config.dmp_on) {
+ val = (BIT_FIFO_RST | BIT_DMP_RST);
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+ st->last_isr_time = get_time_ns();
+ if (st->chip_config.dmp_int_on) {
+ result = inv_i2c_single_write(st, reg->int_enable,
+ BIT_DMP_INT_EN);
+ if (result)
+ return result;
+ }
+ val = (BIT_DMP_EN | BIT_FIFO_EN);
+ if (st->chip_config.compass_enable &
+ (!st->chip_config.dmp_event_int_on))
+ val |= BIT_I2C_MST_EN;
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+
+ if (st->chip_config.compass_enable) {
+ /* I2C_MST_DLY is set according to sample rate,
+ slow down the power*/
+ data = st->chip_config.fifo_rate /
+ st->chip_config.dmp_output_rate;
+ if (data > 0)
+ data -= 1;
+ result = inv_i2c_single_write(st, REG_I2C_SLV4_CTRL,
+ data);
+ if (result)
+ return result;
+ }
+ } else {
+ /* reset FIFO and possibly reset I2C*/
+ val = BIT_FIFO_RST;
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+ st->last_isr_time = get_time_ns();
+ /* enable interrupt */
+ if (st->chip_config.accl_fifo_enable ||
+ st->chip_config.gyro_fifo_enable ||
+ st->chip_config.compass_enable) {
+ result = inv_i2c_single_write(st, reg->int_enable,
+ BIT_DATA_RDY_EN);
+ if (result)
+ return result;
+ }
+ /* enable FIFO reading and I2C master interface*/
+ val = BIT_FIFO_EN;
+ if (st->chip_config.compass_enable)
+ val |= BIT_I2C_MST_EN;
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+ if (st->chip_config.compass_enable) {
+ /* I2C_MST_DLY is set according to sample rate,
+ slow down the power*/
+ data = COMPASS_RATE_SCALE *
+ st->chip_config.fifo_rate / ONE_K_HZ;
+ if (data > 0)
+ data -= 1;
+ result = inv_i2c_single_write(st, REG_I2C_SLV4_CTRL,
+ data);
+ if (result)
+ return result;
+ }
+ /* enable sensor output to FIFO */
+ val = 0;
+ if (st->chip_config.gyro_fifo_enable)
+ val |= BITS_GYRO_OUT;
+ if (st->chip_config.accl_fifo_enable)
+ val |= BIT_ACCEL_OUT;
+ result = inv_i2c_single_write(st, reg->fifo_en, val);
+ if (result)
+ goto reset_fifo_fail;
+ }
+ return 0;
+reset_fifo_fail:
+ if (st->chip_config.dmp_on)
+ val = BIT_DMP_INT_EN;
+ else
+ val = BIT_DATA_RDY_EN;
+ inv_i2c_single_write(st, reg->int_enable, val);
+ pr_err("reset fifo failed\n");
+
+ return result;
+}
+
+/**
+ * inv_reset_fifo() - Reset FIFO related registers.
+ * @st: Device driver instance.
+ */
+static int inv_reset_fifo(struct iio_dev *indio_dev)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ if (INV_MPU3050 == st->chip_type)
+ return reset_fifo_mpu3050(indio_dev);
+ else
+ return reset_fifo_itg(indio_dev);
+}
+
+/**
+ * set_inv_enable() - Reset FIFO related registers.
+ * This also powers on the chip if needed.
+ * @st: Device driver instance.
+ * @fifo_enable: enable/disable
+ */
+int set_inv_enable(struct iio_dev *indio_dev,
+ bool enable) {
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct inv_reg_map_s *reg;
+ int result;
+
+ if (st->chip_config.is_asleep)
+ return -EINVAL;
+ reg = &st->reg;
+ if (enable) {
+ result = inv_reset_fifo(indio_dev);
+ if (result)
+ return result;
+ } else {
+ result = inv_i2c_single_write(st, reg->fifo_en, 0);
+ if (result)
+ return result;
+ /* disable fifo reading */
+ if (INV_MPU3050 != st->chip_type) {
+ result = inv_i2c_single_write(st, reg->int_enable, 0);
+ if (result)
+ return result;
+ result = inv_i2c_single_write(st, reg->user_ctrl, 0);
+ } else {
+ result = inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config);
+ }
+ if (result)
+ return result;
+ }
+ st->chip_config.enable = !!enable;
+
+ return 0;
+}
+
+/**
+ * inv_clear_kfifo() - clear time stamp fifo
+ * @st: Device driver instance.
+ */
+void inv_clear_kfifo(struct inv_mpu_iio_s *st)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&st->time_stamp_lock, flags);
+ kfifo_reset(&st->timestamps);
+ spin_unlock_irqrestore(&st->time_stamp_lock, flags);
+}
+
+/**
+ * inv_irq_handler() - Cache a timestamp at each data ready interrupt.
+ */
+static irqreturn_t inv_irq_handler(int irq, void *dev_id)
+{
+ struct inv_mpu_iio_s *st;
+ long long timestamp;
+ int catch_up;
+ long long time_since_last_irq;
+
+ st = (struct inv_mpu_iio_s *)dev_id;
+ timestamp = get_time_ns();
+ time_since_last_irq = timestamp - st->last_isr_time;
+ spin_lock(&st->time_stamp_lock);
+ catch_up = 0;
+ while ((time_since_last_irq > st->irq_dur_ns * 2) &&
+ (catch_up < MAX_CATCH_UP) &&
+ (!st->chip_config.lpa_mode) &&
+ (!st->chip_config.dmp_on)) {
+ st->last_isr_time += st->irq_dur_ns;
+ kfifo_in(&st->timestamps,
+ &st->last_isr_time, 1);
+ time_since_last_irq = timestamp - st->last_isr_time;
+ catch_up++;
+ }
+ kfifo_in(&st->timestamps, ×tamp, 1);
+ st->last_isr_time = timestamp;
+ spin_unlock(&st->time_stamp_lock);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static int put_scan_to_buf(struct iio_dev *indio_dev, unsigned char *d,
+ short *s, int scan_index, int d_ind) {
+ struct iio_buffer *ring = indio_dev->buffer;
+ int st;
+ int i;
+ for (i = 0; i < 3; i++) {
+ st = iio_scan_mask_query(indio_dev, ring, scan_index + i);
+ if (st) {
+ memcpy(&d[d_ind], &s[i], sizeof(s[i]));
+ d_ind += sizeof(s[i]);
+ }
+ }
+ return d_ind;
+}
+
+static int put_scan_to_buf_q(struct iio_dev *indio_dev, unsigned char *d,
+ int *s, int scan_index, int d_ind) {
+ struct iio_buffer *ring = indio_dev->buffer;
+ int st;
+ int i;
+ for (i = 0; i < 4; i++) {
+ st = iio_scan_mask_query(indio_dev, ring, scan_index + i);
+ if (st) {
+ memcpy(&d[d_ind], &s[i], sizeof(s[i]));
+ d_ind += sizeof(s[i]);
+ }
+ }
+ return d_ind;
+}
+
+static void inv_report_data_3050(struct iio_dev *indio_dev, s64 t,
+ int has_footer, unsigned char *data)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ int ind, i, d_ind;
+ struct inv_chip_config_s *conf;
+ short g[THREE_AXIS], a[THREE_AXIS];
+ s64 buf[8];
+ unsigned char *tmp;
+ int bytes_per_datum, scan_count;
+ conf = &st->chip_config;
+
+ scan_count = bitmap_weight(indio_dev->active_scan_mask,
+ indio_dev->masklength);
+ bytes_per_datum = scan_count * 2;
+
+ ind = 0;
+ if (has_footer)
+ ind += 2;
+ tmp = (unsigned char *)buf;
+ d_ind = 0;
+ if (conf->gyro_fifo_enable) {
+ g[0] = be16_to_cpup((__be16 *)(&data[ind]));
+ g[1] = be16_to_cpup((__be16 *)(&data[ind + 2]));
+ g[2] = be16_to_cpup((__be16 *)(&data[ind + 4]));
+ ind += BYTES_PER_SENSOR;
+ d_ind = put_scan_to_buf(indio_dev, tmp, g,
+ INV_MPU_SCAN_GYRO_X, d_ind);
+ }
+ if (conf->accl_fifo_enable) {
+ st->mpu_slave->combine_data(&data[ind], a);
+ ind += BYTES_PER_SENSOR;
+ d_ind = put_scan_to_buf(indio_dev, tmp, a,
+ INV_MPU_SCAN_ACCL_X, d_ind);
+ }
+
+ i = (bytes_per_datum + 7) / 8;
+ if (ring->scan_timestamp)
+ buf[i] = t;
+ ring->access->store_to(indio_dev->buffer, (u8 *)buf, t);
+}
+
+/**
+ * inv_read_fifo_mpu3050() - Transfer data from FIFO to ring buffer for mpu3050.
+ */
+irqreturn_t inv_read_fifo_mpu3050(int irq, void *dev_id)
+{
+
+ struct inv_mpu_iio_s *st = (struct inv_mpu_iio_s *)dev_id;
+ struct iio_dev *indio_dev = iio_priv_to_dev(st);
+ int bytes_per_datum;
+ unsigned char data[64];
+ int result;
+ short fifo_count, byte_read;
+ unsigned int copied;
+ s64 timestamp;
+ struct inv_reg_map_s *reg;
+ reg = &st->reg;
+ /* It is impossible that chip is asleep or enable is
+ zero when interrupt is on
+ * because interrupt is now connected with enable */
+ if (st->chip_config.dmp_on)
+ bytes_per_datum = BYTES_FOR_DMP;
+ else
+ bytes_per_datum = (st->chip_config.accl_fifo_enable +
+ st->chip_config.gyro_fifo_enable)*BYTES_PER_SENSOR;
+ if (st->chip_config.has_footer)
+ byte_read = bytes_per_datum + MPU3050_FOOTER_SIZE;
+ else
+ byte_read = bytes_per_datum;
+
+ fifo_count = 0;
+ if (byte_read != 0) {
+ result = inv_i2c_read(st, reg->fifo_count_h,
+ FIFO_COUNT_BYTE, data);
+ if (result)
+ goto end_session;
+ fifo_count = be16_to_cpup((__be16 *)(&data[0]));
+ if (fifo_count < byte_read)
+ goto end_session;
+ if (fifo_count & 1)
+ goto flush_fifo;
+ if (fifo_count > FIFO_THRESHOLD)
+ goto flush_fifo;
+ /* Timestamp mismatch. */
+ if (kfifo_len(&st->timestamps) <
+ fifo_count / byte_read)
+ goto flush_fifo;
+ if (kfifo_len(&st->timestamps) >
+ fifo_count / byte_read + TIME_STAMP_TOR) {
+ if (st->chip_config.dmp_on) {
+ result = kfifo_to_user(&st->timestamps,
+ ×tamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ } else {
+ goto flush_fifo;
+ }
+ }
+ }
+ while ((bytes_per_datum != 0) && (fifo_count >= byte_read)) {
+ result = inv_i2c_read(st, reg->fifo_r_w, byte_read, data);
+ if (result)
+ goto flush_fifo;
+
+ result = kfifo_to_user(&st->timestamps,
+ ×tamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ inv_report_data_3050(indio_dev, timestamp,
+ st->chip_config.has_footer, data);
+ fifo_count -= byte_read;
+ if (st->chip_config.has_footer == 0) {
+ st->chip_config.has_footer = 1;
+ byte_read = bytes_per_datum + MPU3050_FOOTER_SIZE;
+ }
+ }
+end_session:
+ return IRQ_HANDLED;
+flush_fifo:
+ /* Flush HW and SW FIFOs. */
+ inv_reset_fifo(indio_dev);
+ inv_clear_kfifo(st);
+ return IRQ_HANDLED;
+}
+
+static int inv_report_gyro_accl_compass(struct iio_dev *indio_dev,
+ unsigned char *data, s64 t)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ short g[THREE_AXIS], a[THREE_AXIS], c[THREE_AXIS];
+ int q[4];
+ int result, ind, d_ind;
+ s64 buf[8];
+ u32 word;
+ u8 d[8], compass_divider;
+ u8 *tmp;
+ int source;
+ struct inv_chip_config_s *conf;
+
+ conf = &st->chip_config;
+ ind = 0;
+ if (conf->quaternion_on & conf->dmp_on) {
+ q[0] = be32_to_cpup((__be32 *)(&data[ind]));
+ q[1] = be32_to_cpup((__be32 *)(&data[ind + 4]));
+ q[2] = be32_to_cpup((__be32 *)(&data[ind + 8]));
+ q[3] = be32_to_cpup((__be32 *)(&data[ind + 12]));
+ st->raw_quaternion[0] = q[0];
+ st->raw_quaternion[1] = q[1];
+ st->raw_quaternion[2] = q[2];
+ st->raw_quaternion[3] = q[3];
+ ind += QUATERNION_BYTES;
+ }
+ if (conf->accl_fifo_enable | conf->dmp_on) {
+ a[0] = be16_to_cpup((__be16 *)(&data[ind]));
+ a[1] = be16_to_cpup((__be16 *)(&data[ind + 2]));
+ a[2] = be16_to_cpup((__be16 *)(&data[ind + 4]));
+
+ a[0] *= st->chip_info.multi;
+ a[1] *= st->chip_info.multi;
+ a[2] *= st->chip_info.multi;
+ st->raw_accel[0] = a[0];
+ st->raw_accel[1] = a[1];
+ st->raw_accel[2] = a[2];
+ ind += BYTES_PER_SENSOR;
+ }
+ if (conf->gyro_fifo_enable | conf->dmp_on) {
+ g[0] = be16_to_cpup((__be16 *)(&data[ind]));
+ g[1] = be16_to_cpup((__be16 *)(&data[ind + 2]));
+ g[2] = be16_to_cpup((__be16 *)(&data[ind + 4]));
+
+ st->raw_gyro[0] = g[0];
+ st->raw_gyro[1] = g[1];
+ st->raw_gyro[2] = g[2];
+ ind += BYTES_PER_SENSOR;
+ }
+ if (conf->dmp_on) {
+ word = (unsigned int)(be32_to_cpup((unsigned int *)&data[ind]));
+ source = ((word >> 16) & 0xff);
+ if (source) {
+ st->tap_data = (DMP_MASK_TAP & (word & 0xff));
+ st->orient_data = ((word >> 8) & 0xff);
+ st->display_orient_data =
+ ((DMP_MASK_DIS_ORIEN & (word & 0xff)) >>
+ DMP_DIS_ORIEN_SHIFT);
+ }
+
+ /* report tap information */
+ if (source & INT_SRC_TAP)
+ sysfs_notify(&indio_dev->dev.kobj, NULL, "event_tap");
+ /* report orientation information */
+ if (source & INT_SRC_ORIENT)
+ sysfs_notify(&indio_dev->dev.kobj, NULL,
+ "event_orientation");
+ /* report orientation information */
+ if (source & INT_SRC_DISPLAY_ORIENT)
+ sysfs_notify(&indio_dev->dev.kobj, NULL,
+ "event_display_orientation");
+ }
+ /*divider and counter is used to decrease the speed of read in
+ high frequency sample rate*/
+ if (conf->compass_fifo_enable) {
+ c[0] = 0;
+ c[1] = 0;
+ c[2] = 0;
+ if (conf->dmp_on)
+ compass_divider = st->compass_dmp_divider;
+ else
+ compass_divider = st->compass_divider;
+ if (compass_divider == st->compass_counter) {
+ /*read from external sensor data register */
+ result = inv_i2c_read(st, REG_EXT_SENS_DATA_00,
+ NUM_BYTES_COMPASS_SLAVE, d);
+ /* d[7] is status 2 register */
+ /*for AKM8975, bit 2 and 3 should be all be zero*/
+ /* for AMK8963, bit 3 should be zero*/
+ if ((DATA_AKM_DRDY == d[0]) &&
+ (0 == (d[7] & DATA_AKM_STAT_MASK)) &&
+ (!result)) {
+ u8 *sens;
+ sens = st->chip_info.compass_sens;
+ c[0] = (short)((d[2] << 8) | d[1]);
+ c[1] = (short)((d[4] << 8) | d[3]);
+ c[2] = (short)((d[6] << 8) | d[5]);
+ c[0] = (short)(((int)c[0] *
+ (sens[0] + 128)) >> 8);
+ c[1] = (short)(((int)c[1] *
+ (sens[1] + 128)) >> 8);
+ c[2] = (short)(((int)c[2] *
+ (sens[2] + 128)) >> 8);
+ st->raw_compass[0] = c[0];
+ st->raw_compass[1] = c[1];
+ st->raw_compass[2] = c[2];
+ }
+ st->compass_counter = 0;
+ } else if (compass_divider != 0) {
+ st->compass_counter++;
+ }
+ }
+
+ tmp = (unsigned char *)buf;
+ d_ind = 0;
+ if (conf->quaternion_on & conf->dmp_on)
+ d_ind = put_scan_to_buf_q(indio_dev, tmp, q,
+ INV_MPU_SCAN_QUAT_R, d_ind);
+ if (conf->gyro_fifo_enable)
+ d_ind = put_scan_to_buf(indio_dev, tmp, g,
+ INV_MPU_SCAN_GYRO_X, d_ind);
+ if (conf->accl_fifo_enable)
+ d_ind = put_scan_to_buf(indio_dev, tmp, a,
+ INV_MPU_SCAN_ACCL_X, d_ind);
+ if (conf->compass_fifo_enable)
+ d_ind = put_scan_to_buf(indio_dev, tmp, c,
+ INV_MPU_SCAN_MAGN_X, d_ind);
+ if (ring->scan_timestamp)
+ buf[(d_ind + 7) / 8] = t;
+ ring->access->store_to(indio_dev->buffer, (u8 *)buf, t);
+
+ return 0;
+}
+
+/**
+ * inv_read_fifo() - Transfer data from FIFO to ring buffer.
+ */
+irqreturn_t inv_read_fifo(int irq, void *dev_id)
+{
+
+ struct inv_mpu_iio_s *st = (struct inv_mpu_iio_s *)dev_id;
+ struct iio_dev *indio_dev = iio_priv_to_dev(st);
+ size_t bytes_per_datum;
+ int result;
+ unsigned char data[BYTES_FOR_DMP + QUATERNION_BYTES];
+ unsigned short fifo_count;
+ unsigned int copied;
+ s64 timestamp;
+ struct inv_reg_map_s *reg;
+ s64 buf[8];
+ unsigned char *tmp;
+ reg = &st->reg;
+ if (!(st->chip_config.accl_fifo_enable |
+ st->chip_config.gyro_fifo_enable |
+ st->chip_config.dmp_on |
+ st->chip_config.compass_fifo_enable))
+ goto end_session;
+ if (st->chip_config.dmp_on && st->chip_config.flick_int_on) {
+ /*dmp interrupt status */
+ inv_i2c_read(st, REG_DMP_INT_STATUS, 1, data);
+ if (data[0] & FLICK_INT_STATUS)
+ sysfs_notify(&indio_dev->dev.kobj, NULL, "event_flick");
+ }
+ if (st->chip_config.lpa_mode) {
+ result = inv_i2c_read(st, reg->raw_accl,
+ BYTES_PER_SENSOR, data);
+ if (result)
+ goto end_session;
+ inv_report_gyro_accl_compass(indio_dev, data,
+ get_time_ns());
+ goto end_session;
+ }
+
+ if (st->chip_config.dmp_on)
+ if (st->chip_config.quaternion_on)
+ bytes_per_datum = BYTES_FOR_DMP + QUATERNION_BYTES;
+ else
+ bytes_per_datum = BYTES_FOR_DMP;
+ else
+ bytes_per_datum = (st->chip_config.accl_fifo_enable +
+ st->chip_config.gyro_fifo_enable)*BYTES_PER_SENSOR;
+ fifo_count = 0;
+ if (bytes_per_datum != 0) {
+ result = inv_i2c_read(st, reg->fifo_count_h,
+ FIFO_COUNT_BYTE, data);
+ if (result)
+ goto end_session;
+ fifo_count = be16_to_cpup((__be16 *)(&data[0]));
+ if (fifo_count < bytes_per_datum)
+ goto end_session;
+ /* fifo count can't be odd number */
+ if (fifo_count & 1)
+ goto flush_fifo;
+ if (fifo_count > FIFO_THRESHOLD)
+ goto flush_fifo;
+ /* Timestamp mismatch. */
+ if (kfifo_len(&st->timestamps) <
+ fifo_count / bytes_per_datum)
+ goto flush_fifo;
+ if (kfifo_len(&st->timestamps) >
+ fifo_count / bytes_per_datum + TIME_STAMP_TOR) {
+ if (st->chip_config.dmp_on) {
+ result = kfifo_to_user(&st->timestamps,
+ ×tamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ } else {
+ goto flush_fifo;
+ }
+ }
+ } else {
+ result = kfifo_to_user(&st->timestamps,
+ ×tamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ }
+ tmp = (char *)buf;
+ while ((bytes_per_datum != 0) && (fifo_count >= bytes_per_datum)) {
+ result = inv_i2c_read(st, reg->fifo_r_w, bytes_per_datum,
+ data);
+ if (result)
+ goto flush_fifo;
+
+ result = kfifo_to_user(&st->timestamps,
+ ×tamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ inv_report_gyro_accl_compass(indio_dev, data, timestamp);
+ fifo_count -= bytes_per_datum;
+ }
+ if (bytes_per_datum == 0)
+ inv_report_gyro_accl_compass(indio_dev, data, timestamp);
+end_session:
+ return IRQ_HANDLED;
+flush_fifo:
+ /* Flush HW and SW FIFOs. */
+ inv_reset_fifo(indio_dev);
+ inv_clear_kfifo(st);
+ return IRQ_HANDLED;
+}
+
+void inv_mpu_unconfigure_ring(struct iio_dev *indio_dev)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ free_irq(st->client->irq, st);
+ iio_kfifo_free(indio_dev->buffer);
+};
+
+int inv_postenable(struct iio_dev *indio_dev)
+{
+ return set_inv_enable(indio_dev, true);
+
+}
+
+int inv_predisable(struct iio_dev *indio_dev)
+{
+ return set_inv_enable(indio_dev, false);
+}
+
+static const struct iio_buffer_setup_ops inv_mpu_ring_setup_ops = {
+ .preenable = &iio_sw_buffer_preenable,
+ .postenable = &inv_postenable,
+ .predisable = &inv_predisable,
+};
+
+int inv_mpu_configure_ring(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring;
+
+ ring = iio_kfifo_allocate(indio_dev);
+ if (!ring)
+ return -ENOMEM;
+ indio_dev->buffer = ring;
+ /* setup ring buffer */
+ ring->scan_timestamp = true;
+ indio_dev->setup_ops = &inv_mpu_ring_setup_ops;
+ /*scan count double count timestamp. should subtract 1. but
+ number of channels still includes timestamp*/
+ if (INV_MPU3050 == st->chip_type)
+ ret = request_threaded_irq(st->client->irq, inv_irq_handler,
+ inv_read_fifo_mpu3050,
+ IRQF_TRIGGER_RISING | IRQF_SHARED, "inv_irq", st);
+ else
+ ret = request_threaded_irq(st->client->irq, inv_irq_handler,
+ inv_read_fifo,
+ IRQF_TRIGGER_RISING | IRQF_SHARED, "inv_irq", st);
+ if (ret)
+ goto error_iio_sw_rb_free;
+
+ indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
+ return 0;
+error_iio_sw_rb_free:
+ iio_kfifo_free(indio_dev->buffer);
+ return ret;
+}
+/**
+ * @}
+ */
+
diff --git a/drivers/staging/iio/imu/mpu/inv_mpu_trigger.c b/drivers/staging/iio/imu/mpu/inv_mpu_trigger.c
new file mode 100644
index 0000000..0726dc3
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/inv_mpu_trigger.c
@@ -0,0 +1,96 @@
+/*
+* Copyright (C) 2012 Invensense, 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_mpu_trigger.c
+ * @brief A sysfs device driver for Invensense devices
+ * @details This file is part of inv mpu iio driver code
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+
+#include "../../iio.h"
+#include "../../sysfs.h"
+#include "../../trigger.h"
+#include "inv_mpu_iio.h"
+
+/**
+ * inv_mpu_data_rdy_trigger_set_state() set datardy interrupt state
+ **/
+static int inv_mpu_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = trig->private_data;
+
+ dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state);
+ return set_inv_enable(indio_dev, state);
+}
+
+static const struct iio_trigger_ops inv_mpu_trigger_ops = {
+ .owner = THIS_MODULE,
+ .set_trigger_state = &inv_mpu_data_rdy_trigger_set_state,
+};
+
+int inv_mpu_probe_trigger(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+
+ st->trig = iio_allocate_trigger("%s-dev%d",
+ indio_dev->name,
+ indio_dev->id);
+ if (st->trig == NULL)
+ return -ENOMEM;
+ st->trig->dev.parent = &st->client->dev;
+ st->trig->private_data = indio_dev;
+ st->trig->ops = &inv_mpu_trigger_ops;
+ ret = iio_trigger_register(st->trig);
+
+ if (ret) {
+ iio_free_trigger(st->trig);
+ return -EPERM;
+ }
+ indio_dev->trig = st->trig;
+
+ return 0;
+}
+
+void inv_mpu_remove_trigger(struct iio_dev *indio_dev)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+
+ iio_trigger_unregister(st->trig);
+ iio_free_trigger(st->trig);
+}
+/**
+ * @}
+ */
+
diff --git a/drivers/staging/iio/imu/mpu/inv_slave_bma250.c b/drivers/staging/iio/imu/mpu/inv_slave_bma250.c
new file mode 100644
index 0000000..e836ddb
--- /dev/null
+++ b/drivers/staging/iio/imu/mpu/inv_slave_bma250.c
@@ -0,0 +1,330 @@
+/*
+* Copyright (C) 2012 Invensense, 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_slave_bma250.c
+ * @brief A sysfs device driver for Invensense devices
+ * @details This file is part of invensense mpu driver code
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+
+#include "inv_mpu_iio.h"
+#define BMA250_CHIP_ID 3
+#define BMA250_RANGE_SET 0
+#define BMA250_BW_SET 4
+
+/* range and bandwidth */
+#define BMA250_RANGE_2G 3
+#define BMA250_RANGE_4G 5
+#define BMA250_RANGE_8G 8
+#define BMA250_RANGE_16G 12
+#define BMA250_RANGE_MAX 4
+#define BMA250_RANGE_MASK 0xF0
+
+#define BMA250_BW_7_81HZ 0x08
+#define BMA250_BW_15_63HZ 0x09
+#define BMA250_BW_31_25HZ 0x0A
+#define BMA250_BW_62_50HZ 0x0B
+#define BMA250_BW_125HZ 0x0C
+#define BMA250_BW_250HZ 0x0D
+#define BMA250_BW_500HZ 0x0E
+#define BMA250_BW_1000HZ 0x0F
+#define BMA250_MAX_BW_SIZE 8
+#define BMA250_BW_REG_MASK 0xE0
+
+/* register definitions */
+#define BMA250_X_AXIS_LSB_REG 0x02
+#define BMA250_RANGE_SEL_REG 0x0F
+#define BMA250_BW_SEL_REG 0x10
+#define BMA250_MODE_CTRL_REG 0x11
+
+/* mode settings */
+#define BMA250_MODE_NORMAL 0
+#define BMA250_MODE_LOWPOWER 1
+#define BMA250_MODE_SUSPEND 2
+#define BMA250_MODE_MAX 3
+#define BMA250_MODE_MASK 0x3F
+#define BMA250_BIT_SUSPEND 0x80
+#define BMA250_BIT_LP 0x40
+
+struct bma_property {
+ int range;
+ int bandwidth;
+ int mode;
+};
+
+static struct bma_property bma_static_property = {
+ .range = BMA250_RANGE_SET,
+ .bandwidth = BMA250_BW_SET,
+ .mode = BMA250_MODE_SUSPEND
+};
+
+static int bma250_set_bandwidth(struct inv_mpu_iio_s *st, unsigned char bw)
+{
+ int res;
+ unsigned char data;
+ int bandwidth;
+ switch (bw) {
+ case 0:
+ bandwidth = BMA250_BW_7_81HZ;
+ break;
+ case 1:
+ bandwidth = BMA250_BW_15_63HZ;
+ break;
+ case 2:
+ bandwidth = BMA250_BW_31_25HZ;
+ break;
+ case 3:
+ bandwidth = BMA250_BW_62_50HZ;
+ break;
+ case 4:
+ bandwidth = BMA250_BW_125HZ;
+ break;
+ case 5:
+ bandwidth = BMA250_BW_250HZ;
+ break;
+ case 6:
+ bandwidth = BMA250_BW_500HZ;
+ break;
+ case 7:
+ bandwidth = BMA250_BW_1000HZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+ res = inv_secondary_read(BMA250_BW_SEL_REG, 1, &data);
+ if (res)
+ return res;
+ data &= BMA250_BW_REG_MASK;
+ data |= bandwidth;
+ res = inv_secondary_write(BMA250_BW_SEL_REG, data);
+ return res;
+}
+
+static int bma250_set_range(struct inv_mpu_iio_s *st, unsigned char range)
+{
+ int res;
+ unsigned char orig, data;
+ switch (range) {
+ case 0:
+ data = BMA250_RANGE_2G;
+ break;
+ case 1:
+ data = BMA250_RANGE_4G;
+ break;
+ case 2:
+ data = BMA250_RANGE_8G;
+ break;
+ case 3:
+ data = BMA250_RANGE_16G;
+ break;
+ default:
+ return -EINVAL;
+ }
+ res = inv_secondary_read(BMA250_RANGE_SEL_REG, 1, &orig);
+ if (res)
+ return res;
+ orig &= BMA250_RANGE_MASK;
+ data |= orig;
+ res = inv_secondary_write(BMA250_RANGE_SEL_REG, data);
+ if (res)
+ return res;
+ bma_static_property.range = range;
+
+ return 0;
+}
+
+static int setup_slave_bma250(struct inv_mpu_iio_s *st)
+{
+ int result;
+ unsigned char data[2];
+ result = set_3050_bypass(st, true);
+ if (result)
+ return result;
+ /*read secondary i2c ID register */
+ result = inv_secondary_read(0, 1, data);
+ if (result)
+ return result;
+ if (BMA250_CHIP_ID != data[0])
+ return -EINVAL;
+ result = set_3050_bypass(st, false);
+ if (result)
+ return result;
+ /*AUX(accel), slave address is set inside set_3050_bypass*/
+ /* bma250 x axis LSB register address is 2 */
+ result = inv_i2c_single_write(st, REG_3050_AUX_BST_ADDR,
+ BMA250_X_AXIS_LSB_REG);
+
+ return result;
+}
+
+static int bma250_set_mode(struct inv_mpu_iio_s *st, unsigned char mode)
+{
+ int res;
+ unsigned char data;
+
+ res = inv_secondary_read(BMA250_MODE_CTRL_REG, 1, &data);
+ if (res)
+ return res;
+ data &= BMA250_MODE_MASK;
+ switch (mode) {
+ case BMA250_MODE_NORMAL:
+ break;
+ case BMA250_MODE_LOWPOWER:
+ data |= BMA250_BIT_LP;
+ break;
+ case BMA250_MODE_SUSPEND:
+ data |= BMA250_BIT_SUSPEND;
+ break;
+ default:
+ return -EINVAL;
+ }
+ res = inv_secondary_write(BMA250_MODE_CTRL_REG, data);
+ if (res)
+ return res;
+ bma_static_property.mode = mode;
+
+ return 0;
+}
+
+static int suspend_slave_bma250(struct inv_mpu_iio_s *st)
+{
+ int result;
+ if (bma_static_property.mode == BMA250_MODE_SUSPEND)
+ return 0;
+ /*set to bypass mode */
+ result = set_3050_bypass(st, true);
+ if (result)
+ return result;
+ bma250_set_mode(st, BMA250_MODE_SUSPEND);
+ /* no need to recover to non-bypass mode because we need it now */
+
+ return 0;
+}
+
+static int resume_slave_bma250(struct inv_mpu_iio_s *st)
+{
+ int result;
+ if (bma_static_property.mode == BMA250_MODE_NORMAL)
+ return 0;
+ /*set to bypass mode */
+ result = set_3050_bypass(st, true);
+ if (result)
+ return result;
+ result = bma250_set_mode(st, BMA250_MODE_NORMAL);
+ /* recover bypass mode */
+ result |= set_3050_bypass(st, false);
+
+ return result ? (-EINVAL) : 0;
+}
+
+static int combine_data_slave_bma250(unsigned char *in, short *out)
+{
+ out[0] = le16_to_cpup((__le16 *)(&in[0]));
+ out[1] = le16_to_cpup((__le16 *)(&in[2]));
+ out[2] = le16_to_cpup((__le16 *)(&in[4]));
+
+ return 0;
+}
+
+static int get_mode_slave_bma250(void)
+{
+ switch (bma_static_property.mode) {
+ case BMA250_MODE_SUSPEND:
+ return INV_MODE_SUSPEND;
+ case BMA250_MODE_NORMAL:
+ return INV_MODE_NORMAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * set_lpf_bma250() - set lpf value
+ */
+
+static int set_lpf_bma250(struct inv_mpu_iio_s *st, int rate)
+{
+ const short hz[] = {1000, 500, 250, 125, 62, 31, 15, 7};
+ const int d[] = {7, 6, 5, 4, 3, 2, 1, 0};
+ int i, h, data, result;
+ h = (rate >> 1);
+ i = 0;
+ while ((h < hz[i]) && (i < ARRAY_SIZE(hz) - 1))
+ i++;
+ data = d[i];
+
+ result = set_3050_bypass(st, true);
+ if (result)
+ return result;
+ result = bma250_set_bandwidth(st, (unsigned char) data);
+ result |= set_3050_bypass(st, false);
+
+ return result ? (-EINVAL) : 0;
+}
+/**
+ * set_fs_bma250() - set range value
+ */
+
+static int set_fs_bma250(struct inv_mpu_iio_s *st, int fs)
+{
+ int result;
+ result = set_3050_bypass(st, true);
+ if (result)
+ return result;
+ result = bma250_set_range(st, (unsigned char) fs);
+ result |= set_3050_bypass(st, false);
+
+ return result ? (-EINVAL) : 0;
+}
+
+static struct inv_mpu_slave slave_bma250 = {
+ .suspend = suspend_slave_bma250,
+ .resume = resume_slave_bma250,
+ .setup = setup_slave_bma250,
+ .combine_data = combine_data_slave_bma250,
+ .get_mode = get_mode_slave_bma250,
+ .set_lpf = set_lpf_bma250,
+ .set_fs = set_fs_bma250
+};
+
+int inv_register_mpu3050_slave(struct inv_mpu_iio_s *st)
+{
+ st->mpu_slave = &slave_bma250;
+
+ return 0;
+}
+/**
+ * @}
+ */
+
diff --git a/drivers/staging/iio/industrialio-buffer.c b/drivers/staging/iio/industrialio-buffer.c
index 386ba76..719ad19 100644
--- a/drivers/staging/iio/industrialio-buffer.c
+++ b/drivers/staging/iio/industrialio-buffer.c
@@ -56,6 +56,8 @@
{
struct iio_dev *indio_dev = filp->private_data;
struct iio_buffer *rb = indio_dev->buffer;
+ if (rb->stufftoread)
+ return POLLIN | POLLRDNORM;
poll_wait(filp, &rb->pollq, wait);
if (rb->stufftoread)
@@ -295,7 +297,8 @@
channels[i].scan_index;
}
if (indio_dev->masklength && buffer->scan_mask == NULL) {
- buffer->scan_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength),
+ buffer->scan_mask =
+ kcalloc(BITS_TO_LONGS(indio_dev->masklength),
sizeof(*buffer->scan_mask),
GFP_KERNEL);
if (buffer->scan_mask == NULL) {
@@ -308,8 +311,8 @@
buffer->scan_el_group.name = iio_scan_elements_group_name;
buffer->scan_el_group.attrs = kcalloc(attrcount + 1,
- sizeof(buffer->scan_el_group.attrs[0]),
- GFP_KERNEL);
+ sizeof(buffer->scan_el_group.attrs[0]),
+ GFP_KERNEL);
if (buffer->scan_el_group.attrs == NULL) {
ret = -ENOMEM;
goto error_free_scan_mask;
@@ -367,7 +370,7 @@
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct iio_buffer *buffer = indio_dev->buffer;
- ret = strict_strtoul(buf, 10, &val);
+ ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
diff --git a/drivers/staging/iio/industrialio-core.c b/drivers/staging/iio/industrialio-core.c
index d303bfb..3bdc5aa 100644
--- a/drivers/staging/iio/industrialio-core.c
+++ b/drivers/staging/iio/industrialio-core.c
@@ -68,6 +68,8 @@
[IIO_ANGL] = "angl",
[IIO_TIMESTAMP] = "timestamp",
[IIO_CAPACITANCE] = "capacitance",
+ [IIO_PRESSURE] = "pressure",
+ [IIO_QUATERNION] = "quaternion",
};
static const char * const iio_modifier_names[] = {
@@ -76,6 +78,7 @@
[IIO_MOD_Z] = "z",
[IIO_MOD_LIGHT_BOTH] = "both",
[IIO_MOD_LIGHT_IR] = "ir",
+ [IIO_MOD_R] = "r",
};
/* relies on pairs of these shared then separate */
@@ -690,8 +693,8 @@
attrcount++;
indio_dev->chan_attr_group.attrs = kcalloc(attrcount + 1,
- sizeof(indio_dev->chan_attr_group.attrs[0]),
- GFP_KERNEL);
+ sizeof(indio_dev->chan_attr_group.attrs[0]),
+ GFP_KERNEL);
if (indio_dev->chan_attr_group.attrs == NULL) {
ret = -ENOMEM;
goto error_clear_attrs;
diff --git a/drivers/staging/iio/kfifo_buf.c b/drivers/staging/iio/kfifo_buf.c
index 9f3bd59..5a70244 100644
--- a/drivers/staging/iio/kfifo_buf.c
+++ b/drivers/staging/iio/kfifo_buf.c
@@ -4,6 +4,7 @@
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/kfifo.h>
+#include <linux/sched.h>
#include <linux/mutex.h>
#include "kfifo_buf.h"
@@ -36,6 +37,7 @@
kfifo_free(&buf->kf);
ret = __iio_allocate_kfifo(buf, buf->buffer.bytes_per_datum,
buf->buffer.length);
+ r->stufftoread = false;
error_ret:
return ret;
}
@@ -95,9 +97,16 @@
{
int ret;
struct iio_kfifo *kf = iio_to_kfifo(r);
- ret = kfifo_in(&kf->kf, data, r->bytes_per_datum);
- if (ret != r->bytes_per_datum)
- return -EBUSY;
+ if (kfifo_avail(&kf->kf) >= r->bytes_per_datum) {
+ ret = kfifo_in(&kf->kf, data, r->bytes_per_datum);
+ if (ret != r->bytes_per_datum)
+ return -EBUSY;
+ } else {
+ return -ENOMEM;
+ }
+ r->stufftoread = true;
+ wake_up_interruptible(&r->pollq);
+
return 0;
}
@@ -107,12 +116,18 @@
int ret, copied;
struct iio_kfifo *kf = iio_to_kfifo(r);
- if (n < r->bytes_per_datum)
+ if (n < r->bytes_per_datum || r->length == 0)
return -EINVAL;
n = rounddown(n, r->bytes_per_datum);
ret = kfifo_to_user(&kf->kf, buf, n, &copied);
+ if (kfifo_is_empty(&kf->kf))
+ r->stufftoread = false;
+ /* verify it is still empty to avoid race */
+ if (!kfifo_is_empty(&kf->kf))
+ r->stufftoread = true;
+
return copied;
}
diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig
index e7e9159..42c7b9a 100644
--- a/drivers/staging/iio/light/Kconfig
+++ b/drivers/staging/iio/light/Kconfig
@@ -3,6 +3,18 @@
#
menu "Light sensors"
+config SENSORS_BH1721
+ tristate "Rohm BH1721FVC Ambient Light Sensor"
+ depends on I2C && GPIOLIB
+ default n
+ help
+ If you say yes here you get support for the ROHM
+ BH1721FVC light sensor. This driver provides ambient
+ light intensity in lux, accessible through sysfs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bh1721fvc.
+
config SENSORS_ISL29018
tristate "ISL 29018 light and proximity sensor"
depends on I2C
diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile
index 3011fbf..d4dfcc7 100644
--- a/drivers/staging/iio/light/Makefile
+++ b/drivers/staging/iio/light/Makefile
@@ -2,6 +2,7 @@
# Makefile for industrial I/O Light sensors
#
+obj-$(CONFIG_SENSORS_BH1721) += bh1721fvc.o
obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o
obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o
obj-$(CONFIG_TSL2583) += tsl2583.o
diff --git a/drivers/staging/iio/light/bh1721fvc.c b/drivers/staging/iio/light/bh1721fvc.c
new file mode 100644
index 0000000..9f65c2c
--- /dev/null
+++ b/drivers/staging/iio/light/bh1721fvc.c
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics. 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 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 St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_data/bh1721fvc.h>
+#include "../iio.h"
+#include "../sysfs.h"
+#include "../events.h"
+
+#define BH1721FVC_MODE_DATA(_cmd, _delay, _freq, _name) \
+ {.cmd = (_cmd), .delay = (_delay), .freq = (_freq), .name = _name}
+
+#define SAMPLING_FREQUENCY_DEFAULT 5
+#define POLL_DELAY_DEFAULT (NSEC_PER_SEC / SAMPLING_FREQUENCY_DEFAULT)
+
+enum BH1721FVC_STATE_AND_MODE {
+ STATE_POWER_DOWN,
+ STATE_POWER_ON,
+ STATE_AUTO_MEASURE,
+ STATE_H_MEASURE,
+ STATE_L_MEASURE,
+ STATE_MAX,
+};
+
+struct bh1721fvc_mode_data_params {
+ const u8 cmd;
+ const u8 delay;
+ const u8 freq;
+ const char *name;
+};
+
+static const struct bh1721fvc_mode_data_params
+ bh1721fvc_mode_data[STATE_MAX] = {
+ [STATE_POWER_DOWN] = BH1721FVC_MODE_DATA(0x00, 0, 0, "invalid"),
+ [STATE_POWER_ON] = BH1721FVC_MODE_DATA(0x01, 0, 0, "invalid"),
+ [STATE_AUTO_MEASURE] = BH1721FVC_MODE_DATA(0x10, 136, 7, "auto"),
+ [STATE_H_MEASURE] = BH1721FVC_MODE_DATA(0x12, 120, 8, "high"),
+ [STATE_L_MEASURE] = BH1721FVC_MODE_DATA(0x13, 16, 62, "low"),
+};
+
+struct bh1721fvc_data {
+ int reset_pin;
+ struct i2c_client *client;
+ struct iio_dev *indio_dev;
+ struct work_struct work_light;
+ bool is_measuring;
+ bool event_en;
+ u16 lux;
+ struct hrtimer timer;
+ struct mutex lock;
+ struct workqueue_struct *wq;
+ ktime_t light_poll_delay;
+ int light_sampling_frequency;
+ enum BH1721FVC_STATE_AND_MODE state;
+ enum BH1721FVC_STATE_AND_MODE measure_mode;
+};
+
+static int bh1721fvc_light_sensor_reset(int reset_pin)
+{
+ int err;
+
+ err = gpio_direction_output(reset_pin, 0);
+ if (err) {
+ pr_err("Failed to make GPIO go low (%d)\n", err);
+ return err;
+ }
+ udelay(2);
+ err = gpio_direction_output(reset_pin, 1);
+ if (err) {
+ pr_err("Failed to make GPIO go high (%d)\n", err);
+ return err;
+ }
+ return 0;
+}
+
+static int bh1721fvc_get_luxvalue(struct i2c_client *client)
+{
+ int err;
+ u16 value;
+
+ err = i2c_master_recv(client, (u8 *)&value, 2);
+ if (err != 2) {
+ pr_err("Light sensor read failed");
+ return err >= 0 ? -EIO : err;
+ }
+
+ be16_to_cpus(&value);
+ /* Scale by 1/1.2 to convert counts to lux */
+ value = value * 5 / 6;
+ return value;
+}
+
+static int bh1721fvc_set_mode(struct bh1721fvc_data *bh1721fvc,
+ enum BH1721FVC_STATE_AND_MODE measure_mode)
+{
+ int err;
+
+ err = i2c_smbus_write_byte(bh1721fvc->client,
+ bh1721fvc_mode_data[measure_mode].cmd);
+ if (err)
+ goto err_write_mode;
+
+ pr_debug("starting poll timer, delay %ldns\n",
+ bh1721fvc_mode_data[measure_mode].delay *
+ NSEC_PER_MSEC);
+
+ hrtimer_start(&bh1721fvc->timer, ns_to_ktime(bh1721fvc_mode_data[
+ measure_mode].delay * NSEC_PER_MSEC),
+ HRTIMER_MODE_REL);
+
+ bh1721fvc->state = measure_mode;
+
+ return 0;
+
+err_write_mode:
+ pr_err("Error writing mode %s to device",
+ bh1721fvc_mode_data[measure_mode].name);
+ i2c_smbus_write_byte(bh1721fvc->client,
+ bh1721fvc_mode_data[STATE_POWER_DOWN].cmd);
+ return err;
+
+}
+
+static int bh1721fvc_disable(struct bh1721fvc_data *bh1721fvc)
+{
+ int err;
+
+ pr_debug("cancelling poll timer\n");
+ hrtimer_cancel(&bh1721fvc->timer);
+ cancel_work_sync(&bh1721fvc->work_light);
+
+ err = i2c_smbus_write_byte(bh1721fvc->client,
+ bh1721fvc_mode_data[STATE_POWER_DOWN].cmd);
+ if (unlikely(err != 0)) {
+ pr_err("Failed to write byte (STATE_POWER_DOWN)\n");
+ return err;
+ }
+
+ bh1721fvc->state = STATE_POWER_DOWN;
+
+ return 0;
+}
+
+static void bh1721fvc_work_func_light(struct work_struct *work)
+{
+ u16 lux;
+ int err;
+ struct bh1721fvc_data *bh1721fvc = container_of(work,
+ struct bh1721fvc_data,
+ work_light);
+
+ lux = bh1721fvc_get_luxvalue(bh1721fvc->client);
+ if (lux < 0) {
+ pr_err("read word failed! (errno=%d)\n", lux);
+ return;
+ }
+ bh1721fvc->lux = lux;
+
+ pr_debug("lux %#04x (%u)\n", lux, lux);
+ err = iio_push_event(iio_priv_to_dev(bh1721fvc),
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+ 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns());
+ if (err)
+ pr_err("Could not push IIO_LIGHT event");
+}
+
+static enum hrtimer_restart bh1721fvc_timer_func(struct hrtimer *timer)
+{
+ struct bh1721fvc_data *bh1721fvc = container_of(timer,
+ struct bh1721fvc_data,
+ timer);
+
+ queue_work(bh1721fvc->wq, &bh1721fvc->work_light);
+ hrtimer_forward_now(&bh1721fvc->timer, bh1721fvc->light_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static ssize_t bh1721fvc_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bh1721fvc_data *bh1721fvc = iio_priv(dev_get_drvdata(dev));
+
+ return sprintf(buf, "%s\n",
+ bh1721fvc_mode_data[bh1721fvc->measure_mode].name);
+}
+
+static ssize_t bh1721fvc_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ u8 new_measure_mode;
+ s64 delay_lowbound;
+ struct bh1721fvc_data *bh1721fvc = iio_priv(dev_get_drvdata(dev));
+ int err;
+
+ if (sysfs_streq(buf, bh1721fvc_mode_data[STATE_AUTO_MEASURE].name)) {
+ new_measure_mode = STATE_AUTO_MEASURE;
+ } else if (sysfs_streq(buf,
+ bh1721fvc_mode_data[STATE_H_MEASURE].name)) {
+ new_measure_mode = STATE_H_MEASURE;
+ } else if (sysfs_streq(buf,
+ bh1721fvc_mode_data[STATE_L_MEASURE].name)) {
+ new_measure_mode = STATE_L_MEASURE;
+ } else {
+ pr_err("invalid value %s\n", buf);
+ return -EINVAL;
+ }
+
+ mutex_lock(&bh1721fvc->lock);
+ if (bh1721fvc->measure_mode != new_measure_mode) {
+ delay_lowbound =
+ bh1721fvc_mode_data[new_measure_mode].delay
+ * NSEC_PER_MSEC;
+ if (ktime_to_ns(bh1721fvc->light_poll_delay) < delay_lowbound) {
+ bh1721fvc->light_poll_delay
+ = ns_to_ktime(delay_lowbound);
+ bh1721fvc->light_sampling_frequency =
+ bh1721fvc_mode_data[new_measure_mode].freq;
+ }
+ if (bh1721fvc->state != STATE_POWER_DOWN) {
+ hrtimer_cancel(&bh1721fvc->timer);
+ cancel_work_sync(&bh1721fvc->work_light);
+ err = bh1721fvc_set_mode(bh1721fvc, new_measure_mode);
+ if (err) {
+ pr_err("Failed to change to mode %s",
+ bh1721fvc_mode_data[new_measure_mode].
+ name);
+ mutex_unlock(&bh1721fvc->lock);
+ return -EIO;
+ }
+ }
+ bh1721fvc->measure_mode = new_measure_mode;
+ }
+ mutex_unlock(&bh1721fvc->lock);
+
+ return size;
+}
+
+static ssize_t bh1721fvc_sampling_frequency_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bh1721fvc_data *bh1721fvc = iio_priv(dev_get_drvdata(dev));
+
+ pr_debug("delay: %lld delay_hz: %d",
+ ktime_to_ns(bh1721fvc->light_poll_delay),
+ bh1721fvc->light_sampling_frequency);
+ return sprintf(buf, "%d\n", bh1721fvc->light_sampling_frequency);
+}
+
+static ssize_t bh1721fvc_sampling_frequency_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ struct bh1721fvc_data *bh1721fvc = iio_priv(dev_get_drvdata(dev));
+
+ int new_freq_hz;
+ s64 new_delay_ns;
+ s64 delay_lowbound;
+ int err;
+
+ err = kstrtoint(buf, 10, &new_freq_hz);
+ if (err < 0)
+ return err;
+
+ if (new_freq_hz <= 0)
+ return -EINVAL;
+
+ /* Conversion from Hz to ns */
+ new_delay_ns = NSEC_PER_SEC / new_freq_hz;
+ delay_lowbound = bh1721fvc_mode_data[bh1721fvc->measure_mode].delay
+ * NSEC_PER_MSEC;
+
+ if (new_delay_ns < delay_lowbound) {
+ new_delay_ns = delay_lowbound;
+ new_freq_hz =
+ bh1721fvc_mode_data[bh1721fvc->measure_mode].freq;
+ }
+ pr_debug("new delay = %lldns, old delay = %lldns\n", new_delay_ns,
+ ktime_to_ns(bh1721fvc->light_poll_delay));
+
+
+ mutex_lock(&bh1721fvc->lock);
+ if (new_delay_ns != ktime_to_ns(bh1721fvc->light_poll_delay)) {
+ bh1721fvc->light_poll_delay = ns_to_ktime(new_delay_ns);
+ bh1721fvc->light_sampling_frequency = new_freq_hz;
+ }
+ mutex_unlock(&bh1721fvc->lock);
+
+ return size;
+}
+
+static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR,
+ bh1721fvc_mode_show, bh1721fvc_mode_store, 0);
+static IIO_CONST_ATTR(mode_available, "auto high low");
+static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR,
+ bh1721fvc_sampling_frequency_show, bh1721fvc_sampling_frequency_store);
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("auto: <=7Hz, high: <=8Hz, low <=62Hz");
+
+#define BH1721FVC_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr)
+#define BH1721FVC_CONST_ATTR(name) (&iio_const_attr_##name.dev_attr.attr)
+static struct attribute *bh1721fvc_attributes[] = {
+ BH1721FVC_DEV_ATTR(mode),
+ BH1721FVC_CONST_ATTR(mode_available),
+ BH1721FVC_DEV_ATTR(sampling_frequency),
+ BH1721FVC_CONST_ATTR(sampling_frequency_available),
+ NULL
+};
+
+static const struct attribute_group bh1721fvc_attribute_group = {
+ .attrs = bh1721fvc_attributes,
+};
+
+static const struct iio_chan_spec bh1721fvc_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .indexed = 1,
+ .channel = 0,
+ .processed_val = IIO_PROCESSED,
+ .event_mask = (IIO_EV_BIT(IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER)),
+ }
+};
+
+static int bh1721fvc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long m)
+{
+ struct bh1721fvc_data *bh1721fvc = iio_priv(indio_dev);
+
+ pr_debug("Reporting lux(%d) to user", bh1721fvc->lux);
+ *val = bh1721fvc->lux;
+
+ return IIO_VAL_INT;
+}
+
+static int bh1721fvc_read_event_value(struct iio_dev *indio_dev,
+ u64 event_code,
+ int *val)
+{
+ struct bh1721fvc_data *bh1721fvc = iio_priv(indio_dev);
+
+ *val = bh1721fvc->lux;
+
+ return 0;
+}
+
+static int bh1721fvc_read_event_config(struct iio_dev *indio_dev,
+ u64 event_code)
+{
+ struct bh1721fvc_data *bh1721fvc = iio_priv(indio_dev);
+
+ pr_debug("is_measuring = %d", bh1721fvc->state != STATE_POWER_DOWN);
+ return (bh1721fvc->state != STATE_POWER_DOWN);
+}
+
+static int bh1721fvc_write_event_config(struct iio_dev *indio_dev,
+ u64 event_code,
+ int state)
+{
+ int err = 0;
+ struct bh1721fvc_data *bh1721fvc = iio_priv(indio_dev);
+
+ pr_debug("state %d->%d\n", (bh1721fvc->state != STATE_POWER_DOWN),
+ state);
+
+ mutex_lock(&bh1721fvc->lock);
+ if (state && (bh1721fvc->state == STATE_POWER_DOWN)) {
+ err = bh1721fvc_set_mode(bh1721fvc, bh1721fvc->measure_mode);
+ if (err)
+ goto err_set_mode;
+ } else if (!state && (bh1721fvc->state != STATE_POWER_DOWN)) {
+ err = bh1721fvc_disable(bh1721fvc);
+ if (err)
+ goto err_set_mode;
+ }
+ bh1721fvc->is_measuring = state;
+ mutex_unlock(&bh1721fvc->lock);
+
+ return 0;
+
+err_set_mode:
+ mutex_unlock(&bh1721fvc->lock);
+ return err;
+}
+
+static const struct iio_info bh1721fvc_info = {
+ .attrs = &bh1721fvc_attribute_group,
+ .driver_module = THIS_MODULE,
+ .read_raw = bh1721fvc_read_raw,
+ .read_event_value = bh1721fvc_read_event_value,
+ .read_event_config = bh1721fvc_read_event_config,
+ .write_event_config = bh1721fvc_write_event_config,
+};
+
+static int __devinit bh1721fvc_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err;
+ struct bh1721fvc_data *bh1721fvc;
+ struct iio_dev *indio_dev;
+ struct bh1721fvc_platform_data *pdata;
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_I2C |
+ I2C_FUNC_SMBUS_WRITE_BYTE))
+ return -ENOSYS;
+
+ pdata = client->dev.platform_data;
+ if (!pdata) {
+ pr_err("no platform data\n");
+ return -EINVAL;
+ }
+
+ indio_dev = iio_allocate_device(sizeof(*bh1721fvc));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ bh1721fvc = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ bh1721fvc->client = client;
+
+ bh1721fvc->reset_pin = pdata->reset_pin;
+ if (bh1721fvc->reset_pin < 0) {
+ pr_err("reset pin is invalid\n");
+ err = -EINVAL;
+ goto err_reset_invalid;
+ }
+
+ err = gpio_request_one(bh1721fvc->reset_pin, GPIOF_OUT_INIT_HIGH,
+ "ALS_NRST");
+ if (err) {
+ pr_err("Failed to request ALS_NRST for light sensor reset\n");
+ goto err_reset_request;
+ }
+
+ err = bh1721fvc_light_sensor_reset(bh1721fvc->reset_pin);
+ if (err) {
+ pr_err("Failed to reset\n");
+ goto err_reset_failed;
+ }
+
+ mutex_init(&bh1721fvc->lock);
+ hrtimer_init(&bh1721fvc->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+
+ bh1721fvc->light_poll_delay = ns_to_ktime(POLL_DELAY_DEFAULT);
+ bh1721fvc->light_sampling_frequency = SAMPLING_FREQUENCY_DEFAULT;
+ bh1721fvc->state = STATE_POWER_DOWN;
+ bh1721fvc->measure_mode = STATE_AUTO_MEASURE;
+ bh1721fvc->is_measuring = false;
+ bh1721fvc->timer.function = bh1721fvc_timer_func;
+
+ bh1721fvc->wq = alloc_workqueue("bh1721fvc_wq",
+ WQ_UNBOUND | WQ_RESCUER, 1);
+ if (!bh1721fvc->wq) {
+ err = -ENOMEM;
+ pr_err("could not create workqueue\n");
+ goto err_create_workqueue;
+ }
+
+ INIT_WORK(&bh1721fvc->work_light, bh1721fvc_work_func_light);
+
+ indio_dev->name = "lightsensor-level";
+ indio_dev->channels = bh1721fvc_channels;
+ indio_dev->num_channels = ARRAY_SIZE(bh1721fvc_channels);
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &bh1721fvc_info;
+
+ pr_info("registering lightsensor-level input device\n");
+ err = iio_device_register(indio_dev);
+ if (err)
+ goto err_iio_register_device_light;
+ return 0;
+
+err_iio_register_device_light:
+ destroy_workqueue(bh1721fvc->wq);
+err_create_workqueue:
+ mutex_destroy(&bh1721fvc->lock);
+err_reset_failed:
+ gpio_free(bh1721fvc->reset_pin);
+err_reset_request:
+err_reset_invalid:
+ iio_free_device(indio_dev);
+ return err;
+}
+
+static int __devexit bh1721fvc_i2c_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct bh1721fvc_data *bh1721fvc = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ if (bh1721fvc->is_measuring)
+ bh1721fvc_disable(bh1721fvc);
+
+ destroy_workqueue(bh1721fvc->wq);
+ mutex_destroy(&bh1721fvc->lock);
+ gpio_free(bh1721fvc->reset_pin);
+ iio_free_device(indio_dev);
+
+ return 0;
+}
+
+static int bh1721fvc_suspend(struct device *dev)
+{
+ int err = 0;
+ struct bh1721fvc_data *bh1721fvc = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+
+ if (bh1721fvc->is_measuring) {
+ err = bh1721fvc_disable(bh1721fvc);
+ if (err)
+ pr_err("could not disable\n");
+ }
+
+ return err;
+}
+
+static int bh1721fvc_resume(struct device *dev)
+{
+ int err;
+ struct bh1721fvc_data *bh1721fvc = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+
+ if (bh1721fvc->is_measuring) {
+ err = bh1721fvc_set_mode(bh1721fvc, bh1721fvc->measure_mode);
+ if (err) {
+ pr_err("could not enable\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id bh1721fvc_device_id[] = {
+ {"bh1721fvc", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, bh1721fvc_device_id);
+
+static const struct dev_pm_ops bh1721fvc_pm_ops = {
+ .suspend = bh1721fvc_suspend,
+ .resume = bh1721fvc_resume,
+};
+
+static struct i2c_driver bh1721fvc_driver = {
+ .driver = {
+ .name = "bh1721fvc",
+ .owner = THIS_MODULE,
+ .pm = &bh1721fvc_pm_ops,
+ },
+ .probe = bh1721fvc_i2c_probe,
+ .remove = __devexit_p(bh1721fvc_i2c_remove),
+ .id_table = bh1721fvc_device_id,
+};
+
+module_i2c_driver(bh1721fvc_driver);
+
+MODULE_AUTHOR("Veeren Mandalia <v.mandalia@sta.samsung.com>");
+MODULE_DESCRIPTION("BH1721FVC Ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/iio/pressure/Kconfig b/drivers/staging/iio/pressure/Kconfig
new file mode 100644
index 0000000..70f98f9
--- /dev/null
+++ b/drivers/staging/iio/pressure/Kconfig
@@ -0,0 +1,17 @@
+#
+# Pressure sensors
+#
+menu "Pressure sensors"
+
+config BMP182
+ tristate "BMP182 digital pressure sensor"
+ depends on I2C
+ default n
+ help
+ If you say yes here you get support for the Bosch Sensortec
+ BMP182 digital pressure sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bmp182.
+
+endmenu
diff --git a/drivers/staging/iio/pressure/Makefile b/drivers/staging/iio/pressure/Makefile
new file mode 100644
index 0000000..2fe005f
--- /dev/null
+++ b/drivers/staging/iio/pressure/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for industrial I/O Pressure sensors
+#
+
+obj-$(CONFIG_BMP182) += bmp182.o
diff --git a/drivers/staging/iio/pressure/bmp182.c b/drivers/staging/iio/pressure/bmp182.c
new file mode 100644
index 0000000..2864c6c
--- /dev/null
+++ b/drivers/staging/iio/pressure/bmp182.c
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics. 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 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 St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include "../iio.h"
+#include "../events.h"
+#include "../sysfs.h"
+
+#define DRIVER_VERSION "1.0"
+
+/* Register definitions */
+#define BMP182_TAKE_MEAS_REG 0xf4
+#define BMP182_READ_MEAS_REG_U 0xf6
+#define BMP182_READ_MEAS_REG_L 0xf7
+#define BMP182_READ_MEAS_REG_XL 0xf8
+
+/*
+ * Bytes defined by the spec to take measurements
+ * Temperature will take 4.5ms before EOC
+ */
+#define BMP182_MEAS_TEMP 0x2e
+/* 4.5ms wait for measurement */
+#define BMP182_MEAS_PRESS_OVERSAMP_0 0x34
+/* 7.5ms wait for measurement */
+#define BMP182_MEAS_PRESS_OVERSAMP_1 0x74
+/* 13.5ms wait for measurement */
+#define BMP182_MEAS_PRESS_OVERSAMP_2 0xb4
+/* 25.5ms wait for measurement */
+#define BMP182_MEAS_PRESS_OVERSAMP_3 0xf4
+
+/*
+ * EEPROM registers each is a two byte value so there is
+ * an upper byte and a lower byte
+ */
+#define BMP182_EEPROM_AC1_U 0xaa
+#define BMP182_EEPROM_AC1_L 0xab
+#define BMP182_EEPROM_AC2_U 0xac
+#define BMP182_EEPROM_AC2_L 0xad
+#define BMP182_EEPROM_AC3_U 0xae
+#define BMP182_EEPROM_AC3_L 0xaf
+#define BMP182_EEPROM_AC4_U 0xb0
+#define BMP182_EEPROM_AC4_L 0xb1
+#define BMP182_EEPROM_AC5_U 0xb2
+#define BMP182_EEPROM_AC5_L 0xb3
+#define BMP182_EEPROM_AC6_U 0xb4
+#define BMP182_EEPROM_AC6_L 0xb5
+#define BMP182_EEPROM_B1_U 0xb6
+#define BMP182_EEPROM_B1_L 0xb7
+#define BMP182_EEPROM_B2_U 0xb8
+#define BMP182_EEPROM_B2_L 0xb9
+#define BMP182_EEPROM_MB_U 0xba
+#define BMP182_EEPROM_MB_L 0xbb
+#define BMP182_EEPROM_MC_U 0xbc
+#define BMP182_EEPROM_MC_L 0xbd
+#define BMP182_EEPROM_MD_U 0xbe
+#define BMP182_EEPROM_MD_L 0xbf
+
+#define SAMP_FREQ_MAX 20
+#define SAMP_FREQ_MIN 1
+#define SAMP_FREQ_DEFAULT 5
+
+#define PRESSURE_MAX 125000
+#define PRESSURE_MIN 95000
+#define PRESSURE_FUZZ 5
+#define PRESSURE_FLAT 5
+
+struct bmp182_eeprom_data {
+ s16 AC1, AC2, AC3;
+ u16 AC4, AC5, AC6;
+ s16 B1, B2;
+ s16 MB, MC, MD;
+} __aligned(2) __packed;
+
+struct bmp182_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct workqueue_struct *wq;
+ struct work_struct work_pressure;
+ bool on_before_suspend;
+ bool enabled;
+ u8 oversampling_rate;
+ int pressure;
+ struct hrtimer timer;
+ int sampling_freq;
+ ktime_t poll_delay;
+ struct bmp182_eeprom_data bmp182_eeprom_vals;
+};
+
+static void bmp182_enable(struct bmp182_data *barom)
+{
+ if (!barom->enabled) {
+ barom->enabled = true;
+ pr_debug("start timer\n");
+ hrtimer_start(&barom->timer, barom->poll_delay,
+ HRTIMER_MODE_REL);
+ }
+}
+
+static void bmp182_disable(struct bmp182_data *barom)
+{
+ if (barom->enabled) {
+ barom->enabled = false;
+ pr_debug("stop timer\n");
+ hrtimer_cancel(&barom->timer);
+ cancel_work_sync(&barom->work_pressure);
+ }
+}
+
+static int bmp182_get_raw_temperature(struct bmp182_data *barom,
+ u16 *raw_temperature)
+{
+ int err;
+ u16 buf;
+
+ err = i2c_smbus_write_byte_data(barom->client,
+ BMP182_TAKE_MEAS_REG,
+ BMP182_MEAS_TEMP);
+ if (err) {
+ pr_err("can't write BMP182_TAKE_MEAS_REG\n");
+ return err;
+ }
+
+ usleep_range(5000, 6000);
+
+ err = i2c_smbus_read_i2c_block_data(barom->client,
+ BMP182_READ_MEAS_REG_U,
+ sizeof(buf), (u8 *)&buf);
+ if (err != sizeof(buf)) {
+ pr_err("Fail to read uncompensated temperature\n");
+ return err >= 0 ? -EIO : err;
+ }
+ *raw_temperature = be16_to_cpu(buf);
+ pr_debug("uncompensated temperature: %d\n", *raw_temperature);
+ return 0;
+}
+
+static int bmp182_get_raw_pressure(struct bmp182_data *barom,
+ u32 *raw_pressure)
+{
+ int err;
+ u32 buf = 0;
+ int range;
+
+ err = i2c_smbus_write_byte_data(barom->client,
+ BMP182_TAKE_MEAS_REG,
+ BMP182_MEAS_PRESS_OVERSAMP_0 |
+ (barom->oversampling_rate << 6));
+ if (err) {
+ pr_err("can't write BMP182_TAKE_MEAS_REG\n");
+ return err;
+ }
+
+ range = 2 + (3 << barom->oversampling_rate);
+ usleep_range(range * 1000, (range + 1) * 1000);
+
+ err = i2c_smbus_read_i2c_block_data(barom->client,
+ BMP182_READ_MEAS_REG_U, 3, ((u8 *)&buf) + 1);
+ if (err != 3) {
+ pr_err("Fail to read uncompensated pressure\n");
+ return err >= 0 ? -EIO : err;
+ }
+
+ *raw_pressure = be32_to_cpu(buf);
+ *raw_pressure >>= (8 - barom->oversampling_rate);
+ pr_debug("uncompensated pressure: %d\n", *raw_pressure);
+ return 0;
+}
+
+static void bmp182_get_pressure_data(struct work_struct *work)
+{
+ u16 raw_temperature;
+ u32 raw_pressure;
+ s32 x1, x2, x3, b3, b5, b6;
+ u32 b4;
+ s32 p;
+
+ struct bmp182_data *barom =
+ container_of(work, struct bmp182_data, work_pressure);
+
+ if (bmp182_get_raw_temperature(barom, &raw_temperature)) {
+ pr_err("can't read uncompensated temperature\n");
+ return;
+ }
+
+ if (bmp182_get_raw_pressure(barom, &raw_pressure)) {
+ pr_err("Fail to read uncompensated pressure\n");
+ return;
+ }
+
+ /* voodoo from BMP182 data sheet, BST-BMP182-DS000-00, page 15 */
+ x1 = ((raw_temperature - barom->bmp182_eeprom_vals.AC6) *
+ barom->bmp182_eeprom_vals.AC5) >> 15;
+ x2 = (barom->bmp182_eeprom_vals.MC << 11) /
+ (x1 + barom->bmp182_eeprom_vals.MD);
+
+ b5 = (x1 + x2 - 4000);
+ x1 = (barom->bmp182_eeprom_vals.B2 * ((b5 * b5) >> 12));
+ x2 = (barom->bmp182_eeprom_vals.AC2 * b5);
+ x3 = (x1 + x2) >> 11;
+ b3 = (((((s32)barom->bmp182_eeprom_vals.AC1) * 4 +
+ x3) << barom->oversampling_rate) + 2) >> 2;
+ x1 = (barom->bmp182_eeprom_vals.AC3 * b5) >> 13;
+ x2 = (barom->bmp182_eeprom_vals.B1 * (b5 * b5 >> 12)) >> 16;
+ x3 = ((x1 + x2) + 2) >> 2;
+ b4 = (barom->bmp182_eeprom_vals.AC4 *
+ (u32)(x3 + 32768)) >> 15;
+ b6 = (raw_pressure - b3) *
+ (50000 >> barom->oversampling_rate);
+ if (b6 < 0x80000000)
+ p = (b6 * 2) / b4;
+ else
+ p = (b6 / b4) * 2;
+
+ x1 = (p >> 8) * (p >> 8);
+ x1 = (x1 * 3038) >> 16;
+ x2 = (-7357 * p) >> 16;
+
+ barom->pressure = p + ((x1 + x2 + 3791) >> 4);
+ pr_debug("calibrated pressure: %d\n", barom->pressure);
+
+ if (iio_push_event(iio_priv_to_dev(barom),
+ IIO_UNMOD_EVENT_CODE(IIO_PRESSURE,
+ 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns()))
+ pr_err("Could not push IIO_PRESSURE event");
+}
+
+static int __devinit bmp182_read_store_eeprom_val(struct bmp182_data *barom)
+{
+ int err;
+
+ err = i2c_smbus_read_i2c_block_data(barom->client,
+ BMP182_EEPROM_AC1_U,
+ sizeof(barom->bmp182_eeprom_vals),
+ (u8 *)&(barom->bmp182_eeprom_vals));
+ if (err != sizeof(barom->bmp182_eeprom_vals)) {
+ pr_err("Cannot read EEPROM values\n");
+ return err >= 0 ? -EIO : err;
+ }
+ be16_to_cpus((u16 *)&(barom->bmp182_eeprom_vals.AC1));
+ be16_to_cpus((u16 *)&(barom->bmp182_eeprom_vals.AC2));
+ be16_to_cpus((u16 *)&(barom->bmp182_eeprom_vals.AC3));
+ be16_to_cpus(&(barom->bmp182_eeprom_vals.AC4));
+ be16_to_cpus(&(barom->bmp182_eeprom_vals.AC5));
+ be16_to_cpus(&(barom->bmp182_eeprom_vals.AC6));
+ be16_to_cpus((u16 *)&(barom->bmp182_eeprom_vals.B1));
+ be16_to_cpus((u16 *)&(barom->bmp182_eeprom_vals.B2));
+ be16_to_cpus((u16 *)&(barom->bmp182_eeprom_vals.MB));
+ be16_to_cpus((u16 *)&(barom->bmp182_eeprom_vals.MC));
+ be16_to_cpus((u16 *)&(barom->bmp182_eeprom_vals.MD));
+ return 0;
+}
+
+static enum hrtimer_restart bmp182_timer_func(struct hrtimer *timer)
+{
+ struct bmp182_data *barom = container_of(timer,
+ struct bmp182_data, timer);
+
+ pr_debug("start\n");
+ queue_work(barom->wq, &barom->work_pressure);
+ hrtimer_forward_now(&barom->timer, barom->poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static ssize_t bmp182_sampling_frequency_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct bmp182_data *barom = iio_priv(dev_get_drvdata(dev));
+
+ return sprintf(buf, "%d Hz\n", barom->sampling_freq);
+}
+
+static ssize_t bmp182_sampling_frequency_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t size)
+{
+ unsigned int new_value;
+ struct bmp182_data *barom = iio_priv(dev_get_drvdata(dev));
+ int err;
+
+ err = kstrtouint(buf, 10, &new_value);
+ if (err)
+ return err;
+
+ if (new_value < SAMP_FREQ_MIN)
+ new_value = SAMP_FREQ_MIN;
+ else if (new_value > SAMP_FREQ_MAX)
+ new_value = SAMP_FREQ_MAX;
+
+ pr_debug("new frequency = %dHz\n", new_value);
+
+ mutex_lock(&barom->lock);
+ if (new_value != barom->sampling_freq) {
+ barom->sampling_freq = new_value;
+ barom->poll_delay = ns_to_ktime(NSEC_PER_SEC / new_value);
+ }
+ mutex_unlock(&barom->lock);
+
+ return size;
+}
+
+static ssize_t bmp182_oversampling_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct bmp182_data *barom = iio_priv(dev_get_drvdata(dev));
+
+ return sprintf(buf, "%d\n", barom->oversampling_rate);
+}
+
+static ssize_t bmp182_oversampling_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct bmp182_data *barom = iio_priv(dev_get_drvdata(dev));
+ unsigned long oversampling;
+ int err = kstrtoul(buf, 10, &oversampling);
+
+ if (err)
+ return err;
+ if (oversampling > 3)
+ oversampling = 3;
+ barom->oversampling_rate = oversampling;
+ return count;
+}
+
+static int bmp182_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask)
+{
+ struct bmp182_data *barom = iio_priv(indio_dev);
+
+ *val = barom->pressure;
+ return IIO_VAL_INT;
+}
+
+static int bmp182_read_event_config(struct iio_dev *indio_dev,
+ u64 event_code)
+{
+ struct bmp182_data *barom = iio_priv(indio_dev);
+
+ return barom->enabled;
+}
+
+static int bmp182_write_event_config(struct iio_dev *indio_dev,
+ u64 event_code,
+ int state)
+{
+ struct bmp182_data *barom = iio_priv(indio_dev);
+
+ pr_debug("enable = %d, old state = %d\n",
+ state, barom->enabled);
+
+ mutex_lock(&barom->lock);
+ if (state)
+ bmp182_enable(barom);
+ else
+ bmp182_disable(barom);
+ mutex_unlock(&barom->lock);
+
+ return 0;
+}
+
+static int bmp182_read_event_value(struct iio_dev *indio_dev,
+ u64 event_code,
+ int *val)
+{
+ struct bmp182_data *barom = iio_priv(indio_dev);
+
+ *val = barom->pressure;
+ return 0;
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR,
+ bmp182_sampling_frequency_show,
+ bmp182_sampling_frequency_store);
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("1 ~ 20Hz");
+static IIO_DEVICE_ATTR(oversampling, S_IRUGO | S_IWUSR,
+ bmp182_oversampling_show,
+ bmp182_oversampling_store,
+ 0);
+static IIO_CONST_ATTR(oversampling_modes, "0 1 2 3");
+
+#define BMP182_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr)
+#define BMP182_CONST_ATTR(name) (&iio_const_attr_##name.dev_attr.attr)
+static struct attribute *bmp182_sysfs_attrs[] = {
+ BMP182_DEV_ATTR(sampling_frequency),
+ BMP182_CONST_ATTR(sampling_frequency_available),
+ BMP182_DEV_ATTR(oversampling),
+ BMP182_CONST_ATTR(oversampling_modes),
+ NULL
+};
+
+static struct attribute_group bmp182_attribute_group = {
+ .attrs = bmp182_sysfs_attrs,
+};
+
+static const struct iio_info bmp182_info = {
+ .attrs = &bmp182_attribute_group,
+ .read_raw = bmp182_read_raw,
+ .read_event_config = bmp182_read_event_config,
+ .write_event_config = bmp182_write_event_config,
+ .read_event_value = bmp182_read_event_value,
+ .driver_module = THIS_MODULE,
+};
+
+static const struct iio_chan_spec bmp182_channels[] = {
+ {
+ .type = IIO_PRESSURE,
+ .indexed = 1,
+ .channel = 0,
+ .processed_val = IIO_PROCESSED,
+ .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ }
+};
+
+static int __devinit bmp182_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err;
+ struct bmp182_data *barom;
+ struct iio_dev *indio_dev;
+
+ pr_debug("enter\n");
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+ pr_err("client not i2c capable\n");
+ return -ENOSYS;
+ }
+
+ indio_dev = iio_allocate_device(sizeof(*barom));
+ if (!indio_dev) {
+ pr_err("failed to allocate memory for iio device\n");
+ return -ENOMEM;
+ }
+
+ barom = iio_priv(indio_dev);
+ mutex_init(&barom->lock);
+ barom->client = client;
+
+ i2c_set_clientdata(client, indio_dev);
+
+ err = bmp182_read_store_eeprom_val(barom);
+ if (err) {
+ pr_err("Reading the EEPROM failed\n");
+ err = -ENODEV;
+ goto err_read_eeprom;
+ }
+
+ hrtimer_init(&barom->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ barom->sampling_freq = SAMP_FREQ_DEFAULT;
+ barom->poll_delay = ns_to_ktime(NSEC_PER_SEC / SAMP_FREQ_DEFAULT);
+ barom->timer.function = bmp182_timer_func;
+
+ barom->wq = alloc_workqueue("bmp182_wq",
+ WQ_UNBOUND | WQ_RESCUER, 1);
+ if (!barom->wq) {
+ err = -ENOMEM;
+ pr_err("could not create workqueue\n");
+ goto err_create_workqueue;
+ }
+
+ INIT_WORK(&barom->work_pressure, bmp182_get_pressure_data);
+
+ indio_dev->name = "barometer";
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &bmp182_info;
+ indio_dev->channels = bmp182_channels;
+ indio_dev->num_channels = ARRAY_SIZE(bmp182_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ err = iio_device_register(indio_dev);
+ if (err)
+ goto err_iio_register;
+
+ pr_debug("%s sensor registered\n", id->name);
+ return 0;
+
+err_iio_register:
+ destroy_workqueue(barom->wq);
+err_create_workqueue:
+err_read_eeprom:
+ mutex_destroy(&barom->lock);
+ iio_free_device(indio_dev);
+ return err;
+}
+
+static int __devexit bmp182_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct bmp182_data *barom = iio_priv(indio_dev);
+
+ bmp182_disable(barom);
+ destroy_workqueue(barom->wq);
+ mutex_destroy(&barom->lock);
+ iio_device_unregister(indio_dev);
+ iio_free_device(indio_dev);
+ return 0;
+}
+
+static int bmp182_resume(struct device *dev)
+{
+ struct bmp182_data *barom = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+
+ pr_debug("on_before_suspend %d\n", barom->on_before_suspend);
+
+ if (barom->on_before_suspend)
+ bmp182_enable(barom);
+ return 0;
+}
+
+static int bmp182_suspend(struct device *dev)
+{
+ struct bmp182_data *barom = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+
+ barom->on_before_suspend = barom->enabled;
+ pr_debug("on_before_suspend %d\n", barom->on_before_suspend);
+ bmp182_disable(barom);
+ return 0;
+}
+
+static const struct i2c_device_id bmp182_id[] = {
+ {"bmp182", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, bmp182_id);
+static const struct dev_pm_ops bmp182_pm_ops = {
+ .suspend = bmp182_suspend,
+ .resume = bmp182_resume,
+};
+
+static struct i2c_driver bmp182_driver = {
+ .driver = {
+ .name = "bmp182",
+ .owner = THIS_MODULE,
+ .pm = &bmp182_pm_ops,
+ },
+ .probe = bmp182_probe,
+ .remove = __devexit_p(bmp182_remove),
+ .id_table = bmp182_id,
+};
+
+module_i2c_driver(bmp182_driver);
+
+MODULE_AUTHOR("Hyoung Wook Ham <hwham@sta.samsung.com>");
+MODULE_DESCRIPTION("BMP182 Pressure sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
diff --git a/drivers/staging/iio/types.h b/drivers/staging/iio/types.h
index 0c32136..7873357 100644
--- a/drivers/staging/iio/types.h
+++ b/drivers/staging/iio/types.h
@@ -27,6 +27,8 @@
IIO_ANGL,
IIO_TIMESTAMP,
IIO_CAPACITANCE,
+ IIO_PRESSURE,
+ IIO_QUATERNION,
};
enum iio_modifier {
@@ -44,6 +46,7 @@
IIO_MOD_X_OR_Y_OR_Z,
IIO_MOD_LIGHT_BOTH,
IIO_MOD_LIGHT_IR,
+ IIO_MOD_R,
};
#define IIO_VAL_INT 1
diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c
index 18e3a14..5ce1b055 100644
--- a/drivers/tty/serial/samsung.c
+++ b/drivers/tty/serial/samsung.c
@@ -875,6 +875,13 @@
return 0;
}
+static void s3c24xx_serial_wake_peer(struct uart_port *port)
+{
+ struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
+
+ if (cfg->wake_peer)
+ cfg->wake_peer(port);
+}
#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE
@@ -903,6 +910,7 @@
.request_port = s3c24xx_serial_request_port,
.config_port = s3c24xx_serial_config_port,
.verify_port = s3c24xx_serial_verify_port,
+ .wake_peer = s3c24xx_serial_wake_peer,
};
static struct uart_driver s3c24xx_uart_drv = {
diff --git a/drivers/usb/gadget/s3c_udc.h b/drivers/usb/gadget/s3c_udc.h
index 2c21ef8..bfb36ac 100644
--- a/drivers/usb/gadget/s3c_udc.h
+++ b/drivers/usb/gadget/s3c_udc.h
@@ -140,6 +140,9 @@
struct resource *regs_res;
unsigned int irq;
unsigned req_pending:1, req_std:1, req_config:1;
+ int udc_enabled:1;
+ int soft_connected:1;
+ struct usb_phy *phy;
};
extern struct s3c_udc *the_controller;
diff --git a/drivers/usb/gadget/s3c_udc_otg.c b/drivers/usb/gadget/s3c_udc_otg.c
index ad85378..17a001f 100644
--- a/drivers/usb/gadget/s3c_udc_otg.c
+++ b/drivers/usb/gadget/s3c_udc_otg.c
@@ -24,6 +24,7 @@
#include <linux/clk.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
+#include <linux/usb/otg.h>
#include <plat/regs-otg.h>
#include <plat/usb-phy.h>
#include <plat/udc-hs.h>
@@ -141,8 +142,7 @@
static void set_max_pktsize(struct s3c_udc *dev, enum usb_device_speed speed);
static void nuke(struct s3c_ep *ep, int status);
static int s3c_udc_set_halt(struct usb_ep *_ep, int value);
-static void s3c_udc_soft_connect(void);
-static void s3c_udc_soft_disconnect(void);
+static void s3c_udc_update_soft_flag(void);
static struct usb_ep_ops s3c_ep_ops = {
.enable = s3c_ep_enable,
@@ -216,7 +216,6 @@
{
struct platform_device *pdev = dev->dev;
struct s3c_hsotg_plat *pdata = pdev->dev.platform_data;
- u32 utemp;
DEBUG_SETUP("%s: %p\n", __func__, dev);
disable_irq(dev->irq);
@@ -229,12 +228,9 @@
/* Mask the core interrupt */
__raw_writel(0, dev->regs + S3C_UDC_OTG_GINTMSK);
- /* Put the OTG device core in the disconnected state.*/
- utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
- utemp |= SOFT_DISCONNECT;
- __raw_writel(utemp, dev->regs + S3C_UDC_OTG_DCTL);
- udelay(20);
- if (pdata && pdata->phy_exit)
+ if (dev->phy)
+ usb_phy_shutdown(dev->phy);
+ else if (pdata && pdata->phy_exit)
pdata->phy_exit(pdev, S5P_USB_PHY_DEVICE);
clk_disable(dev->clk);
}
@@ -283,7 +279,9 @@
enable_irq(dev->irq);
clk_enable(dev->clk);
- if (pdata->phy_init)
+ if (dev->phy)
+ usb_phy_init(dev->phy);
+ else if (pdata->phy_init)
pdata->phy_init(pdev, S5P_USB_PHY_DEVICE);
reconfig_usbd();
@@ -300,16 +298,26 @@
unsigned long flags;
struct s3c_udc *dev = container_of(gadget, struct s3c_udc, gadget);
- if (!is_active) {
- spin_lock_irqsave(&dev->lock, flags);
- stop_activity(dev, dev->driver);
- spin_unlock_irqrestore(&dev->lock, flags);
- udc_disable(dev);
- } else {
- udc_reinit(dev);
- udc_enable(dev);
- s3c_udc_soft_connect();
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->udc_enabled != is_active) {
+ dev->udc_enabled = is_active;
+
+ if (!is_active) {
+ s3c_udc_update_soft_flag();
+ spin_unlock_irqrestore(&dev->lock, flags);
+ udc_disable(dev);
+ spin_lock_irqsave(&dev->lock, flags);
+ } else {
+ udc_reinit(dev);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ udc_enable(dev);
+
+ spin_lock_irqsave(&dev->lock, flags);
+ s3c_udc_update_soft_flag();
+ }
}
+ spin_unlock_irqrestore(&dev->lock, flags);
return 0;
}
@@ -349,8 +357,6 @@
printk(KERN_INFO "bound driver '%s'\n",
driver->driver.name);
- udc_enable(dev);
-
return 0;
}
@@ -824,39 +830,35 @@
return -ENOTSUPP;
}
-static void s3c_udc_soft_connect(void)
+static void s3c_udc_update_soft_flag(void)
{
struct s3c_udc *dev = the_controller;
- u32 uTemp;
+ u32 val;
- DEBUG("[%s]\n", __func__);
- uTemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
- uTemp = uTemp & ~SOFT_DISCONNECT;
- __raw_writel(uTemp, dev->regs + S3C_UDC_OTG_DCTL);
-}
-
-static void s3c_udc_soft_disconnect(void)
-{
- struct s3c_udc *dev = the_controller;
- u32 uTemp;
- unsigned long flags;
-
- DEBUG("[%s]\n", __func__);
- uTemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
- uTemp |= SOFT_DISCONNECT;
- __raw_writel(uTemp, dev->regs + S3C_UDC_OTG_DCTL);
-
- spin_lock_irqsave(&dev->lock, flags);
- stop_activity(dev, dev->driver);
- spin_unlock_irqrestore(&dev->lock, flags);
+ if (dev->udc_enabled && dev->soft_connected) {
+ val = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ val &= ~SOFT_DISCONNECT;
+ __raw_writel(val, dev->regs + S3C_UDC_OTG_DCTL);
+ } else {
+ val = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ if (!(val & SOFT_DISCONNECT)) {
+ val |= SOFT_DISCONNECT;
+ __raw_writel(val, dev->regs + S3C_UDC_OTG_DCTL);
+ stop_activity(dev, dev->driver);
+ }
+ }
}
static int s3c_udc_pullup(struct usb_gadget *gadget, int is_on)
{
- if (is_on)
- s3c_udc_soft_connect();
- else
- s3c_udc_soft_disconnect();
+ struct s3c_udc *dev = the_controller;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ dev->soft_connected = is_on;
+ s3c_udc_update_soft_flag();
+ spin_unlock_irqrestore(&dev->lock, flags);
+
return 0;
}
@@ -1129,6 +1131,7 @@
struct resource *res;
unsigned int irq;
int retval;
+ u32 tmp;
DEBUG("%s: %p\n", __func__, pdev);
@@ -1176,6 +1179,7 @@
goto err_regs_res;
}
+ dev->phy = usb_get_transceiver();
udc_reinit(dev);
dev->clk = clk_get(&pdev->dev, "usbotg");
@@ -1200,6 +1204,10 @@
/* Mask any interrupt left unmasked by the bootloader */
__raw_writel(0, dev->regs + S3C_UDC_OTG_GINTMSK);
+ /* Stay disconnected until vbus_session is called */
+ tmp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ __raw_writel(tmp | SOFT_DISCONNECT, dev->regs + S3C_UDC_OTG_DCTL);
+
/* irq setup after old hardware state is cleaned up */
irq = platform_get_irq(pdev, 0);
retval =
@@ -1228,6 +1236,9 @@
goto err_add_udc;
}
+ if (dev->phy)
+ otg_set_peripheral(dev->phy->otg, &dev->gadget);
+
create_proc_files();
return retval;
@@ -1245,6 +1256,8 @@
err_regs:
iounmap(dev->regs);
err_regs_res:
+ if (dev->phy)
+ usb_put_transceiver(dev->phy);
release_mem_region(res->start, resource_size(res));
return retval;
}
@@ -1261,6 +1274,8 @@
usb_del_gadget_udc(&dev->gadget);
device_unregister(&dev->gadget.dev);
+ if (dev->phy)
+ usb_put_transceiver(dev->phy);
clk_put(dev->clk);
if (dev->usb_ctrl)
dma_free_coherent(&pdev->dev,
@@ -1304,7 +1319,8 @@
if (dev->driver->disconnect)
dev->driver->disconnect(&dev->gadget);
- udc_disable(dev);
+ if (!dev->phy)
+ udc_disable(dev);
}
return 0;
@@ -1315,9 +1331,10 @@
struct s3c_udc *dev = the_controller;
if (dev->driver) {
- udc_reinit(dev);
- udc_enable(dev);
- s3c_udc_soft_connect();
+ if (!dev->phy) {
+ udc_reinit(dev);
+ udc_enable(dev);
+ }
if (dev->driver->resume)
dev->driver->resume(&dev->gadget);
diff --git a/drivers/usb/host/ehci-s5p.c b/drivers/usb/host/ehci-s5p.c
index 02febbb..b571c2b 100644
--- a/drivers/usb/host/ehci-s5p.c
+++ b/drivers/usb/host/ehci-s5p.c
@@ -30,6 +30,7 @@
struct device *dev;
struct usb_hcd *hcd;
struct clk *clk;
+ struct usb_phy *phy;
};
static const struct hc_driver s5p_ehci_hc_driver = {
@@ -95,6 +96,7 @@
s5p_ehci->hcd = hcd;
s5p_ehci->clk = clk_get(&pdev->dev, "usbhost");
+ s5p_ehci->phy = usb_get_transceiver();
if (IS_ERR(s5p_ehci->clk)) {
dev_err(&pdev->dev, "Failed to get usbhost clock\n");
@@ -129,7 +131,9 @@
goto fail;
}
- if (pdata->phy_init)
+ if (s5p_ehci->phy)
+ usb_phy_init(s5p_ehci->phy);
+ else if (pdata->phy_init)
pdata->phy_init(pdev, S5P_USB_PHY_HOST);
ehci = hcd_to_ehci(hcd);
@@ -156,6 +160,14 @@
platform_set_drvdata(pdev, s5p_ehci);
+ if (s5p_ehci->phy) {
+ err = otg_set_host(s5p_ehci->phy->otg, &hcd->self);
+ if (err)
+ goto fail;
+ }
+
+ clk_disable(s5p_ehci->clk);
+
return 0;
fail:
@@ -167,6 +179,8 @@
fail_clk:
usb_put_hcd(hcd);
fail_hcd:
+ if (s5p_ehci->phy)
+ usb_put_transceiver(s5p_ehci->phy);
kfree(s5p_ehci);
return err;
}
@@ -179,8 +193,12 @@
usb_remove_hcd(hcd);
- if (pdata && pdata->phy_exit)
+ if (s5p_ehci->phy) {
+ otg_set_host(s5p_ehci->phy->otg, NULL);
+ usb_put_transceiver(s5p_ehci->phy);
+ } else if (pdata && pdata->phy_exit) {
pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
+ }
iounmap(hcd->regs);
@@ -216,6 +234,15 @@
unsigned long flags;
int rc = 0;
+ /*
+ * If we have an otg driver, the otg wakelock blocks suspend while
+ * we are in host mode. When you unplug the host cable, the otg driver
+ * stops the ehci controller and shuts down the phy immediately, so the
+ * ehci has already been stopped when this function is called.
+ */
+ if (s5p_ehci->phy)
+ return 0;
+
if (time_before(jiffies, ehci->next_statechange))
msleep(20);
@@ -246,6 +273,9 @@
struct platform_device *pdev = to_platform_device(dev);
struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
+ if (s5p_ehci->phy)
+ return 0;
+
if (pdata && pdata->phy_init)
pdata->phy_init(pdev, S5P_USB_PHY_HOST);
diff --git a/drivers/usb/host/ohci-exynos.c b/drivers/usb/host/ohci-exynos.c
index b75d19b..328fae6 100644
--- a/drivers/usb/host/ohci-exynos.c
+++ b/drivers/usb/host/ohci-exynos.c
@@ -13,6 +13,7 @@
#include <linux/clk.h>
#include <linux/platform_device.h>
+#include <linux/usb/otg.h>
#include <mach/ohci.h>
#include <plat/usb-phy.h>
@@ -20,6 +21,7 @@
struct device *dev;
struct usb_hcd *hcd;
struct clk *clk;
+ struct usb_phy *phy;
};
static int ohci_exynos_init(struct usb_hcd *hcd)
@@ -113,6 +115,7 @@
exynos_ohci->hcd = hcd;
exynos_ohci->clk = clk_get(&pdev->dev, "usbhost");
+ exynos_ohci->phy = usb_get_transceiver();
if (IS_ERR(exynos_ohci->clk)) {
dev_err(&pdev->dev, "Failed to get usbhost clock\n");
@@ -147,7 +150,9 @@
goto fail;
}
- if (pdata->phy_init)
+ if (exynos_ohci->phy)
+ usb_phy_init(exynos_ohci->phy);
+ else if (pdata->phy_init)
pdata->phy_init(pdev, S5P_USB_PHY_HOST);
ohci = hcd_to_ohci(hcd);
@@ -159,8 +164,13 @@
goto fail;
}
+ if (exynos_ohci->phy)
+ otg_set_host(exynos_ohci->phy->otg, &hcd->self);
+
platform_set_drvdata(pdev, exynos_ohci);
+ clk_disable(exynos_ohci->clk);
+
return 0;
fail:
@@ -172,6 +182,8 @@
fail_clk:
usb_put_hcd(hcd);
fail_hcd:
+ if (exynos_ohci->phy)
+ usb_put_transceiver(exynos_ohci->phy);
kfree(exynos_ohci);
return err;
}
@@ -184,7 +196,9 @@
usb_remove_hcd(hcd);
- if (pdata && pdata->phy_exit)
+ if (exynos_ohci->phy)
+ usb_put_transceiver(exynos_ohci->phy);
+ else if (pdata && pdata->phy_exit)
pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
iounmap(hcd->regs);
@@ -221,6 +235,9 @@
unsigned long flags;
int rc = 0;
+ if (exynos_ohci->phy)
+ return 0;
+
/*
* Root hub was already suspended. Disable irq emission and
* mark HW unaccessible, bail out if RH has been resumed. Use
@@ -251,6 +268,9 @@
struct platform_device *pdev = to_platform_device(dev);
struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data;
+ if (exynos_ohci->phy)
+ return 0;
+
if (pdata && pdata->phy_init)
pdata->phy_init(pdev, S5P_USB_PHY_HOST);
diff --git a/drivers/usb/otg/otg-wakelock.c b/drivers/usb/otg/otg-wakelock.c
index e17e272..34c3668f 100644
--- a/drivers/usb/otg/otg-wakelock.c
+++ b/drivers/usb/otg/otg-wakelock.c
@@ -90,12 +90,12 @@
switch (event) {
case USB_EVENT_VBUS:
+ case USB_EVENT_ID:
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;
diff --git a/drivers/video/fbmon.c b/drivers/video/fbmon.c
index cef6557..6b43462 100644
--- a/drivers/video/fbmon.c
+++ b/drivers/video/fbmon.c
@@ -551,6 +551,9 @@
static void get_detailed_timing(unsigned char *block,
struct fb_videomode *mode)
{
+ int v_size = V_SIZE;
+ int h_size = H_SIZE;
+
mode->xres = H_ACTIVE;
mode->yres = V_ACTIVE;
mode->pixclock = PIXEL_CLOCK;
@@ -579,11 +582,18 @@
}
mode->flag = FB_MODE_IS_DETAILED;
+ /* get aspect ratio */
+ if (h_size * 18 > v_size * 31 && h_size * 18 < v_size * 33)
+ mode->flag |= FB_FLAG_RATIO_16_9;
+ if (h_size * 18 > v_size * 23 && h_size * 18 < v_size * 25)
+ mode->flag |= FB_FLAG_RATIO_4_3;
+
DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000);
DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET,
H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING);
DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET,
V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING);
+ DPRINTK("%dmm %dmm ", H_SIZE, V_SIZE);
DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-",
(VSYNC_POSITIVE) ? "+" : "-");
}
@@ -983,17 +993,75 @@
}
/**
+ * fb_edid_get_cea_sample_rates() - convert from CEA sample rate format
+ * @cea_sample_rates: sample rate bitfield
+ *
+ * DESCRIPTION:
+ *
+ * This function converts from the sample rates bitfield given in
+ * SAD byte 2 of the Audio Data Block from the CEA EDID Timing
+ * Extension v3 to something that can be understood here.
+ */
+static u8 fb_edid_get_cea_sample_rates(u8 cea_sample_rates)
+{
+ u8 rates = 0;
+
+ if (cea_sample_rates & (1 << 0))
+ rates |= FB_AUDIO_32KHZ;
+ if (cea_sample_rates & (1 << 1))
+ rates |= FB_AUDIO_44KHZ;
+ if (cea_sample_rates & (1 << 2))
+ rates |= FB_AUDIO_48KHZ;
+ if (cea_sample_rates & (1 << 3))
+ rates |= FB_AUDIO_88KHZ;
+ if (cea_sample_rates & (1 << 4))
+ rates |= FB_AUDIO_96KHZ;
+ if (cea_sample_rates & (1 << 5))
+ rates |= FB_AUDIO_176KHZ;
+ if (cea_sample_rates & (1 << 6))
+ rates |= FB_AUDIO_192KHZ;
+
+ return rates;
+}
+
+/**
+ * fb_edid_get_cea_bit_rates() - convert from CEA bit rate format
+ * @cea_bit_rates: bit rate bitfield
+ *
+ * DESCRIPTION:
+ *
+ * This function converts from the bit rate bitfield given in
+ * SAD byte 3 of the Audio Data Block from the CEA EDID Timing
+ * Extension v3 to something that can be understood here.
+ */
+static u8 fb_edid_get_cea_bit_rates(u8 cea_bit_rates)
+{
+ u8 rates = 0;
+
+ if (cea_bit_rates & (1 << 0))
+ rates |= FB_AUDIO_16BIT;
+ if (cea_bit_rates & (1 << 1))
+ rates |= FB_AUDIO_20BIT;
+ if (cea_bit_rates & (1 << 2))
+ rates |= FB_AUDIO_24BIT;
+
+ return rates;
+}
+
+/**
* fb_edid_add_monspecs() - add monitor video modes from E-EDID data
* @edid: 128 byte array with an E-EDID block
- * @spacs: monitor specs to be extended
+ * @specs: monitor specs to be extended
*/
void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs)
{
unsigned char *block;
struct fb_videomode *m;
+ struct fb_audio *audiodb;
int num = 0, i;
- u8 svd[64], edt[(128 - 4) / DETAILED_TIMING_DESCRIPTION_SIZE];
- u8 pos = 4, svd_n = 0;
+ u8 sad[128 - 5], svd[64];
+ u8 edt[(128 - 4) / DETAILED_TIMING_DESCRIPTION_SIZE];
+ u8 pos = 4, sad_n = 0, svd_n = 0;
if (!edid)
return;
@@ -1001,23 +1069,74 @@
if (!edid_checksum(edid))
return;
- if (edid[0] != 0x2 ||
+ if (edid[0] != 0x2 || edid[1] != 0x3 ||
edid[2] < 4 || edid[2] > 128 - DETAILED_TIMING_DESCRIPTION_SIZE)
return;
- DPRINTK(" Short Video Descriptors\n");
+ DPRINTK(" Data Block Collection\n");
while (pos < edid[2]) {
u8 len = edid[pos] & 0x1f, type = (edid[pos] >> 5) & 7;
pr_debug("Data block %u of %u bytes\n", type, len);
- if (type == 2)
+
+ if (len == 0)
+ break;
+
+ pos++;
+ if (type == 1) {
+ /* Short Audio Descriptors */
+ for (i = pos; i < pos + len; i += 3) {
+ if (((edid[i] >> 3) & 0xf) != 1)
+ continue; /* skip non-lpcm */
+
+ pr_debug("LPCM ch=%d\n", (edid[i] & 7) + 1);
+
+ sad[sad_n++] = (edid[i] & 7) + 1;
+ sad[sad_n++] = edid[i + 1];
+ sad[sad_n++] = edid[i + 2];
+ }
+ } else if (type == 2) {
+ /* Short Video Descriptors */
for (i = pos; i < pos + len; i++) {
- u8 idx = edid[pos + i] & 0x7f;
+ u8 idx = edid[i] & 0x7f;
svd[svd_n++] = idx;
pr_debug("N%sative mode #%d\n",
- edid[pos + i] & 0x80 ? "" : "on-n", idx);
+ edid[i] & 0x80 ? "" : "on-n", idx);
}
- pos += len + 1;
+ } else if (type == 3 && len >= 3) {
+ /* Vendor block */
+ u32 ieee_reg = edid[pos] | (edid[pos + 1] << 8) |
+ (edid[pos + 2] << 16);
+ if (ieee_reg == 0x000c03)
+ specs->misc |= FB_MISC_HDMI;
+ }
+
+ pos += len;
+ }
+
+ if (sad_n > 0) {
+ /* Short audio descriptors are in blocks of 3 bytes */
+ sad_n /= 3;
+ pr_debug("Found %d lpcm audio blocks\n", sad_n);
+ audiodb = kzalloc(sad_n * sizeof(struct fb_audio), GFP_KERNEL);
+ if (!audiodb)
+ return;
+
+ for (i = 0; i < sad_n; i++) {
+ audiodb[i].format = FB_AUDIO_LPCM;
+ audiodb[i].channel_count = sad[i * 3];
+ audiodb[i].sample_rates =
+ fb_edid_get_cea_sample_rates(sad[i * 3 + 1]);
+ audiodb[i].bit_rates =
+ fb_edid_get_cea_bit_rates(sad[i * 3 + 2]);
+ }
+
+ kfree(specs->audiodb);
+ specs->audiodb = audiodb;
+ specs->audiodb_len = sad_n;
+ } else {
+ kfree(specs->audiodb);
+ specs->audiodb_len = 0;
}
block = edid + edid[2];
@@ -1029,7 +1148,7 @@
if (PIXEL_CLOCK)
edt[num++] = block - edid;
- /* Yikes, EDID data is totally useless */
+ /* No video descriptors, so nothing more to do */
if (!(num + svd_n))
return;
@@ -1050,10 +1169,8 @@
for (i = specs->modedb_len + num; i < specs->modedb_len + num + svd_n; i++) {
int idx = svd[i - specs->modedb_len - num];
- if (!idx || idx > 63) {
+ if (!idx || idx > (CEA_MODEDB_SIZE - 1)) {
pr_warning("Reserved SVD code %d\n", idx);
- } else if (idx > ARRAY_SIZE(cea_modes) || !cea_modes[idx].xres) {
- pr_warning("Unimplemented SVD code %d\n", idx);
} else {
memcpy(&m[i], cea_modes + idx, sizeof(m[i]));
pr_debug("Adding SVD #%d: %ux%u@%u\n", idx,
diff --git a/drivers/video/modedb.c b/drivers/video/modedb.c
index a9a907c..d67f8a6 100644
--- a/drivers/video/modedb.c
+++ b/drivers/video/modedb.c
@@ -292,64 +292,524 @@
};
#ifdef CONFIG_FB_MODE_HELPERS
-const struct fb_videomode cea_modes[64] = {
- /* #1: 640x480p@59.94/60Hz */
- [1] = {
- NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0,
- FB_VMODE_NONINTERLACED, 0,
- },
- /* #3: 720x480p@59.94/60Hz */
- [3] = {
- NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
- FB_VMODE_NONINTERLACED, 0,
- },
- /* #5: 1920x1080i@59.94/60Hz */
- [5] = {
- NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5,
- FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
- FB_VMODE_INTERLACED, 0,
- },
- /* #7: 720(1440)x480iH@59.94/60Hz */
- [7] = {
- NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
- FB_VMODE_INTERLACED, 0,
- },
- /* #9: 720(1440)x240pH@59.94/60Hz */
- [9] = {
- NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0,
- FB_VMODE_NONINTERLACED, 0,
- },
- /* #18: 720x576pH@50Hz */
- [18] = {
- NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
- FB_VMODE_NONINTERLACED, 0,
- },
- /* #19: 1280x720p@50Hz */
- [19] = {
- NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5,
- FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
- FB_VMODE_NONINTERLACED, 0,
- },
- /* #20: 1920x1080i@50Hz */
- [20] = {
- NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5,
- FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
- FB_VMODE_INTERLACED, 0,
- },
- /* #32: 1920x1080p@23.98/24Hz */
- [32] = {
- NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5,
- FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
- FB_VMODE_NONINTERLACED, 0,
- },
- /* #35: (2880)x480p4x@59.94/60Hz */
- [35] = {
- NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0,
- FB_VMODE_NONINTERLACED, 0,
- },
+const struct fb_videomode cea_modes[CEA_MODEDB_SIZE] = {
+ {},
+ /* 1: 640x480p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 640, .yres = 480, .pixclock = 39721,
+ .left_margin = 48, .right_margin = 16,
+ .upper_margin = 33, .lower_margin = 1,
+ .hsync_len = 96, .vsync_len = 2,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 2: 720x480p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 720, .yres = 480, .pixclock = 37037,
+ .left_margin = 60, .right_margin = 16,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 62, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 3: 720x480p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 720, .yres = 480, .pixclock = 37037,
+ .left_margin = 60, .right_margin = 16,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 62, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 4: 1280x720p @ 59.94Hz/60Hz */
+ {.refresh = 60, .xres = 1280, .yres = 720, .pixclock = 13468,
+ .left_margin = 220, .right_margin = 110,
+ .upper_margin = 20, .lower_margin = 5,
+ .hsync_len = 40, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 5: 1920x1080i @ 59.94Hz/60Hz */
+ {.refresh = 60, .xres = 1920, .yres = 1080, .pixclock = 13468,
+ .left_margin = 148, .right_margin = 88,
+ .upper_margin = 15, .lower_margin = 2,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 6: 720(1440)x480i @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 1440, .yres = 480, .pixclock = 37037,
+ .left_margin = 114, .right_margin = 38,
+ .upper_margin = 15, .lower_margin = 4,
+ .hsync_len = 124, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 7: 720(1440)x480i @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 1440, .yres = 480, .pixclock = 37037,
+ .left_margin = 114, .right_margin = 38,
+ .upper_margin = 15, .lower_margin = 4,
+ .hsync_len = 124, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 8: 720(1440)x240p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 1440, .yres = 240, .pixclock = 37037,
+ .left_margin = 114, .right_margin = 38,
+ .upper_margin = 15, .lower_margin = 5,
+ .hsync_len = 124, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 9: 720(1440)x240p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 1440, .yres = 240, .pixclock = 37037,
+ .left_margin = 114, .right_margin = 38,
+ .upper_margin = 15, .lower_margin = 5,
+ .hsync_len = 124, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 10: 2880x480i @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 2880, .yres = 480, .pixclock = 18518,
+ .left_margin = 228, .right_margin = 76,
+ .upper_margin = 15, .lower_margin = 4,
+ .hsync_len = 248, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 11: 2880x480i @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 2880, .yres = 480, .pixclock = 18518,
+ .left_margin = 228, .right_margin = 76,
+ .upper_margin = 15, .lower_margin = 4,
+ .hsync_len = 248, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 12: 2880x240p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 2880, .yres = 240, .pixclock = 18518,
+ .left_margin = 228, .right_margin = 76,
+ .upper_margin = 15, .lower_margin = 5,
+ .hsync_len = 248, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 13: 2880x240p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 2880, .yres = 240, .pixclock = 18518,
+ .left_margin = 228, .right_margin = 76,
+ .upper_margin = 15, .lower_margin = 5,
+ .hsync_len = 248, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 14: 1440x480p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 1440, .yres = 480, .pixclock = 18518,
+ .left_margin = 120, .right_margin = 32,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 124, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 15: 1440x480p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 1440, .yres = 480, .pixclock = 18518,
+ .left_margin = 120, .right_margin = 32,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 124, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 16: 1920x1080p @ 59.94Hz/60Hz */
+ {.refresh = 60, .xres = 1920, .yres = 1080, .pixclock = 6734,
+ .left_margin = 148, .right_margin = 88,
+ .upper_margin = 36, .lower_margin = 4,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 17: 720x576p @ 50Hz */
+ {.refresh = 50, .xres = 720, .yres = 576, .pixclock = 37037,
+ .left_margin = 68, .right_margin = 12,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 64, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 18: 720x576p @ 50Hz */
+ {.refresh = 50, .xres = 720, .yres = 576, .pixclock = 37037,
+ .left_margin = 68, .right_margin = 12,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 64, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 19: 1280x720p @ 50Hz */
+ {.refresh = 50, .xres = 1280, .yres = 720, .pixclock = 13468,
+ .left_margin = 220, .right_margin = 440,
+ .upper_margin = 20, .lower_margin = 5,
+ .hsync_len = 40, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 20: 1920x1080i @ 50Hz */
+ {.refresh = 50, .xres = 1920, .yres = 1080, .pixclock = 13468,
+ .left_margin = 148, .right_margin = 528,
+ .upper_margin = 15, .lower_margin = 2,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 21: 720(1440)x576i @ 50Hz */
+ {.refresh = 50, .xres = 1440, .yres = 576, .pixclock = 37037,
+ .left_margin = 138, .right_margin = 24,
+ .upper_margin = 19, .lower_margin = 2,
+ .hsync_len = 126, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 22: 720(1440)x576i @ 50Hz */
+ {.refresh = 50, .xres = 1440, .yres = 576, .pixclock = 37037,
+ .left_margin = 138, .right_margin = 24,
+ .upper_margin = 19, .lower_margin = 2,
+ .hsync_len = 126, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 23: 720(1440)x288p @ 50Hz */
+ {.refresh = 49, .xres = 1440, .yres = 288, .pixclock = 37037,
+ .left_margin = 138, .right_margin = 24,
+ .upper_margin = 19, .lower_margin = 4,
+ .hsync_len = 126, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 24: 720(1440)x288p @ 50Hz */
+ {.refresh = 49, .xres = 1440, .yres = 288, .pixclock = 37037,
+ .left_margin = 138, .right_margin = 24,
+ .upper_margin = 19, .lower_margin = 4,
+ .hsync_len = 126, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 25: 2880x576i @ 50Hz */
+ {.refresh = 50, .xres = 2880, .yres = 576, .pixclock = 18518,
+ .left_margin = 276, .right_margin = 48,
+ .upper_margin = 19, .lower_margin = 2,
+ .hsync_len = 252, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 26: 2880x576i @ 50Hz */
+ {.refresh = 50, .xres = 2880, .yres = 576, .pixclock = 18518,
+ .left_margin = 276, .right_margin = 48,
+ .upper_margin = 19, .lower_margin = 2,
+ .hsync_len = 252, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 27: 2880x288p @ 50Hz */
+ {.refresh = 49, .xres = 2880, .yres = 288, .pixclock = 18518,
+ .left_margin = 276, .right_margin = 48,
+ .upper_margin = 19, .lower_margin = 4,
+ .hsync_len = 252, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 28: 2880x288p @ 50Hz */
+ {.refresh = 49, .xres = 2880, .yres = 288, .pixclock = 18518,
+ .left_margin = 276, .right_margin = 48,
+ .upper_margin = 19, .lower_margin = 4,
+ .hsync_len = 252, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 29: 1440x576p @ 50Hz */
+ {.refresh = 50, .xres = 1440, .yres = 576, .pixclock = 18518,
+ .left_margin = 136, .right_margin = 24,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 128, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 30: 1440x576p @ 50Hz */
+ {.refresh = 50, .xres = 1440, .yres = 576, .pixclock = 18518,
+ .left_margin = 136, .right_margin = 24,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 128, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 31: 1920x1080p @ 50Hz */
+ {.refresh = 50, .xres = 1920, .yres = 1080, .pixclock = 6734,
+ .left_margin = 148, .right_margin = 528,
+ .upper_margin = 36, .lower_margin = 4,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 32: 1920x1080p @ 23.97Hz/24Hz */
+ {.refresh = 24, .xres = 1920, .yres = 1080, .pixclock = 13468,
+ .left_margin = 148, .right_margin = 638,
+ .upper_margin = 36, .lower_margin = 4,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 33: 1920x1080p @ 25Hz */
+ {.refresh = 25, .xres = 1920, .yres = 1080, .pixclock = 13468,
+ .left_margin = 148, .right_margin = 528,
+ .upper_margin = 36, .lower_margin = 4,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 34: 1920x1080p @ 29.97Hz/30Hz */
+ {.refresh = 30, .xres = 1920, .yres = 1080, .pixclock = 13468,
+ .left_margin = 148, .right_margin = 88,
+ .upper_margin = 36, .lower_margin = 4,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 35: 2880x480p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 2880, .yres = 480, .pixclock = 9259,
+ .left_margin = 240, .right_margin = 64,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 248, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 36: 2880x480p @ 59.94Hz/60Hz */
+ {.refresh = 59, .xres = 2880, .yres = 480, .pixclock = 9259,
+ .left_margin = 240, .right_margin = 64,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 248, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 37: 2880x576p @ 50Hz */
+ {.refresh = 50, .xres = 2880, .yres = 576, .pixclock = 9259,
+ .left_margin = 272, .right_margin = 48,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 256, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 38: 2880x576p @ 50Hz */
+ {.refresh = 50, .xres = 2880, .yres = 576, .pixclock = 9259,
+ .left_margin = 272, .right_margin = 48,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 256, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 39: 1920x1080i @ 50Hz */
+ {.refresh = 50, .xres = 1920, .yres = 1080, .pixclock = 13888,
+ .left_margin = 184, .right_margin = 32,
+ .upper_margin = 57, .lower_margin = 2,
+ .hsync_len = 168, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 40: 1920x1080i @ 100Hz */
+ {.refresh = 100, .xres = 1920, .yres = 1080, .pixclock = 6734,
+ .left_margin = 148, .right_margin = 528,
+ .upper_margin = 15, .lower_margin = 2,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 41: 1280x720p @ 100Hz */
+ {.refresh = 100, .xres = 1280, .yres = 720, .pixclock = 6734,
+ .left_margin = 220, .right_margin = 440,
+ .upper_margin = 20, .lower_margin = 5,
+ .hsync_len = 40, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 42: 720x576p @ 100Hz */
+ {.refresh = 100, .xres = 720, .yres = 576, .pixclock = 18518,
+ .left_margin = 68, .right_margin = 12,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 64, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 43: 720x576p @ 100Hz */
+ {.refresh = 100, .xres = 720, .yres = 576, .pixclock = 18518,
+ .left_margin = 68, .right_margin = 12,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 64, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 44: 720(1440)x576i @ 100Hz */
+ {.refresh = 100, .xres = 1440, .yres = 576, .pixclock = 18518,
+ .left_margin = 138, .right_margin = 24,
+ .upper_margin = 19, .lower_margin = 2,
+ .hsync_len = 126, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 45: 720(1440)x576i @ 100Hz */
+ {.refresh = 100, .xres = 1440, .yres = 576, .pixclock = 18518,
+ .left_margin = 138, .right_margin = 24,
+ .upper_margin = 19, .lower_margin = 2,
+ .hsync_len = 126, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 46: 1920x1080i @ 119.88/120Hz */
+ {.refresh = 120, .xres = 1920, .yres = 1080, .pixclock = 6734,
+ .left_margin = 148, .right_margin = 88,
+ .upper_margin = 15, .lower_margin = 2,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 47: 1280x720p @ 119.88/120Hz */
+ {.refresh = 120, .xres = 1280, .yres = 720, .pixclock = 6734,
+ .left_margin = 220, .right_margin = 110,
+ .upper_margin = 20, .lower_margin = 5,
+ .hsync_len = 40, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 48: 720x480p @ 119.88/120Hz */
+ {.refresh = 119, .xres = 720, .yres = 480, .pixclock = 18518,
+ .left_margin = 60, .right_margin = 16,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 62, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 49: 720x480p @ 119.88/120Hz */
+ {.refresh = 119, .xres = 720, .yres = 480, .pixclock = 18518,
+ .left_margin = 60, .right_margin = 16,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 62, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 50: 720(1440)x480i @ 119.88/120Hz */
+ {.refresh = 119, .xres = 1440, .yres = 480, .pixclock = 18518,
+ .left_margin = 114, .right_margin = 38,
+ .upper_margin = 15, .lower_margin = 4,
+ .hsync_len = 124, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 51: 720(1440)x480i @ 119.88/120Hz */
+ {.refresh = 119, .xres = 1440, .yres = 480, .pixclock = 18518,
+ .left_margin = 114, .right_margin = 38,
+ .upper_margin = 15, .lower_margin = 4,
+ .hsync_len = 124, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 52: 720x576p @ 200Hz */
+ {.refresh = 200, .xres = 720, .yres = 576, .pixclock = 9259,
+ .left_margin = 68, .right_margin = 12,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 64, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 53: 720x576p @ 200Hz */
+ {.refresh = 200, .xres = 720, .yres = 576, .pixclock = 9259,
+ .left_margin = 68, .right_margin = 12,
+ .upper_margin = 39, .lower_margin = 5,
+ .hsync_len = 64, .vsync_len = 5,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 54: 720(1440)x576i @ 200Hz */
+ {.refresh = 200, .xres = 1440, .yres = 576, .pixclock = 9259,
+ .left_margin = 138, .right_margin = 24,
+ .upper_margin = 19, .lower_margin = 2,
+ .hsync_len = 126, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 55: 720(1440)x576i @ 200Hz */
+ {.refresh = 200, .xres = 1440, .yres = 576, .pixclock = 9259,
+ .left_margin = 138, .right_margin = 24,
+ .upper_margin = 19, .lower_margin = 2,
+ .hsync_len = 126, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 56: 720x480p @ 239.76/240Hz */
+ {.refresh = 239, .xres = 720, .yres = 480, .pixclock = 9259,
+ .left_margin = 60, .right_margin = 16,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 62, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 57: 720x480p @ 239.76/240Hz */
+ {.refresh = 239, .xres = 720, .yres = 480, .pixclock = 9259,
+ .left_margin = 60, .right_margin = 16,
+ .upper_margin = 30, .lower_margin = 9,
+ .hsync_len = 62, .vsync_len = 6,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 58: 720(1440)x480i @ 239.76/240Hz */
+ {.refresh = 239, .xres = 1440, .yres = 480, .pixclock = 9259,
+ .left_margin = 114, .right_margin = 38,
+ .upper_margin = 15, .lower_margin = 4,
+ .hsync_len = 124, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_4_3 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 59: 720(1440)x480i @ 239.76/240Hz */
+ {.refresh = 239, .xres = 1440, .yres = 480, .pixclock = 9259,
+ .left_margin = 114, .right_margin = 38,
+ .upper_margin = 15, .lower_margin = 4,
+ .hsync_len = 124, .vsync_len = 3,
+ .sync = 0,
+ .flag = FB_FLAG_RATIO_16_9 | FB_FLAG_PIXEL_REPEAT,
+ .vmode = FB_VMODE_INTERLACED},
+ /* 60: 1280x720p @ 23.97Hz/24Hz */
+ {.refresh = 24, .xres = 1280, .yres = 720, .pixclock = 16835,
+ .left_margin = 220, .right_margin = 1760,
+ .upper_margin = 20, .lower_margin = 5,
+ .hsync_len = 40, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 61: 1280x720p @ 25Hz */
+ {.refresh = 25, .xres = 1280, .yres = 720, .pixclock = 13468,
+ .left_margin = 220, .right_margin = 2420,
+ .upper_margin = 20, .lower_margin = 5,
+ .hsync_len = 40, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 62: 1280x720p @ 29.97Hz/30Hz */
+ {.refresh = 30, .xres = 1280, .yres = 720, .pixclock = 13468,
+ .left_margin = 220, .right_margin = 1760,
+ .upper_margin = 20, .lower_margin = 5,
+ .hsync_len = 40, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 63: 1920x1080p @ 119.88/120Hz */
+ {.refresh = 120, .xres = 1920, .yres = 1080, .pixclock = 3367,
+ .left_margin = 148, .right_margin = 88,
+ .upper_margin = 36, .lower_margin = 4,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
+ /* 64: 1920x1080p @ 100Hz */
+ {.refresh = 100, .xres = 1920, .yres = 1080, .pixclock = 3367,
+ .left_margin = 148, .right_margin = 528,
+ .upper_margin = 36, .lower_margin = 4,
+ .hsync_len = 44, .vsync_len = 5,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ .flag = FB_FLAG_RATIO_16_9,
+ .vmode = FB_VMODE_NONINTERLACED},
};
+EXPORT_SYMBOL(cea_modes);
-const struct fb_videomode vesa_modes[] = {
+const struct fb_videomode vesa_modes[VESA_MODEDB_SIZE] = {
/* 0 640x350-85 VESA */
{ NULL, 85, 640, 350, 31746, 96, 32, 60, 32, 64, 3,
FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA},
diff --git a/drivers/video/s5p-dp-core.c b/drivers/video/s5p-dp-core.c
index 7135bee..ea3eae3 100644
--- a/drivers/video/s5p-dp-core.c
+++ b/drivers/video/s5p-dp-core.c
@@ -113,8 +113,8 @@
}
sum = s5p_dp_calc_edid_check_sum(edid);
if (sum != 0) {
- dev_err(dp->dev, "EDID bad checksum!\n");
- return -EIO;
+ dev_warn(dp->dev, "EDID bad checksum!\n");
+ return 0;
}
/* Read additional EDID data */
@@ -129,8 +129,8 @@
}
sum = s5p_dp_calc_edid_check_sum(&edid[EDID_BLOCK_LENGTH]);
if (sum != 0) {
- dev_err(dp->dev, "EDID bad checksum!\n");
- return -EIO;
+ dev_warn(dp->dev, "EDID bad checksum!\n");
+ return 0;
}
retval = s5p_dp_read_byte_from_dpcd(dp,
@@ -172,8 +172,8 @@
}
sum = s5p_dp_calc_edid_check_sum(edid);
if (sum != 0) {
- dev_err(dp->dev, "EDID bad checksum!\n");
- return -EIO;
+ dev_warn(dp->dev, "EDID bad checksum!\n");
+ return 0;
}
retval = s5p_dp_read_byte_from_dpcd(dp,
diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig
index eb9e376..966501e 100644
--- a/drivers/w1/slaves/Kconfig
+++ b/drivers/w1/slaves/Kconfig
@@ -94,6 +94,19 @@
If you are unsure, say N.
+config W1_SLAVE_DS2784
+ tristate "Dallas 2784 fuel gauge chip"
+ depends on W1
+ help
+ If you enable this you will have DS2784 fuel gauge
+ chip support.
+
+ This fuel gauge chip is used to monitor remaining battery
+ capacity, and possibly protect against charging errors, for
+ Li+ batteries.
+
+ If you are unsure, say N.
+
config W1_SLAVE_BQ27000
tristate "BQ27000 slave support"
depends on W1
diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile
index c4f1859..5c5720e 100644
--- a/drivers/w1/slaves/Makefile
+++ b/drivers/w1/slaves/Makefile
@@ -11,4 +11,5 @@
obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o
obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o
obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o
+obj-$(CONFIG_W1_SLAVE_DS2784) += w1_ds2784.o
obj-$(CONFIG_W1_SLAVE_BQ27000) += w1_bq27000.o
diff --git a/drivers/w1/slaves/w1_ds2784.c b/drivers/w1/slaves/w1_ds2784.c
new file mode 100644
index 0000000..2df25aa
--- /dev/null
+++ b/drivers/w1/slaves/w1_ds2784.c
@@ -0,0 +1,118 @@
+/*
+ * 1-Wire implementation for the ds2784 chip
+ *
+ * Copyright (C) 2012 Invensense, Inc.
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include "../w1.h"
+#include "../w1_int.h"
+#include "../w1_family.h"
+#include "w1_ds2784.h"
+
+static int w1_ds2784_io(struct device *dev, char *buf, int addr, size_t count,
+ int io)
+{
+ struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
+
+ mutex_lock(&sl->master->mutex);
+
+ if (addr > DS2784_DATA_SIZE || addr < 0) {
+ count = 0;
+ goto out;
+ }
+ if (addr + count > DS2784_DATA_SIZE)
+ count = DS2784_DATA_SIZE - addr;
+
+ if (!w1_reset_select_slave(sl)) {
+ if (!io) {
+ w1_write_8(sl->master, W1_DS2784_READ_DATA);
+ w1_write_8(sl->master, addr);
+ count = w1_read_block(sl->master, buf, count);
+ } else {
+ w1_write_8(sl->master, W1_DS2784_WRITE_DATA);
+ w1_write_8(sl->master, addr);
+ w1_write_block(sl->master, buf, count);
+ }
+ }
+
+out:
+ mutex_unlock(&sl->master->mutex);
+
+ return count;
+}
+
+int w1_ds2784_read(struct device *dev, char *buf, int addr, size_t count)
+{
+ return w1_ds2784_io(dev, buf, addr, count, 0);
+}
+EXPORT_SYMBOL(w1_ds2784_read);
+
+static int w1_ds2784_add_slave(struct w1_slave *sl)
+{
+ struct platform_device *pdev;
+ int ret;
+
+ pdev = platform_device_alloc("ds2784-fuelgauge", -1);
+ if (!pdev)
+ return -ENOMEM;
+
+ pdev->dev.parent = &sl->dev;
+ ret = platform_device_add(pdev);
+
+ if (ret)
+ goto add_failed;
+
+ dev_set_drvdata(&sl->dev, pdev);
+ return 0;
+
+add_failed:
+ platform_device_unregister(pdev);
+ return ret;
+}
+
+static void w1_ds2784_remove_slave(struct w1_slave *sl)
+{
+ struct platform_device *pdev = dev_get_drvdata(&sl->dev);
+
+ platform_device_unregister(pdev);
+}
+
+static struct w1_family_ops w1_ds2784_fops = {
+ .add_slave = w1_ds2784_add_slave,
+ .remove_slave = w1_ds2784_remove_slave,
+};
+
+static struct w1_family w1_ds2784_family = {
+ .fid = W1_FAMILY_DS2784,
+ .fops = &w1_ds2784_fops,
+};
+
+int __init w1_ds2784_init(void)
+{
+ return w1_register_family(&w1_ds2784_family);
+}
+
+static void __exit w1_ds2784_exit(void)
+{
+ w1_unregister_family(&w1_ds2784_family);
+}
+
+module_init(w1_ds2784_init);
+module_exit(w1_ds2784_exit);
+
+MODULE_AUTHOR("Samsung");
+MODULE_DESCRIPTION("1-wire Driver for Maxim/Dallas DS2784 Fuel Gauge IC");
+MODULE_LICENSE("GPL");
diff --git a/drivers/w1/slaves/w1_ds2784.h b/drivers/w1/slaves/w1_ds2784.h
new file mode 100644
index 0000000..9b17453
--- /dev/null
+++ b/drivers/w1/slaves/w1_ds2784.h
@@ -0,0 +1,92 @@
+/*
+ * 1-Wire implementation for the ds2784 chip
+ *
+ * Copyright (C) 2012 Invensense, Inc.
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _W1_DS2784_H
+#define _W1_DS2784_H
+
+#define W1_DS2784_READ_DATA 0x69
+#define W1_DS2784_WRITE_DATA 0x6C
+
+#define DS2784_REG_PORT 0x00
+#define DS2784_REG_STS 0x01
+#define DS2784_REG_RAAC_MSB 0x02
+#define DS2784_REG_RAAC_LSB 0x03
+#define DS2784_REG_RSAC_MSB 0x04
+#define DS2784_REG_RSAC_LSB 0x05
+#define DS2784_REG_RARC 0x06
+#define DS2784_REG_RSRC 0x07
+#define DS2784_REG_AVG_CURR_MSB 0x08
+#define DS2784_REG_AVG_CURR_LSB 0x09
+#define DS2784_REG_TEMP_MSB 0x0A
+#define DS2784_REG_TEMP_LSB 0x0B
+#define DS2784_REG_VOLT_MSB 0x0C
+#define DS2784_REG_VOLT_LSB 0x0D
+#define DS2784_REG_CURR_MSB 0x0E
+#define DS2784_REG_CURR_LSB 0x0F
+#define DS2784_REG_ACCUMULATE_CURR_MSB 0x10
+#define DS2784_REG_ACCUMULATE_CURR_LSB 0x11
+#define DS2784_REG_ACCUMULATE_CURR_LSB1 0x12
+#define DS2784_REG_ACCUMULATE_CURR_LSB2 0x13
+#define DS2784_REG_AGE_SCALAR 0x14
+#define DS2784_REG_SPECIALL_FEATURE 0x15
+#define DS2784_REG_FULL_MSB 0x16
+#define DS2784_REG_FULL_LSB 0x17
+#define DS2784_REG_ACTIVE_EMPTY_MSB 0x18
+#define DS2784_REG_ACTIVE_EMPTY_LSB 0x19
+#define DS2784_REG_STBY_EMPTY_MSB 0x1A
+#define DS2784_REG_STBY_EMPTY_LSB 0x1B
+#define DS2784_REG_EEPROM 0x1F
+#define DS2784_REG_MFG_GAIN_RSGAIN_MSB 0xB0
+#define DS2784_REG_MFG_GAIN_RSGAIN_LSB 0xB1
+
+#define DS2784_REG_CTRL 0x60
+#define DS2784_REG_ACCUMULATE_BIAS 0x61
+#define DS2784_REG_AGE_CAPA_MSB 0x62
+#define DS2784_REG_AGE_CAPA_LSB 0x63
+#define DS2784_REG_CHARGE_VOLT 0x64
+#define DS2784_REG_MIN_CHARGE_CURR 0x65
+#define DS2784_REG_ACTIVE_EMPTY_VOLT 0x66
+#define DS2784_REG_ACTIVE_EMPTY_CURR 0x67
+#define DS2784_REG_ACTIVE_EMPTY_40 0x68
+#define DS2784_REG_RSNSP 0x69
+#define DS2784_REG_FULL_40_MSB 0x6A
+#define DS2784_REG_FULL_40_LSB 0x6B
+#define DS2784_REG_FULL_SEG_4_SLOPE 0x6C
+#define DS2784_REG_FULL_SEG_3_SLOPE 0x6D
+#define DS2784_REG_FULL_SEG_2_SLOPE 0x6E
+#define DS2784_REG_FULL_SEG_1_SLOPE 0x6F
+#define DS2784_REG_AE_SEG_4_SLOPE 0x70
+#define DS2784_REG_AE_SEG_3_SLOPE 0x71
+#define DS2784_REG_AE_SEG_2_SLOPE 0x72
+#define DS2784_REG_AE_SEG_1_SLOPE 0x73
+#define DS2784_REG_SE_SEG_4_SLOPE 0x74
+#define DS2784_REG_SE_SEG_3_SLOPE 0x75
+#define DS2784_REG_SE_SEG_2_SLOPE 0x76
+#define DS2784_REG_SE_SEG_1_SLOPE 0x77
+#define DS2784_REG_RSGAIN_MSB 0x78
+#define DS2784_REG_RSGAIN_LSB 0x79
+#define DS2784_REG_RSTC 0x7A
+#define DS2784_REG_CURR_OFFSET_BIAS 0x7B
+#define DS2784_REG_TBP34 0x7C
+#define DS2784_REG_TBP23 0x7D
+#define DS2784_REG_TBP12 0x7E
+#define DS2784_REG_PROTECTOR_THRESHOLD 0x7F
+#define DS2784_REG_USER_EEPROM_20 0x20
+
+#define DS2784_READ_DATA 0x69
+#define DS2784_WRITE_DATA 0x6C
+
+#define DS2784_DATA_SIZE 0xB2
+
+extern int w1_ds2784_read(struct device *dev, char *buf, int addr,
+ size_t count);
+#endif /* !_W1_DS2784_H */
diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h
index 874aeb0..2aee2ac 100644
--- a/drivers/w1/w1_family.h
+++ b/drivers/w1/w1_family.h
@@ -38,6 +38,7 @@
#define W1_EEPROM_DS2431 0x2D
#define W1_FAMILY_DS2760 0x30
#define W1_FAMILY_DS2780 0x32
+#define W1_FAMILY_DS2784 0x32
#define W1_FAMILY_DS2781 0x3D
#define W1_THERM_DS28EA00 0x42
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
index 8558da9..bf9ca4e 100644
--- a/drivers/watchdog/watchdog_dev.c
+++ b/drivers/watchdog/watchdog_dev.c
@@ -69,6 +69,19 @@
}
/*
+ * touch_hw_watchdog: ping the current watchdog device.
+ *
+ * If there is no watchdog device, the nothing happens.
+ * Otherwise we try to ping the watchdog.
+ */
+void touch_hw_watchdog(void)
+{
+ if (wdd)
+ watchdog_ping(wdd);
+}
+EXPORT_SYMBOL_GPL(touch_hw_watchdog);
+
+/*
* watchdog_start: wrapper to start the watchdog.
* @wddev: the watchdog device to start
*
diff --git a/fs/exec.c b/fs/exec.c
index e3a7e36d..b2ae41a 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1261,6 +1261,13 @@
bprm->unsafe |= LSM_UNSAFE_PTRACE;
}
+ /*
+ * This isn't strictly necessary, but it makes it harder for LSMs to
+ * mess up.
+ */
+ if (current->no_new_privs)
+ bprm->unsafe |= LSM_UNSAFE_NO_NEW_PRIVS;
+
n_fs = 1;
spin_lock(&p->fs->lock);
rcu_read_lock();
@@ -1304,7 +1311,8 @@
bprm->cred->euid = current_euid();
bprm->cred->egid = current_egid();
- if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
+ if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) &&
+ !current->no_new_privs) {
/* Set-uid? */
if (mode & S_ISUID) {
bprm->per_clear |= PER_CLEAR_ON_SETID;
diff --git a/fs/open.c b/fs/open.c
index cf1d34f..70f6f3e 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -836,6 +836,8 @@
static void __put_unused_fd(struct files_struct *files, unsigned int fd)
{
struct fdtable *fdt = files_fdtable(files);
+ BUG_ON(fdt->fd[fd] != NULL);
+ smp_wmb();
__clear_open_fd(fd, fdt);
if (fd < files->next_fd)
files->next_fd = fd;
diff --git a/include/asm-generic/siginfo.h b/include/asm-generic/siginfo.h
index 5e5e386..8ed6777 100644
--- a/include/asm-generic/siginfo.h
+++ b/include/asm-generic/siginfo.h
@@ -98,9 +98,18 @@
__ARCH_SI_BAND_T _band; /* POLL_IN, POLL_OUT, POLL_MSG */
int _fd;
} _sigpoll;
+
+ /* SIGSYS */
+ struct {
+ void __user *_call_addr; /* calling user insn */
+ int _syscall; /* triggering system call number */
+ unsigned int _arch; /* AUDIT_ARCH_* of syscall */
+ } _sigsys;
} _sifields;
} __ARCH_SI_ATTRIBUTES siginfo_t;
+/* If the arch shares siginfo, then it has SIGSYS. */
+#define __ARCH_SIGSYS
#endif
/*
@@ -124,6 +133,11 @@
#define si_addr_lsb _sifields._sigfault._addr_lsb
#define si_band _sifields._sigpoll._band
#define si_fd _sifields._sigpoll._fd
+#ifdef __ARCH_SIGSYS
+#define si_call_addr _sifields._sigsys._call_addr
+#define si_syscall _sifields._sigsys._syscall
+#define si_arch _sifields._sigsys._arch
+#endif
#ifdef __KERNEL__
#define __SI_MASK 0xffff0000u
@@ -134,6 +148,7 @@
#define __SI_CHLD (4 << 16)
#define __SI_RT (5 << 16)
#define __SI_MESGQ (6 << 16)
+#define __SI_SYS (7 << 16)
#define __SI_CODE(T,N) ((T) | ((N) & 0xffff))
#else
#define __SI_KILL 0
@@ -143,6 +158,7 @@
#define __SI_CHLD 0
#define __SI_RT 0
#define __SI_MESGQ 0
+#define __SI_SYS 0
#define __SI_CODE(T,N) (N)
#endif
@@ -240,6 +256,12 @@
#define NSIGPOLL 6
/*
+ * SIGSYS si_codes
+ */
+#define SYS_SECCOMP (__SI_SYS|1) /* seccomp triggered */
+#define NSIGSYS 1
+
+/*
* sigevent definitions
*
* It seems likely that SIGEV_THREAD will have to be handled from
diff --git a/include/asm-generic/syscall.h b/include/asm-generic/syscall.h
index 5c122ae..5b09392 100644
--- a/include/asm-generic/syscall.h
+++ b/include/asm-generic/syscall.h
@@ -142,4 +142,18 @@
unsigned int i, unsigned int n,
const unsigned long *args);
+/**
+ * syscall_get_arch - return the AUDIT_ARCH for the current system call
+ * @task: task of interest, must be in system call entry tracing
+ * @regs: task_pt_regs() of @task
+ *
+ * Returns the AUDIT_ARCH_* based on the system call convention in use.
+ *
+ * It's only valid to call this when @task is stopped on entry to a system
+ * call, due to %TIF_SYSCALL_TRACE, %TIF_SYSCALL_AUDIT, or %TIF_SECCOMP.
+ *
+ * Architectures which permit CONFIG_HAVE_ARCH_SECCOMP_FILTER must
+ * provide an implementation of this.
+ */
+int syscall_get_arch(struct task_struct *task, struct pt_regs *regs);
#endif /* _ASM_SYSCALL_H */
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index 4bf4100..d984dac 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -336,6 +336,7 @@
header-y += sched.h
header-y += screen_info.h
header-y += sdla.h
+header-y += seccomp.h
header-y += securebits.h
header-y += selinux_netlink.h
header-y += sem.h
diff --git a/include/linux/alarmtimer.h b/include/linux/alarmtimer.h
index 96c5c24..f122c9f 100644
--- a/include/linux/alarmtimer.h
+++ b/include/linux/alarmtimer.h
@@ -35,6 +35,7 @@
*/
struct alarm {
struct timerqueue_node node;
+ struct hrtimer timer;
enum alarmtimer_restart (*function)(struct alarm *, ktime_t now);
enum alarmtimer_type type;
int state;
@@ -43,7 +44,7 @@
void alarm_init(struct alarm *alarm, enum alarmtimer_type type,
enum alarmtimer_restart (*function)(struct alarm *, ktime_t));
-void alarm_start(struct alarm *alarm, ktime_t start);
+int alarm_start(struct alarm *alarm, ktime_t start);
int alarm_try_to_cancel(struct alarm *alarm);
int alarm_cancel(struct alarm *alarm);
diff --git a/include/linux/audit.h b/include/linux/audit.h
index ed3ef19..22f292a 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -463,7 +463,7 @@
extern void __audit_inode(const char *name, const struct dentry *dentry);
extern void __audit_inode_child(const struct dentry *dentry,
const struct inode *parent);
-extern void __audit_seccomp(unsigned long syscall);
+extern void __audit_seccomp(unsigned long syscall, long signr, int code);
extern void __audit_ptrace(struct task_struct *t);
static inline int audit_dummy_context(void)
@@ -508,10 +508,10 @@
}
void audit_core_dumps(long signr);
-static inline void audit_seccomp(unsigned long syscall)
+static inline void audit_seccomp(unsigned long syscall, long signr, int code)
{
if (unlikely(!audit_dummy_context()))
- __audit_seccomp(syscall);
+ __audit_seccomp(syscall, signr, code);
}
static inline void audit_ptrace(struct task_struct *t)
@@ -634,7 +634,7 @@
#define audit_inode(n,d) do { (void)(d); } while (0)
#define audit_inode_child(i,p) do { ; } while (0)
#define audit_core_dumps(i) do { ; } while (0)
-#define audit_seccomp(i) do { ; } while (0)
+#define audit_seccomp(i,s,c) do { ; } while (0)
#define auditsc_get_stamp(c,t,s) (0)
#define audit_get_loginuid(t) (-1)
#define audit_get_sessionid(t) (-1)
diff --git a/include/linux/fb.h b/include/linux/fb.h
index d31cb68..45e73aa 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -231,6 +231,10 @@
#define FB_VMODE_SMOOTH_XPAN 512 /* smooth xpan possible (internally used) */
#define FB_VMODE_CONUPDATE 512 /* don't update x/yoffset */
+#define FB_FLAG_RATIO_4_3 64
+#define FB_FLAG_RATIO_16_9 128
+#define FB_FLAG_PIXEL_REPEAT 256
+
/*
* Display rotation support
*/
@@ -444,6 +448,8 @@
#define FB_MISC_PRIM_COLOR 1
#define FB_MISC_1ST_DETAIL 2 /* First Detailed Timing is preferred */
+#define FB_MISC_HDMI 4 /* display supports HDMI signaling */
+
struct fb_chroma {
__u32 redx; /* in fraction of 1024 */
__u32 greenx;
@@ -483,6 +489,8 @@
__u8 revision; /* ...and revision */
__u8 max_x; /* Maximum horizontal size (cm) */
__u8 max_y; /* Maximum vertical size (cm) */
+ struct fb_audio *audiodb; /* audio database */
+ __u32 audiodb_len; /* audio database length */
};
struct fb_cmap_user {
@@ -1110,6 +1118,7 @@
/* drivers/video/modedb.c */
#define VESA_MODEDB_SIZE 34
+#define CEA_MODEDB_SIZE 65
extern void fb_var_to_videomode(struct fb_videomode *mode,
const struct fb_var_screeninfo *var);
extern void fb_videomode_to_var(struct fb_var_screeninfo *var,
@@ -1160,9 +1169,30 @@
u32 flag;
};
+#define FB_AUDIO_LPCM 1
+
+#define FB_AUDIO_192KHZ (1 << 6)
+#define FB_AUDIO_176KHZ (1 << 5)
+#define FB_AUDIO_96KHZ (1 << 4)
+#define FB_AUDIO_88KHZ (1 << 3)
+#define FB_AUDIO_48KHZ (1 << 2)
+#define FB_AUDIO_44KHZ (1 << 1)
+#define FB_AUDIO_32KHZ (1 << 0)
+
+#define FB_AUDIO_24BIT (1 << 2)
+#define FB_AUDIO_20BIT (1 << 1)
+#define FB_AUDIO_16BIT (1 << 0)
+
+struct fb_audio {
+ u8 format;
+ u8 channel_count;
+ u8 sample_rates;
+ u8 bit_rates;
+};
+
extern const char *fb_mode_option;
extern const struct fb_videomode vesa_modes[];
-extern const struct fb_videomode cea_modes[64];
+extern const struct fb_videomode cea_modes[];
struct fb_modelist {
struct list_head list;
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 8eeb205..f2e5315 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -10,6 +10,7 @@
#ifdef __KERNEL__
#include <linux/atomic.h>
+#include <linux/compat.h>
#endif
/*
@@ -132,6 +133,16 @@
#ifdef __KERNEL__
+#ifdef CONFIG_COMPAT
+/*
+ * A struct sock_filter is architecture independent.
+ */
+struct compat_sock_fprog {
+ u16 len;
+ compat_uptr_t filter; /* struct sock_filter * */
+};
+#endif
+
struct sk_buff;
struct sock;
@@ -228,6 +239,7 @@
BPF_S_ANC_HATYPE,
BPF_S_ANC_RXHASH,
BPF_S_ANC_CPU,
+ BPF_S_ANC_SECCOMP_LD_W,
};
#endif /* __KERNEL__ */
diff --git a/include/linux/i2c/atmel_mxt_ts.h b/include/linux/i2c/atmel_mxt_ts.h
index f027f7a..47c5ac3 100644
--- a/include/linux/i2c/atmel_mxt_ts.h
+++ b/include/linux/i2c/atmel_mxt_ts.h
@@ -39,6 +39,10 @@
unsigned int voltage;
unsigned char orient;
unsigned long irqflags;
+ unsigned char boot_address;
+ const char *firmware_name;
+ unsigned int gpio_reset;
+ unsigned int reset_msec;
};
#endif /* __LINUX_ATMEL_MXT_TS_H */
diff --git a/include/linux/input.h b/include/linux/input.h
index 49fb20e..bdbf553 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -1310,7 +1310,9 @@
struct mutex mutex;
unsigned int users;
+ unsigned int users_private;
bool going_away;
+ bool disabled;
bool sync;
diff --git a/include/linux/keyreset.h b/include/linux/keyreset.h
index a2ac49e..22ebe11 100644
--- a/include/linux/keyreset.h
+++ b/include/linux/keyreset.h
@@ -21,6 +21,7 @@
struct keyreset_platform_data {
int (*reset_fn)(void);
+ int down_time_ms;
int *keys_up;
int keys_down[]; /* 0 terminated */
};
diff --git a/include/linux/leds-as3668.h b/include/linux/leds-as3668.h
new file mode 100644
index 0000000..1307bb5
--- /dev/null
+++ b/include/linux/leds-as3668.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics. 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 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 St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#ifndef _LINUX_AS3668_H
+#define _LINUX_AS3668_H
+
+#define AS3668_VMON_VBAT_2_0V 0x00
+#define AS3668_VMON_VBAT_3_0V 0x01
+#define AS3668_VMON_VBAT_3_15V 0x02
+#define AS3668_VMON_VBAT_3_3V 0x03
+
+#define AS3668_SHUTDOWN_ENABLE_ON 1
+#define AS3668_SHUTDOWN_ENABLE_OFF 0
+
+#define AS3668_PATTERN_START_SOURCE_SW 0
+#define AS3668_PATTERN_START_SOURCE_GPIO 1
+
+#define AS3668_PWM_SOURCE_INTERNAL 0
+#define AS3668_PWM_SOURCE_EXTERNAL 1
+
+#define AS3668_GPIO_INPUT_NONINVERT 0
+#define AS3668_GPIO_INPUT_INVERT 1
+
+#define AS3668_GPIO_INPUT_MODE_ANALOG 0
+#define AS3668_GPIO_INPUT_MODE_DIGITAL 1
+
+#define AS3668_GPIO_MODE_INPUT_ONLY 0
+#define AS3668_GPIO_MODE_OUTPUT 1
+
+#define AS3668_AUDIO_CTRL_INPUT_GPIO 0
+#define AS3668_AUDIO_CTRL_INPUT_CURR4 1
+
+#define AS3668_AUDIO_CTRL_PLDN_ENABLE 0
+#define AS3668_AUDIO_CTRL_PLDN_DISABLE 1
+
+#define AS3668_AUDIO_CTRL_ADC_CHAR_250 0
+#define AS3668_AUDIO_CTRL_ADC_CHAR_50 1
+
+#define AS3668_AUDIO_INPUT_CAP_PRECHARGE 0
+#define AS3668_AUDIO_INPUT_CAP_NO_PRECHARGE 1
+
+#define AS3668_AUDIO_INPUT_AUTO_PRECHARGE 0
+#define AS3668_AUDIO_INPUT_MANUAL_PRECHARGE 1
+
+#define AS3668_RED 24
+#define AS3668_GREEN 16
+#define AS3668_BLUE 8
+#define AS3668_WHITE 0
+
+#define AS3668_LED_NUM 4
+
+struct as3668_platform_data {
+ u8 led_array[AS3668_LED_NUM];
+ u16 vbat_monitor_voltage_index:2;
+ u16 shutdown_enable:1;
+ u16 pattern_start_source:1;
+ u16 pwm_source:1;
+ u16 gpio_input_invert:1;
+ u16 gpio_input_mode:1;
+ u16 gpio_mode:1;
+ u16 audio_input_pin:1;
+ u16 audio_pulldown_off:1;
+ u16 audio_adc_characteristic:1;
+ u16 audio_dis_start:1;
+ u16 audio_man_start:1;
+} __packed;
+#endif
diff --git a/include/linux/mfd/max77686-private.h b/include/linux/mfd/max77686-private.h
new file mode 100644
index 0000000..a63a1c6
--- /dev/null
+++ b/include/linux/mfd/max77686-private.h
@@ -0,0 +1,254 @@
+/*
+ * max77686.h - Voltage regulator driver for the Maxim 77686
+ *
+ * Copyright (C) 2011 Samsung Electrnoics
+ * Chiwoong Byun <woong.byun@samsung.com>
+ *
+ * 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
+ */
+
+#ifndef __LINUX_MFD_MAX77686_PRIV_H
+#define __LINUX_MFD_MAX77686_PRIV_H
+
+#include <linux/i2c.h>
+
+#define MAX77686_REG_INVALID (0xff)
+
+enum max77686_pmic_reg {
+ MAX77686_REG_DEVICE_ID = 0x00,
+ MAX77686_REG_INTSRC = 0x01,
+ MAX77686_REG_INT1 = 0x02,
+ MAX77686_REG_INT2 = 0x03,
+
+ MAX77686_REG_INT1MSK = 0x04,
+ MAX77686_REG_INT2MSK = 0x05,
+
+ MAX77686_REG_STATUS1 = 0x06,
+ MAX77686_REG_STATUS2 = 0x07,
+
+ MAX77686_REG_PWRON = 0x08,
+ MAX77686_REG_ONOFF_DELAY = 0x09,
+ MAX77686_REG_MRSTB = 0x0A,
+ /* Reserved: 0x0B-0x0F */
+
+ MAX77686_REG_BUCK1CTRL = 0x10,
+ MAX77686_REG_BUCK1OUT = 0x11,
+ MAX77686_REG_BUCK2CTRL1 = 0x12,
+ MAX77686_REG_BUCK234FREQ = 0x13,
+ MAX77686_REG_BUCK2DVS1 = 0x14,
+ MAX77686_REG_BUCK2DVS2 = 0x15,
+ MAX77686_REG_BUCK2DVS3 = 0x16,
+ MAX77686_REG_BUCK2DVS4 = 0x17,
+ MAX77686_REG_BUCK2DVS5 = 0x18,
+ MAX77686_REG_BUCK2DVS6 = 0x19,
+ MAX77686_REG_BUCK2DVS7 = 0x1A,
+ MAX77686_REG_BUCK2DVS8 = 0x1B,
+ MAX77686_REG_BUCK3CTRL1 = 0x1C,
+ /* Reserved: 0x1D */
+ MAX77686_REG_BUCK3DVS1 = 0x1E,
+ MAX77686_REG_BUCK3DVS2 = 0x1F,
+ MAX77686_REG_BUCK3DVS3 = 0x20,
+ MAX77686_REG_BUCK3DVS4 = 0x21,
+ MAX77686_REG_BUCK3DVS5 = 0x22,
+ MAX77686_REG_BUCK3DVS6 = 0x23,
+ MAX77686_REG_BUCK3DVS7 = 0x24,
+ MAX77686_REG_BUCK3DVS8 = 0x25,
+ MAX77686_REG_BUCK4CTRL1 = 0x26,
+ /* Reserved: 0x27 */
+ MAX77686_REG_BUCK4DVS1 = 0x28,
+ MAX77686_REG_BUCK4DVS2 = 0x29,
+ MAX77686_REG_BUCK4DVS3 = 0x2A,
+ MAX77686_REG_BUCK4DVS4 = 0x2B,
+ MAX77686_REG_BUCK4DVS5 = 0x2C,
+ MAX77686_REG_BUCK4DVS6 = 0x2D,
+ MAX77686_REG_BUCK4DVS7 = 0x2E,
+ MAX77686_REG_BUCK4DVS8 = 0x2F,
+ MAX77686_REG_BUCK5CTRL = 0x30,
+ MAX77686_REG_BUCK5OUT = 0x31,
+ MAX77686_REG_BUCK6CTRL = 0x32,
+ MAX77686_REG_BUCK6OUT = 0x33,
+ MAX77686_REG_BUCK7CTRL = 0x34,
+ MAX77686_REG_BUCK7OUT = 0x35,
+ MAX77686_REG_BUCK8CTRL = 0x36,
+ MAX77686_REG_BUCK8OUT = 0x37,
+ MAX77686_REG_BUCK9CTRL = 0x38,
+ MAX77686_REG_BUCK9OUT = 0x39,
+ /* Reserved: 0x3A-0x3F */
+
+ MAX77686_REG_LDO1CTRL1 = 0x40,
+ MAX77686_REG_LDO2CTRL1 = 0x41,
+ MAX77686_REG_LDO3CTRL1 = 0x42,
+ MAX77686_REG_LDO4CTRL1 = 0x43,
+ MAX77686_REG_LDO5CTRL1 = 0x44,
+ MAX77686_REG_LDO6CTRL1 = 0x45,
+ MAX77686_REG_LDO7CTRL1 = 0x46,
+ MAX77686_REG_LDO8CTRL1 = 0x47,
+ MAX77686_REG_LDO9CTRL1 = 0x48,
+ MAX77686_REG_LDO10CTRL1 = 0x49,
+ MAX77686_REG_LDO11CTRL1 = 0x4A,
+ MAX77686_REG_LDO12CTRL1 = 0x4B,
+ MAX77686_REG_LDO13CTRL1 = 0x4C,
+ MAX77686_REG_LDO14CTRL1 = 0x4D,
+ MAX77686_REG_LDO15CTRL1 = 0x4E,
+ MAX77686_REG_LDO16CTRL1 = 0x4F,
+ MAX77686_REG_LDO17CTRL1 = 0x50,
+ MAX77686_REG_LDO18CTRL1 = 0x51,
+ MAX77686_REG_LDO19CTRL1 = 0x52,
+ MAX77686_REG_LDO20CTRL1 = 0x53,
+ MAX77686_REG_LDO21CTRL1 = 0x54,
+ MAX77686_REG_LDO22CTRL1 = 0x55,
+ MAX77686_REG_LDO23CTRL1 = 0x56,
+ MAX77686_REG_LDO24CTRL1 = 0x57,
+ MAX77686_REG_LDO25CTRL1 = 0x58,
+ MAX77686_REG_LDO26CTRL1 = 0x59,
+ /* Reserved: 0x5A-0x5F */
+ MAX77686_REG_LDO1CTRL2 = 0x60,
+ MAX77686_REG_LDO2CTRL2 = 0x61,
+ MAX77686_REG_LDO3CTRL2 = 0x62,
+ MAX77686_REG_LDO4CTRL2 = 0x63,
+ MAX77686_REG_LDO5CTRL2 = 0x64,
+ MAX77686_REG_LDO6CTRL2 = 0x65,
+ MAX77686_REG_LDO7CTRL2 = 0x66,
+ MAX77686_REG_LDO8CTRL2 = 0x67,
+ MAX77686_REG_LDO9CTRL2 = 0x68,
+ MAX77686_REG_LDO10CTRL2 = 0x69,
+ MAX77686_REG_LDO11CTRL2 = 0x6A,
+ MAX77686_REG_LDO12CTRL2 = 0x6B,
+ MAX77686_REG_LDO13CTRL2 = 0x6C,
+ MAX77686_REG_LDO14CTRL2 = 0x6D,
+ MAX77686_REG_LDO15CTRL2 = 0x6E,
+ MAX77686_REG_LDO16CTRL2 = 0x6F,
+ MAX77686_REG_LDO17CTRL2 = 0x70,
+ MAX77686_REG_LDO18CTRL2 = 0x71,
+ MAX77686_REG_LDO19CTRL2 = 0x72,
+ MAX77686_REG_LDO20CTRL2 = 0x73,
+ MAX77686_REG_LDO21CTRL2 = 0x74,
+ MAX77686_REG_LDO22CTRL2 = 0x75,
+ MAX77686_REG_LDO23CTRL2 = 0x76,
+ MAX77686_REG_LDO24CTRL2 = 0x77,
+ MAX77686_REG_LDO25CTRL2 = 0x78,
+ MAX77686_REG_LDO26CTRL2 = 0x79,
+ /* Reserved: 0x7A-0x7D */
+
+ MAX77686_REG_BBAT_CHG = 0x7E,
+ MAX77686_REG_32KHZ = 0x7F,
+
+ MAX77686_REG_PMIC_END = 0x80,
+};
+
+enum max77686_rtc_reg {
+ MAX77686_RTC_INT = 0x00,
+ MAX77686_RTC_INTM = 0x01,
+ MAX77686_RTC_CONTROLM = 0x02,
+ MAX77686_RTC_CONTROL = 0x03,
+ MAX77686_RTC_UPDATE0 = 0x04,
+ /* Reserved: 0x5 */
+ MAX77686_WTSR_SMPL_CNTL = 0x06,
+ MAX77686_RTC_SEC = 0x07,
+ MAX77686_RTC_MIN = 0x08,
+ MAX77686_RTC_HOUR = 0x09,
+ MAX77686_RTC_WEEKDAY = 0x0A,
+ MAX77686_RTC_MONTH = 0x0B,
+ MAX77686_RTC_YEAR = 0x0C,
+ MAX77686_RTC_DATE = 0x0D,
+ MAX77686_ALARM1_SEC = 0x0E,
+ MAX77686_ALARM1_MIN = 0x0F,
+ MAX77686_ALARM1_HOUR = 0x10,
+ MAX77686_ALARM1_WEEKDAY = 0x11,
+ MAX77686_ALARM1_MONTH = 0x12,
+ MAX77686_ALARM1_YEAR = 0x13,
+ MAX77686_ALARM1_DATE = 0x14,
+ MAX77686_ALARM2_SEC = 0x15,
+ MAX77686_ALARM2_MIN = 0x16,
+ MAX77686_ALARM2_HOUR = 0x17,
+ MAX77686_ALARM2_WEEKDAY = 0x18,
+ MAX77686_ALARM2_MONTH = 0x19,
+ MAX77686_ALARM2_YEAR = 0x1A,
+ MAX77686_ALARM2_DATE = 0x1B,
+};
+
+#define MAX77686_IRQSRC_PMIC (0)
+#define MAX77686_IRQSRC_RTC (1 << 0)
+
+#define MAX77686_REG_RAMP_RATE_100MV (0x3<<6)
+#define MAX77686_REG_RAMP_RATE_55MV (0x2<<6)
+#define MAX77686_REG_RAMP_RATE_27MV (0x1<<6)
+#define MAX77686_REG_RAMP_RATE_13MV (0x0<<6)
+
+enum max77686_irq_source {
+ PMIC_INT1 = 0,
+ PMIC_INT2,
+ RTC_INT,
+
+ MAX77686_IRQ_GROUP_NR,
+};
+
+enum max77686_irq {
+ MAX77686_PMICIRQ_PWRONF,
+ MAX77686_PMICIRQ_PWRONR,
+ MAX77686_PMICIRQ_JIGONBF,
+ MAX77686_PMICIRQ_JIGONBR,
+ MAX77686_PMICIRQ_ACOKBF,
+ MAX77686_PMICIRQ_ACOKBR,
+ MAX77686_PMICIRQ_ONKEY1S,
+ MAX77686_PMICIRQ_MRSTB,
+
+ MAX77686_PMICIRQ_140C,
+ MAX77686_PMICIRQ_120C,
+
+ MAX77686_RTCIRQ_RTC60S,
+ MAX77686_RTCIRQ_RTCA1,
+ MAX77686_RTCIRQ_RTCA2,
+ MAX77686_RTCIRQ_SMPL,
+ MAX77686_RTCIRQ_RTC1S,
+ MAX77686_RTCIRQ_WTSR,
+
+ MAX77686_IRQ_NR,
+};
+
+struct max77686_dev {
+ struct device *dev;
+ struct i2c_client *i2c; /* 0xcc / PMIC, Battery Control, and FLASH */
+ struct i2c_client *rtc; /* slave addr 0x0c */
+ struct mutex iolock;
+
+ int type;
+
+ int irq;
+ int irq_gpio;
+ int irq_base;
+ bool wakeup;
+ struct mutex irqlock;
+ int irq_masks_cur[MAX77686_IRQ_GROUP_NR];
+ int irq_masks_cache[MAX77686_IRQ_GROUP_NR];
+};
+
+enum max77686_types {
+ TYPE_MAX77686,
+};
+
+extern int max77686_irq_init(struct max77686_dev *max77686);
+extern void max77686_irq_exit(struct max77686_dev *max77686);
+extern int max77686_irq_resume(struct max77686_dev *max77686);
+
+extern int max77686_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest);
+extern int max77686_bulk_read(struct i2c_client *i2c, u8 reg, int count,
+ u8 *buf);
+extern int max77686_write_reg(struct i2c_client *i2c, u8 reg, u8 value);
+extern int max77686_bulk_write(struct i2c_client *i2c, u8 reg, int count,
+ u8 *buf);
+extern int max77686_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask);
+
+#endif /* __LINUX_MFD_MAX77686_PRIV_H */
diff --git a/include/linux/mfd/max77686.h b/include/linux/mfd/max77686.h
new file mode 100644
index 0000000..5e36917
--- /dev/null
+++ b/include/linux/mfd/max77686.h
@@ -0,0 +1,151 @@
+/*
+ * max77686.h - Driver for the Maxim 77686
+ *
+ * Copyright (C) 2011 Samsung Electrnoics
+ * Chiwoong Byun <woong.byun@samsung.com>
+ *
+ * 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.h
+ *
+ * MAX77686 has PMIC, RTC devices.
+ * The devices share the same I2C bus and included in
+ * this mfd driver.
+ */
+
+#ifndef __LINUX_MFD_MAX77686_H
+#define __LINUX_MFD_MAX77686_H
+
+#include <linux/regulator/consumer.h>
+#include <linux/rtc.h>
+
+/* MAX77686 regulator IDs */
+enum max77686_regulators {
+ MAX77686_LDO1 = 0,
+ MAX77686_LDO2,
+ MAX77686_LDO3,
+ MAX77686_LDO4,
+ MAX77686_LDO5,
+ MAX77686_LDO6,
+ MAX77686_LDO7,
+ MAX77686_LDO8,
+ MAX77686_LDO9,
+ MAX77686_LDO10,
+ MAX77686_LDO11,
+ MAX77686_LDO12,
+ MAX77686_LDO13,
+ MAX77686_LDO14,
+ MAX77686_LDO15,
+ MAX77686_LDO16,
+ MAX77686_LDO17,
+ MAX77686_LDO18,
+ MAX77686_LDO19,
+ MAX77686_LDO20,
+ MAX77686_LDO21,
+ MAX77686_LDO22,
+ MAX77686_LDO23,
+ MAX77686_LDO24,
+ MAX77686_LDO25,
+ MAX77686_LDO26,
+ MAX77686_BUCK1,
+ MAX77686_BUCK2,
+ MAX77686_BUCK3,
+ MAX77686_BUCK4,
+ MAX77686_BUCK5,
+ MAX77686_BUCK6,
+ MAX77686_BUCK7,
+ MAX77686_BUCK8,
+ MAX77686_BUCK9,
+ MAX77686_EN32KHZ_AP,
+ MAX77686_EN32KHZ_CP,
+ MAX77686_P32KH,
+
+ MAX77686_REG_MAX,
+};
+
+struct max77686_regulator_data {
+ int id;
+ struct regulator_init_data *initdata;
+};
+
+enum max77686_opmode {
+ MAX77686_OPMODE_NORMAL,
+ MAX77686_OPMODE_LP,
+ MAX77686_OPMODE_STANDBY,
+};
+
+enum max77686_ramp_rate {
+ MAX77686_RAMP_RATE_100MV,
+ MAX77686_RAMP_RATE_13MV,
+ MAX77686_RAMP_RATE_27MV,
+ MAX77686_RAMP_RATE_55MV,
+};
+
+struct max77686_opmode_data {
+ int id;
+ int mode;
+};
+
+/**
+ * struct max77686_wtsr_smpl - settings for WTSR/SMPL
+ * @wtsr_en: WTSR Function Enable Control
+ * @smpl_en: SMPL Function Enable Control
+ * @wtsr_timer_val: Set the WTSR timer Threshold
+ * 0(250ms), 1(500ms), 2(750ms), 3(1000ms)
+ * @smpl_timer_val: Set the SMPL timer Threshold
+ * 0(0.5s), 1(1.0s), 2(1.5s), 3(2.0s)
+ * @check_jigon: if this value is true, do not enable SMPL function when
+ * JIGONB is low(JIG cable is attached)
+ */
+struct max77686_wtsr_smpl {
+ bool wtsr_en;
+ bool smpl_en;
+ int wtsr_timer_val;
+ int smpl_timer_val;
+ bool check_jigon;
+};
+
+struct max77686_platform_data {
+ /* IRQ */
+ int irq_gpio;
+ int irq_base;
+ int ono;
+ int wakeup;
+
+ /* ---- PMIC ---- */
+ struct max77686_regulator_data *regulators;
+ int num_regulators;
+ int has_full_constraints;
+
+ struct max77686_opmode_data *opmode_data;
+ int ramp_rate;
+
+ /*
+ * GPIO-DVS feature is not enabled with the current version of
+ * MAX77686 driver. Buck2/3/4_voltages[0] is used as the default
+ * voltage at probe. DVS/SELB gpios are set as OUTPUT-LOW.
+ */
+ int buck234_gpio_dvs[3]; /* GPIO of [0]DVS1, [1]DVS2, [2]DVS3 */
+ int buck234_gpio_selb[3]; /* [0]SELB2, [1]SELB3, [2]SELB4 */
+ unsigned int buck2_voltage[8]; /* buckx_voltage in uV */
+ unsigned int buck3_voltage[8];
+ unsigned int buck4_voltage[8];
+
+ /* ---- RTC ---- */
+ struct max77686_wtsr_smpl *wtsr_smpl;
+ struct rtc_time *init_time;
+};
+
+#endif /* __LINUX_MFD_MAX77686_H */
diff --git a/include/linux/mfd/wm8994/core.h b/include/linux/mfd/wm8994/core.h
index 9eff2a3..d41bc7b 100644
--- a/include/linux/mfd/wm8994/core.h
+++ b/include/linux/mfd/wm8994/core.h
@@ -57,6 +57,7 @@
enum wm8994_type type;
int revision;
+ int cust_id;
struct device *dev;
struct regmap *regmap;
diff --git a/include/linux/mfd/wm8994/pdata.h b/include/linux/mfd/wm8994/pdata.h
index 893267b..681b912 100644
--- a/include/linux/mfd/wm8994/pdata.h
+++ b/include/linux/mfd/wm8994/pdata.h
@@ -17,6 +17,7 @@
#define WM8994_NUM_LDO 2
#define WM8994_NUM_GPIO 11
+#define WM8994_NUM_AIF 3
struct wm8994_ldo_pdata {
/** GPIOs to enable regulator, 0 or less if not available */
@@ -28,7 +29,7 @@
#define WM8994_CONFIGURE_GPIO 0x10000
#define WM8994_DRC_REGS 5
-#define WM8994_EQ_REGS 20
+#define WM8994_EQ_REGS 21
#define WM8958_MBC_CUTOFF_REGS 20
#define WM8958_MBC_COEFF_REGS 48
#define WM8958_MBC_COMBINED_REGS 56
@@ -141,6 +142,7 @@
struct wm8994_ldo_pdata ldo[WM8994_NUM_LDO];
int irq_base; /** Base IRQ number for WM8994, required for IRQs */
+ unsigned long irq_flags; /** user irq flags */
int num_drc_cfgs;
struct wm8994_drc_cfg *drc_cfgs;
@@ -171,6 +173,11 @@
unsigned int lineout1fb:1;
unsigned int lineout2fb:1;
+ /* Delay between detecting a jack and starting microphone
+ * detect (specified in ms)
+ */
+ int micdet_delay;
+
/* IRQ for microphone detection if brought out directly as a
* signal.
*/
@@ -205,6 +212,13 @@
* system.
*/
bool spkmode_pu;
+
+ /**
+ * Maximum number of channels clocks will be generated for,
+ * useful for systems where and I2S bus with multiple data
+ * lines is mastered.
+ */
+ int max_channels_clocked[WM8994_NUM_AIF];
};
#endif
diff --git a/include/linux/mfd/wm8994/registers.h b/include/linux/mfd/wm8994/registers.h
index 86e6a03..0535489 100644
--- a/include/linux/mfd/wm8994/registers.h
+++ b/include/linux/mfd/wm8994/registers.h
@@ -2212,6 +2212,9 @@
/*
* R256 (0x100) - Chip Revision
*/
+#define WM8994_CUST_ID_MASK 0xFF00 /* CUST_ID - [15:8] */
+#define WM8994_CUST_ID_SHIFT 8 /* CUST_ID - [15:8] */
+#define WM8994_CUST_ID_WIDTH 8 /* CUST_ID - [15:8] */
#define WM8994_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */
#define WM8994_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */
#define WM8994_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index de65861..2bd1769 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -53,9 +53,6 @@
u8 part_config;
u8 cache_ctrl;
u8 rst_n_function;
- u8 max_packed_writes;
- u8 max_packed_reads;
- u8 packed_event_en;
unsigned int part_time; /* Units: ms */
unsigned int sa_timeout; /* Units: 100ns */
unsigned int generic_cmd6_time; /* Units: 10ms */
diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h
index d787037..1b431c7 100644
--- a/include/linux/mmc/core.h
+++ b/include/linux/mmc/core.h
@@ -18,9 +18,6 @@
struct mmc_command {
u32 opcode;
u32 arg;
-#define MMC_CMD23_ARG_REL_WR (1 << 31)
-#define MMC_CMD23_ARG_PACKED ((0 << 31) | (1 << 30))
-#define MMC_CMD23_ARG_TAG_REQ (1 << 29)
u32 resp[4];
unsigned int flags; /* expected response type */
#define MMC_RSP_PRESENT (1 << 0)
@@ -146,7 +143,6 @@
extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *,
struct mmc_command *, int);
extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int);
-extern int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
#define MMC_ERASE_ARG 0x00000000
#define MMC_SECURE_ERASE_ARG 0x80000000
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 168148d..5674504 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -144,8 +144,6 @@
struct mmc_async_req {
/* active mmc request */
struct mmc_request *mrq;
- struct mmc_request *__mrq;
- bool __cond;
/*
* Check error status of completed mmc request.
* Returns 0 if success otherwise non zero.
@@ -241,10 +239,6 @@
#define MMC_CAP2_BROKEN_VOLTAGE (1 << 7) /* Use the broken voltage */
#define MMC_CAP2_DETECT_ON_ERR (1 << 8) /* On I/O err check card removal */
#define MMC_CAP2_HC_ERASE_SZ (1 << 9) /* High-capacity erase size */
-#define MMC_CAP2_PACKED_RD (1 << 10) /* Allow packed read */
-#define MMC_CAP2_PACKED_WR (1 << 11) /* Allow packed write */
-#define MMC_CAP2_PACKED_CMD (MMC_CAP2_PACKED_RD | \
- MMC_CAP2_PACKED_WR) /* Allow packed commands */
mmc_pm_flag_t pm_caps; /* supported pm features */
unsigned int power_notify_type;
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
index 254901a..d425cab 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -139,7 +139,6 @@
#define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */
#define R1_READY_FOR_DATA (1 << 8) /* sx, a */
#define R1_SWITCH_ERROR (1 << 7) /* sx, c */
-#define R1_EXP_EVENT (1 << 6) /* sr, a */
#define R1_APP_CMD (1 << 5) /* sr, c */
#define R1_STATE_IDLE 0
@@ -275,10 +274,6 @@
#define EXT_CSD_FLUSH_CACHE 32 /* W */
#define EXT_CSD_CACHE_CTRL 33 /* R/W */
#define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */
-#define EXT_CSD_PACKED_FAILURE_INDEX 35 /* RO */
-#define EXT_CSD_PACKED_CMD_STATUS 36 /* RO */
-#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO, 2 bytes */
-#define EXT_CSD_EXP_EVENTS_CTRL 56 /* R/W, 2 bytes */
#define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */
#define EXT_CSD_GP_SIZE_MULT 143 /* R/W */
#define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */
@@ -323,8 +318,6 @@
#define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */
#define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */
#define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */
-#define EXT_CSD_MAX_PACKED_WRITES 500 /* RO */
-#define EXT_CSD_MAX_PACKED_READS 501 /* RO */
#define EXT_CSD_HPI_FEATURES 503 /* RO */
/*
@@ -384,14 +377,6 @@
#define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */
#define EXT_CSD_PWR_CL_8BIT_SHIFT 4
#define EXT_CSD_PWR_CL_4BIT_SHIFT 0
-
-#define EXT_CSD_PACKED_EVENT_EN (1 << 3)
-
-#define EXT_CSD_PACKED_FAILURE (1 << 3)
-
-#define EXT_CSD_PACKED_GENERIC_ERROR (1 << 0)
-#define EXT_CSD_PACKED_INDEXED_ERROR (1 << 1)
-
/*
* MMC_SWITCH access modes
*/
diff --git a/include/linux/mpu.h b/include/linux/mpu.h
new file mode 100644
index 0000000..4391226
--- /dev/null
+++ b/include/linux/mpu.h
@@ -0,0 +1,108 @@
+/*
+* Copyright (C) 2012 Invensense, 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file mpu.h
+ * @brief mpu definition
+ */
+
+#ifndef __MPU_H_
+#define __MPU_H_
+
+#ifdef __KERNEL__
+#include <linux/types.h>
+#include <linux/ioctl.h>
+#endif
+
+enum secondary_slave_type {
+ SECONDARY_SLAVE_TYPE_NONE,
+ SECONDARY_SLAVE_TYPE_ACCEL,
+ SECONDARY_SLAVE_TYPE_COMPASS,
+ SECONDARY_SLAVE_TYPE_PRESSURE,
+
+ SECONDARY_SLAVE_TYPE_TYPES
+};
+
+enum ext_slave_id {
+ ID_INVALID = 0,
+ GYRO_ID_MPU3050,
+ GYRO_ID_MPU6050A2,
+ GYRO_ID_MPU6050B1,
+ GYRO_ID_MPU6050B1_NO_ACCEL,
+ GYRO_ID_ITG3500,
+
+ ACCEL_ID_LIS331,
+ ACCEL_ID_LSM303DLX,
+ ACCEL_ID_LIS3DH,
+ ACCEL_ID_KXSD9,
+ ACCEL_ID_KXTF9,
+ ACCEL_ID_BMA150,
+ ACCEL_ID_BMA222,
+ ACCEL_ID_BMA250,
+ ACCEL_ID_ADXL34X,
+ ACCEL_ID_MMA8450,
+ ACCEL_ID_MMA845X,
+ ACCEL_ID_MPU6050,
+
+ COMPASS_ID_AK8963,
+ COMPASS_ID_AK8975,
+ COMPASS_ID_AK8972,
+ COMPASS_ID_AMI30X,
+ COMPASS_ID_AMI306,
+ COMPASS_ID_YAS529,
+ COMPASS_ID_YAS530,
+ COMPASS_ID_HMC5883,
+ COMPASS_ID_LSM303DLH,
+ COMPASS_ID_LSM303DLM,
+ COMPASS_ID_MMC314X,
+ COMPASS_ID_HSCDTD002B,
+ COMPASS_ID_HSCDTD004A,
+
+ PRESSURE_ID_BMA085,
+};
+
+#define INV_PROD_KEY(ver, rev) (ver * 100 + rev)
+/**
+ * struct mpu_platform_data - Platform data for the mpu driver
+ * @int_config: Bits [7:3] of the int config register.
+ * @level_shifter: 0: VLogic, 1: VDD
+ * @orientation: Orientation matrix of the gyroscope
+ * @sec_slave_type: secondary slave device type, can be compass, accel, etc
+ * @sec_slave_id: id of the secondary slave device
+ * @secondary_i2c_address: secondary device's i2c address
+ * @secondary_orientation: secondary device's orientation matrix
+ * @key: key for MPL library.
+ *
+ * Contains platform specific information on how to configure the MPU3050 to
+ * work on this platform. The orientation matricies are 3x3 rotation matricies
+ * that are applied to the data to rotate from the mounting orientation to the
+ * platform orientation. The values must be one of 0, 1, or -1 and each row and
+ * column should have exactly 1 non-zero value.
+ */
+struct mpu_platform_data {
+ __u8 int_config;
+ __u8 level_shifter;
+ __s8 orientation[9];
+ enum secondary_slave_type sec_slave_type;
+ enum ext_slave_id sec_slave_id;
+ __u16 secondary_i2c_addr;
+ __s8 secondary_orientation[9];
+ __u8 key[16];
+};
+
+#endif /* __MPU_H_ */
diff --git a/include/linux/nfc/bcm2079x.h b/include/linux/nfc/bcm2079x.h
new file mode 100644
index 0000000..c1349ca
--- /dev/null
+++ b/include/linux/nfc/bcm2079x.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 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; 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
+ */
+
+#ifndef _BCM2079X_H
+#define _BCM2079X_H
+
+#define BCMNFC_MAGIC 0xFA
+
+/*
+ * BCMNFC power control via ioctl
+ * BCMNFC_POWER_CTL(0): power off
+ * BCMNFC_POWER_CTL(1): power on
+ * BCMNFC_WAKE_CTL(0): wake off
+ * BCMNFC_WAKE_CTL(1): wake on
+ */
+#define BCMNFC_POWER_CTL _IO(BCMNFC_MAGIC, 0x01)
+#define BCMNFC_CHANGE_ADDR _IO(BCMNFC_MAGIC, 0x02)
+#define BCMNFC_READ_FULL_PACKET _IO(BCMNFC_MAGIC, 0x03)
+#define BCMNFC_SET_WAKE_ACTIVE_STATE _IO(BCMNFC_MAGIC, 0x04)
+#define BCMNFC_WAKE_CTL _IO(BCMNFC_MAGIC, 0x05)
+#define BCMNFC_READ_MULTI_PACKETS _IO(BCMNFC_MAGIC, 0x06)
+
+struct bcm2079x_platform_data {
+ unsigned int irq_gpio;
+ unsigned int en_gpio;
+ unsigned int wake_gpio;
+};
+
+#endif
diff --git a/include/linux/platform_data/bh1721fvc.h b/include/linux/platform_data/bh1721fvc.h
new file mode 100644
index 0000000..9d49068
--- /dev/null
+++ b/include/linux/platform_data/bh1721fvc.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics. 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 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 St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#ifndef _LINUX_BH1721FVC_H
+#define _LINUX_BH1721FVC_H
+
+struct bh1721fvc_platform_data {
+ int reset_pin;
+};
+
+#endif
diff --git a/include/linux/platform_data/es305.h b/include/linux/platform_data/es305.h
new file mode 100644
index 0000000..2f27505
--- /dev/null
+++ b/include/linux/platform_data/es305.h
@@ -0,0 +1,31 @@
+/*
+ * include/linux/platform_data/es305.h - Audience ES305 Voice Processor driver
+ *
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (C) 2012 Samsung 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.
+ */
+
+#ifndef __ES305_H__
+#define __ES305_H__
+
+struct es305_platform_data {
+ int gpio_wakeup;
+ int gpio_reset;
+
+ void (*clk_enable)(bool enable);
+
+ /* PORT A = 1, B = 2, C = 3, D = 4 */
+ int passthrough_src;
+ int passthrough_dst;
+};
+
+#endif
diff --git a/include/linux/platform_data/haptic_isa1200.h b/include/linux/platform_data/haptic_isa1200.h
new file mode 100644
index 0000000..cdc07c5
--- /dev/null
+++ b/include/linux/platform_data/haptic_isa1200.h
@@ -0,0 +1,26 @@
+/*
+ * haptic_isa1200.h - ISA1200 Haptic Motor driver
+ *
+ * Copyright (C) 2012 Samsung Electronics Co. Ltd. All Rights Reserved.
+ * Author: Vishnudev Ramakrishnan <vramakri@sta.samsung.com>
+ *
+ * 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 _LINUX_HAPTIC_ISA1200_H
+#define _LINUX_HAPTIC_ISA1200_H
+
+struct isa1200_platform_data {
+ int pwm_ch;
+ int hap_en_gpio;
+ int max_timeout;
+};
+
+#endif /* _LINUX_HAPTIC_ISA1200_H */
diff --git a/include/linux/platform_data/stmpe811-adc.h b/include/linux/platform_data/stmpe811-adc.h
new file mode 100644
index 0000000..69eaf5c
--- /dev/null
+++ b/include/linux/platform_data/stmpe811-adc.h
@@ -0,0 +1,29 @@
+/*
+ * stmpe811-adc.h
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * SangYoung Son <hello.son@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 and
+ * only 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 __STMPE811_ADC_H_
+#define __STMPE811_ADC_H_
+
+struct stmpe811_callbacks {
+ int (*get_adc_data)(u8 channel);
+};
+
+struct stmpe811_platform_data {
+ void (*register_cb)(struct stmpe811_callbacks *);
+};
+
+#endif
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index e1f5447..5a8e570 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -128,6 +128,10 @@
POWER_SUPPLY_PROP_USB_HC,
POWER_SUPPLY_PROP_USB_OTG,
POWER_SUPPLY_PROP_CHARGE_ENABLED,
+ POWER_SUPPLY_PROP_USB_INPRIORITY,
+ POWER_SUPPLY_PROP_AUTO_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_REMOTE_TYPE,
+ POWER_SUPPLY_PROP_CHARGER_DETECTION,
/* Properties of type `const char *' */
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
diff --git a/include/linux/prctl.h b/include/linux/prctl.h
index e0cfec2..78b76e2 100644
--- a/include/linux/prctl.h
+++ b/include/linux/prctl.h
@@ -124,4 +124,19 @@
#define PR_SET_CHILD_SUBREAPER 36
#define PR_GET_CHILD_SUBREAPER 37
+/*
+ * If no_new_privs is set, then operations that grant new privileges (i.e.
+ * execve) will either fail or not grant them. This affects suid/sgid,
+ * file capabilities, and LSMs.
+ *
+ * Operations that merely manipulate or drop existing privileges (setresuid,
+ * capset, etc.) will still work. Drop those privileges if you want them gone.
+ *
+ * Changing LSM security domain is considered a new privilege. So, for example,
+ * asking selinux for a specific new context (e.g. with runcon) will result
+ * in execve returning -EPERM.
+ */
+#define PR_SET_NO_NEW_PRIVS 38
+#define PR_GET_NO_NEW_PRIVS 39
+
#endif /* _LINUX_PRCTL_H */
diff --git a/include/linux/ptrace.h b/include/linux/ptrace.h
index 5c719627..597e4fd 100644
--- a/include/linux/ptrace.h
+++ b/include/linux/ptrace.h
@@ -58,6 +58,7 @@
#define PTRACE_EVENT_EXEC 4
#define PTRACE_EVENT_VFORK_DONE 5
#define PTRACE_EVENT_EXIT 6
+#define PTRACE_EVENT_SECCOMP 7
/* Extended result codes which enabled by means other than options. */
#define PTRACE_EVENT_STOP 128
@@ -69,8 +70,9 @@
#define PTRACE_O_TRACEEXEC (1 << PTRACE_EVENT_EXEC)
#define PTRACE_O_TRACEVFORKDONE (1 << PTRACE_EVENT_VFORK_DONE)
#define PTRACE_O_TRACEEXIT (1 << PTRACE_EVENT_EXIT)
+#define PTRACE_O_TRACESECCOMP (1 << PTRACE_EVENT_SECCOMP)
-#define PTRACE_O_MASK 0x0000007f
+#define PTRACE_O_MASK 0x000000ff
#include <asm/ptrace.h>
@@ -98,6 +100,7 @@
#define PT_TRACE_EXEC PT_EVENT_FLAG(PTRACE_EVENT_EXEC)
#define PT_TRACE_VFORK_DONE PT_EVENT_FLAG(PTRACE_EVENT_VFORK_DONE)
#define PT_TRACE_EXIT PT_EVENT_FLAG(PTRACE_EVENT_EXIT)
+#define PT_TRACE_SECCOMP PT_EVENT_FLAG(PTRACE_EVENT_SECCOMP)
/* single stepping state bits (used on ARM and PA-RISC) */
#define PT_SINGLESTEP_BIT 31
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 937ab61..71df39a 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1344,6 +1344,8 @@
* execve */
unsigned in_iowait:1;
+ /* task may not gain privileges */
+ unsigned no_new_privs:1;
/* Revert to default priority/policy when forking */
unsigned sched_reset_on_fork:1;
@@ -1453,7 +1455,7 @@
uid_t loginuid;
unsigned int sessionid;
#endif
- seccomp_t seccomp;
+ struct seccomp seccomp;
/* Thread group tracking */
u32 parent_exec_id;
diff --git a/include/linux/sec_jack.h b/include/linux/sec_jack.h
new file mode 100755
index 0000000..d8182e3
--- /dev/null
+++ b/include/linux/sec_jack.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2008 Samsung Electronics, 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_ARCH_SEC_HEADSET_H
+#define __ASM_ARCH_SEC_HEADSET_H
+
+#ifdef __KERNEL__
+
+enum {
+ SEC_JACK_NO_DEVICE = 0x0,
+ SEC_HEADSET_4POLE = 0x01 << 0,
+ SEC_HEADSET_3POLE = 0x01 << 1,
+ SEC_TTY_DEVICE = 0x01 << 2,
+ SEC_FM_HEADSET = 0x01 << 3,
+ SEC_FM_SPEAKER = 0x01 << 4,
+ SEC_TVOUT_DEVICE = 0x01 << 5,
+ SEC_EXTRA_DOCK_SPEAKER = 0x01 << 6,
+ SEC_EXTRA_CAR_DOCK_SPEAKER = 0x01 << 7,
+ SEC_UNKNOWN_DEVICE = 0x01 << 8,
+};
+
+struct sec_jack_zone {
+ unsigned int adc_high;
+ unsigned int delay_ms;
+ unsigned int check_count;
+ unsigned int jack_type;
+};
+
+struct sec_jack_buttons_zone {
+ unsigned int code;
+ unsigned int adc_low;
+ unsigned int adc_high;
+};
+
+struct sec_jack_platform_data {
+ void (*set_micbias_state) (bool);
+ int (*get_adc_value) (void);
+ struct sec_jack_zone *zones;
+ struct sec_jack_buttons_zone *buttons_zones;
+ int num_zones;
+ int num_buttons_zones;
+ int det_gpio;
+ int send_end_gpio;
+ bool det_active_high;
+ bool send_end_active_high;
+};
+#endif
+
+#endif
diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h
index cc7a4e9..84f6320d 100644
--- a/include/linux/seccomp.h
+++ b/include/linux/seccomp.h
@@ -1,25 +1,90 @@
#ifndef _LINUX_SECCOMP_H
#define _LINUX_SECCOMP_H
+#include <linux/compiler.h>
+#include <linux/types.h>
+
+/* Valid values for seccomp.mode and prctl(PR_SET_SECCOMP, <mode>) */
+#define SECCOMP_MODE_DISABLED 0 /* seccomp is not in use. */
+#define SECCOMP_MODE_STRICT 1 /* uses hard-coded filter. */
+#define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */
+
+/*
+ * All BPF programs must return a 32-bit value.
+ * The bottom 16-bits are for optional return data.
+ * The upper 16-bits are ordered from least permissive values to most.
+ *
+ * The ordering ensures that a min_t() over composed return values always
+ * selects the least permissive choice.
+ */
+#define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */
+#define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */
+#define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */
+#define SECCOMP_RET_TRACE 0x7ff00000U /* pass to a tracer or disallow */
+#define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */
+
+/* Masks for the return value sections. */
+#define SECCOMP_RET_ACTION 0x7fff0000U
+#define SECCOMP_RET_DATA 0x0000ffffU
+
+/**
+ * struct seccomp_data - the format the BPF program executes over.
+ * @nr: the system call number
+ * @arch: indicates system call convention as an AUDIT_ARCH_* value
+ * as defined in <linux/audit.h>.
+ * @instruction_pointer: at the time of the system call.
+ * @args: up to 6 system call arguments always stored as 64-bit values
+ * regardless of the architecture.
+ */
+struct seccomp_data {
+ int nr;
+ __u32 arch;
+ __u64 instruction_pointer;
+ __u64 args[6];
+};
+
+#ifdef __KERNEL__
#ifdef CONFIG_SECCOMP
#include <linux/thread_info.h>
#include <asm/seccomp.h>
-typedef struct { int mode; } seccomp_t;
+struct seccomp_filter;
+/**
+ * struct seccomp - the state of a seccomp'ed process
+ *
+ * @mode: indicates one of the valid values above for controlled
+ * system calls available to a process.
+ * @filter: The metadata and ruleset for determining what system calls
+ * are allowed for a task.
+ *
+ * @filter must only be accessed from the context of current as there
+ * is no locking.
+ */
+struct seccomp {
+ int mode;
+ struct seccomp_filter *filter;
+};
-extern void __secure_computing(int);
-static inline void secure_computing(int this_syscall)
+extern int __secure_computing(int);
+static inline int secure_computing(int this_syscall)
{
if (unlikely(test_thread_flag(TIF_SECCOMP)))
- __secure_computing(this_syscall);
+ return __secure_computing(this_syscall);
+ return 0;
+}
+
+/* A wrapper for architectures supporting only SECCOMP_MODE_STRICT. */
+static inline void secure_computing_strict(int this_syscall)
+{
+ BUG_ON(secure_computing(this_syscall) != 0);
}
extern long prctl_get_seccomp(void);
-extern long prctl_set_seccomp(unsigned long);
+extern long prctl_set_seccomp(unsigned long, char __user *);
-static inline int seccomp_mode(seccomp_t *s)
+static inline int seccomp_mode(struct seccomp *s)
{
return s->mode;
}
@@ -28,25 +93,41 @@
#include <linux/errno.h>
-typedef struct { } seccomp_t;
+struct seccomp { };
+struct seccomp_filter { };
-#define secure_computing(x) do { } while (0)
+static inline int secure_computing(int this_syscall) { return 0; }
+static inline void secure_computing_strict(int this_syscall) { return; }
static inline long prctl_get_seccomp(void)
{
return -EINVAL;
}
-static inline long prctl_set_seccomp(unsigned long arg2)
+static inline long prctl_set_seccomp(unsigned long arg2, char __user *arg3)
{
return -EINVAL;
}
-static inline int seccomp_mode(seccomp_t *s)
+static inline int seccomp_mode(struct seccomp *s)
{
return 0;
}
-
#endif /* CONFIG_SECCOMP */
+#ifdef CONFIG_SECCOMP_FILTER
+extern void put_seccomp_filter(struct task_struct *tsk);
+extern void get_seccomp_filter(struct task_struct *tsk);
+extern u32 seccomp_bpf_load(int off);
+#else /* CONFIG_SECCOMP_FILTER */
+static inline void put_seccomp_filter(struct task_struct *tsk)
+{
+ return;
+}
+static inline void get_seccomp_filter(struct task_struct *tsk)
+{
+ return;
+}
+#endif /* CONFIG_SECCOMP_FILTER */
+#endif /* __KERNEL__ */
#endif /* _LINUX_SECCOMP_H */
diff --git a/include/linux/security.h b/include/linux/security.h
index b62f396..2a82530 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -144,6 +144,7 @@
#define LSM_UNSAFE_SHARE 1
#define LSM_UNSAFE_PTRACE 2
#define LSM_UNSAFE_PTRACE_CAP 4
+#define LSM_UNSAFE_NO_NEW_PRIVS 8
#ifdef CONFIG_MMU
extern int mmap_min_addr_handler(struct ctl_table *table, int write,
diff --git a/include/linux/videodev2_exynos_media.h b/include/linux/videodev2_exynos_media.h
index 545667a..e15812b 100644
--- a/include/linux/videodev2_exynos_media.h
+++ b/include/linux/videodev2_exynos_media.h
@@ -81,6 +81,12 @@
#define V4L2_CID_TV_SET_DVI_MODE (V4L2_CID_EXYNOS_BASE + 57)
#define V4L2_CID_TV_GET_DVI_MODE (V4L2_CID_EXYNOS_BASE + 58)
#define V4L2_CID_TV_SET_ASPECT_RATIO (V4L2_CID_EXYNOS_BASE + 59)
+#define V4L2_CID_TV_MAX_AUDIO_CHANNELS (V4L2_CID_EXYNOS_BASE + 60)
+#define V4L2_CID_TV_ENABLE_HDMI_AUDIO (V4L2_CID_EXYNOS_BASE + 61)
+#define V4L2_CID_TV_SET_NUM_CHANNELS (V4L2_CID_EXYNOS_BASE + 62)
+#define V4L2_CID_TV_UPDATE (V4L2_CID_EXYNOS_BASE + 63)
+#define V4L2_CID_TV_SET_COLOR_RANGE (V4L2_CID_EXYNOS_BASE + 64)
+#define V4L2_CID_TV_HDCP_ENABLE (V4L2_CID_EXYNOS_BASE + 65)
/* for color space conversion equation selection */
#define V4L2_CID_CSC_EQ_MODE (V4L2_CID_EXYNOS_BASE + 100)
diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h
index ac40716..9eb35ff 100644
--- a/include/linux/watchdog.h
+++ b/include/linux/watchdog.h
@@ -149,6 +149,7 @@
/* drivers/watchdog/core/watchdog_core.c */
extern int watchdog_register_device(struct watchdog_device *);
extern void watchdog_unregister_device(struct watchdog_device *);
+extern void touch_hw_watchdog(void);
#endif /* __KERNEL__ */
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index af1de0f..4b96415 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -67,6 +67,7 @@
#include <linux/syscalls.h>
#include <linux/capability.h>
#include <linux/fs_struct.h>
+#include <linux/compat.h>
#include "audit.h"
@@ -2710,13 +2711,16 @@
audit_log_end(ab);
}
-void __audit_seccomp(unsigned long syscall)
+void __audit_seccomp(unsigned long syscall, long signr, int code)
{
struct audit_buffer *ab;
ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_ANOM_ABEND);
- audit_log_abend(ab, "seccomp", SIGKILL);
+ audit_log_abend(ab, "seccomp", signr);
audit_log_format(ab, " syscall=%ld", syscall);
+ audit_log_format(ab, " compat=%d", is_compat_task());
+ audit_log_format(ab, " ip=0x%lx", KSTK_EIP(current));
+ audit_log_format(ab, " code=0x%x", code);
audit_log_end(ab);
}
diff --git a/kernel/fork.c b/kernel/fork.c
index bc3398e..0de7002 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -34,6 +34,7 @@
#include <linux/cgroup.h>
#include <linux/security.h>
#include <linux/hugetlb.h>
+#include <linux/seccomp.h>
#include <linux/swap.h>
#include <linux/syscalls.h>
#include <linux/jiffies.h>
@@ -174,6 +175,7 @@
free_thread_info(tsk->stack);
rt_mutex_debug_task_free(tsk);
ftrace_graph_exit_task(tsk);
+ put_seccomp_filter(tsk);
free_task_struct(tsk);
}
EXPORT_SYMBOL(free_task);
@@ -1181,6 +1183,7 @@
goto fork_out;
ftrace_graph_init_task(p);
+ get_seccomp_filter(p);
rt_mutex_init_task(p);
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index d10a575..0a5590d 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -72,6 +72,7 @@
#include <linux/slab.h>
#include <linux/init_task.h>
#include <linux/binfmts.h>
+#include <linux/watchdog.h> /* for touch_hw_watchdog() */
#include <asm/switch_to.h>
#include <asm/tlb.h>
@@ -5008,13 +5009,15 @@
#endif
rcu_read_lock();
do_each_thread(g, p) {
- /*
- * reset the NMI-timeout, listing all files on a slow
- * console might take a lot of time:
- */
- touch_nmi_watchdog();
- if (!state_filter || (p->state & state_filter))
+ if (!state_filter || (p->state & state_filter)) {
+ /*
+ * reset the NMI-timeout, listing all files on a slow
+ * console might take a lot of time:
+ */
+ touch_nmi_watchdog();
+ touch_hw_watchdog();
sched_show_task(p);
+ }
} while_each_thread(g, p);
touch_all_softlockup_watchdogs();
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index e8d76c5..6290f6f 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -3,16 +3,357 @@
*
* Copyright 2004-2005 Andrea Arcangeli <andrea@cpushare.com>
*
- * This defines a simple but solid secure-computing mode.
+ * Copyright (C) 2012 Google, Inc.
+ * Will Drewry <wad@chromium.org>
+ *
+ * This defines a simple but solid secure-computing facility.
+ *
+ * Mode 1 uses a fixed list of allowed system calls.
+ * Mode 2 allows user-defined system call filters in the form
+ * of Berkeley Packet Filters/Linux Socket Filters.
*/
+#include <linux/atomic.h>
#include <linux/audit.h>
-#include <linux/seccomp.h>
-#include <linux/sched.h>
#include <linux/compat.h>
+#include <linux/sched.h>
+#include <linux/seccomp.h>
/* #define SECCOMP_DEBUG 1 */
-#define NR_SECCOMP_MODES 1
+
+#ifdef CONFIG_SECCOMP_FILTER
+#include <asm/syscall.h>
+#include <linux/filter.h>
+#include <linux/ptrace.h>
+#include <linux/security.h>
+#include <linux/slab.h>
+#include <linux/tracehook.h>
+#include <linux/uaccess.h>
+
+/**
+ * struct seccomp_filter - container for seccomp BPF programs
+ *
+ * @usage: reference count to manage the object lifetime.
+ * get/put helpers should be used when accessing an instance
+ * outside of a lifetime-guarded section. In general, this
+ * is only needed for handling filters shared across tasks.
+ * @prev: points to a previously installed, or inherited, filter
+ * @len: the number of instructions in the program
+ * @insns: the BPF program instructions to evaluate
+ *
+ * seccomp_filter objects are organized in a tree linked via the @prev
+ * pointer. For any task, it appears to be a singly-linked list starting
+ * with current->seccomp.filter, the most recently attached or inherited filter.
+ * However, multiple filters may share a @prev node, by way of fork(), which
+ * results in a unidirectional tree existing in memory. This is similar to
+ * how namespaces work.
+ *
+ * seccomp_filter objects should never be modified after being attached
+ * to a task_struct (other than @usage).
+ */
+struct seccomp_filter {
+ atomic_t usage;
+ struct seccomp_filter *prev;
+ unsigned short len; /* Instruction count */
+ struct sock_filter insns[];
+};
+
+/* Limit any path through the tree to 256KB worth of instructions. */
+#define MAX_INSNS_PER_PATH ((1 << 18) / sizeof(struct sock_filter))
+
+/**
+ * get_u32 - returns a u32 offset into data
+ * @data: a unsigned 64 bit value
+ * @index: 0 or 1 to return the first or second 32-bits
+ *
+ * This inline exists to hide the length of unsigned long. If a 32-bit
+ * unsigned long is passed in, it will be extended and the top 32-bits will be
+ * 0. If it is a 64-bit unsigned long, then whatever data is resident will be
+ * properly returned.
+ *
+ * Endianness is explicitly ignored and left for BPF program authors to manage
+ * as per the specific architecture.
+ */
+static inline u32 get_u32(u64 data, int index)
+{
+ return ((u32 *)&data)[index];
+}
+
+/* Helper for bpf_load below. */
+#define BPF_DATA(_name) offsetof(struct seccomp_data, _name)
+/**
+ * bpf_load: checks and returns a pointer to the requested offset
+ * @off: offset into struct seccomp_data to load from
+ *
+ * Returns the requested 32-bits of data.
+ * seccomp_check_filter() should assure that @off is 32-bit aligned
+ * and not out of bounds. Failure to do so is a BUG.
+ */
+u32 seccomp_bpf_load(int off)
+{
+ struct pt_regs *regs = task_pt_regs(current);
+ if (off == BPF_DATA(nr))
+ return syscall_get_nr(current, regs);
+ if (off == BPF_DATA(arch))
+ return syscall_get_arch(current, regs);
+ if (off >= BPF_DATA(args[0]) && off < BPF_DATA(args[6])) {
+ unsigned long value;
+ int arg = (off - BPF_DATA(args[0])) / sizeof(u64);
+ int index = !!(off % sizeof(u64));
+ syscall_get_arguments(current, regs, arg, 1, &value);
+ return get_u32(value, index);
+ }
+ if (off == BPF_DATA(instruction_pointer))
+ return get_u32(KSTK_EIP(current), 0);
+ if (off == BPF_DATA(instruction_pointer) + sizeof(u32))
+ return get_u32(KSTK_EIP(current), 1);
+ /* seccomp_check_filter should make this impossible. */
+ BUG();
+}
+
+/**
+ * seccomp_check_filter - verify seccomp filter code
+ * @filter: filter to verify
+ * @flen: length of filter
+ *
+ * Takes a previously checked filter (by sk_chk_filter) and
+ * redirects all filter code that loads struct sk_buff data
+ * and related data through seccomp_bpf_load. It also
+ * enforces length and alignment checking of those loads.
+ *
+ * Returns 0 if the rule set is legal or -EINVAL if not.
+ */
+static int seccomp_check_filter(struct sock_filter *filter, unsigned int flen)
+{
+ int pc;
+ for (pc = 0; pc < flen; pc++) {
+ struct sock_filter *ftest = &filter[pc];
+ u16 code = ftest->code;
+ u32 k = ftest->k;
+
+ switch (code) {
+ case BPF_S_LD_W_ABS:
+ ftest->code = BPF_S_ANC_SECCOMP_LD_W;
+ /* 32-bit aligned and not out of bounds. */
+ if (k >= sizeof(struct seccomp_data) || k & 3)
+ return -EINVAL;
+ continue;
+ case BPF_S_LD_W_LEN:
+ ftest->code = BPF_S_LD_IMM;
+ ftest->k = sizeof(struct seccomp_data);
+ continue;
+ case BPF_S_LDX_W_LEN:
+ ftest->code = BPF_S_LDX_IMM;
+ ftest->k = sizeof(struct seccomp_data);
+ continue;
+ /* Explicitly include allowed calls. */
+ case BPF_S_RET_K:
+ case BPF_S_RET_A:
+ case BPF_S_ALU_ADD_K:
+ case BPF_S_ALU_ADD_X:
+ case BPF_S_ALU_SUB_K:
+ case BPF_S_ALU_SUB_X:
+ case BPF_S_ALU_MUL_K:
+ case BPF_S_ALU_MUL_X:
+ case BPF_S_ALU_DIV_X:
+ case BPF_S_ALU_AND_K:
+ case BPF_S_ALU_AND_X:
+ case BPF_S_ALU_OR_K:
+ case BPF_S_ALU_OR_X:
+ case BPF_S_ALU_LSH_K:
+ case BPF_S_ALU_LSH_X:
+ case BPF_S_ALU_RSH_K:
+ case BPF_S_ALU_RSH_X:
+ case BPF_S_ALU_NEG:
+ case BPF_S_LD_IMM:
+ case BPF_S_LDX_IMM:
+ case BPF_S_MISC_TAX:
+ case BPF_S_MISC_TXA:
+ case BPF_S_ALU_DIV_K:
+ case BPF_S_LD_MEM:
+ case BPF_S_LDX_MEM:
+ case BPF_S_ST:
+ case BPF_S_STX:
+ case BPF_S_JMP_JA:
+ case BPF_S_JMP_JEQ_K:
+ case BPF_S_JMP_JEQ_X:
+ case BPF_S_JMP_JGE_K:
+ case BPF_S_JMP_JGE_X:
+ case BPF_S_JMP_JGT_K:
+ case BPF_S_JMP_JGT_X:
+ case BPF_S_JMP_JSET_K:
+ case BPF_S_JMP_JSET_X:
+ continue;
+ default:
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+/**
+ * seccomp_run_filters - evaluates all seccomp filters against @syscall
+ * @syscall: number of the current system call
+ *
+ * Returns valid seccomp BPF response codes.
+ */
+static u32 seccomp_run_filters(int syscall)
+{
+ struct seccomp_filter *f;
+ u32 ret = SECCOMP_RET_ALLOW;
+
+ /* Ensure unexpected behavior doesn't result in failing open. */
+ if (WARN_ON(current->seccomp.filter == NULL))
+ return SECCOMP_RET_KILL;
+
+ /*
+ * All filters in the list are evaluated and the lowest BPF return
+ * value always takes priority (ignoring the DATA).
+ */
+ for (f = current->seccomp.filter; f; f = f->prev) {
+ u32 cur_ret = sk_run_filter(NULL, f->insns);
+ if ((cur_ret & SECCOMP_RET_ACTION) < (ret & SECCOMP_RET_ACTION))
+ ret = cur_ret;
+ }
+ return ret;
+}
+
+/**
+ * seccomp_attach_filter: Attaches a seccomp filter to current.
+ * @fprog: BPF program to install
+ *
+ * Returns 0 on success or an errno on failure.
+ */
+static long seccomp_attach_filter(struct sock_fprog *fprog)
+{
+ struct seccomp_filter *filter;
+ unsigned long fp_size = fprog->len * sizeof(struct sock_filter);
+ unsigned long total_insns = fprog->len;
+ long ret;
+
+ if (fprog->len == 0 || fprog->len > BPF_MAXINSNS)
+ return -EINVAL;
+
+ for (filter = current->seccomp.filter; filter; filter = filter->prev)
+ total_insns += filter->len + 4; /* include a 4 instr penalty */
+ if (total_insns > MAX_INSNS_PER_PATH)
+ return -ENOMEM;
+
+ /*
+ * Installing a seccomp filter requires that the task have
+ * CAP_SYS_ADMIN in its namespace or be running with no_new_privs.
+ * This avoids scenarios where unprivileged tasks can affect the
+ * behavior of privileged children.
+ */
+ if (!current->no_new_privs &&
+ security_capable_noaudit(current_cred(), current_user_ns(),
+ CAP_SYS_ADMIN) != 0)
+ return -EACCES;
+
+ /* Allocate a new seccomp_filter */
+ filter = kzalloc(sizeof(struct seccomp_filter) + fp_size,
+ GFP_KERNEL|__GFP_NOWARN);
+ if (!filter)
+ return -ENOMEM;
+ atomic_set(&filter->usage, 1);
+ filter->len = fprog->len;
+
+ /* Copy the instructions from fprog. */
+ ret = -EFAULT;
+ if (copy_from_user(filter->insns, fprog->filter, fp_size))
+ goto fail;
+
+ /* Check and rewrite the fprog via the skb checker */
+ ret = sk_chk_filter(filter->insns, filter->len);
+ if (ret)
+ goto fail;
+
+ /* Check and rewrite the fprog for seccomp use */
+ ret = seccomp_check_filter(filter->insns, filter->len);
+ if (ret)
+ goto fail;
+
+ /*
+ * If there is an existing filter, make it the prev and don't drop its
+ * task reference.
+ */
+ filter->prev = current->seccomp.filter;
+ current->seccomp.filter = filter;
+ return 0;
+fail:
+ kfree(filter);
+ return ret;
+}
+
+/**
+ * seccomp_attach_user_filter - attaches a user-supplied sock_fprog
+ * @user_filter: pointer to the user data containing a sock_fprog.
+ *
+ * Returns 0 on success and non-zero otherwise.
+ */
+long seccomp_attach_user_filter(char __user *user_filter)
+{
+ struct sock_fprog fprog;
+ long ret = -EFAULT;
+
+#ifdef CONFIG_COMPAT
+ if (is_compat_task()) {
+ struct compat_sock_fprog fprog32;
+ if (copy_from_user(&fprog32, user_filter, sizeof(fprog32)))
+ goto out;
+ fprog.len = fprog32.len;
+ fprog.filter = compat_ptr(fprog32.filter);
+ } else /* falls through to the if below. */
+#endif
+ if (copy_from_user(&fprog, user_filter, sizeof(fprog)))
+ goto out;
+ ret = seccomp_attach_filter(&fprog);
+out:
+ return ret;
+}
+
+/* get_seccomp_filter - increments the reference count of the filter on @tsk */
+void get_seccomp_filter(struct task_struct *tsk)
+{
+ struct seccomp_filter *orig = tsk->seccomp.filter;
+ if (!orig)
+ return;
+ /* Reference count is bounded by the number of total processes. */
+ atomic_inc(&orig->usage);
+}
+
+/* put_seccomp_filter - decrements the ref count of tsk->seccomp.filter */
+void put_seccomp_filter(struct task_struct *tsk)
+{
+ struct seccomp_filter *orig = tsk->seccomp.filter;
+ /* Clean up single-reference branches iteratively. */
+ while (orig && atomic_dec_and_test(&orig->usage)) {
+ struct seccomp_filter *freeme = orig;
+ orig = orig->prev;
+ kfree(freeme);
+ }
+}
+
+/**
+ * seccomp_send_sigsys - signals the task to allow in-process syscall emulation
+ * @syscall: syscall number to send to userland
+ * @reason: filter-supplied reason code to send to userland (via si_errno)
+ *
+ * Forces a SIGSYS with a code of SYS_SECCOMP and related sigsys info.
+ */
+static void seccomp_send_sigsys(int syscall, int reason)
+{
+ struct siginfo info;
+ memset(&info, 0, sizeof(info));
+ info.si_signo = SIGSYS;
+ info.si_code = SYS_SECCOMP;
+ info.si_call_addr = (void __user *)KSTK_EIP(current);
+ info.si_errno = reason;
+ info.si_arch = syscall_get_arch(current, task_pt_regs(current));
+ info.si_syscall = syscall;
+ force_sig_info(SIGSYS, &info, current);
+}
+#endif /* CONFIG_SECCOMP_FILTER */
/*
* Secure computing mode 1 allows only read/write/exit/sigreturn.
@@ -31,13 +372,15 @@
};
#endif
-void __secure_computing(int this_syscall)
+int __secure_computing(int this_syscall)
{
int mode = current->seccomp.mode;
- int * syscall;
+ int exit_sig = 0;
+ int *syscall;
+ u32 ret;
switch (mode) {
- case 1:
+ case SECCOMP_MODE_STRICT:
syscall = mode1_syscalls;
#ifdef CONFIG_COMPAT
if (is_compat_task())
@@ -45,9 +388,58 @@
#endif
do {
if (*syscall == this_syscall)
- return;
+ return 0;
} while (*++syscall);
+ exit_sig = SIGKILL;
+ ret = SECCOMP_RET_KILL;
break;
+#ifdef CONFIG_SECCOMP_FILTER
+ case SECCOMP_MODE_FILTER: {
+ int data;
+ ret = seccomp_run_filters(this_syscall);
+ data = ret & SECCOMP_RET_DATA;
+ ret &= SECCOMP_RET_ACTION;
+ switch (ret) {
+ case SECCOMP_RET_ERRNO:
+ /* Set the low-order 16-bits as a errno. */
+ syscall_set_return_value(current, task_pt_regs(current),
+ -data, 0);
+ goto skip;
+ case SECCOMP_RET_TRAP:
+ /* Show the handler the original registers. */
+ syscall_rollback(current, task_pt_regs(current));
+ /* Let the filter pass back 16 bits of data. */
+ seccomp_send_sigsys(this_syscall, data);
+ goto skip;
+ case SECCOMP_RET_TRACE:
+ /* Skip these calls if there is no tracer. */
+ if (!ptrace_event_enabled(current, PTRACE_EVENT_SECCOMP)) {
+ /* Make sure userspace sees an ENOSYS. */
+ syscall_set_return_value(current,
+ task_pt_regs(current), -ENOSYS, 0);
+ goto skip;
+ }
+ /* Allow the BPF to provide the event message */
+ ptrace_event(PTRACE_EVENT_SECCOMP, data);
+ /*
+ * The delivery of a fatal signal during event
+ * notification may silently skip tracer notification.
+ * Terminating the task now avoids executing a system
+ * call that may not be intended.
+ */
+ if (fatal_signal_pending(current))
+ break;
+ return 0;
+ case SECCOMP_RET_ALLOW:
+ return 0;
+ case SECCOMP_RET_KILL:
+ default:
+ break;
+ }
+ exit_sig = SIGSYS;
+ break;
+ }
+#endif
default:
BUG();
}
@@ -55,8 +447,13 @@
#ifdef SECCOMP_DEBUG
dump_stack();
#endif
- audit_seccomp(this_syscall);
- do_exit(SIGKILL);
+ audit_seccomp(this_syscall, exit_sig, ret);
+ do_exit(exit_sig);
+#ifdef CONFIG_SECCOMP_FILTER
+skip:
+ audit_seccomp(this_syscall, exit_sig, ret);
+#endif
+ return -1;
}
long prctl_get_seccomp(void)
@@ -64,25 +461,48 @@
return current->seccomp.mode;
}
-long prctl_set_seccomp(unsigned long seccomp_mode)
+/**
+ * prctl_set_seccomp: configures current->seccomp.mode
+ * @seccomp_mode: requested mode to use
+ * @filter: optional struct sock_fprog for use with SECCOMP_MODE_FILTER
+ *
+ * This function may be called repeatedly with a @seccomp_mode of
+ * SECCOMP_MODE_FILTER to install additional filters. Every filter
+ * successfully installed will be evaluated (in reverse order) for each system
+ * call the task makes.
+ *
+ * Once current->seccomp.mode is non-zero, it may not be changed.
+ *
+ * Returns 0 on success or -EINVAL on failure.
+ */
+long prctl_set_seccomp(unsigned long seccomp_mode, char __user *filter)
{
- long ret;
+ long ret = -EINVAL;
- /* can set it only once to be even more secure */
- ret = -EPERM;
- if (unlikely(current->seccomp.mode))
+ if (current->seccomp.mode &&
+ current->seccomp.mode != seccomp_mode)
goto out;
- ret = -EINVAL;
- if (seccomp_mode && seccomp_mode <= NR_SECCOMP_MODES) {
- current->seccomp.mode = seccomp_mode;
- set_thread_flag(TIF_SECCOMP);
+ switch (seccomp_mode) {
+ case SECCOMP_MODE_STRICT:
+ ret = 0;
#ifdef TIF_NOTSC
disable_TSC();
#endif
- ret = 0;
+ break;
+#ifdef CONFIG_SECCOMP_FILTER
+ case SECCOMP_MODE_FILTER:
+ ret = seccomp_attach_user_filter(filter);
+ if (ret)
+ goto out;
+ break;
+#endif
+ default:
+ goto out;
}
- out:
+ current->seccomp.mode = seccomp_mode;
+ set_thread_flag(TIF_SECCOMP);
+out:
return ret;
}
diff --git a/kernel/signal.c b/kernel/signal.c
index 98059e2..c9868a4 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -160,7 +160,7 @@
#define SYNCHRONOUS_MASK \
(sigmask(SIGSEGV) | sigmask(SIGBUS) | sigmask(SIGILL) | \
- sigmask(SIGTRAP) | sigmask(SIGFPE))
+ sigmask(SIGTRAP) | sigmask(SIGFPE) | sigmask(SIGSYS))
int next_signal(struct sigpending *pending, sigset_t *mask)
{
@@ -2708,6 +2708,13 @@
err |= __put_user(from->si_uid, &to->si_uid);
err |= __put_user(from->si_ptr, &to->si_ptr);
break;
+#ifdef __ARCH_SIGSYS
+ case __SI_SYS:
+ err |= __put_user(from->si_call_addr, &to->si_call_addr);
+ err |= __put_user(from->si_syscall, &to->si_syscall);
+ err |= __put_user(from->si_arch, &to->si_arch);
+ break;
+#endif
default: /* this is just in case for now ... */
err |= __put_user(from->si_pid, &to->si_pid);
err |= __put_user(from->si_uid, &to->si_uid);
diff --git a/kernel/sys.c b/kernel/sys.c
index b0003db..5849fff 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -1911,7 +1911,7 @@
error = prctl_get_seccomp();
break;
case PR_SET_SECCOMP:
- error = prctl_set_seccomp(arg2);
+ error = prctl_set_seccomp(arg2, (char __user *)arg3);
break;
case PR_GET_TSC:
error = GET_TSC_CTL(arg2);
@@ -1982,6 +1982,16 @@
error = put_user(me->signal->is_child_subreaper,
(int __user *) arg2);
break;
+ case PR_SET_NO_NEW_PRIVS:
+ if (arg2 != 1 || arg3 || arg4 || arg5)
+ return -EINVAL;
+
+ current->no_new_privs = 1;
+ break;
+ case PR_GET_NO_NEW_PRIVS:
+ if (arg2 || arg3 || arg4 || arg5)
+ return -EINVAL;
+ return current->no_new_privs ? 1 : 0;
default:
error = -EINVAL;
break;
diff --git a/kernel/time/alarmtimer.c b/kernel/time/alarmtimer.c
index f979d85..b07241c 100644
--- a/kernel/time/alarmtimer.c
+++ b/kernel/time/alarmtimer.c
@@ -37,7 +37,6 @@
static struct alarm_base {
spinlock_t lock;
struct timerqueue_head timerqueue;
- struct hrtimer timer;
ktime_t (*gettime)(void);
clockid_t base_clockid;
} alarm_bases[ALARM_NUMTYPE];
@@ -132,21 +131,17 @@
* @base: pointer to the base where the timer is being run
* @alarm: pointer to alarm being enqueued.
*
- * Adds alarm to a alarm_base timerqueue and if necessary sets
- * an hrtimer to run.
+ * Adds alarm to a alarm_base timerqueue
*
* Must hold base->lock when calling.
*/
static void alarmtimer_enqueue(struct alarm_base *base, struct alarm *alarm)
{
+ if (alarm->state & ALARMTIMER_STATE_ENQUEUED)
+ timerqueue_del(&base->timerqueue, &alarm->node);
+
timerqueue_add(&base->timerqueue, &alarm->node);
alarm->state |= ALARMTIMER_STATE_ENQUEUED;
-
- if (&alarm->node == timerqueue_getnext(&base->timerqueue)) {
- hrtimer_try_to_cancel(&base->timer);
- hrtimer_start(&base->timer, alarm->node.expires,
- HRTIMER_MODE_ABS);
- }
}
/**
@@ -154,28 +149,17 @@
* @base: pointer to the base where the timer is running
* @alarm: pointer to alarm being removed
*
- * Removes alarm to a alarm_base timerqueue and if necessary sets
- * a new timer to run.
+ * Removes alarm to a alarm_base timerqueue
*
* Must hold base->lock when calling.
*/
static void alarmtimer_remove(struct alarm_base *base, struct alarm *alarm)
{
- struct timerqueue_node *next = timerqueue_getnext(&base->timerqueue);
-
if (!(alarm->state & ALARMTIMER_STATE_ENQUEUED))
return;
timerqueue_del(&base->timerqueue, &alarm->node);
alarm->state &= ~ALARMTIMER_STATE_ENQUEUED;
-
- if (next == &alarm->node) {
- hrtimer_try_to_cancel(&base->timer);
- next = timerqueue_getnext(&base->timerqueue);
- if (!next)
- return;
- hrtimer_start(&base->timer, next->expires, HRTIMER_MODE_ABS);
- }
}
@@ -190,42 +174,23 @@
*/
static enum hrtimer_restart alarmtimer_fired(struct hrtimer *timer)
{
- struct alarm_base *base = container_of(timer, struct alarm_base, timer);
- struct timerqueue_node *next;
+ struct alarm *alarm = container_of(timer, struct alarm, timer);
+ struct alarm_base *base = &alarm_bases[alarm->type];
unsigned long flags;
- ktime_t now;
int ret = HRTIMER_NORESTART;
int restart = ALARMTIMER_NORESTART;
spin_lock_irqsave(&base->lock, flags);
- now = base->gettime();
- while ((next = timerqueue_getnext(&base->timerqueue))) {
- struct alarm *alarm;
- ktime_t expired = next->expires;
+ alarmtimer_remove(base, alarm);
+ spin_unlock_irqrestore(&base->lock, flags);
- if (expired.tv64 > now.tv64)
- break;
+ if (alarm->function)
+ restart = alarm->function(alarm, base->gettime());
- alarm = container_of(next, struct alarm, node);
-
- timerqueue_del(&base->timerqueue, &alarm->node);
- alarm->state &= ~ALARMTIMER_STATE_ENQUEUED;
-
- alarm->state |= ALARMTIMER_STATE_CALLBACK;
- spin_unlock_irqrestore(&base->lock, flags);
- if (alarm->function)
- restart = alarm->function(alarm, now);
- spin_lock_irqsave(&base->lock, flags);
- alarm->state &= ~ALARMTIMER_STATE_CALLBACK;
-
- if (restart != ALARMTIMER_NORESTART) {
- timerqueue_add(&base->timerqueue, &alarm->node);
- alarm->state |= ALARMTIMER_STATE_ENQUEUED;
- }
- }
-
- if (next) {
- hrtimer_set_expires(&base->timer, next->expires);
+ spin_lock_irqsave(&base->lock, flags);
+ if (restart != ALARMTIMER_NORESTART) {
+ hrtimer_set_expires(&alarm->timer, alarm->node.expires);
+ alarmtimer_enqueue(base, alarm);
ret = HRTIMER_RESTART;
}
spin_unlock_irqrestore(&base->lock, flags);
@@ -331,6 +296,9 @@
enum alarmtimer_restart (*function)(struct alarm *, ktime_t))
{
timerqueue_init(&alarm->node);
+ hrtimer_init(&alarm->timer, alarm_bases[type].base_clockid,
+ HRTIMER_MODE_ABS);
+ alarm->timer.function = alarmtimer_fired;
alarm->function = function;
alarm->type = type;
alarm->state = ALARMTIMER_STATE_INACTIVE;
@@ -341,17 +309,19 @@
* @alarm: ptr to alarm to set
* @start: time to run the alarm
*/
-void alarm_start(struct alarm *alarm, ktime_t start)
+int alarm_start(struct alarm *alarm, ktime_t start)
{
struct alarm_base *base = &alarm_bases[alarm->type];
unsigned long flags;
+ int ret;
spin_lock_irqsave(&base->lock, flags);
- if (alarmtimer_active(alarm))
- alarmtimer_remove(base, alarm);
alarm->node.expires = start;
alarmtimer_enqueue(base, alarm);
+ ret = hrtimer_start(&alarm->timer, alarm->node.expires,
+ HRTIMER_MODE_ABS);
spin_unlock_irqrestore(&base->lock, flags);
+ return ret;
}
/**
@@ -365,18 +335,12 @@
{
struct alarm_base *base = &alarm_bases[alarm->type];
unsigned long flags;
- int ret = -1;
+ int ret;
+
spin_lock_irqsave(&base->lock, flags);
-
- if (alarmtimer_callback_running(alarm))
- goto out;
-
- if (alarmtimer_is_queued(alarm)) {
+ ret = hrtimer_try_to_cancel(&alarm->timer);
+ if (ret >= 0)
alarmtimer_remove(base, alarm);
- ret = 1;
- } else
- ret = 0;
-out:
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
@@ -809,10 +773,6 @@
for (i = 0; i < ALARM_NUMTYPE; i++) {
timerqueue_init_head(&alarm_bases[i].timerqueue);
spin_lock_init(&alarm_bases[i].lock);
- hrtimer_init(&alarm_bases[i].timer,
- alarm_bases[i].base_clockid,
- HRTIMER_MODE_ABS);
- alarm_bases[i].timer.function = alarmtimer_fired;
}
error = alarmtimer_rtc_interface_setup();
diff --git a/net/compat.c b/net/compat.c
index ae6d67a..736bbeb 100644
--- a/net/compat.c
+++ b/net/compat.c
@@ -328,14 +328,6 @@
__scm_destroy(scm);
}
-/*
- * A struct sock_filter is architecture independent.
- */
-struct compat_sock_fprog {
- u16 len;
- compat_uptr_t filter; /* struct sock_filter * */
-};
-
static int do_set_attach_filter(struct socket *sock, int level, int optname,
char __user *optval, unsigned int optlen)
{
diff --git a/net/core/filter.c b/net/core/filter.c
index 6f755cc..491e2e1 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -38,6 +38,7 @@
#include <linux/filter.h>
#include <linux/reciprocal_div.h>
#include <linux/ratelimit.h>
+#include <linux/seccomp.h>
/* No hurry in this branch
*
@@ -352,6 +353,11 @@
A = 0;
continue;
}
+#ifdef CONFIG_SECCOMP_FILTER
+ case BPF_S_ANC_SECCOMP_LD_W:
+ A = seccomp_bpf_load(fentry->k);
+ continue;
+#endif
default:
WARN_RATELIMIT(1, "Unknown code:%u jt:%u tf:%u k:%u\n",
fentry->code, fentry->jt,
diff --git a/samples/Makefile b/samples/Makefile
index 2f75851..5ef08bb 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -1,4 +1,4 @@
# Makefile for Linux samples code
obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ tracepoints/ trace_events/ \
- hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/
+ hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/
diff --git a/samples/seccomp/Makefile b/samples/seccomp/Makefile
new file mode 100644
index 0000000..16aa2d4
--- /dev/null
+++ b/samples/seccomp/Makefile
@@ -0,0 +1,32 @@
+# kbuild trick to avoid linker error. Can be omitted if a module is built.
+obj- := dummy.o
+
+hostprogs-$(CONFIG_SECCOMP_FILTER) := bpf-fancy dropper bpf-direct
+
+HOSTCFLAGS_bpf-fancy.o += -I$(objtree)/usr/include
+HOSTCFLAGS_bpf-fancy.o += -idirafter $(objtree)/include
+HOSTCFLAGS_bpf-helper.o += -I$(objtree)/usr/include
+HOSTCFLAGS_bpf-helper.o += -idirafter $(objtree)/include
+bpf-fancy-objs := bpf-fancy.o bpf-helper.o
+
+HOSTCFLAGS_dropper.o += -I$(objtree)/usr/include
+HOSTCFLAGS_dropper.o += -idirafter $(objtree)/include
+dropper-objs := dropper.o
+
+HOSTCFLAGS_bpf-direct.o += -I$(objtree)/usr/include
+HOSTCFLAGS_bpf-direct.o += -idirafter $(objtree)/include
+bpf-direct-objs := bpf-direct.o
+
+# Try to match the kernel target.
+ifeq ($(CONFIG_64BIT),)
+HOSTCFLAGS_bpf-direct.o += -m32
+HOSTCFLAGS_dropper.o += -m32
+HOSTCFLAGS_bpf-helper.o += -m32
+HOSTCFLAGS_bpf-fancy.o += -m32
+HOSTLOADLIBES_bpf-direct += -m32
+HOSTLOADLIBES_bpf-fancy += -m32
+HOSTLOADLIBES_dropper += -m32
+endif
+
+# Tell kbuild to always build the programs
+always := $(hostprogs-y)
diff --git a/samples/seccomp/bpf-direct.c b/samples/seccomp/bpf-direct.c
new file mode 100644
index 0000000..151ec3f
--- /dev/null
+++ b/samples/seccomp/bpf-direct.c
@@ -0,0 +1,190 @@
+/*
+ * Seccomp filter example for x86 (32-bit and 64-bit) with BPF macros
+ *
+ * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * Author: Will Drewry <wad@chromium.org>
+ *
+ * The code may be used by anyone for any purpose,
+ * and can serve as a starting point for developing
+ * applications using prctl(PR_SET_SECCOMP, 2, ...).
+ */
+#if defined(__i386__) || defined(__x86_64__)
+#define SUPPORTED_ARCH 1
+#endif
+
+#if defined(SUPPORTED_ARCH)
+#define __USE_GNU 1
+#define _GNU_SOURCE 1
+
+#include <linux/types.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <linux/unistd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#define syscall_arg(_n) (offsetof(struct seccomp_data, args[_n]))
+#define syscall_nr (offsetof(struct seccomp_data, nr))
+
+#if defined(__i386__)
+#define REG_RESULT REG_EAX
+#define REG_SYSCALL REG_EAX
+#define REG_ARG0 REG_EBX
+#define REG_ARG1 REG_ECX
+#define REG_ARG2 REG_EDX
+#define REG_ARG3 REG_ESI
+#define REG_ARG4 REG_EDI
+#define REG_ARG5 REG_EBP
+#elif defined(__x86_64__)
+#define REG_RESULT REG_RAX
+#define REG_SYSCALL REG_RAX
+#define REG_ARG0 REG_RDI
+#define REG_ARG1 REG_RSI
+#define REG_ARG2 REG_RDX
+#define REG_ARG3 REG_R10
+#define REG_ARG4 REG_R8
+#define REG_ARG5 REG_R9
+#endif
+
+#ifndef PR_SET_NO_NEW_PRIVS
+#define PR_SET_NO_NEW_PRIVS 38
+#endif
+
+#ifndef SYS_SECCOMP
+#define SYS_SECCOMP 1
+#endif
+
+static void emulator(int nr, siginfo_t *info, void *void_context)
+{
+ ucontext_t *ctx = (ucontext_t *)(void_context);
+ int syscall;
+ char *buf;
+ ssize_t bytes;
+ size_t len;
+ if (info->si_code != SYS_SECCOMP)
+ return;
+ if (!ctx)
+ return;
+ syscall = ctx->uc_mcontext.gregs[REG_SYSCALL];
+ buf = (char *) ctx->uc_mcontext.gregs[REG_ARG1];
+ len = (size_t) ctx->uc_mcontext.gregs[REG_ARG2];
+
+ if (syscall != __NR_write)
+ return;
+ if (ctx->uc_mcontext.gregs[REG_ARG0] != STDERR_FILENO)
+ return;
+ /* Redirect stderr messages to stdout. Doesn't handle EINTR, etc */
+ ctx->uc_mcontext.gregs[REG_RESULT] = -1;
+ if (write(STDOUT_FILENO, "[ERR] ", 6) > 0) {
+ bytes = write(STDOUT_FILENO, buf, len);
+ ctx->uc_mcontext.gregs[REG_RESULT] = bytes;
+ }
+ return;
+}
+
+static int install_emulator(void)
+{
+ struct sigaction act;
+ sigset_t mask;
+ memset(&act, 0, sizeof(act));
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGSYS);
+
+ act.sa_sigaction = &emulator;
+ act.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSYS, &act, NULL) < 0) {
+ perror("sigaction");
+ return -1;
+ }
+ if (sigprocmask(SIG_UNBLOCK, &mask, NULL)) {
+ perror("sigprocmask");
+ return -1;
+ }
+ return 0;
+}
+
+static int install_filter(void)
+{
+ struct sock_filter filter[] = {
+ /* Grab the system call number */
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr),
+ /* Jump table for the allowed syscalls */
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_rt_sigreturn, 0, 1),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
+#ifdef __NR_sigreturn
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_sigreturn, 0, 1),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
+#endif
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_exit_group, 0, 1),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_exit, 0, 1),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_read, 1, 0),
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_write, 3, 2),
+
+ /* Check that read is only using stdin. */
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_arg(0)),
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, STDIN_FILENO, 4, 0),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
+
+ /* Check that write is only using stdout */
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_arg(0)),
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, STDOUT_FILENO, 1, 0),
+ /* Trap attempts to write to stderr */
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, STDERR_FILENO, 1, 2),
+
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRAP),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
+ };
+ struct sock_fprog prog = {
+ .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
+ .filter = filter,
+ };
+
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ perror("prctl(NO_NEW_PRIVS)");
+ return 1;
+ }
+
+
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
+ perror("prctl");
+ return 1;
+ }
+ return 0;
+}
+
+#define payload(_c) (_c), sizeof((_c))
+int main(int argc, char **argv)
+{
+ char buf[4096];
+ ssize_t bytes = 0;
+ if (install_emulator())
+ return 1;
+ if (install_filter())
+ return 1;
+ syscall(__NR_write, STDOUT_FILENO,
+ payload("OHAI! WHAT IS YOUR NAME? "));
+ bytes = syscall(__NR_read, STDIN_FILENO, buf, sizeof(buf));
+ syscall(__NR_write, STDOUT_FILENO, payload("HELLO, "));
+ syscall(__NR_write, STDOUT_FILENO, buf, bytes);
+ syscall(__NR_write, STDERR_FILENO,
+ payload("Error message going to STDERR\n"));
+ return 0;
+}
+#else /* SUPPORTED_ARCH */
+/*
+ * This sample is x86-only. Since kernel samples are compiled with the
+ * host toolchain, a non-x86 host will result in using only the main()
+ * below.
+ */
+int main(void)
+{
+ return 1;
+}
+#endif /* SUPPORTED_ARCH */
diff --git a/samples/seccomp/bpf-fancy.c b/samples/seccomp/bpf-fancy.c
new file mode 100644
index 0000000..8eb483aa
--- /dev/null
+++ b/samples/seccomp/bpf-fancy.c
@@ -0,0 +1,102 @@
+/*
+ * Seccomp BPF example using a macro-based generator.
+ *
+ * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * Author: Will Drewry <wad@chromium.org>
+ *
+ * The code may be used by anyone for any purpose,
+ * and can serve as a starting point for developing
+ * applications using prctl(PR_ATTACH_SECCOMP_FILTER).
+ */
+
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <linux/unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "bpf-helper.h"
+
+#ifndef PR_SET_NO_NEW_PRIVS
+#define PR_SET_NO_NEW_PRIVS 38
+#endif
+
+int main(int argc, char **argv)
+{
+ struct bpf_labels l;
+ static const char msg1[] = "Please type something: ";
+ static const char msg2[] = "You typed: ";
+ char buf[256];
+ struct sock_filter filter[] = {
+ /* TODO: LOAD_SYSCALL_NR(arch) and enforce an arch */
+ LOAD_SYSCALL_NR,
+ SYSCALL(__NR_exit, ALLOW),
+ SYSCALL(__NR_exit_group, ALLOW),
+ SYSCALL(__NR_write, JUMP(&l, write_fd)),
+ SYSCALL(__NR_read, JUMP(&l, read)),
+ DENY, /* Don't passthrough into a label */
+
+ LABEL(&l, read),
+ ARG(0),
+ JNE(STDIN_FILENO, DENY),
+ ARG(1),
+ JNE((unsigned long)buf, DENY),
+ ARG(2),
+ JGE(sizeof(buf), DENY),
+ ALLOW,
+
+ LABEL(&l, write_fd),
+ ARG(0),
+ JEQ(STDOUT_FILENO, JUMP(&l, write_buf)),
+ JEQ(STDERR_FILENO, JUMP(&l, write_buf)),
+ DENY,
+
+ LABEL(&l, write_buf),
+ ARG(1),
+ JEQ((unsigned long)msg1, JUMP(&l, msg1_len)),
+ JEQ((unsigned long)msg2, JUMP(&l, msg2_len)),
+ JEQ((unsigned long)buf, JUMP(&l, buf_len)),
+ DENY,
+
+ LABEL(&l, msg1_len),
+ ARG(2),
+ JLT(sizeof(msg1), ALLOW),
+ DENY,
+
+ LABEL(&l, msg2_len),
+ ARG(2),
+ JLT(sizeof(msg2), ALLOW),
+ DENY,
+
+ LABEL(&l, buf_len),
+ ARG(2),
+ JLT(sizeof(buf), ALLOW),
+ DENY,
+ };
+ struct sock_fprog prog = {
+ .filter = filter,
+ .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
+ };
+ ssize_t bytes;
+ bpf_resolve_jumps(&l, filter, sizeof(filter)/sizeof(*filter));
+
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ perror("prctl(NO_NEW_PRIVS)");
+ return 1;
+ }
+
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
+ perror("prctl(SECCOMP)");
+ return 1;
+ }
+ syscall(__NR_write, STDOUT_FILENO, msg1, strlen(msg1));
+ bytes = syscall(__NR_read, STDIN_FILENO, buf, sizeof(buf)-1);
+ bytes = (bytes > 0 ? bytes : 0);
+ syscall(__NR_write, STDERR_FILENO, msg2, strlen(msg2));
+ syscall(__NR_write, STDERR_FILENO, buf, bytes);
+ /* Now get killed */
+ syscall(__NR_write, STDERR_FILENO, msg2, strlen(msg2)+2);
+ return 0;
+}
diff --git a/samples/seccomp/bpf-helper.c b/samples/seccomp/bpf-helper.c
new file mode 100644
index 0000000..579cfe3
--- /dev/null
+++ b/samples/seccomp/bpf-helper.c
@@ -0,0 +1,89 @@
+/*
+ * Seccomp BPF helper functions
+ *
+ * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * Author: Will Drewry <wad@chromium.org>
+ *
+ * The code may be used by anyone for any purpose,
+ * and can serve as a starting point for developing
+ * applications using prctl(PR_ATTACH_SECCOMP_FILTER).
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "bpf-helper.h"
+
+int bpf_resolve_jumps(struct bpf_labels *labels,
+ struct sock_filter *filter, size_t count)
+{
+ struct sock_filter *begin = filter;
+ __u8 insn = count - 1;
+
+ if (count < 1)
+ return -1;
+ /*
+ * Walk it once, backwards, to build the label table and do fixups.
+ * Since backward jumps are disallowed by BPF, this is easy.
+ */
+ filter += insn;
+ for (; filter >= begin; --insn, --filter) {
+ if (filter->code != (BPF_JMP+BPF_JA))
+ continue;
+ switch ((filter->jt<<8)|filter->jf) {
+ case (JUMP_JT<<8)|JUMP_JF:
+ if (labels->labels[filter->k].location == 0xffffffff) {
+ fprintf(stderr, "Unresolved label: '%s'\n",
+ labels->labels[filter->k].label);
+ return 1;
+ }
+ filter->k = labels->labels[filter->k].location -
+ (insn + 1);
+ filter->jt = 0;
+ filter->jf = 0;
+ continue;
+ case (LABEL_JT<<8)|LABEL_JF:
+ if (labels->labels[filter->k].location != 0xffffffff) {
+ fprintf(stderr, "Duplicate label use: '%s'\n",
+ labels->labels[filter->k].label);
+ return 1;
+ }
+ labels->labels[filter->k].location = insn;
+ filter->k = 0; /* fall through */
+ filter->jt = 0;
+ filter->jf = 0;
+ continue;
+ }
+ }
+ return 0;
+}
+
+/* Simple lookup table for labels. */
+__u32 seccomp_bpf_label(struct bpf_labels *labels, const char *label)
+{
+ struct __bpf_label *begin = labels->labels, *end;
+ int id;
+ if (labels->count == 0) {
+ begin->label = label;
+ begin->location = 0xffffffff;
+ labels->count++;
+ return 0;
+ }
+ end = begin + labels->count;
+ for (id = 0; begin < end; ++begin, ++id) {
+ if (!strcmp(label, begin->label))
+ return id;
+ }
+ begin->label = label;
+ begin->location = 0xffffffff;
+ labels->count++;
+ return id;
+}
+
+void seccomp_bpf_print(struct sock_filter *filter, size_t count)
+{
+ struct sock_filter *end = filter + count;
+ for ( ; filter < end; ++filter)
+ printf("{ code=%u,jt=%u,jf=%u,k=%u },\n",
+ filter->code, filter->jt, filter->jf, filter->k);
+}
diff --git a/samples/seccomp/bpf-helper.h b/samples/seccomp/bpf-helper.h
new file mode 100644
index 0000000..643279d
--- /dev/null
+++ b/samples/seccomp/bpf-helper.h
@@ -0,0 +1,238 @@
+/*
+ * Example wrapper around BPF macros.
+ *
+ * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * Author: Will Drewry <wad@chromium.org>
+ *
+ * The code may be used by anyone for any purpose,
+ * and can serve as a starting point for developing
+ * applications using prctl(PR_SET_SECCOMP, 2, ...).
+ *
+ * No guarantees are provided with respect to the correctness
+ * or functionality of this code.
+ */
+#ifndef __BPF_HELPER_H__
+#define __BPF_HELPER_H__
+
+#include <asm/bitsperlong.h> /* for __BITS_PER_LONG */
+#include <endian.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h> /* for seccomp_data */
+#include <linux/types.h>
+#include <linux/unistd.h>
+#include <stddef.h>
+
+#define BPF_LABELS_MAX 256
+struct bpf_labels {
+ int count;
+ struct __bpf_label {
+ const char *label;
+ __u32 location;
+ } labels[BPF_LABELS_MAX];
+};
+
+int bpf_resolve_jumps(struct bpf_labels *labels,
+ struct sock_filter *filter, size_t count);
+__u32 seccomp_bpf_label(struct bpf_labels *labels, const char *label);
+void seccomp_bpf_print(struct sock_filter *filter, size_t count);
+
+#define JUMP_JT 0xff
+#define JUMP_JF 0xff
+#define LABEL_JT 0xfe
+#define LABEL_JF 0xfe
+
+#define ALLOW \
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
+#define DENY \
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
+#define JUMP(labels, label) \
+ BPF_JUMP(BPF_JMP+BPF_JA, FIND_LABEL((labels), (label)), \
+ JUMP_JT, JUMP_JF)
+#define LABEL(labels, label) \
+ BPF_JUMP(BPF_JMP+BPF_JA, FIND_LABEL((labels), (label)), \
+ LABEL_JT, LABEL_JF)
+#define SYSCALL(nr, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (nr), 0, 1), \
+ jt
+
+/* Lame, but just an example */
+#define FIND_LABEL(labels, label) seccomp_bpf_label((labels), #label)
+
+#define EXPAND(...) __VA_ARGS__
+/* Map all width-sensitive operations */
+#if __BITS_PER_LONG == 32
+
+#define JEQ(x, jt) JEQ32(x, EXPAND(jt))
+#define JNE(x, jt) JNE32(x, EXPAND(jt))
+#define JGT(x, jt) JGT32(x, EXPAND(jt))
+#define JLT(x, jt) JLT32(x, EXPAND(jt))
+#define JGE(x, jt) JGE32(x, EXPAND(jt))
+#define JLE(x, jt) JLE32(x, EXPAND(jt))
+#define JA(x, jt) JA32(x, EXPAND(jt))
+#define ARG(i) ARG_32(i)
+#define LO_ARG(idx) offsetof(struct seccomp_data, args[(idx)])
+
+#elif __BITS_PER_LONG == 64
+
+/* Ensure that we load the logically correct offset. */
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define ENDIAN(_lo, _hi) _lo, _hi
+#define LO_ARG(idx) offsetof(struct seccomp_data, args[(idx)])
+#define HI_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) + sizeof(__u32)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define ENDIAN(_lo, _hi) _hi, _lo
+#define LO_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) + sizeof(__u32)
+#define HI_ARG(idx) offsetof(struct seccomp_data, args[(idx)])
+#else
+#error "Unknown endianness"
+#endif
+
+union arg64 {
+ struct {
+ __u32 ENDIAN(lo32, hi32);
+ };
+ __u64 u64;
+};
+
+#define JEQ(x, jt) \
+ JEQ64(((union arg64){.u64 = (x)}).lo32, \
+ ((union arg64){.u64 = (x)}).hi32, \
+ EXPAND(jt))
+#define JGT(x, jt) \
+ JGT64(((union arg64){.u64 = (x)}).lo32, \
+ ((union arg64){.u64 = (x)}).hi32, \
+ EXPAND(jt))
+#define JGE(x, jt) \
+ JGE64(((union arg64){.u64 = (x)}).lo32, \
+ ((union arg64){.u64 = (x)}).hi32, \
+ EXPAND(jt))
+#define JNE(x, jt) \
+ JNE64(((union arg64){.u64 = (x)}).lo32, \
+ ((union arg64){.u64 = (x)}).hi32, \
+ EXPAND(jt))
+#define JLT(x, jt) \
+ JLT64(((union arg64){.u64 = (x)}).lo32, \
+ ((union arg64){.u64 = (x)}).hi32, \
+ EXPAND(jt))
+#define JLE(x, jt) \
+ JLE64(((union arg64){.u64 = (x)}).lo32, \
+ ((union arg64){.u64 = (x)}).hi32, \
+ EXPAND(jt))
+
+#define JA(x, jt) \
+ JA64(((union arg64){.u64 = (x)}).lo32, \
+ ((union arg64){.u64 = (x)}).hi32, \
+ EXPAND(jt))
+#define ARG(i) ARG_64(i)
+
+#else
+#error __BITS_PER_LONG value unusable.
+#endif
+
+/* Loads the arg into A */
+#define ARG_32(idx) \
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, LO_ARG(idx))
+
+/* Loads hi into A and lo in X */
+#define ARG_64(idx) \
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, LO_ARG(idx)), \
+ BPF_STMT(BPF_ST, 0), /* lo -> M[0] */ \
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, HI_ARG(idx)), \
+ BPF_STMT(BPF_ST, 1) /* hi -> M[1] */
+
+#define JEQ32(value, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (value), 0, 1), \
+ jt
+
+#define JNE32(value, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (value), 1, 0), \
+ jt
+
+/* Checks the lo, then swaps to check the hi. A=lo,X=hi */
+#define JEQ64(lo, hi, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 5), \
+ BPF_STMT(BPF_LD+BPF_MEM, 0), /* swap in lo */ \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (lo), 0, 2), \
+ BPF_STMT(BPF_LD+BPF_MEM, 1), /* passed: swap hi back in */ \
+ jt, \
+ BPF_STMT(BPF_LD+BPF_MEM, 1) /* failed: swap hi back in */
+
+#define JNE64(lo, hi, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 5, 0), \
+ BPF_STMT(BPF_LD+BPF_MEM, 0), /* swap in lo */ \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (lo), 2, 0), \
+ BPF_STMT(BPF_LD+BPF_MEM, 1), /* passed: swap hi back in */ \
+ jt, \
+ BPF_STMT(BPF_LD+BPF_MEM, 1) /* failed: swap hi back in */
+
+#define JA32(value, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, (value), 0, 1), \
+ jt
+
+#define JA64(lo, hi, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, (hi), 3, 0), \
+ BPF_STMT(BPF_LD+BPF_MEM, 0), /* swap in lo */ \
+ BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, (lo), 0, 2), \
+ BPF_STMT(BPF_LD+BPF_MEM, 1), /* passed: swap hi back in */ \
+ jt, \
+ BPF_STMT(BPF_LD+BPF_MEM, 1) /* failed: swap hi back in */
+
+#define JGE32(value, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (value), 0, 1), \
+ jt
+
+#define JLT32(value, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (value), 1, 0), \
+ jt
+
+/* Shortcut checking if hi > arg.hi. */
+#define JGE64(lo, hi, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (hi), 4, 0), \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 5), \
+ BPF_STMT(BPF_LD+BPF_MEM, 0), /* swap in lo */ \
+ BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (lo), 0, 2), \
+ BPF_STMT(BPF_LD+BPF_MEM, 1), /* passed: swap hi back in */ \
+ jt, \
+ BPF_STMT(BPF_LD+BPF_MEM, 1) /* failed: swap hi back in */
+
+#define JLT64(lo, hi, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (hi), 0, 4), \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 5), \
+ BPF_STMT(BPF_LD+BPF_MEM, 0), /* swap in lo */ \
+ BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (lo), 2, 0), \
+ BPF_STMT(BPF_LD+BPF_MEM, 1), /* passed: swap hi back in */ \
+ jt, \
+ BPF_STMT(BPF_LD+BPF_MEM, 1) /* failed: swap hi back in */
+
+#define JGT32(value, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (value), 0, 1), \
+ jt
+
+#define JLE32(value, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (value), 1, 0), \
+ jt
+
+/* Check hi > args.hi first, then do the GE checking */
+#define JGT64(lo, hi, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (hi), 4, 0), \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 5), \
+ BPF_STMT(BPF_LD+BPF_MEM, 0), /* swap in lo */ \
+ BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (lo), 0, 2), \
+ BPF_STMT(BPF_LD+BPF_MEM, 1), /* passed: swap hi back in */ \
+ jt, \
+ BPF_STMT(BPF_LD+BPF_MEM, 1) /* failed: swap hi back in */
+
+#define JLE64(lo, hi, jt) \
+ BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (hi), 6, 0), \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 3), \
+ BPF_STMT(BPF_LD+BPF_MEM, 0), /* swap in lo */ \
+ BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (lo), 2, 0), \
+ BPF_STMT(BPF_LD+BPF_MEM, 1), /* passed: swap hi back in */ \
+ jt, \
+ BPF_STMT(BPF_LD+BPF_MEM, 1) /* failed: swap hi back in */
+
+#define LOAD_SYSCALL_NR \
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
+ offsetof(struct seccomp_data, nr))
+
+#endif /* __BPF_HELPER_H__ */
diff --git a/samples/seccomp/dropper.c b/samples/seccomp/dropper.c
new file mode 100644
index 0000000..c69c347
--- /dev/null
+++ b/samples/seccomp/dropper.c
@@ -0,0 +1,68 @@
+/*
+ * Naive system call dropper built on seccomp_filter.
+ *
+ * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * Author: Will Drewry <wad@chromium.org>
+ *
+ * The code may be used by anyone for any purpose,
+ * and can serve as a starting point for developing
+ * applications using prctl(PR_SET_SECCOMP, 2, ...).
+ *
+ * When run, returns the specified errno for the specified
+ * system call number against the given architecture.
+ *
+ * Run this one as root as PR_SET_NO_NEW_PRIVS is not called.
+ */
+
+#include <errno.h>
+#include <linux/audit.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <linux/unistd.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+static int install_filter(int nr, int arch, int error)
+{
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS,
+ (offsetof(struct seccomp_data, arch))),
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, arch, 0, 3),
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS,
+ (offsetof(struct seccomp_data, nr))),
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, nr, 0, 1),
+ BPF_STMT(BPF_RET+BPF_K,
+ SECCOMP_RET_ERRNO|(error & SECCOMP_RET_DATA)),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
+ };
+ struct sock_fprog prog = {
+ .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
+ .filter = filter,
+ };
+ if (prctl(PR_SET_SECCOMP, 2, &prog)) {
+ perror("prctl");
+ return 1;
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 5) {
+ fprintf(stderr, "Usage:\n"
+ "dropper <syscall_nr> <arch> <errno> <prog> [<args>]\n"
+ "Hint: AUDIT_ARCH_I386: 0x%X\n"
+ " AUDIT_ARCH_X86_64: 0x%X\n"
+ "\n", AUDIT_ARCH_I386, AUDIT_ARCH_X86_64);
+ return 1;
+ }
+ if (install_filter(strtol(argv[1], NULL, 0), strtol(argv[2], NULL, 0),
+ strtol(argv[3], NULL, 0)))
+ return 1;
+ execv(argv[4], &argv[4]);
+ printf("Failed to execv\n");
+ return 255;
+}
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 6327685..b81ea10 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -394,6 +394,11 @@
new_profile = find_attach(ns, &ns->base.profiles, name);
if (!new_profile)
goto cleanup;
+ /*
+ * NOTE: Domain transitions from unconfined are allowed
+ * even when no_new_privs is set because this aways results
+ * in a further reduction of permissions.
+ */
goto apply;
}
@@ -455,6 +460,16 @@
/* fail exec */
error = -EACCES;
+ /*
+ * Policy has specified a domain transition, if no_new_privs then
+ * fail the exec.
+ */
+ if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) {
+ aa_put_profile(new_profile);
+ error = -EPERM;
+ goto cleanup;
+ }
+
if (!new_profile)
goto audit;
@@ -609,6 +624,14 @@
const char *target = NULL, *info = NULL;
int error = 0;
+ /*
+ * Fail explicitly requested domain transitions if no_new_privs.
+ * There is no exception for unconfined as change_hat is not
+ * available.
+ */
+ if (current->no_new_privs)
+ return -EPERM;
+
/* released below */
cred = get_current_cred();
cxt = cred->security;
@@ -750,6 +773,18 @@
cxt = cred->security;
profile = aa_cred_profile(cred);
+ /*
+ * Fail explicitly requested domain transitions if no_new_privs
+ * and not unconfined.
+ * Domain transitions from unconfined are allowed even when
+ * no_new_privs is set because this aways results in a reduction
+ * of permissions.
+ */
+ if (current->no_new_privs && !unconfined(profile)) {
+ put_cred(cred);
+ return -EPERM;
+ }
+
if (ns_name) {
/* released below */
ns = aa_find_namespace(profile->ns, ns_name);
diff --git a/security/commoncap.c b/security/commoncap.c
index 0051ac2..98ff463 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -523,14 +523,17 @@
/* Don't let someone trace a set[ug]id/setpcap binary with the revised
- * credentials unless they have the appropriate permit
+ * credentials unless they have the appropriate permit.
+ *
+ * In addition, if NO_NEW_PRIVS, then ensure we get no new privs.
*/
if ((new->euid != old->uid ||
new->egid != old->gid ||
!cap_issubset(new->cap_permitted, old->cap_permitted)) &&
bprm->unsafe & ~LSM_UNSAFE_PTRACE_CAP) {
/* downgrade; they get no more than they had, and maybe less */
- if (!capable(CAP_SETUID)) {
+ if (!capable(CAP_SETUID) ||
+ (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)) {
new->euid = new->uid;
new->egid = new->gid;
}
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 1ae096e..9f22cf8 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2077,6 +2077,13 @@
new_tsec->sid = old_tsec->exec_sid;
/* Reset exec SID on execve. */
new_tsec->exec_sid = 0;
+
+ /*
+ * Minimize confusion: if no_new_privs and a transition is
+ * explicitly requested, then fail the exec.
+ */
+ if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)
+ return -EPERM;
} else {
/* Check for a default transition on this program. */
rc = security_transition_sid(old_tsec->sid, isec->sid,
@@ -2090,7 +2097,8 @@
ad.selinux_audit_data = &sad;
ad.u.path = bprm->file->f_path;
- if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)
+ if ((bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) ||
+ (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS))
new_tsec->sid = old_tsec->sid;
if (new_tsec->sid == old_tsec->sid) {
diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c
index 1332692..00121ba 100644
--- a/sound/soc/codecs/wm8958-dsp2.c
+++ b/sound/soc/codecs/wm8958-dsp2.c
@@ -946,7 +946,7 @@
wm8994->mbc_texts = kmalloc(sizeof(char *)
* pdata->num_mbc_cfgs, GFP_KERNEL);
if (!wm8994->mbc_texts) {
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to allocate %d MBC config texts\n",
pdata->num_mbc_cfgs);
return;
@@ -958,9 +958,10 @@
wm8994->mbc_enum.max = pdata->num_mbc_cfgs;
wm8994->mbc_enum.texts = wm8994->mbc_texts;
- ret = snd_soc_add_codec_controls(wm8994->codec, control, 1);
+ ret = snd_soc_add_codec_controls(wm8994->hubs.codec,
+ control, 1);
if (ret != 0)
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to add MBC mode controls: %d\n", ret);
}
@@ -974,7 +975,7 @@
wm8994->vss_texts = kmalloc(sizeof(char *)
* pdata->num_vss_cfgs, GFP_KERNEL);
if (!wm8994->vss_texts) {
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to allocate %d VSS config texts\n",
pdata->num_vss_cfgs);
return;
@@ -986,9 +987,10 @@
wm8994->vss_enum.max = pdata->num_vss_cfgs;
wm8994->vss_enum.texts = wm8994->vss_texts;
- ret = snd_soc_add_codec_controls(wm8994->codec, control, 1);
+ ret = snd_soc_add_codec_controls(wm8994->hubs.codec,
+ control, 1);
if (ret != 0)
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to add VSS mode controls: %d\n", ret);
}
@@ -1003,7 +1005,7 @@
wm8994->vss_hpf_texts = kmalloc(sizeof(char *)
* pdata->num_vss_hpf_cfgs, GFP_KERNEL);
if (!wm8994->vss_hpf_texts) {
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to allocate %d VSS HPF config texts\n",
pdata->num_vss_hpf_cfgs);
return;
@@ -1015,9 +1017,10 @@
wm8994->vss_hpf_enum.max = pdata->num_vss_hpf_cfgs;
wm8994->vss_hpf_enum.texts = wm8994->vss_hpf_texts;
- ret = snd_soc_add_codec_controls(wm8994->codec, control, 1);
+ ret = snd_soc_add_codec_controls(wm8994->hubs.codec,
+ control, 1);
if (ret != 0)
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to add VSS HPFmode controls: %d\n",
ret);
}
@@ -1033,7 +1036,7 @@
wm8994->enh_eq_texts = kmalloc(sizeof(char *)
* pdata->num_enh_eq_cfgs, GFP_KERNEL);
if (!wm8994->enh_eq_texts) {
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to allocate %d enhanced EQ config texts\n",
pdata->num_enh_eq_cfgs);
return;
@@ -1045,9 +1048,10 @@
wm8994->enh_eq_enum.max = pdata->num_enh_eq_cfgs;
wm8994->enh_eq_enum.texts = wm8994->enh_eq_texts;
- ret = snd_soc_add_codec_controls(wm8994->codec, control, 1);
+ ret = snd_soc_add_codec_controls(wm8994->hubs.codec,
+ control, 1);
if (ret != 0)
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to add enhanced EQ controls: %d\n",
ret);
}
diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c
index d256a93..36acfcc 100644
--- a/sound/soc/codecs/wm8993.c
+++ b/sound/soc/codecs/wm8993.c
@@ -218,7 +218,6 @@
unsigned int sysclk_rate;
unsigned int fs;
unsigned int bclk;
- int class_w_users;
unsigned int fll_fref;
unsigned int fll_fout;
int fll_src;
@@ -824,84 +823,6 @@
return 0;
}
-/*
- * When used with DAC outputs only the WM8993 charge pump supports
- * operation in class W mode, providing very low power consumption
- * when used with digital sources. Enable and disable this mode
- * automatically depending on the mixer configuration.
- *
- * Currently the only supported paths are the direct DAC->headphone
- * paths (which provide minimum power consumption anyway).
- */
-static int class_w_put(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
- struct snd_soc_dapm_widget *widget = wlist->widgets[0];
- struct snd_soc_codec *codec = widget->codec;
- struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
- int ret;
-
- /* Turn it off if we're using the main output mixer */
- if (ucontrol->value.integer.value[0] == 0) {
- if (wm8993->class_w_users == 0) {
- dev_dbg(codec->dev, "Disabling Class W\n");
- snd_soc_update_bits(codec, WM8993_CLASS_W_0,
- WM8993_CP_DYN_FREQ |
- WM8993_CP_DYN_V,
- 0);
- }
- wm8993->class_w_users++;
- wm8993->hubs_data.class_w = true;
- }
-
- /* Implement the change */
- ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
-
- /* Enable it if we're using the direct DAC path */
- if (ucontrol->value.integer.value[0] == 1) {
- if (wm8993->class_w_users == 1) {
- dev_dbg(codec->dev, "Enabling Class W\n");
- snd_soc_update_bits(codec, WM8993_CLASS_W_0,
- WM8993_CP_DYN_FREQ |
- WM8993_CP_DYN_V,
- WM8993_CP_DYN_FREQ |
- WM8993_CP_DYN_V);
- }
- wm8993->class_w_users--;
- wm8993->hubs_data.class_w = false;
- }
-
- dev_dbg(codec->dev, "Indirect DAC use count now %d\n",
- wm8993->class_w_users);
-
- return ret;
-}
-
-#define SOC_DAPM_ENUM_W(xname, xenum) \
-{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
- .info = snd_soc_info_enum_double, \
- .get = snd_soc_dapm_get_enum_double, \
- .put = class_w_put, \
- .private_value = (unsigned long)&xenum }
-
-static const char *hp_mux_text[] = {
- "Mixer",
- "DAC",
-};
-
-static const struct soc_enum hpl_enum =
- SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text);
-
-static const struct snd_kcontrol_new hpl_mux =
- SOC_DAPM_ENUM_W("Left Headphone Mux", hpl_enum);
-
-static const struct soc_enum hpr_enum =
- SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text);
-
-static const struct snd_kcontrol_new hpr_mux =
- SOC_DAPM_ENUM_W("Right Headphone Mux", hpr_enum);
-
static const struct snd_kcontrol_new left_speaker_mixer[] = {
SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),
SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),
@@ -988,8 +909,8 @@
SND_SOC_DAPM_DAC("DACL", NULL, WM8993_POWER_MANAGEMENT_3, 1, 0),
SND_SOC_DAPM_DAC("DACR", NULL, WM8993_POWER_MANAGEMENT_3, 0, 0),
-SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
-SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux),
SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0,
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
@@ -1579,9 +1500,6 @@
return ret;
}
- /* By default we're using the output mixers */
- wm8993->class_w_users = 2;
-
/* Latch volume update bits and default ZC on */
snd_soc_update_bits(codec, WM8993_RIGHT_DAC_DIGITAL_VOLUME,
WM8993_DAC_VU, WM8993_DAC_VU);
diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c
index 539c074..43611b5 100644
--- a/sound/soc/codecs/wm8994.c
+++ b/sound/soc/codecs/wm8994.c
@@ -91,8 +91,6 @@
WM8994_AIF2_EQ_GAINS_1,
};
-static void wm8958_default_micdet(u16 status, void *data);
-
static const struct wm8958_micd_rate micdet_rates[] = {
{ 32768, true, 1, 4 },
{ 32768, false, 1, 1 },
@@ -103,8 +101,8 @@
static const struct wm8958_micd_rate jackdet_rates[] = {
{ 32768, true, 0, 1 },
{ 32768, false, 0, 1 },
- { 44100 * 256, true, 7, 10 },
- { 44100 * 256, false, 7, 10 },
+ { 44100 * 256, true, 10, 10 },
+ { 44100 * 256, false, 7, 8 },
};
static void wm8958_micd_set_rate(struct snd_soc_codec *codec)
@@ -115,9 +113,6 @@
const struct wm8958_micd_rate *rates;
int num_rates;
- if (wm8994->jack_cb != wm8958_default_micdet)
- return;
-
idle = !wm8994->jack_mic;
sysclk = snd_soc_read(codec, WM8994_CLOCKING_1);
@@ -151,6 +146,10 @@
val = rates[best].start << WM8958_MICD_BIAS_STARTTIME_SHIFT
| rates[best].rate << WM8958_MICD_RATE_SHIFT;
+ dev_dbg(codec->dev, "MICD rate %d,%d for %dHz %s\n",
+ rates[best].start, rates[best].rate, sysclk,
+ idle ? "idle" : "active");
+
snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
WM8958_MICD_BIAS_STARTTIME_MASK |
WM8958_MICD_RATE_MASK, val);
@@ -431,7 +430,7 @@
wm8994->dac_rates[iface]);
/* The EQ will be disabled while reconfiguring it, remember the
- * current configuration.
+ * current configuration.
*/
save = snd_soc_read(codec, base);
save &= WM8994_AIF1DAC1_EQ_ENA;
@@ -666,6 +665,18 @@
eq_tlv),
};
+static const struct snd_kcontrol_new wm8994_drc_controls[] = {
+SND_SOC_BYTES_MASK("AIF1.1 DRC", WM8994_AIF1_DRC1_1, 5,
+ WM8994_AIF1DAC1_DRC_ENA | WM8994_AIF1ADC1L_DRC_ENA |
+ WM8994_AIF1ADC1R_DRC_ENA),
+SND_SOC_BYTES_MASK("AIF1.2 DRC", WM8994_AIF1_DRC2_1, 5,
+ WM8994_AIF1DAC2_DRC_ENA | WM8994_AIF1ADC2L_DRC_ENA |
+ WM8994_AIF1ADC2R_DRC_ENA),
+SND_SOC_BYTES_MASK("AIF2 DRC", WM8994_AIF2_DRC_1, 5,
+ WM8994_AIF2DAC_DRC_ENA | WM8994_AIF2ADCL_DRC_ENA |
+ WM8994_AIF2ADCR_DRC_ENA),
+};
+
static const char *wm8958_ng_text[] = {
"30ms", "125ms", "250ms", "500ms",
};
@@ -719,7 +730,7 @@
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
- if (!wm8994->jackdet || !wm8994->jack_cb)
+ if (!wm8994->jackdet || !wm8994->micdet[0].jack)
return;
if (wm8994->active_refcount)
@@ -784,11 +795,27 @@
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = w->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
return configure_clock(codec);
+ case SND_SOC_DAPM_POST_PMU:
+ /*
+ * JACKDET won't run until we start the clock and it
+ * only reports deltas, make sure we notify the state
+ * up the stack on startup. Use a *very* generous
+ * timeout for paranoia, there's no urgency and we
+ * don't want false reports.
+ */
+ if (wm8994->jackdet && !wm8994->clk_has_run) {
+ schedule_delayed_work(&wm8994->jackdet_bootstrap,
+ msecs_to_jiffies(1000));
+ wm8994->clk_has_run = true;
+ }
+ break;
+
case SND_SOC_DAPM_POST_PMD:
configure_clock(codec);
break;
@@ -817,7 +844,7 @@
switch (wm8994->vmid_mode) {
default:
- WARN_ON(0 == "Invalid VMID mode");
+ WARN_ON(NULL == "Invalid VMID mode");
case WM8994_VMID_NORMAL:
/* Startup bias, VMID ramp & buffer */
snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
@@ -970,27 +997,12 @@
return 0;
}
-static void wm8994_update_class_w(struct snd_soc_codec *codec)
+static bool wm8994_check_class_w_digital(struct snd_soc_codec *codec)
{
- struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
- int enable = 1;
int source = 0; /* GCC flow analysis can't track enable */
int reg, reg_r;
- /* Only support direct DAC->headphone paths */
- reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1);
- if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) {
- dev_vdbg(codec->dev, "HPL connected to output mixer\n");
- enable = 0;
- }
-
- reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2);
- if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) {
- dev_vdbg(codec->dev, "HPR connected to output mixer\n");
- enable = 0;
- }
-
- /* We also need the same setting for L/R and only one path */
+ /* We also need the same AIF source for L/R and only one path */
reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING);
switch (reg) {
case WM8994_AIF2DACL_TO_DAC1L:
@@ -1007,36 +1019,27 @@
break;
default:
dev_vdbg(codec->dev, "DAC mixer setting: %x\n", reg);
- enable = 0;
- break;
+ return false;
}
reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING);
if (reg_r != reg) {
dev_vdbg(codec->dev, "Left and right DAC mixers different\n");
- enable = 0;
+ return false;
}
- if (enable) {
- dev_dbg(codec->dev, "Class W enabled\n");
- snd_soc_update_bits(codec, WM8994_CLASS_W_1,
- WM8994_CP_DYN_PWR |
- WM8994_CP_DYN_SRC_SEL_MASK,
- source | WM8994_CP_DYN_PWR);
- wm8994->hubs.class_w = true;
-
- } else {
- dev_dbg(codec->dev, "Class W disabled\n");
- snd_soc_update_bits(codec, WM8994_CLASS_W_1,
- WM8994_CP_DYN_PWR, 0);
- wm8994->hubs.class_w = false;
- }
+ /* Set the source up */
+ snd_soc_update_bits(codec, WM8994_CLASS_W_1,
+ WM8994_CP_DYN_SRC_SEL_MASK, source);
+
+ return true;
}
static int aif1clk_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = w->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
struct wm8994 *control = codec->control_data;
int mask = WM8994_AIF1DAC1L_ENA | WM8994_AIF1DAC1R_ENA;
int i;
@@ -1055,6 +1058,10 @@
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
+ /* Don't enable timeslot 2 if not in use */
+ if (wm8994->channels[0] <= 2)
+ mask &= ~(WM8994_AIF1DAC2L_ENA | WM8994_AIF1DAC2R_ENA);
+
val = snd_soc_read(codec, WM8994_AIF1_CONTROL_1);
if ((val & WM8994_AIF1ADCL_SRC) &&
(val & WM8994_AIF1ADCR_SRC))
@@ -1333,45 +1340,6 @@
return 0;
}
-static const char *hp_mux_text[] = {
- "Mixer",
- "DAC",
-};
-
-#define WM8994_HP_ENUM(xname, xenum) \
-{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
- .info = snd_soc_info_enum_double, \
- .get = snd_soc_dapm_get_enum_double, \
- .put = wm8994_put_hp_enum, \
- .private_value = (unsigned long)&xenum }
-
-static int wm8994_put_hp_enum(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
- struct snd_soc_dapm_widget *w = wlist->widgets[0];
- struct snd_soc_codec *codec = w->codec;
- int ret;
-
- ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
-
- wm8994_update_class_w(codec);
-
- return ret;
-}
-
-static const struct soc_enum hpl_enum =
- SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_1, 8, 2, hp_mux_text);
-
-static const struct snd_kcontrol_new hpl_mux =
- WM8994_HP_ENUM("Left Headphone Mux", hpl_enum);
-
-static const struct soc_enum hpr_enum =
- SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_2, 8, 2, hp_mux_text);
-
-static const struct snd_kcontrol_new hpr_mux =
- WM8994_HP_ENUM("Right Headphone Mux", hpr_enum);
-
static const char *adc_mux_text[] = {
"ADC",
"DMIC",
@@ -1483,7 +1451,7 @@
ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
- wm8994_update_class_w(codec);
+ wm_hubs_update_class_w(codec);
return ret;
}
@@ -1577,7 +1545,7 @@
SOC_DAPM_ENUM("AIF3ADC Mux", wm8958_aif3adc_enum);
static const char *mono_pcm_out_text[] = {
- "None", "AIF2ADCL", "AIF2ADCR",
+ "None", "AIF2ADCL", "AIF2ADCR",
};
static const struct soc_enum mono_pcm_out_enum =
@@ -1626,9 +1594,9 @@
SND_SOC_DAPM_MIXER_E("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer),
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
-SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux,
+SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux,
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
-SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux,
+SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux,
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev)
@@ -1646,8 +1614,8 @@
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
-SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
-SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux),
};
static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = {
@@ -1691,7 +1659,8 @@
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_SUPPLY("DSP1CLK", SND_SOC_NOPM, 3, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("DSP2CLK", SND_SOC_NOPM, 2, 0, NULL, 0),
@@ -1787,6 +1756,7 @@
};
static const struct snd_soc_dapm_widget wm8958_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("AIF3", WM8994_POWER_MANAGEMENT_6, 5, 1, NULL, 0),
SND_SOC_DAPM_MUX("Mono PCM Out Mux", SND_SOC_NOPM, 0, 0, &mono_pcm_out_mux),
SND_SOC_DAPM_MUX("AIF2DACL Mux", SND_SOC_NOPM, 0, 0, &aif2dacl_src_mux),
SND_SOC_DAPM_MUX("AIF2DACR Mux", SND_SOC_NOPM, 0, 0, &aif2dacr_src_mux),
@@ -2027,6 +1997,9 @@
{ "AIF2DACR Mux", "AIF2", "AIF2DAC Mux" },
{ "AIF2DACR Mux", "AIF3", "AIF3DACDAT" },
+ { "AIF3DACDAT", NULL, "AIF3" },
+ { "AIF3ADCDAT", NULL, "AIF3" },
+
{ "Mono PCM Out Mux", "AIF2ADCL", "AIF2ADCL" },
{ "Mono PCM Out Mux", "AIF2ADCR", "AIF2ADCR" },
@@ -2123,24 +2096,20 @@
struct wm8994 *control = wm8994->wm8994;
int reg_offset, ret;
struct fll_div fll;
- u16 reg, aif1, aif2;
+ u16 reg, clk1, aif_reg, aif_src;
unsigned long timeout;
bool was_enabled;
- aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1)
- & WM8994_AIF1CLK_ENA;
-
- aif2 = snd_soc_read(codec, WM8994_AIF2_CLOCKING_1)
- & WM8994_AIF2CLK_ENA;
-
switch (id) {
case WM8994_FLL1:
reg_offset = 0;
id = 0;
+ aif_src = 0x10;
break;
case WM8994_FLL2:
reg_offset = 0x20;
id = 1;
+ aif_src = 0x18;
break;
default:
return -EINVAL;
@@ -2161,6 +2130,10 @@
case WM8994_FLL_SRC_LRCLK:
case WM8994_FLL_SRC_BCLK:
break;
+ case WM8994_FLL_SRC_INTERNAL:
+ freq_in = 12000000;
+ freq_out = 12000000;
+ break;
default:
return -EINVAL;
}
@@ -2182,16 +2155,33 @@
if (ret < 0)
return ret;
- /* Gate the AIF clocks while we reclock */
- snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
- WM8994_AIF1CLK_ENA, 0);
- snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
- WM8994_AIF2CLK_ENA, 0);
+ /* Make sure that we're not providing SYSCLK right now */
+ clk1 = snd_soc_read(codec, WM8994_CLOCKING_1);
+ if (clk1 & WM8994_SYSCLK_SRC)
+ aif_reg = WM8994_AIF2_CLOCKING_1;
+ else
+ aif_reg = WM8994_AIF1_CLOCKING_1;
+ reg = snd_soc_read(codec, aif_reg);
+
+ if ((reg & WM8994_AIF1CLK_ENA) &&
+ (reg & WM8994_AIF1CLK_SRC_MASK) == aif_src) {
+ dev_err(codec->dev, "FLL%d is currently providing SYSCLK\n",
+ id + 1);
+ return -EBUSY;
+ }
/* We always need to disable the FLL while reconfiguring */
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset,
WM8994_FLL1_ENA, 0);
+ if (wm8994->fll_byp && src == WM8994_FLL_SRC_BCLK &&
+ freq_in == freq_out && freq_out) {
+ dev_dbg(codec->dev, "Bypassing FLL%d\n", id + 1);
+ snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset,
+ WM8958_FLL1_BYP, WM8958_FLL1_BYP);
+ goto out;
+ }
+
reg = (fll.outdiv << WM8994_FLL1_OUTDIV_SHIFT) |
(fll.fll_fratio << WM8994_FLL1_FRATIO_SHIFT);
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_2 + reg_offset,
@@ -2203,11 +2193,14 @@
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_4 + reg_offset,
WM8994_FLL1_N_MASK,
- fll.n << WM8994_FLL1_N_SHIFT);
+ fll.n << WM8994_FLL1_N_SHIFT);
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset,
+ WM8994_FLL1_FRC_NCO | WM8958_FLL1_BYP |
WM8994_FLL1_REFCLK_DIV_MASK |
WM8994_FLL1_REFCLK_SRC_MASK,
+ ((src == WM8994_FLL_SRC_INTERNAL)
+ << WM8994_FLL1_FRC_NCO_SHIFT) |
(fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) |
(src - 1));
@@ -2233,13 +2226,16 @@
}
}
+ reg = WM8994_FLL1_ENA;
+
if (fll.k)
- reg = WM8994_FLL1_ENA | WM8994_FLL1_FRAC;
- else
- reg = WM8994_FLL1_ENA;
+ reg |= WM8994_FLL1_FRAC;
+ if (src == WM8994_FLL_SRC_INTERNAL)
+ reg |= WM8994_FLL1_OSC_ENA;
+
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset,
- WM8994_FLL1_ENA | WM8994_FLL1_FRAC,
- reg);
+ WM8994_FLL1_ENA | WM8994_FLL1_OSC_ENA |
+ WM8994_FLL1_FRAC, reg);
if (wm8994->fll_locked_irq) {
timeout = wait_for_completion_timeout(&wm8994->fll_locked[id],
@@ -2268,16 +2264,11 @@
}
}
+out:
wm8994->fll[id].in = freq_in;
wm8994->fll[id].out = freq_out;
wm8994->fll[id].src = src;
- /* Enable any gated AIF clocks */
- snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
- WM8994_AIF1CLK_ENA, aif1);
- snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
- WM8994_AIF2CLK_ENA, aif2);
-
configure_clock(codec);
return 0;
@@ -2345,7 +2336,7 @@
case WM8994_SYSCLK_OPCLK:
/* Special case - a division (times 10) is given and
- * no effect on main clocking.
+ * no effect on main clocking.
*/
if (freq) {
for (i = 0; i < ARRAY_SIZE(opclk_divs); i++)
@@ -2662,6 +2653,7 @@
{
struct snd_soc_codec *codec = dai->codec;
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
int aif1_reg;
int aif2_reg;
int bclk_reg;
@@ -2707,7 +2699,7 @@
return -EINVAL;
}
- bclk_rate = params_rate(params) * 4;
+ bclk_rate = params_rate(params);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
bclk_rate *= 16;
@@ -2728,6 +2720,24 @@
return -EINVAL;
}
+ wm8994->channels[id] = params_channels(params);
+ if (pdata->max_channels_clocked[id] &&
+ wm8994->channels[id] > pdata->max_channels_clocked[id]) {
+ dev_dbg(dai->dev, "Constraining channels to %d from %d\n",
+ pdata->max_channels_clocked[id], wm8994->channels[id]);
+ wm8994->channels[id] = pdata->max_channels_clocked[id];
+ }
+
+ switch (wm8994->channels[id]) {
+ case 1:
+ case 2:
+ bclk_rate *= 2;
+ break;
+ default:
+ bclk_rate *= 4;
+ break;
+ }
+
/* Try to find an appropriate sample rate; look for an exact match. */
for (i = 0; i < ARRAY_SIZE(srs); i++)
if (srs[i].rate == params_rate(params))
@@ -2740,7 +2750,7 @@
dev_dbg(dai->dev, "AIF%dCLK is %dHz, target BCLK %dHz\n",
dai->id, wm8994->aifclk[id], bclk_rate);
- if (params_channels(params) == 1 &&
+ if (wm8994->channels[id] == 1 &&
(snd_soc_read(codec, aif1_reg) & 0x18) == 0x18)
aif2 |= WM8994_AIF1_MONO;
@@ -2859,33 +2869,6 @@
return snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1);
}
-static void wm8994_aif_shutdown(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- struct snd_soc_codec *codec = dai->codec;
- int rate_reg = 0;
-
- switch (dai->id) {
- case 1:
- rate_reg = WM8994_AIF1_RATE;
- break;
- case 2:
- rate_reg = WM8994_AIF2_RATE;
- break;
- default:
- break;
- }
-
- /* If the DAI is idle then configure the divider tree for the
- * lowest output rate to save a little power if the clock is
- * still active (eg, because it is system clock).
- */
- if (rate_reg && !dai->playback_active && !dai->capture_active)
- snd_soc_update_bits(codec, rate_reg,
- WM8994_AIF1_SR_MASK |
- WM8994_AIF1CLK_RATE_MASK, 0x9);
-}
-
static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute)
{
struct snd_soc_codec *codec = codec_dai->codec;
@@ -2927,10 +2910,6 @@
reg = WM8994_AIF2_MASTER_SLAVE;
mask = WM8994_AIF2_TRI;
break;
- case 3:
- reg = WM8994_POWER_MANAGEMENT_6;
- mask = WM8994_AIF3_TRI;
- break;
default:
return -EINVAL;
}
@@ -2967,7 +2946,6 @@
.set_sysclk = wm8994_set_dai_sysclk,
.set_fmt = wm8994_set_dai_fmt,
.hw_params = wm8994_hw_params,
- .shutdown = wm8994_aif_shutdown,
.digital_mute = wm8994_aif_mute,
.set_pll = wm8994_set_fll,
.set_tristate = wm8994_set_tristate,
@@ -2977,7 +2955,6 @@
.set_sysclk = wm8994_set_dai_sysclk,
.set_fmt = wm8994_set_dai_fmt,
.hw_params = wm8994_hw_params,
- .shutdown = wm8994_aif_shutdown,
.digital_mute = wm8994_aif_mute,
.set_pll = wm8994_set_fll,
.set_tristate = wm8994_set_tristate,
@@ -2985,7 +2962,6 @@
static const struct snd_soc_dai_ops wm8994_aif3_dai_ops = {
.hw_params = wm8994_aif3_hw_params,
- .set_tristate = wm8994_set_tristate,
};
static struct snd_soc_dai_driver wm8994_dai[] = {
@@ -3125,28 +3101,6 @@
i + 1, ret);
}
- switch (control->type) {
- case WM8994:
- if (wm8994->micdet[0].jack || wm8994->micdet[1].jack)
- snd_soc_update_bits(codec, WM8994_MICBIAS,
- WM8994_MICD_ENA, WM8994_MICD_ENA);
- break;
- case WM1811:
- if (wm8994->jackdet && wm8994->jack_cb) {
- /* Restart from idle */
- snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
- WM1811_JACKDET_MODE_MASK,
- WM1811_JACKDET_MODE_JACK);
- break;
- }
- break;
- case WM8958:
- if (wm8994->jack_cb)
- snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
- WM8958_MICD_ENA, WM8958_MICD_ENA);
- break;
- }
-
return 0;
}
#else
@@ -3156,7 +3110,7 @@
static void wm8994_handle_retune_mobile_pdata(struct wm8994_priv *wm8994)
{
- struct snd_soc_codec *codec = wm8994->codec;
+ struct snd_soc_codec *codec = wm8994->hubs.codec;
struct wm8994_pdata *pdata = wm8994->pdata;
struct snd_kcontrol_new controls[] = {
SOC_ENUM_EXT("AIF1.1 EQ Mode",
@@ -3193,14 +3147,14 @@
/* Expand the array... */
t = krealloc(wm8994->retune_mobile_texts,
- sizeof(char *) *
+ sizeof(char *) *
(wm8994->num_retune_mobile_texts + 1),
GFP_KERNEL);
if (t == NULL)
continue;
/* ...store the new entry... */
- t[wm8994->num_retune_mobile_texts] =
+ t[wm8994->num_retune_mobile_texts] =
pdata->retune_mobile_cfgs[i].name;
/* ...and remember the new version. */
@@ -3214,16 +3168,16 @@
wm8994->retune_mobile_enum.max = wm8994->num_retune_mobile_texts;
wm8994->retune_mobile_enum.texts = wm8994->retune_mobile_texts;
- ret = snd_soc_add_codec_controls(wm8994->codec, controls,
+ ret = snd_soc_add_codec_controls(wm8994->hubs.codec, controls,
ARRAY_SIZE(controls));
if (ret != 0)
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to add ReTune Mobile controls: %d\n", ret);
}
static void wm8994_handle_pdata(struct wm8994_priv *wm8994)
{
- struct snd_soc_codec *codec = wm8994->codec;
+ struct snd_soc_codec *codec = wm8994->hubs.codec;
struct wm8994_pdata *pdata = wm8994->pdata;
int ret, i;
@@ -3252,10 +3206,10 @@
};
/* We need an array of texts for the enum API */
- wm8994->drc_texts = devm_kzalloc(wm8994->codec->dev,
+ wm8994->drc_texts = devm_kzalloc(wm8994->hubs.codec->dev,
sizeof(char *) * pdata->num_drc_cfgs, GFP_KERNEL);
if (!wm8994->drc_texts) {
- dev_err(wm8994->codec->dev,
+ dev_err(wm8994->hubs.codec->dev,
"Failed to allocate %d DRC config texts\n",
pdata->num_drc_cfgs);
return;
@@ -3267,23 +3221,28 @@
wm8994->drc_enum.max = pdata->num_drc_cfgs;
wm8994->drc_enum.texts = wm8994->drc_texts;
- ret = snd_soc_add_codec_controls(wm8994->codec, controls,
+ ret = snd_soc_add_codec_controls(wm8994->hubs.codec, controls,
ARRAY_SIZE(controls));
- if (ret != 0)
- dev_err(wm8994->codec->dev,
- "Failed to add DRC mode controls: %d\n", ret);
-
for (i = 0; i < WM8994_NUM_DRC; i++)
wm8994_set_drc(codec, i);
+ } else {
+ ret = snd_soc_add_codec_controls(wm8994->hubs.codec,
+ wm8994_drc_controls,
+ ARRAY_SIZE(wm8994_drc_controls));
}
+ if (ret != 0)
+ dev_err(wm8994->hubs.codec->dev,
+ "Failed to add DRC mode controls: %d\n", ret);
+
+
dev_dbg(codec->dev, "%d ReTune Mobile configurations\n",
pdata->num_retune_mobile_cfgs);
if (pdata->num_retune_mobile_cfgs)
wm8994_handle_retune_mobile_pdata(wm8994);
else
- snd_soc_add_codec_controls(wm8994->codec, wm8994_eq_controls,
+ snd_soc_add_codec_controls(wm8994->hubs.codec, wm8994_eq_controls,
ARRAY_SIZE(wm8994_eq_controls));
for (i = 0; i < ARRAY_SIZE(pdata->micbias); i++) {
@@ -3365,31 +3324,40 @@
snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, reg);
+ /* enable MICDET and MICSHRT deboune */
+ snd_soc_update_bits(codec, WM8994_IRQ_DEBOUNCE,
+ WM8994_MIC1_DET_DB_MASK | WM8994_MIC1_SHRT_DB_MASK |
+ WM8994_MIC2_DET_DB_MASK | WM8994_MIC2_SHRT_DB_MASK,
+ WM8994_MIC1_DET_DB | WM8994_MIC1_SHRT_DB);
+
snd_soc_dapm_sync(&codec->dapm);
return 0;
}
EXPORT_SYMBOL_GPL(wm8994_mic_detect);
-static irqreturn_t wm8994_mic_irq(int irq, void *data)
+static void wm8994_mic_work(struct work_struct *work)
{
- struct wm8994_priv *priv = data;
- struct snd_soc_codec *codec = priv->codec;
- int reg;
+ struct wm8994_priv *priv = container_of(work,
+ struct wm8994_priv,
+ mic_work.work);
+ struct regmap *regmap = priv->wm8994->regmap;
+ struct device *dev = priv->wm8994->dev;
+ unsigned int reg;
+ int ret;
int report;
-#ifndef CONFIG_SND_SOC_WM8994_MODULE
- trace_snd_soc_jack_irq(dev_name(codec->dev));
-#endif
+ pm_runtime_get_sync(dev);
- reg = snd_soc_read(codec, WM8994_INTERRUPT_RAW_STATUS_2);
- if (reg < 0) {
- dev_err(codec->dev, "Failed to read microphone status: %d\n",
- reg);
- return IRQ_HANDLED;
+ ret = regmap_read(regmap, WM8994_INTERRUPT_RAW_STATUS_2, ®);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read microphone status: %d\n",
+ ret);
+ pm_runtime_put(dev);
+ return;
}
- dev_dbg(codec->dev, "Microphone status: %x\n", reg);
+ dev_dbg(dev, "Microphone status: %x\n", reg);
report = 0;
if (reg & WM8994_MIC1_DET_STS) {
@@ -3429,41 +3397,113 @@
snd_soc_jack_report(priv->micdet[1].jack, report,
SND_JACK_HEADSET | SND_JACK_BTN_0);
+ pm_runtime_put(dev);
+}
+
+static irqreturn_t wm8994_mic_irq(int irq, void *data)
+{
+ struct wm8994_priv *priv = data;
+ struct snd_soc_codec *codec = priv->hubs.codec;
+
+#ifndef CONFIG_SND_SOC_WM8994_MODULE
+ trace_snd_soc_jack_irq(dev_name(codec->dev));
+#endif
+
+ pm_wakeup_event(codec->dev, 300);
+
+ schedule_delayed_work(&priv->mic_work, msecs_to_jiffies(250));
+
return IRQ_HANDLED;
}
-/* Default microphone detection handler for WM8958 - the user can
- * override this if they wish.
- */
-static void wm8958_default_micdet(u16 status, void *data)
+static void wm1811_micd_stop(struct snd_soc_codec *codec)
{
- struct snd_soc_codec *codec = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (!wm8994->jackdet)
+ return;
+
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, WM8958_MICD_ENA, 0);
+
+ wm1811_jackdet_set_mode(codec, WM1811_JACKDET_MODE_JACK);
+
+ if (wm8994->pdata->jd_ext_cap)
+ snd_soc_dapm_disable_pin(&codec->dapm,
+ "MICBIAS2");
+}
+
+static void wm8958_button_det(struct snd_soc_codec *codec, u16 status)
+{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
int report;
- dev_dbg(codec->dev, "MICDET %x\n", status);
+ report = 0;
+ if (status & 0x4)
+ report |= SND_JACK_BTN_0;
+
+ if (status & 0x8)
+ report |= SND_JACK_BTN_1;
+
+ if (status & 0x10)
+ report |= SND_JACK_BTN_2;
+
+ if (status & 0x20)
+ report |= SND_JACK_BTN_3;
+
+ if (status & 0x40)
+ report |= SND_JACK_BTN_4;
+
+ if (status & 0x80)
+ report |= SND_JACK_BTN_5;
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, report,
+ wm8994->btn_mask);
+}
+
+static void wm8958_open_circuit_work(struct work_struct *work)
+{
+ struct wm8994_priv *wm8994 = container_of(work,
+ struct wm8994_priv,
+ open_circuit_work.work);
+ struct device *dev = wm8994->wm8994->dev;
+
+ mutex_lock(&wm8994->accdet_lock);
+
+ dev_dbg(dev, "Reporting open circuit\n");
+
+ wm8994->jack_mic = false;
+ wm8994->mic_detecting = true;
+
+ wm1811_micd_stop(wm8994->hubs.codec);
+
+ wm8958_micd_set_rate(wm8994->hubs.codec);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ wm8994->btn_mask |
+ SND_JACK_HEADSET);
+
+ mutex_unlock(&wm8994->accdet_lock);
+}
+
+void wm8958_mic_id(void *data, u16 status)
+{
+ struct snd_soc_codec *codec = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
/* Either nothing present or just starting detection */
if (!(status & WM8958_MICD_STS)) {
- if (!wm8994->jackdet) {
- /* If nothing present then clear our statuses */
- dev_dbg(codec->dev, "Detected open circuit\n");
- wm8994->jack_mic = false;
- wm8994->mic_detecting = true;
+ /* If nothing present then clear our statuses */
+ dev_dbg(codec->dev, "Detected open circuit\n");
- wm8958_micd_set_rate(codec);
-
- snd_soc_jack_report(wm8994->micdet[0].jack, 0,
- wm8994->btn_mask |
- SND_JACK_HEADSET);
- }
+ schedule_delayed_work(&wm8994->open_circuit_work,
+ msecs_to_jiffies(2000));
return;
}
/* If the measurement is showing a high impedence we've got a
* microphone.
*/
- if (wm8994->mic_detecting && (status & 0x600)) {
+ if (status & 0x600) {
dev_dbg(codec->dev, "Detected microphone\n");
wm8994->mic_detecting = false;
@@ -3476,7 +3516,7 @@
}
- if (wm8994->mic_detecting && status & 0xfc) {
+ if (status & 0xfc) {
dev_dbg(codec->dev, "Detected headphone\n");
wm8994->mic_detecting = false;
@@ -3486,66 +3526,72 @@
SND_JACK_HEADSET);
/* If we have jackdet that will detect removal */
- if (wm8994->jackdet) {
- mutex_lock(&wm8994->accdet_lock);
+ mutex_lock(&wm8994->accdet_lock);
+ wm1811_micd_stop(codec);
+ mutex_unlock(&wm8994->accdet_lock);
+ }
+}
- snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
- WM8958_MICD_ENA, 0);
+/* Deferred mic detection to allow for extra settling time */
+static void wm1811_mic_work(struct work_struct *work)
+{
+ struct wm8994_priv *wm8994 = container_of(work, struct wm8994_priv,
+ mic_work.work);
+ struct snd_soc_codec *codec = wm8994->hubs.codec;
- wm1811_jackdet_set_mode(codec,
- WM1811_JACKDET_MODE_JACK);
+ pm_runtime_get_sync(codec->dev);
- mutex_unlock(&wm8994->accdet_lock);
+ mutex_lock(&codec->mutex);
+ /* If required for an external cap force MICBIAS on */
+ if (wm8994->pdata->jd_ext_cap) {
+ snd_soc_dapm_force_enable_pin(&codec->dapm,
+ "MICBIAS2");
+ snd_soc_dapm_sync(&codec->dapm);
+ }
+ mutex_unlock(&codec->mutex);
- if (wm8994->pdata->jd_ext_cap) {
- mutex_lock(&codec->mutex);
- snd_soc_dapm_disable_pin(&codec->dapm,
- "MICBIAS2");
- snd_soc_dapm_sync(&codec->dapm);
- mutex_unlock(&codec->mutex);
- }
- }
+ mutex_lock(&wm8994->accdet_lock);
+
+ dev_dbg(codec->dev, "Starting mic detection\n");
+
+ /* Use a user-supplied callback if we have one */
+ if (wm8994->micd_cb) {
+ wm8994->micd_cb(wm8994->micd_cb_data);
+ } else {
+ /*
+ * Start off measument of microphone impedence to find out
+ * what's actually there.
+ */
+ wm8994->mic_detecting = true;
+ wm1811_jackdet_set_mode(codec, WM1811_JACKDET_MODE_MIC);
+
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, WM8958_MICD_ENA);
}
- /* Report short circuit as a button */
- if (wm8994->jack_mic) {
- report = 0;
- if (status & 0x4)
- report |= SND_JACK_BTN_0;
+ mutex_unlock(&wm8994->accdet_lock);
- if (status & 0x8)
- report |= SND_JACK_BTN_1;
-
- if (status & 0x10)
- report |= SND_JACK_BTN_2;
-
- if (status & 0x20)
- report |= SND_JACK_BTN_3;
-
- if (status & 0x40)
- report |= SND_JACK_BTN_4;
-
- if (status & 0x80)
- report |= SND_JACK_BTN_5;
-
- snd_soc_jack_report(wm8994->micdet[0].jack, report,
- wm8994->btn_mask);
- }
+ pm_runtime_put(codec->dev);
}
static irqreturn_t wm1811_jackdet_irq(int irq, void *data)
{
struct wm8994_priv *wm8994 = data;
- struct snd_soc_codec *codec = wm8994->codec;
- int reg;
+ struct snd_soc_codec *codec = wm8994->hubs.codec;
+ int reg, delay;
bool present;
+ cancel_delayed_work_sync(&wm8994->mic_work);
+
+ pm_runtime_get_sync(codec->dev);
+
mutex_lock(&wm8994->accdet_lock);
reg = snd_soc_read(codec, WM1811_JACKDET_CTRL);
if (reg < 0) {
dev_err(codec->dev, "Failed to read jack status: %d\n", reg);
mutex_unlock(&wm8994->accdet_lock);
+ pm_runtime_put(codec->dev);
return IRQ_NONE;
}
@@ -3556,6 +3602,8 @@
if (present) {
dev_dbg(codec->dev, "Jack detected\n");
+ wm8958_micd_set_rate(codec);
+
snd_soc_update_bits(codec, WM8958_MICBIAS2,
WM8958_MICB2_DISCH, 0);
@@ -3563,15 +3611,9 @@
snd_soc_update_bits(codec, WM1811_JACKDET_CTRL,
WM1811_JACKDET_DB, 0);
- /*
- * Start off measument of microphone impedence to find
- * out what's actually there.
- */
- wm8994->mic_detecting = true;
- wm1811_jackdet_set_mode(codec, WM1811_JACKDET_MODE_MIC);
-
- snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
- WM8958_MICD_ENA, WM8958_MICD_ENA);
+ delay = wm8994->pdata->micdet_delay;
+ schedule_delayed_work(&wm8994->mic_work,
+ msecs_to_jiffies(delay));
} else {
dev_dbg(codec->dev, "Jack not detected\n");
@@ -3591,19 +3633,11 @@
mutex_unlock(&wm8994->accdet_lock);
- /* If required for an external cap force MICBIAS on */
- if (wm8994->pdata->jd_ext_cap) {
- mutex_lock(&codec->mutex);
-
- if (present)
- snd_soc_dapm_force_enable_pin(&codec->dapm,
- "MICBIAS2");
- else
- snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS2");
-
- snd_soc_dapm_sync(&codec->dapm);
- mutex_unlock(&codec->mutex);
- }
+ mutex_lock(&codec->mutex);
+ /* Turn off MICBIAS if it was on for an external cap */
+ if (wm8994->pdata->jd_ext_cap && !present)
+ snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS2");
+ mutex_unlock(&codec->mutex);
if (present)
snd_soc_jack_report(wm8994->micdet[0].jack,
@@ -3613,9 +3647,22 @@
SND_JACK_MECHANICAL | SND_JACK_HEADSET |
wm8994->btn_mask);
+ /* Since we only report deltas force an update, ensures we
+ * avoid bootstrapping issues with the core. */
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0, 0);
+
+ pm_runtime_put(codec->dev);
return IRQ_HANDLED;
}
+static void wm1811_jackdet_bootstrap(struct work_struct *work)
+{
+ struct wm8994_priv *wm8994 = container_of(work,
+ struct wm8994_priv,
+ jackdet_bootstrap.work);
+ wm1811_jackdet_irq(0, wm8994);
+}
+
/**
* wm8958_mic_detect - Enable microphone detection via the WM8958 IRQ
*
@@ -3633,7 +3680,8 @@
* detection algorithm.
*/
int wm8958_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
- wm8958_micdet_cb cb, void *cb_data)
+ wm1811_micdet_cb det_cb, void *det_cb_data,
+ wm1811_mic_id_cb id_cb, void *id_cb_data)
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
struct wm8994 *control = wm8994->wm8994;
@@ -3648,21 +3696,26 @@
}
if (jack) {
- if (!cb) {
- dev_dbg(codec->dev, "Using default micdet callback\n");
- cb = wm8958_default_micdet;
- cb_data = codec;
- }
-
snd_soc_dapm_force_enable_pin(&codec->dapm, "CLK_SYS");
snd_soc_dapm_sync(&codec->dapm);
wm8994->micdet[0].jack = jack;
- wm8994->jack_cb = cb;
- wm8994->jack_cb_data = cb_data;
- wm8994->mic_detecting = true;
- wm8994->jack_mic = false;
+ if (det_cb) {
+ wm8994->micd_cb = det_cb;
+ wm8994->micd_cb_data = det_cb_data;
+ } else {
+ wm8994->mic_detecting = true;
+ wm8994->jack_mic = false;
+ }
+
+ if (id_cb) {
+ wm8994->mic_id_cb = id_cb;
+ wm8994->mic_id_cb_data = id_cb_data;
+ } else {
+ wm8994->mic_id_cb = wm8958_mic_id;
+ wm8994->mic_id_cb_data = codec;
+ }
wm8958_micd_set_rate(codec);
@@ -3686,6 +3739,10 @@
* otherwise jump straight to microphone detection.
*/
if (wm8994->jackdet) {
+ /* Disable debounce for the initial detect */
+ snd_soc_update_bits(codec, WM1811_JACKDET_CTRL,
+ WM1811_JACKDET_DB, 0);
+
snd_soc_update_bits(codec, WM8958_MICBIAS2,
WM8958_MICB2_DISCH,
WM8958_MICB2_DISCH);
@@ -3713,8 +3770,8 @@
static irqreturn_t wm8958_mic_irq(int irq, void *data)
{
struct wm8994_priv *wm8994 = data;
- struct snd_soc_codec *codec = wm8994->codec;
- int reg, count;
+ struct snd_soc_codec *codec = wm8994->hubs.codec;
+ int reg, count, ret;
/*
* Jack detection may have detected a removal simulataneously
@@ -3724,6 +3781,10 @@
if (!(snd_soc_read(codec, WM8958_MIC_DETECT_1) & WM8958_MICD_ENA))
return IRQ_HANDLED;
+ cancel_delayed_work_sync(&wm8994->open_circuit_work);
+
+ pm_runtime_get_sync(codec->dev);
+
/* We may occasionally read a detection without an impedence
* range being provided - if that happens loop again.
*/
@@ -3734,6 +3795,7 @@
dev_err(codec->dev,
"Failed to read mic detect status: %d\n",
reg);
+ pm_runtime_put(codec->dev);
return IRQ_NONE;
}
@@ -3755,12 +3817,25 @@
trace_snd_soc_jack_irq(dev_name(codec->dev));
#endif
- if (wm8994->jack_cb)
- wm8994->jack_cb(reg, wm8994->jack_cb_data);
+ /* Avoid a transient report when the accessory is being removed */
+ if (wm8994->jackdet) {
+ ret = snd_soc_read(codec, WM1811_JACKDET_CTRL);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read jack status: %d\n",
+ ret);
+ } else if (!(ret & WM1811_JACKDET_LVL)) {
+ dev_dbg(codec->dev, "Ignoring removed jack\n");
+ return IRQ_HANDLED;
+ }
+ }
+
+ if (wm8994->mic_detecting)
+ wm8994->mic_id_cb(wm8994->mic_id_cb_data, reg);
else
- dev_warn(codec->dev, "Accessory detection with no callback\n");
+ wm8958_button_det(codec, reg);
out:
+ pm_runtime_put(codec->dev);
return IRQ_HANDLED;
}
@@ -3799,14 +3874,27 @@
unsigned int reg;
int ret, i;
- wm8994->codec = codec;
+ wm8994->hubs.codec = codec;
codec->control_data = control->regmap;
snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_REGMAP);
- wm8994->codec = codec;
-
mutex_init(&wm8994->accdet_lock);
+ INIT_DELAYED_WORK(&wm8994->jackdet_bootstrap,
+ wm1811_jackdet_bootstrap);
+ INIT_DELAYED_WORK(&wm8994->open_circuit_work,
+ wm8958_open_circuit_work);
+
+ switch (control->type) {
+ case WM8994:
+ INIT_DELAYED_WORK(&wm8994->mic_work, wm8994_mic_work);
+ break;
+ case WM1811:
+ INIT_DELAYED_WORK(&wm8994->mic_work, wm1811_mic_work);
+ break;
+ default:
+ break;
+ }
for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
init_completion(&wm8994->fll_locked[i]);
@@ -3850,22 +3938,34 @@
case WM8958:
wm8994->hubs.dcs_readback_mode = 1;
wm8994->hubs.hp_startup_mode = 1;
+
+ switch (wm8994->revision) {
+ case 0:
+ break;
+ default:
+ wm8994->fll_byp = true;
+ break;
+ }
break;
case WM1811:
wm8994->hubs.dcs_readback_mode = 2;
wm8994->hubs.no_series_update = 1;
wm8994->hubs.hp_startup_mode = 1;
- wm8994->hubs.no_cache_class_w = true;
+ wm8994->hubs.no_cache_dac_hp_direct = true;
+ wm8994->fll_byp = true;
- switch (wm8994->revision) {
+ switch (control->cust_id) {
case 0:
- case 1:
case 2:
- case 3:
wm8994->hubs.dcs_codes_l = -9;
wm8994->hubs.dcs_codes_r = -7;
break;
+ case 1:
+ case 3:
+ wm8994->hubs.dcs_codes_l = -8;
+ wm8994->hubs.dcs_codes_r = -7;
+ break;
default:
break;
}
@@ -3950,7 +4050,7 @@
switch (control->type) {
case WM1811:
- if (wm8994->revision > 1) {
+ if (control->cust_id > 1 || wm8994->revision > 1) {
ret = wm8994_request_irq(wm8994->wm8994,
WM8994_IRQ_GPIO(6),
wm1811_jackdet_irq, "JACKDET",
@@ -4049,7 +4149,8 @@
break;
}
- wm8994_update_class_w(codec);
+ wm8994->hubs.check_class_w_digital = wm8994_check_class_w_digital;
+ wm_hubs_update_class_w(codec);
wm8994_handle_pdata(wm8994);
@@ -4114,7 +4215,6 @@
ARRAY_SIZE(wm8994_dac_widgets));
break;
}
-
wm_hubs_add_analogue_routes(codec, 0, 0);
snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
@@ -4136,6 +4236,8 @@
break;
case WM8958:
if (wm8994->revision < 1) {
+ snd_soc_dapm_add_routes(dapm, wm8994_intercon,
+ ARRAY_SIZE(wm8994_intercon));
snd_soc_dapm_add_routes(dapm, wm8994_revd_intercon,
ARRAY_SIZE(wm8994_revd_intercon));
snd_soc_dapm_add_routes(dapm, wm8994_lateclk_revd_intercon,
@@ -4179,7 +4281,7 @@
return ret;
}
-static int wm8994_codec_remove(struct snd_soc_codec *codec)
+static int wm8994_codec_remove(struct snd_soc_codec *codec)
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
struct wm8994 *control = wm8994->wm8994;
@@ -4220,14 +4322,10 @@
free_irq(wm8994->micdet_irq, wm8994);
break;
}
- if (wm8994->mbc)
- release_firmware(wm8994->mbc);
- if (wm8994->mbc_vss)
- release_firmware(wm8994->mbc_vss);
- if (wm8994->enh_eq)
- release_firmware(wm8994->enh_eq);
+ release_firmware(wm8994->mbc);
+ release_firmware(wm8994->mbc_vss);
+ release_firmware(wm8994->enh_eq);
kfree(wm8994->retune_mobile_texts);
-
return 0;
}
@@ -4280,7 +4378,7 @@
{
struct wm8994_priv *wm8994 = dev_get_drvdata(dev);
- if (wm8994->jackdet && wm8994->jack_cb)
+ if (wm8994->jackdet && wm8994->jackdet_mode)
regmap_update_bits(wm8994->wm8994->regmap, WM8994_ANTIPOP_2,
WM1811_JACKDET_MODE_MASK,
WM1811_JACKDET_MODE_AUDIO);
diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h
index c724112..b2b83e7 100644
--- a/sound/soc/codecs/wm8994.h
+++ b/sound/soc/codecs/wm8994.h
@@ -12,6 +12,7 @@
#include <sound/soc.h>
#include <linux/firmware.h>
#include <linux/completion.h>
+#include <linux/workqueue.h>
#include "wm_hubs.h"
@@ -27,22 +28,25 @@
#define WM8994_FLL1 1
#define WM8994_FLL2 2
-#define WM8994_FLL_SRC_MCLK1 1
-#define WM8994_FLL_SRC_MCLK2 2
-#define WM8994_FLL_SRC_LRCLK 3
-#define WM8994_FLL_SRC_BCLK 4
+#define WM8994_FLL_SRC_MCLK1 1
+#define WM8994_FLL_SRC_MCLK2 2
+#define WM8994_FLL_SRC_LRCLK 3
+#define WM8994_FLL_SRC_BCLK 4
+#define WM8994_FLL_SRC_INTERNAL 5
enum wm8994_vmid_mode {
WM8994_VMID_NORMAL,
WM8994_VMID_FORCE,
};
-typedef void (*wm8958_micdet_cb)(u16 status, void *data);
+typedef void (*wm1811_micdet_cb)(void *data);
+typedef void (*wm1811_mic_id_cb)(void *data, u16 status);
int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
int micbias);
int wm8958_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
- wm8958_micdet_cb cb, void *cb_data);
+ wm1811_micdet_cb cb, void *det_cb_data,
+ wm1811_mic_id_cb id_cb, void *id_cb_data);
int wm8994_vmid_mode(struct snd_soc_codec *codec, enum wm8994_vmid_mode mode);
@@ -51,6 +55,8 @@
void wm8958_dsp2_init(struct snd_soc_codec *codec);
+void wm8958_mic_id(void *data, u16 status);
+
struct wm8994_micdet {
struct snd_soc_jack *jack;
bool detecting;
@@ -71,14 +77,16 @@
struct wm8994_priv {
struct wm_hubs_data hubs;
struct wm8994 *wm8994;
- struct snd_soc_codec *codec;
int sysclk[2];
int sysclk_rate[2];
int mclk[2];
int aifclk[2];
+ int channels[2];
struct wm8994_fll_config fll[2], fll_suspend[2];
struct completion fll_locked[2];
bool fll_locked_irq;
+ bool fll_byp;
+ bool clk_has_run;
int vmid_refcount;
int active_refcount;
@@ -126,15 +134,20 @@
struct mutex accdet_lock;
struct wm8994_micdet micdet[2];
+ struct delayed_work mic_work;
+ struct delayed_work open_circuit_work;
bool mic_detecting;
bool jack_mic;
int btn_mask;
bool jackdet;
int jackdet_mode;
+ struct delayed_work jackdet_bootstrap;
- wm8958_micdet_cb jack_cb;
- void *jack_cb_data;
int micdet_irq;
+ wm1811_micdet_cb micd_cb;
+ void *micd_cb_data;
+ wm1811_mic_id_cb mic_id_cb;
+ void *mic_id_cb_data;
int revision;
struct wm8994_pdata *pdata;
diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c
index 6c028c4..b7dea8e 100644
--- a/sound/soc/codecs/wm_hubs.c
+++ b/sound/soc/codecs/wm_hubs.c
@@ -109,14 +109,146 @@
}
EXPORT_SYMBOL_GPL(wm_hubs_dcs_done);
+static bool wm_hubs_dac_hp_direct(struct snd_soc_codec *codec)
+{
+ int reg;
+
+ /* If we're going via the mixer we'll need to do additional checks */
+ reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER1);
+ if (!(reg & WM8993_DACL_TO_HPOUT1L)) {
+ if (reg & ~WM8993_DACL_TO_MIXOUTL) {
+ dev_vdbg(codec->dev, "Analogue paths connected: %x\n",
+ reg & ~WM8993_DACL_TO_HPOUT1L);
+ return false;
+ } else {
+ dev_vdbg(codec->dev, "HPL connected to mixer\n");
+ }
+ } else {
+ dev_vdbg(codec->dev, "HPL connected to DAC\n");
+ }
+
+ reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER2);
+ if (!(reg & WM8993_DACR_TO_HPOUT1R)) {
+ if (reg & ~WM8993_DACR_TO_MIXOUTR) {
+ dev_vdbg(codec->dev, "Analogue paths connected: %x\n",
+ reg & ~WM8993_DACR_TO_HPOUT1R);
+ return false;
+ } else {
+ dev_vdbg(codec->dev, "HPR connected to mixer\n");
+ }
+ } else {
+ dev_vdbg(codec->dev, "HPR connected to DAC\n");
+ }
+
+ return true;
+}
+
+struct wm_hubs_dcs_cache {
+ struct list_head list;
+ unsigned int left;
+ unsigned int right;
+ u16 dcs_cfg;
+};
+
+static bool wm_hubs_dcs_cache_get(struct snd_soc_codec *codec,
+ struct wm_hubs_dcs_cache **entry)
+{
+ struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+ struct wm_hubs_dcs_cache *cache;
+ unsigned int left, right;
+
+ left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME);
+ left &= WM8993_HPOUT1L_VOL_MASK;
+
+ right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME);
+ right &= WM8993_HPOUT1R_VOL_MASK;
+
+ list_for_each_entry(cache, &hubs->dcs_cache, list) {
+ if (cache->left != left || cache->right != right)
+ continue;
+
+ *entry = cache;
+ return true;
+ }
+
+ return false;
+}
+
+static void wm_hubs_dcs_cache_set(struct snd_soc_codec *codec, u16 dcs_cfg)
+{
+ struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+ struct wm_hubs_dcs_cache *cache;
+
+ if (hubs->no_cache_dac_hp_direct)
+ return;
+
+ cache = devm_kzalloc(codec->dev, sizeof(*cache), GFP_KERNEL);
+ if (!cache) {
+ dev_err(codec->dev, "Failed to allocate DCS cache entry\n");
+ return;
+ }
+
+ cache->left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME);
+ cache->left &= WM8993_HPOUT1L_VOL_MASK;
+
+ cache->right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME);
+ cache->right &= WM8993_HPOUT1R_VOL_MASK;
+
+ cache->dcs_cfg = dcs_cfg;
+
+ list_add_tail(&cache->list, &hubs->dcs_cache);
+}
+
+static void wm_hubs_read_dc_servo(struct snd_soc_codec *codec,
+ u16 *reg_l, u16 *reg_r)
+{
+ struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+ u16 dcs_reg, reg;
+
+ switch (hubs->dcs_readback_mode) {
+ case 2:
+ dcs_reg = WM8994_DC_SERVO_4E;
+ break;
+ case 1:
+ dcs_reg = WM8994_DC_SERVO_READBACK;
+ break;
+ default:
+ dcs_reg = WM8993_DC_SERVO_3;
+ break;
+ }
+
+ /* Different chips in the family support different readback
+ * methods.
+ */
+ switch (hubs->dcs_readback_mode) {
+ case 0:
+ *reg_l = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1)
+ & WM8993_DCS_INTEG_CHAN_0_MASK;
+ *reg_r = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2)
+ & WM8993_DCS_INTEG_CHAN_1_MASK;
+ break;
+ case 2:
+ case 1:
+ reg = snd_soc_read(codec, dcs_reg);
+ *reg_r = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
+ >> WM8993_DCS_DAC_WR_VAL_1_SHIFT;
+ *reg_l = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
+ break;
+ default:
+ WARN(1, "Unknown DCS readback method\n");
+ return;
+ }
+}
+
/*
* Startup calibration of the DC servo
*/
-static void calibrate_dc_servo(struct snd_soc_codec *codec)
+static void enable_dc_servo(struct snd_soc_codec *codec)
{
struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+ struct wm_hubs_dcs_cache *cache;
s8 offset;
- u16 reg, reg_l, reg_r, dcs_cfg, dcs_reg;
+ u16 reg_l, reg_r, dcs_cfg, dcs_reg;
switch (hubs->dcs_readback_mode) {
case 2:
@@ -129,10 +261,11 @@
/* If we're using a digital only path and have a previously
* callibrated DC servo offset stored then use that. */
- if (hubs->class_w && hubs->class_w_dcs) {
- dev_dbg(codec->dev, "Using cached DC servo offset %x\n",
- hubs->class_w_dcs);
- snd_soc_write(codec, dcs_reg, hubs->class_w_dcs);
+ if (wm_hubs_dac_hp_direct(codec) &&
+ wm_hubs_dcs_cache_get(codec, &cache)) {
+ dev_dbg(codec->dev, "Using cached DCS offset %x for %d,%d\n",
+ cache->dcs_cfg, cache->left, cache->right);
+ snd_soc_write(codec, dcs_reg, cache->dcs_cfg);
wait_for_dc_servo(codec,
WM8993_DCS_TRIG_DAC_WR_0 |
WM8993_DCS_TRIG_DAC_WR_1);
@@ -153,27 +286,7 @@
WM8993_DCS_TRIG_STARTUP_1);
}
- /* Different chips in the family support different readback
- * methods.
- */
- switch (hubs->dcs_readback_mode) {
- case 0:
- reg_l = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1)
- & WM8993_DCS_INTEG_CHAN_0_MASK;
- reg_r = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2)
- & WM8993_DCS_INTEG_CHAN_1_MASK;
- break;
- case 2:
- case 1:
- reg = snd_soc_read(codec, dcs_reg);
- reg_r = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
- >> WM8993_DCS_DAC_WR_VAL_1_SHIFT;
- reg_l = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
- break;
- default:
- WARN(1, "Unknown DCS readback method\n");
- return;
- }
+ wm_hubs_read_dc_servo(codec, ®_l, ®_r);
dev_dbg(codec->dev, "DCS input: %x %x\n", reg_l, reg_r);
@@ -184,12 +297,16 @@
hubs->dcs_codes_l, hubs->dcs_codes_r);
/* HPOUT1R */
- offset = reg_r;
+ offset = (s8)reg_r;
+ dev_dbg(codec->dev, "DCS right %d->%d\n", offset,
+ offset + hubs->dcs_codes_r);
offset += hubs->dcs_codes_r;
dcs_cfg = (u8)offset << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
/* HPOUT1L */
- offset = reg_l;
+ offset = (s8)reg_l;
+ dev_dbg(codec->dev, "DCS left %d->%d\n", offset,
+ offset + hubs->dcs_codes_l);
offset += hubs->dcs_codes_l;
dcs_cfg |= (u8)offset;
@@ -207,8 +324,8 @@
/* Save the callibrated offset if we're in class W mode and
* therefore don't have any analogue signal mixed in. */
- if (hubs->class_w && !hubs->no_cache_class_w)
- hubs->class_w_dcs = dcs_cfg;
+ if (wm_hubs_dac_hp_direct(codec))
+ wm_hubs_dcs_cache_set(codec, dcs_cfg);
}
/*
@@ -223,9 +340,6 @@
ret = snd_soc_put_volsw(kcontrol, ucontrol);
- /* Updating the analogue gains invalidates the DC servo cache */
- hubs->class_w_dcs = 0;
-
/* If we're applying an offset correction then updating the
* callibration would be likely to introduce further offsets. */
if (hubs->dcs_codes_l || hubs->dcs_codes_r || hubs->no_series_update)
@@ -446,7 +560,7 @@
snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
WM8993_DCS_TIMER_PERIOD_01_MASK, 0);
- calibrate_dc_servo(codec);
+ enable_dc_servo(codec);
reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT |
WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT;
@@ -530,6 +644,91 @@
return 0;
}
+void wm_hubs_update_class_w(struct snd_soc_codec *codec)
+{
+ struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+ int enable = WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ;
+
+ if (!wm_hubs_dac_hp_direct(codec))
+ enable = false;
+
+ if (hubs->check_class_w_digital && !hubs->check_class_w_digital(codec))
+ enable = false;
+
+ dev_vdbg(codec->dev, "Class W %s\n", enable ? "enabled" : "disabled");
+
+ snd_soc_update_bits(codec, WM8993_CLASS_W_0,
+ WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ, enable);
+
+ snd_soc_write(codec, WM8993_LEFT_OUTPUT_VOLUME,
+ snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME));
+ snd_soc_write(codec, WM8993_RIGHT_OUTPUT_VOLUME,
+ snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME));
+}
+EXPORT_SYMBOL_GPL(wm_hubs_update_class_w);
+
+#define WM_HUBS_SINGLE_W(xname, reg, shift, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_dapm_get_volsw, .put = class_w_put_volsw, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+static int class_w_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ int ret;
+
+ ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+ wm_hubs_update_class_w(codec);
+
+ return ret;
+}
+
+#define WM_HUBS_ENUM_W(xname, xenum) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_enum_double, \
+ .get = snd_soc_dapm_get_enum_double, \
+ .put = class_w_put_double, \
+ .private_value = (unsigned long)&xenum }
+
+static int class_w_put_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ int ret;
+
+ ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+
+ wm_hubs_update_class_w(codec);
+
+ return ret;
+}
+
+static const char *hp_mux_text[] = {
+ "Mixer",
+ "DAC",
+};
+
+static const struct soc_enum hpl_enum =
+ SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text);
+
+const struct snd_kcontrol_new wm_hubs_hpl_mux =
+ WM_HUBS_ENUM_W("Left Headphone Mux", hpl_enum);
+EXPORT_SYMBOL_GPL(wm_hubs_hpl_mux);
+
+static const struct soc_enum hpr_enum =
+ SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text);
+
+const struct snd_kcontrol_new wm_hubs_hpr_mux =
+ WM_HUBS_ENUM_W("Right Headphone Mux", hpr_enum);
+EXPORT_SYMBOL_GPL(wm_hubs_hpr_mux);
+
static const struct snd_kcontrol_new in1l_pga[] = {
SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0),
SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0),
@@ -561,25 +760,25 @@
};
static const struct snd_kcontrol_new left_output_mixer[] = {
-SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0),
-SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0),
-SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0),
-SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0),
-SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0),
-SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0),
-SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0),
-SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0),
+WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0),
+WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0),
+WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0),
+WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0),
+WM_HUBS_SINGLE_W("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0),
+WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0),
+WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0),
+WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0),
};
static const struct snd_kcontrol_new right_output_mixer[] = {
-SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0),
-SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0),
-SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0),
-SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0),
-SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0),
-SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0),
-SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0),
-SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0),
+WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0),
+WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0),
+WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0),
+WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0),
+WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0),
+WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0),
+WM_HUBS_SINGLE_W("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0),
+WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0),
};
static const struct snd_kcontrol_new earpiece_mixer[] = {
@@ -943,6 +1142,9 @@
struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
struct snd_soc_dapm_context *dapm = &codec->dapm;
+ hubs->codec = codec;
+
+ INIT_LIST_HEAD(&hubs->dcs_cache);
init_completion(&hubs->dcs_done);
snd_soc_dapm_add_routes(dapm, analogue_routes,
diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h
index 5705276..a5a09e6 100644
--- a/sound/soc/codecs/wm_hubs.h
+++ b/sound/soc/codecs/wm_hubs.h
@@ -16,6 +16,8 @@
#include <linux/completion.h>
#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <sound/control.h>
struct snd_soc_codec;
@@ -30,9 +32,9 @@
int series_startup;
int no_series_update;
- bool no_cache_class_w;
- bool class_w;
- u16 class_w_dcs;
+ bool no_cache_dac_hp_direct;
+ struct list_head dcs_cache;
+ bool (*check_class_w_digital)(struct snd_soc_codec *);
bool lineout1_se;
bool lineout1n_ena;
@@ -44,6 +46,8 @@
bool dcs_done_irq;
struct completion dcs_done;
+
+ struct snd_soc_codec *codec;
};
extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *);
@@ -58,5 +62,9 @@
extern void wm_hubs_vmid_ena(struct snd_soc_codec *codec);
extern void wm_hubs_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level);
+extern void wm_hubs_update_class_w(struct snd_soc_codec *codec);
+
+extern const struct snd_kcontrol_new wm_hubs_hpl_mux;
+extern const struct snd_kcontrol_new wm_hubs_hpr_mux;
#endif
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig
index 1a71d2e..ac941f0 100644
--- a/sound/soc/samsung/Kconfig
+++ b/sound/soc/samsung/Kconfig
@@ -85,9 +85,26 @@
help
Say Y if you want to add support for SoC audio on the SMDKs.
+config SND_SOC_SAMSUNG_MANTA_WM1811
+ tristate "SoC I2S Audio support for WM1811 on MANTA"
+ depends on SND_SOC_SAMSUNG && MACH_MANTA
+ depends on I2C=y && GENERIC_HARDIRQS
+ select MFD_WM8994
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_I2S
+ help
+ This adds support for the WM1811 codec on manta.
+
# For support ALP audio
source "sound/soc/samsung/srp_alp/Kconfig"
+config SND_SOC_SAMSUNG_MANTA_SPDIF
+ tristate "SoC S/PDIF Audio support for MANTA"
+ depends on SND_SOC_SAMSUNG && MACH_MANTA
+ select SND_SAMSUNG_SPDIF
+ help
+ This adds support spdif for manta.
+
config SND_SOC_SAMSUNG_SMDK2443_WM9710
tristate "SoC AC97 Audio support for SMDK2443 - WM9710"
depends on SND_SOC_SAMSUNG && MACH_SMDK2443
@@ -202,6 +219,16 @@
help
Say Y if you want to add support for SoC audio on the SMDK
+config SND_SOC_MANTA_WM1811_PCM
+ tristate "SoC PCM Audio support for WM1811 on MANTA"
+ depends on SND_SOC_SAMSUNG && MACH_MANTA
+ depends on I2C=y && GENERIC_HARDIRQS
+ select MFD_WM8994
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_PCM
+ help
+ Say Y if you want to add support for SoC audio on manta.
+
config SND_SOC_SPEYSIDE
tristate "Audio support for Wolfson Speyside"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile
index 29e9aab..0110871 100644
--- a/sound/soc/samsung/Makefile
+++ b/sound/soc/samsung/Makefile
@@ -43,6 +43,8 @@
snd-soc-tobermory-objs := tobermory.o
snd-soc-lowland-objs := lowland.o
snd-soc-littlemill-objs := littlemill.o
+snd-soc-manta-wm1811-objs := manta_wm1811.o
+snd-soc-manta-spdif-objs := manta_spdif.o
obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o
obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
@@ -66,3 +68,5 @@
obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o
obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o
obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_MANTA_WM1811) += snd-soc-manta-wm1811.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_MANTA_SPDIF) += snd-soc-manta-spdif.o
diff --git a/sound/soc/samsung/littlemill.c b/sound/soc/samsung/littlemill.c
index e741685..34a5efc 100644
--- a/sound/soc/samsung/littlemill.c
+++ b/sound/soc/samsung/littlemill.c
@@ -187,7 +187,7 @@
return ret;
/* This will check device compatibility itself */
- wm8958_mic_detect(codec, &littlemill_headset, NULL, NULL);
+ wm8958_mic_detect(codec, &littlemill_headset, NULL, NULL, NULL, NULL);
/* As will this */
wm8994_mic_detect(codec, &littlemill_headset, 1);
diff --git a/sound/soc/samsung/manta_spdif.c b/sound/soc/samsung/manta_spdif.c
new file mode 100644
index 0000000..03c303a
--- /dev/null
+++ b/sound/soc/samsung/manta_spdif.c
@@ -0,0 +1,164 @@
+/*
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ * 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 <linux/clk.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <sound/soc.h>
+
+#include "spdif.h"
+
+struct manta_spdif {
+ struct clk *clk_parent;
+ struct clk *clk_spdif;
+};
+
+static int manta_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct manta_spdif *machine =
+ snd_soc_card_get_drvdata(rtd->codec->card);
+
+ unsigned long pll_out, rclk_rate;
+ int ret;
+
+ switch (params_rate(params)) {
+ case 44100:
+ pll_out = 45158400;
+ break;
+ case 32000:
+ case 48000:
+ case 96000:
+ pll_out = 49152000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Setting ratio to 512fs helps to use SPDIF with HDMI without
+ * modify SPDIF ASoC machine driver.
+ */
+ rclk_rate = params_rate(params) * 512;
+
+ clk_set_rate(machine->clk_parent, pll_out);
+ clk_set_rate(machine->clk_spdif, rclk_rate);
+
+ /* Set SPDIF uses internal source clock */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK,
+ rclk_rate, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+static struct snd_soc_ops manta_spdif_ops = {
+ .hw_params = manta_hw_params,
+};
+
+static struct snd_soc_dai_link manta_dai = {
+ .name = "SPDIF",
+ .stream_name = "SPDIF PCM Playback",
+ .platform_name = "samsung-audio",
+ .cpu_dai_name = "samsung-spdif",
+ .codec_dai_name = "dit-hifi",
+ .codec_name = "spdif-dit",
+ .ops = &manta_spdif_ops,
+};
+
+static struct snd_soc_card manta = {
+ .name = "Manta-SPDIF",
+ .owner = THIS_MODULE,
+ .dai_link = &manta_dai,
+ .num_links = 1,
+};
+
+static int __devinit snd_manta_probe(struct platform_device *pdev)
+{
+ struct manta_spdif *machine;
+ int ret;
+
+ machine = kzalloc(sizeof(*machine), GFP_KERNEL);
+ if (!machine) {
+ pr_err("%s: Failed to allocate memory\n", __func__);
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ machine->clk_parent = clk_get(NULL, "fout_epll");
+ if (IS_ERR(machine->clk_parent)) {
+ pr_err("%s: failed to get fout_epll\n", __func__);
+ ret = PTR_ERR(machine->clk_parent);
+ goto err_clk_parent_get;
+ }
+
+ machine->clk_spdif = clk_get(NULL, "sclk_spdif");
+ if (IS_ERR(machine->clk_spdif)) {
+ pr_err("%s: failed to get sclk_spdif\n", __func__);
+ ret = PTR_ERR(machine->clk_spdif);
+ goto err_clk_spdif_get;
+ }
+
+ snd_soc_card_set_drvdata(&manta, machine);
+
+ manta.dev = &pdev->dev;
+ ret = snd_soc_register_card(&manta);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret);
+ goto err_register_card;
+ }
+
+ return 0;
+
+err_register_card:
+ clk_put(machine->clk_spdif);
+err_clk_spdif_get:
+ clk_put(machine->clk_parent);
+err_clk_parent_get:
+ kfree(machine);
+err_kzalloc:
+ return ret;
+}
+
+static int __devexit snd_manta_remove(struct platform_device *pdev)
+{
+ struct manta_spdif *machine = snd_soc_card_get_drvdata(&manta);
+
+ snd_soc_unregister_card(&manta);
+ clk_put(machine->clk_parent);
+ clk_put(machine->clk_spdif);
+ kfree(machine);
+
+ return 0;
+}
+
+static struct platform_driver snd_manta_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "manta-spdif",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = snd_manta_probe,
+ .remove = __devexit_p(snd_manta_remove),
+};
+
+module_platform_driver(snd_manta_driver);
+
+MODULE_DESCRIPTION("ALSA SoC Manta SPDIF");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/manta_wm1811.c b/sound/soc/samsung/manta_wm1811.c
new file mode 100644
index 0000000..8a6c819
--- /dev/null
+++ b/sound/soc/samsung/manta_wm1811.c
@@ -0,0 +1,732 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * Copyright (C) 2012 Wolfson Microelectronics
+ *
+ * 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 <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/wm8994/registers.h>
+
+#include <plat/adc.h>
+
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../codecs/wm8994.h"
+#include "../../../arch/arm/mach-exynos/board-manta.h"
+
+#define MCLK1_FREQ 24000000
+#define MCLK2_FREQ 32768
+
+#define EAR_ADC_CHANNEL 7
+#define ADC_HEADPHONE_3POLE 0x4BA
+#define ADC_HEADSET_4POLE 0xED8
+/* HEADPHONE: [2] - <3 ohm, [1] - Valid, [0] - Mic Accessory is present */
+#define STATUS_HEADPHONE_3POLE 0x7
+/* HEADSET: [10] - >475 ohm, [1] - Valid, [0] - Mic Accessory is present */
+#define STATUS_HEADSET_4POLE 0x403
+#define ADC_MIC_TEST_NUM 10
+#define ADC_MIC_WAIT_US 10000
+
+struct manta_wm1811 {
+ struct clk *clk;
+ unsigned int pll1_out;
+ unsigned int prev_pll1_out;
+ unsigned int pll2_out;
+ unsigned int prev_pll2_out;
+ struct snd_soc_jack jack;
+ struct s3c_adc_client *adc_client;
+};
+
+static const struct snd_kcontrol_new manta_controls[] = {
+ SOC_DAPM_PIN_SWITCH("HP"),
+ SOC_DAPM_PIN_SWITCH("SPK"),
+};
+
+const struct snd_soc_dapm_widget manta_widgets_lunchbox[] = {
+ SND_SOC_DAPM_HP("HP", NULL),
+ SND_SOC_DAPM_SPK("SPK", NULL),
+
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", NULL),
+ SND_SOC_DAPM_MIC("Sub Mic", NULL),
+
+ SND_SOC_DAPM_INPUT("S5P RP"),
+};
+
+const struct snd_soc_dapm_widget manta_widgets[] = {
+ SND_SOC_DAPM_HP("HP", NULL),
+ SND_SOC_DAPM_SPK("SPK", NULL),
+
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", NULL),
+ SND_SOC_DAPM_MIC("2nd Mic", NULL),
+ SND_SOC_DAPM_MIC("3rd Mic", NULL),
+
+ SND_SOC_DAPM_INPUT("S5P RP"),
+};
+
+const struct snd_soc_dapm_route manta_paths_lunchbox[] = {
+ { "HP", NULL, "HPOUT1L" },
+ { "HP", NULL, "HPOUT1R" },
+
+ { "SPK", NULL, "SPKOUTLN" },
+ { "SPK", NULL, "SPKOUTLP" },
+ { "SPK", NULL, "SPKOUTRN" },
+ { "SPK", NULL, "SPKOUTRP" },
+
+ { "IN1LP", NULL, "MICBIAS1" },
+ { "IN1LN", NULL, "MICBIAS1" },
+ { "MICBIAS1", NULL, "Main Mic" },
+
+ { "IN1RP", NULL, "MICBIAS1" },
+ { "IN1RN", NULL, "MICBIAS1" },
+ { "MICBIAS1", NULL, "Sub Mic" },
+
+ { "IN2RP:VXRP", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+
+ { "AIF1DAC1L", NULL, "S5P RP" },
+ { "AIF1DAC1R", NULL, "S5P RP" },
+};
+
+const struct snd_soc_dapm_route manta_paths[] = {
+ { "HP", NULL, "HPOUT1L" },
+ { "HP", NULL, "HPOUT1R" },
+
+ { "SPK", NULL, "SPKOUTLN" },
+ { "SPK", NULL, "SPKOUTLP" },
+ { "SPK", NULL, "SPKOUTRN" },
+ { "SPK", NULL, "SPKOUTRP" },
+
+ { "IN1LP", NULL, "MICBIAS1" },
+ { "IN1LN", NULL, "MICBIAS1" },
+ { "MICBIAS1", NULL, "3rd Mic" },
+
+ { "IN1RP", NULL, "MICBIAS2" },
+ { "IN1RN", NULL, "MICBIAS2" },
+ { "MICBIAS1", NULL, "Headset Mic" },
+
+ { "IN2LP:VXRN", NULL, "MICBIAS1" },
+ { "MICBIAS2", NULL, "2nd Mic" },
+
+ { "IN2RP:VXRP", NULL, "MICBIAS1" },
+ { "MICBIAS2", NULL, "Main Mic" },
+
+ { "AIF1DAC1L", NULL, "S5P RP" },
+ { "AIF1DAC1R", NULL, "S5P RP" },
+};
+
+static int manta_start_fll1(struct snd_soc_dai *codec_dai,
+ struct manta_wm1811 *machine)
+{
+ int ret;
+
+ if (machine->pll1_out != machine->prev_pll1_out) {
+ /*
+ * FLL1's frequency needs to be changed. Make sure that we
+ * have a system clock not derived from the FLL, since we
+ * cannot change the FLL when the system clock is derived
+ * from it.
+ * Set FFL clock to maximum during transition in case AIF2
+ * is active to ensure SYSCLK > 256 x fs
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ WM8994_SYSCLK_MCLK1,
+ MCLK1_FREQ / 2, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->dev,
+ "Failed to switch away from FLL1: %d\n", ret);
+ return ret;
+ }
+
+ machine->prev_pll1_out = machine->pll1_out;
+ }
+
+ /* Switch the FLL */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1, MCLK1_FREQ,
+ machine->pll1_out);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Unable to start FLL1\n");
+ return ret;
+ }
+
+ /* Then switch AIF1CLK to it */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+ machine->pll1_out, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Unable to switch to FLL1\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int manta_stop_flls(struct snd_soc_dai *codec_dai,
+ struct manta_wm1811 *machine)
+{
+ int ret;
+
+ /*
+ * Playback/capture has stopped, so switch to the slower
+ * MCLK2 for reduced power consumption. hw_params handles
+ * turning the FLL back on when needed.
+ * Turn FLL2 off as AIF2 is never used if AIF1 is idle. This is
+ * necessary so that SYSCLK can switch to 32kHz clock.
+ */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, 0, 0, 0);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Failed to stop FLL2: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK2,
+ MCLK2_FREQ, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Failed to switch away from FLL: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
+ 0, 0, 0);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Failed to stop FLL1: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int manta_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
+ struct manta_wm1811 *machine =
+ snd_soc_card_get_drvdata(card);
+ int ret = 0;
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ if ((level == SND_SOC_BIAS_PREPARE) &&
+ (dapm->bias_level == SND_SOC_BIAS_STANDBY))
+ ret = manta_start_fll1(codec_dai, machine);
+
+ return ret;
+}
+
+static int manta_set_bias_level_post(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
+ struct manta_wm1811 *machine =
+ snd_soc_card_get_drvdata(card);
+ int ret = 0;
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ if (level == SND_SOC_BIAS_STANDBY)
+ ret = manta_stop_flls(codec_dai, machine);
+
+ dapm->bias_level = level;
+
+ return ret;
+}
+
+static int manta_wm1811_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct manta_wm1811 *machine =
+ snd_soc_card_get_drvdata(rtd->codec->card);
+ int ret;
+
+ machine->pll1_out = params_rate(params) * 512;
+
+ ret = manta_start_fll1(codec_dai, machine);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Unable to start FLL1\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Unable to set codec DAIFMT\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Unable to set CPU DAIFMT\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops manta_wm1811_aif1_ops = {
+ .hw_params = manta_wm1811_aif1_hw_params,
+};
+
+static int manta_wm1811_aif2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct manta_wm1811 *machine =
+ snd_soc_card_get_drvdata(rtd->codec->card);
+ int ret;
+ int prate;
+
+ prate = params_rate(params);
+
+ switch (prate) {
+ case 8000:
+ case 16000:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Use 512 multiplier to make sure that SYSCLK > 4096kHz
+ * when fs is 8kHz */
+ machine->pll2_out = prate * 512;
+
+ if (machine->pll2_out != machine->prev_pll2_out) {
+ /*
+ * FLL2's frequency needs to be changed. Make sure that we
+ * have a system clock not derived from the FLL, since we
+ * cannot change the FLL when the system clock is derived
+ * from it.
+ * Set FFL clock to maximum during transition in case AIF1
+ * is active to ensure SYSCLK > 256 x fs
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1,
+ MCLK1_FREQ / 2, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->dev,
+ "Failed to switch away from FLL2: %d\n", ret);
+ return ret;
+ }
+
+ machine->prev_pll2_out = machine->pll2_out;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, WM8994_FLL_SRC_MCLK1,
+ MCLK1_FREQ, machine->pll2_out);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Unable to configure FLL2: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2,
+ machine->pll2_out, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "Unable to switch to FLL2: %d\n", ret);
+ return ret;
+ }
+
+ /* Set the codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(
+ codec_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "%s snd_soc_dai_set_fmt error %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops manta_wm1811_aif2_ops = {
+ .hw_params = manta_wm1811_aif2_hw_params,
+};
+
+static struct snd_soc_dai_link manta_dai[] = {
+ {
+ .name = "media-pri",
+ .stream_name = "Media primary",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8994-aif1",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8994-codec",
+ .ops = &manta_wm1811_aif1_ops,
+ },
+ {
+ .name = "media-sec",
+ .stream_name = "Media secondary",
+ .cpu_dai_name = "samsung-i2s.4",
+ .codec_dai_name = "wm8994-aif1",
+#ifdef CONFIG_SND_SAMSUNG_USE_IDMA
+ .platform_name = "samsung-idma",
+#else
+ .platform_name = "samsung-audio",
+#endif
+ .codec_name = "wm8994-codec",
+ .ops = &manta_wm1811_aif1_ops,
+ },
+ {
+ .name = "voice",
+ .stream_name = "Voice",
+ .cpu_dai_name = "manta-voice",
+ .codec_dai_name = "wm8994-aif2",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ops = &manta_wm1811_aif2_ops,
+ .ignore_suspend = 1,
+ },
+ {
+ .name = "bt",
+ .stream_name = "Bluetooth",
+ .cpu_dai_name = "manta-bt",
+ .codec_dai_name = "wm8994-aif3",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ignore_suspend = 1,
+ },
+};
+
+static struct snd_soc_dai_driver manta_ext_dai[] = {
+ {
+ .name = "manta-voice",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ .name = "manta-bt",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+static struct platform_device android_device_ear = {
+ .name = "android-ear",
+ .id = -1,
+};
+
+static void manta_mic_id(void *data, u16 status)
+{
+ struct snd_soc_codec *codec = data;
+ struct manta_wm1811 *machine =
+ snd_soc_card_get_drvdata(codec->card);
+ int sum = -1;
+ int count = 0;
+ int ret;
+ int i;
+
+ status = STATUS_HEADPHONE_3POLE;
+ if (machine->adc_client) {
+ for (i = 0; i < ADC_MIC_TEST_NUM; i++) {
+ usleep_range(ADC_MIC_WAIT_US, ADC_MIC_WAIT_US);
+ ret = s3c_adc_read(machine->adc_client,
+ EAR_ADC_CHANNEL);
+ if (ret >= 0) {
+ sum += ret;
+ count++;
+ }
+ }
+ if (count > 0)
+ sum = (sum + 1) / count;
+ }
+ if (sum < 0)
+ pr_err("Error reading ADC line\n");
+ else if (sum > ADC_HEADPHONE_3POLE && sum <= ADC_HEADSET_4POLE)
+ status = STATUS_HEADSET_4POLE;
+
+ wm8958_mic_id(data, status);
+}
+
+static int manta_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd[0].codec;
+ struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
+ struct snd_soc_dai *cpu_dai = card->rtd[0].cpu_dai;
+ struct manta_wm1811 *machine =
+ snd_soc_card_get_drvdata(codec->card);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ /*
+ * Hack: permit the codec to open streams with the same number
+ * of channels that the CPU DAI (samsung-i2s) supports, since
+ * the HDMI block takes its audio from the i2s0 channel shared
+ * with the codec.
+ */
+ codec_dai->driver->playback.channels_max =
+ cpu_dai->driver->playback.channels_max;
+
+ /*
+ * Hack: For using DCS cache from wm1811
+ * because current wm1811 driver does not use cached value
+ * and it increases audio warmup time for headphone routing.
+ * it can help decreasing warmup time
+ */
+ wm8994->hubs.no_cache_dac_hp_direct = false;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK2,
+ MCLK2_FREQ, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to switch to MCLK2\n");
+
+ /* Force AIF1CLK on as it will be master for jack detection */
+ ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to enable AIF1CLK\n");
+
+ ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to disable S5P RP\n");
+
+ ret = snd_soc_jack_new(codec, "Headset",
+ SND_JACK_HEADSET | SND_JACK_MECHANICAL |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2, &machine->jack);
+ if (ret) {
+ dev_err(codec->dev, "Failed to create jack: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Settings provided by Wolfson for Samsung-specific customization
+ * of MICBIAS levels
+ */
+ snd_soc_write(codec, 0x102, 0x3);
+ snd_soc_write(codec, 0xcb, 0x5151);
+ snd_soc_write(codec, 0xd3, 0x3f3f);
+ snd_soc_write(codec, 0xd4, 0x3f3f);
+ snd_soc_write(codec, 0xd5, 0x3f3f);
+ snd_soc_write(codec, 0xd6, 0x3226);
+ snd_soc_write(codec, 0x102, 0x0);
+ snd_soc_write(codec, 0xd1, 0x87);
+ snd_soc_write(codec, 0x3b, 0x9);
+ snd_soc_write(codec, 0x3c, 0x2);
+
+ ret = snd_jack_set_key(machine->jack.jack, SND_JACK_BTN_0,
+ KEY_MEDIA);
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to set KEY_MEDIA: %d\n", ret);
+
+ ret = snd_jack_set_key(machine->jack.jack, SND_JACK_BTN_1,
+ KEY_VOLUMEUP);
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to set KEY_VOLUMEUP: %d\n", ret);
+
+ ret = snd_jack_set_key(machine->jack.jack, SND_JACK_BTN_2,
+ KEY_VOLUMEDOWN);
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret);
+
+ /* certain manta revisions must use SoC ADC mic detection */
+ if (exynos5_manta_get_revision() >= MANTA_REV_DOGFOOD05) {
+ machine->adc_client =
+ s3c_adc_register(&android_device_ear, NULL, NULL, 0);
+ if (IS_ERR(machine->adc_client)) {
+ dev_err(codec->dev, "Failed to set ADC client: %ld\n",
+ PTR_ERR(machine->adc_client));
+ machine->adc_client = NULL;
+ }
+ wm8958_mic_detect(codec, &machine->jack,
+ NULL, NULL, manta_mic_id, codec);
+ } else {
+ wm8958_mic_detect(codec, &machine->jack,
+ NULL, NULL, NULL, NULL);
+ }
+
+ return 0;
+}
+
+static int manta_card_suspend_post(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct manta_wm1811 *machine =
+ snd_soc_card_get_drvdata(codec->card);
+
+ snd_soc_update_bits(codec, WM8994_AIF1_MASTER_SLAVE,
+ WM8994_AIF1_TRI_MASK, WM8994_AIF1_TRI);
+
+ clk_disable(machine->clk);
+
+ return 0;
+}
+
+static int manta_card_resume_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct manta_wm1811 *machine =
+ snd_soc_card_get_drvdata(codec->card);
+
+ clk_enable(machine->clk);
+
+ snd_soc_update_bits(codec, WM8994_AIF1_MASTER_SLAVE,
+ WM8994_AIF1_TRI_MASK, 0);
+
+ return 0;
+}
+
+static struct snd_soc_card manta = {
+ .name = "Manta-I2S",
+ .owner = THIS_MODULE,
+ .dai_link = manta_dai,
+ .num_links = ARRAY_SIZE(manta_dai),
+
+ .set_bias_level = manta_set_bias_level,
+ .set_bias_level_post = manta_set_bias_level_post,
+
+ .controls = manta_controls,
+ .num_controls = ARRAY_SIZE(manta_controls),
+ .dapm_widgets = manta_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(manta_widgets),
+ .dapm_routes = manta_paths,
+ .num_dapm_routes = ARRAY_SIZE(manta_paths),
+
+ .late_probe = manta_late_probe,
+
+ .suspend_post = manta_card_suspend_post,
+ .resume_pre = manta_card_resume_pre,
+};
+
+static int __devinit snd_manta_probe(struct platform_device *pdev)
+{
+ struct manta_wm1811 *machine;
+ int ret;
+ int hwrev = exynos5_manta_get_revision();
+
+ machine = kzalloc(sizeof(*machine), GFP_KERNEL);
+ if (!machine) {
+ pr_err("Failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ machine->clk = clk_get(&pdev->dev, "system_clk");
+ if (IS_ERR(machine->clk)) {
+ pr_err("failed to get system_clk\n");
+ ret = PTR_ERR(machine->clk);
+ goto err_clk_get;
+ }
+
+ /* Start the reference clock for the codec's FLL */
+ clk_enable(machine->clk);
+
+ machine->pll1_out = 44100 * 512; /* default sample rate */
+ machine->pll2_out = 0;
+
+ ret = snd_soc_register_dais(&pdev->dev, manta_ext_dai,
+ ARRAY_SIZE(manta_ext_dai));
+ if (ret != 0)
+ pr_err("Failed to register external DAIs: %d\n", ret);
+
+ snd_soc_card_set_drvdata(&manta, machine);
+
+ if (hwrev < MANTA_REV_PRE_ALPHA) {
+ manta.dapm_widgets = manta_widgets_lunchbox,
+ manta.num_dapm_widgets = ARRAY_SIZE(manta_widgets_lunchbox),
+ manta.dapm_routes = manta_paths_lunchbox;
+ manta.num_dapm_routes = ARRAY_SIZE(manta_paths_lunchbox);
+ }
+
+ manta.dev = &pdev->dev;
+ ret = snd_soc_register_card(&manta);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret);
+ goto err_register_card;
+ }
+
+ return 0;
+
+err_register_card:
+ clk_put(machine->clk);
+err_clk_get:
+ kfree(machine);
+err_kzalloc:
+ return ret;
+}
+
+static int __devexit snd_manta_remove(struct platform_device *pdev)
+{
+ struct manta_wm1811 *machine = snd_soc_card_get_drvdata(&manta);
+
+ if (machine->adc_client)
+ s3c_adc_release(machine->adc_client);
+ snd_soc_unregister_card(&manta);
+ clk_disable(machine->clk);
+ clk_put(machine->clk);
+ kfree(machine);
+
+ return 0;
+}
+
+static struct platform_driver snd_manta_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "manta-i2s",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = snd_manta_probe,
+ .remove = __devexit_p(snd_manta_remove),
+};
+
+module_platform_driver(snd_manta_driver);
+
+MODULE_DESCRIPTION("ALSA SoC Manta WM1811");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
index 0ad8dca..20cee6f 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -351,13 +351,15 @@
/* Muting the DAC suppresses artifacts caused during digital
* shutdown, for example from stopping clocks.
*/
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+ && !codec_dai->playback_active)
snd_soc_dai_digital_mute(codec_dai, 1);
if (cpu_dai->driver->ops->shutdown)
cpu_dai->driver->ops->shutdown(substream, cpu_dai);
- if (codec_dai->driver->ops->shutdown)
+ if (codec_dai->driver->ops->shutdown
+ && !codec_dai->playback_active)
codec_dai->driver->ops->shutdown(substream, codec_dai);
if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
@@ -367,24 +369,26 @@
platform->driver->ops->close(substream);
cpu_dai->runtime = NULL;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- if (!rtd->pmdown_time || codec->ignore_pmdown_time ||
- rtd->dai_link->ignore_pmdown_time) {
- /* powered down playback stream now */
- snd_soc_dapm_stream_event(rtd,
+ if (!codec_dai->playback_active) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (!rtd->pmdown_time || codec->ignore_pmdown_time ||
+ rtd->dai_link->ignore_pmdown_time) {
+ /* powered down playback stream now */
+ snd_soc_dapm_stream_event(rtd,
SNDRV_PCM_STREAM_PLAYBACK,
codec_dai,
SND_SOC_DAPM_STREAM_STOP);
+ } else {
+ /* start delayed pop wq here for playback streams */
+ codec_dai->pop_wait = 1;
+ schedule_delayed_work(&rtd->delayed_work,
+ msecs_to_jiffies(rtd->pmdown_time));
+ }
} else {
- /* start delayed pop wq here for playback streams */
- codec_dai->pop_wait = 1;
- schedule_delayed_work(&rtd->delayed_work,
- msecs_to_jiffies(rtd->pmdown_time));
- }
- } else {
- /* capture streams can be powered down now */
- snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE,
+ /* capture streams can be powered down now */
+ snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE,
codec_dai, SND_SOC_DAPM_STREAM_STOP);
+ }
}
mutex_unlock(&rtd->pcm_mutex);