diff options
224 files changed, 58940 insertions, 2848 deletions
diff --git a/arch/arm/configs/tuna_defconfig b/arch/arm/configs/tuna_defconfig new file mode 100644 index 0000000..ac5e41a --- /dev/null +++ b/arch/arm/configs/tuna_defconfig @@ -0,0 +1,429 @@ +CONFIG_EXPERIMENTAL=y +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=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_ASHMEM=y +# CONFIG_AIO is not set +CONFIG_EMBEDDED=y +# CONFIG_PERF_EVENTS is not set +CONFIG_PROFILING=y +CONFIG_OPROFILE=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_ARCH_OMAP=y +CONFIG_OMAP_SMARTREFLEX=y +CONFIG_OMAP_SMARTREFLEX_CLASS3=y +CONFIG_OMAP_SMARTREFLEX_CLASS1P5=y +CONFIG_OMAP_RESET_CLOCKS=y +CONFIG_OMAP_TEMP_SENSOR=y +CONFIG_OMAP_REMOTEPROC_MEMPOOL_SIZE=0x0 +# CONFIG_ARCH_OMAP2 is not set +# CONFIG_ARCH_OMAP3 is not set +# CONFIG_MACH_OMAP_4430SDP is not set +CONFIG_OMAP_ALLOW_OSWR=y +CONFIG_ARM_THUMBEE=y +CONFIG_PL310_ERRATA_727915=y +CONFIG_FIQ_DEBUGGER_CONSOLE=y +CONFIG_ARM_ERRATA_764369=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +CONFIG_NR_CPUS=2 +CONFIG_PREEMPT=y +CONFIG_HIGHMEM=y +CONFIG_COMPACTION=y +CONFIG_ARM_FLUSH_CONSOLE_ON_RESTART=y +CONFIG_CMDLINE="console=ttyFIQ0 androidboot.console=ttyFIQ0 mem=1G vmalloc=768M omap_wdt.timer_margin=30 no_console_suspend" +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_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_HOTPLUG=y +CONFIG_CPU_IDLE=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_BINFMT_MISC=y +CONFIG_WAKELOCK=y +CONFIG_PM_DEBUG=y +CONFIG_SUSPEND_TIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=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_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_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_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_IP_NF_TARGET_LOG=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_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_TARGET_LOG=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_BRIDGE=y +# CONFIG_BRIDGE_IGMP_SNOOPING is not set +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_L2CAP=y +CONFIG_BT_SCO=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_WEXT is not set +CONFIG_CFG80211_ALLOW_RECONNECT=y +CONFIG_RFKILL=y +CONFIG_RFKILL_INPUT=y +CONFIG_MTD=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_M25P80=y +CONFIG_MTD_NAND_IDS=y +CONFIG_MTD_ONENAND=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_MISC_DEVICES=y +CONFIG_SAMSUNG_JACK=y +CONFIG_UID_STAT=y +CONFIG_BMP180=y +CONFIG_USB_SWITCH_FSA9480=y +CONFIG_OMAP_DIE_TEMP_SENSOR=y +CONFIG_LEDS_AN30259A=y +CONFIG_MPU_SENSORS_TIMERIRQ=y +CONFIG_INV_SENSORS=y +CONFIG_MPU_SENSORS_MPU3050=y +CONFIG_MPU_SENSORS_BMA250=y +CONFIG_MPU_SENSORS_YAS530=y +CONFIG_SEC_MODEM=y +CONFIG_UMTS_LINK_MIPI=y +CONFIG_UMTS_MODEM_XMM6260=y +CONFIG_CDMA_LINK_DPRAM=y +CONFIG_CDMA_MODEM_CBP71=y +CONFIG_LTE_LINK_USB=y +CONFIG_LTE_MODEM_CMC221=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_DEBUG=y +CONFIG_DM_CRYPT=y +CONFIG_DM_UEVENT=y +CONFIG_NETDEVICES=y +CONFIG_IFB=y +CONFIG_TUN=y +CONFIG_WIFI_CONTROL_FUNC=y +CONFIG_BCMDHD=y +CONFIG_BCMDHD_FW_PATH="/system/vendor/firmware/fw_bcmdhd.bin" +CONFIG_PPP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_MPPE=y +CONFIG_PPPOLAC=y +CONFIG_PPPOPNS=y +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_KEYRESET=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_OMAP4=y +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_XPAD=y +CONFIG_JOYSTICK_XPAD_FF=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_MMS=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_KEYCHORD=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_GPIO=y +CONFIG_OPTICAL_GP2A=y +CONFIG_SERIO_LIBPS2=y +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +CONFIG_HW_RANDOM=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_GPIO=y +CONFIG_SPI=y +CONFIG_SPI_OMAP24XX=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_TWL4030=y +CONFIG_POWER_SUPPLY=y +CONFIG_PDA_POWER=y +CONFIG_BATTERY_MAX17040=y +# CONFIG_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_OMAP_WATCHDOG=y +CONFIG_TWL6030_POWER=y +CONFIG_TWL6030_PWM=y +CONFIG_TWL6030_MADC=y +CONFIG_REGULATOR_TWL4030=y +CONFIG_MEDIA_SUPPORT=y +# CONFIG_RC_CORE is not set +CONFIG_PVR_SGX=y +CONFIG_PVR_NEED_PVR_DPF=y +CONFIG_PVR_NEED_PVR_ASSERT=y +CONFIG_PVR_USSE_EDM_STATUS_DEBUG=y +CONFIG_SGX_DVFS_MODE_OPTIMIZED=y +CONFIG_ION=y +CONFIG_ION_OMAP=y +CONFIG_FB=y +CONFIG_SII9234=y +CONFIG_FB_OMAP_BOOTLOADER_INIT=y +CONFIG_OMAP2_DSS=y +CONFIG_OMAP2_VRAM_SIZE=16 +# CONFIG_OMAP2_DSS_DPI is not set +# CONFIG_OMAP2_DSS_VENC is not set +CONFIG_OMAP2_DSS_DSI=y +CONFIG_FB_OMAP2=y +CONFIG_PANEL_S6E8AA0=y +CONFIG_OMAP4_HDCP=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_DISPLAY_SUPPORT=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SOC=y +CONFIG_SND_OMAP_SOC=y +CONFIG_SND_OMAP_SOC_SDP4430=y +CONFIG_SND_OMAP_SOC_OMAP4_HDMI=y +CONFIG_HID_A4TECH=y +CONFIG_HID_ACRUX=y +CONFIG_HID_ACRUX_FF=y +CONFIG_HID_APPLE=y +CONFIG_HID_BELKIN=y +CONFIG_HID_CHERRY=y +CONFIG_HID_CHICONY=y +CONFIG_HID_CYPRESS=y +CONFIG_HID_DRAGONRISE=y +CONFIG_DRAGONRISE_FF=y +CONFIG_HID_EMS_FF=y +CONFIG_HID_ELECOM=y +CONFIG_HID_EZKEY=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_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_LOGIWII_FF=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_PANTHERLORD_FF=y +CONFIG_HID_PETALYNX=y +CONFIG_HID_PICOLCD=y +CONFIG_HID_QUANTA=y +CONFIG_HID_ROCCAT_ARVO=y +CONFIG_HID_ROCCAT_KONE=y +CONFIG_HID_ROCCAT_KONEPLUS=y +CONFIG_HID_ROCCAT_KOVAPLUS=y +CONFIG_HID_ROCCAT_PYRA=y +CONFIG_HID_SAMSUNG=y +CONFIG_HID_SONY=y +CONFIG_HID_SUNPLUS=y +CONFIG_HID_GREENASIA=y +CONFIG_GREENASIA_FF=y +CONFIG_HID_SMARTJOYPLUS=y +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_HID_TOPSEED=y +CONFIG_HID_THRUSTMASTER=y +CONFIG_THRUSTMASTER_FF=y +CONFIG_HID_WACOM=y +CONFIG_HID_ZEROPLUS=y +CONFIG_ZEROPLUS_FF=y +CONFIG_HID_ZYDACRON=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_DEVICEFS=y +CONFIG_USB_SUSPEND=y +# CONFIG_USB_OTG_WHITELIST is not set +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_MUSB_HDRC=y +CONFIG_USB_MUSB_OMAP2PLUS=y +CONFIG_USB_MUSB_OTG=y +CONFIG_USB_GADGET_MUSB_HDRC=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_SERIAL=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_G_ANDROID=y +CONFIG_USB_OTG_WAKELOCK=y +CONFIG_MMC=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_EMBEDDED_SDIO=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_OMAP=y +CONFIG_MMC_OMAP_HS=y +CONFIG_SWITCH=y +CONFIG_SWITCH_GPIO=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_TWL4030=y +CONFIG_STAGING=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_LOGGER=y +CONFIG_ANDROID_RAM_CONSOLE=y +CONFIG_ANDROID_RAM_CONSOLE_ERROR_CORRECTION=y +CONFIG_ANDROID_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_OMAP_HSI=y +CONFIG_EXT2_FS=y +CONFIG_EXT4_FS=y +# CONFIG_EXT4_FS_XATTR is not set +# 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_PARTITION_ADVANCED=y +CONFIG_EFI_PARTITION=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_PRINTK_TIME=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_KERNEL=y +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=10 +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_SPINLOCK_SLEEP=y +CONFIG_DEBUG_INFO=y +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_SCHED_TRACER=y +# CONFIG_ARM_UNWIND is not set +CONFIG_DEBUG_USER=y +CONFIG_SECURITY_MIDDLEWARE_COMPONENT=y +# CONFIG_SMC_KERNEL_CRYPTO is not set +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRC_CCITT=y diff --git a/arch/arm/mach-omap2/Kconfig b/arch/arm/mach-omap2/Kconfig index f7b228c..97e7205 100644 --- a/arch/arm/mach-omap2/Kconfig +++ b/arch/arm/mach-omap2/Kconfig @@ -325,6 +325,15 @@ config MACH_OMAP4_PANDA select OMAP_PACKAGE_CBS select REGULATOR_FIXED_VOLTAGE +config MACH_TUNA + bool "Tuna Board" + default y + depends on ARCH_OMAP4 + select OMAP_PACKAGE_CBL + select OMAP_PACKAGE_CBS + select REGULATOR_FIXED_VOLTAGE + select OMAP_TPS6236X + config OMAP3_EMU bool "OMAP3 debugging peripherals" depends on ARCH_OMAP3 diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index aa42baf..6d72578 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -254,6 +254,22 @@ obj-$(CONFIG_MACH_OMAP_4430SDP) += board-4430sdp.o \ obj-$(CONFIG_MACH_OMAP4_PANDA) += board-omap4panda.o \ hsmmc.o \ omap_phy_internal.o +obj-$(CONFIG_MACH_TUNA) += board-tuna.o \ + hsmmc.o \ + omap_phy_internal.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-display.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-input.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-jack.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-nfc.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-power.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-sensors.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-vibrator.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-wifi.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-bluetooth.o \ + board-tuna-emif.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-connector.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-pogo.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-usbhost.o obj-$(CONFIG_MACH_OMAP3517EVM) += board-am3517evm.o \ omap_phy_internal.o \ @@ -292,5 +308,6 @@ obj-y += common-board-devices.o obj-$(CONFIG_OMAP_REMOTE_PROC) += remoteproc.o obj-$(CONFIG_OMAP_HSI_DEVICE) += omap_hsi.o +obj-$(CONFIG_SEC_MODEM) += board-tuna-modems.o obj-$(CONFIG_ARCH_OMAP4) += omap_dmm.o obj-$(CONFIG_OMAP_FIQ_DEBUGGER) += omap_fiq_debugger.o diff --git a/arch/arm/mach-omap2/board-omap4panda.c b/arch/arm/mach-omap2/board-omap4panda.c index 4fcd26a..5a00efc 100644 --- a/arch/arm/mach-omap2/board-omap4panda.c +++ b/arch/arm/mach-omap2/board-omap4panda.c @@ -680,8 +680,6 @@ void omap4_panda_display_init(void) omap_display_init(&omap4_panda_dss_data); } -extern void __init omap4_panda_android_init(void); - static void __init omap4_panda_init(void) { int package = OMAP_PACKAGE_CBS; @@ -712,7 +710,6 @@ static void __init omap4_panda_init(void) omap_dmm_init(); omap4_panda_display_init(); - } static void __init omap4_panda_map_io(void) diff --git a/arch/arm/mach-omap2/board-tuna-bluetooth.c b/arch/arm/mach-omap2/board-tuna-bluetooth.c new file mode 100644 index 0000000..bfe66ab --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-bluetooth.c @@ -0,0 +1,344 @@ +/* + * Bluetooth Broadcomm and low power control via GPIO + * + * Copyright (C) 2011 Google, Inc. + * + * 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 + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/hrtimer.h> +#include <linux/irq.h> +#include <linux/rfkill.h> +#include <linux/platform_device.h> +#include <linux/wakelock.h> +#include <asm/mach-types.h> +#include <plat/serial.h> +#include <plat/board-tuna-bluetooth.h> +#include <linux/regulator/driver.h> + +#define BT_REG_GPIO 103 +#define BT_RESET_GPIO 42 + +#define BT_WAKE_GPIO 27 +#define BT_HOST_WAKE_GPIO 177 + +static struct rfkill *bt_rfkill; +static struct regulator *clk32kaudio_reg; +static bool bt_enabled; +static bool host_wake_uart_enabled; +static bool wake_uart_enabled; + +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; + char wake_lock_name[100]; +} bt_lpm; + +static int bcm4330_bt_rfkill_set_power(void *data, bool blocked) +{ + // rfkill_ops callback. Turn transmitter on when blocked is false + if (!blocked) { + if (clk32kaudio_reg && !bt_enabled) + regulator_enable(clk32kaudio_reg); + + gpio_set_value(BT_REG_GPIO, 1); + gpio_set_value(BT_RESET_GPIO, 1); + + } else { + gpio_set_value(BT_RESET_GPIO, 0); + gpio_set_value(BT_REG_GPIO, 0); + if (clk32kaudio_reg && bt_enabled) + regulator_disable(clk32kaudio_reg); + } + + bt_enabled = !blocked; + + return 0; +} + +static const struct rfkill_ops bcm4330_bt_rfkill_ops = { + .set_block = bcm4330_bt_rfkill_set_power, +}; + +static void set_wake_locked(int wake) +{ + bt_lpm.wake = wake; + + if (!wake) + wake_unlock(&bt_lpm.wake_lock); + + if (!wake_uart_enabled && wake) + omap_uart_enable(2); + + gpio_set_value(BT_WAKE_GPIO, wake); + + if (wake_uart_enabled && !wake) + omap_uart_disable(2); + + wake_uart_enabled = wake; +} + +static enum hrtimer_restart enter_lpm(struct hrtimer *timer) { + unsigned long flags; + spin_lock_irqsave(&bt_lpm.uport->lock, flags); + set_wake_locked(0); + spin_unlock_irqrestore(&bt_lpm.uport->lock, flags); + + 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); +} +EXPORT_SYMBOL(bcm_bt_lpm_exit_lpm_locked); + +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.wake_lock); + if (!host_wake_uart_enabled) + omap_uart_enable(2); + } else { + if (host_wake_uart_enabled) + omap_uart_disable(2); + // 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.wake_lock, HZ/2); + } + + host_wake_uart_enabled = host_wake; + +} + +static irqreturn_t host_wake_isr(int irq, void *dev) +{ + int host_wake; + unsigned long flags; + + host_wake = gpio_get_value(BT_HOST_WAKE_GPIO); + 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; + } + + spin_lock_irqsave(&bt_lpm.uport->lock, flags); + update_host_wake_locked(host_wake); + spin_unlock_irqrestore(&bt_lpm.uport->lock, flags); + + return IRQ_HANDLED; +} + +static int bcm_bt_lpm_init(struct platform_device *pdev) +{ + int irq; + int ret; + int rc; + + rc = gpio_request(BT_WAKE_GPIO, "bcm4330_wake_gpio"); + if (unlikely(rc)) { + return rc; + } + + rc = gpio_request(BT_HOST_WAKE_GPIO, "bcm4330_host_wake_gpio"); + if (unlikely(rc)) { + gpio_free(BT_WAKE_GPIO); + return rc; + } + + hrtimer_init(&bt_lpm.enter_lpm_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + bt_lpm.enter_lpm_delay = ktime_set(1, 0); /* 1 sec */ + bt_lpm.enter_lpm_timer.function = enter_lpm; + + bt_lpm.host_wake = 0; + + irq = gpio_to_irq(BT_HOST_WAKE_GPIO); + ret = request_irq(irq, host_wake_isr, IRQF_TRIGGER_HIGH, + "bt host_wake", NULL); + if (ret) { + gpio_free(BT_WAKE_GPIO); + gpio_free(BT_HOST_WAKE_GPIO); + return ret; + } + + ret = irq_set_irq_wake(irq, 1); + if (ret) { + gpio_free(BT_WAKE_GPIO); + gpio_free(BT_HOST_WAKE_GPIO); + return ret; + } + + gpio_direction_output(BT_WAKE_GPIO, 0); + gpio_direction_input(BT_HOST_WAKE_GPIO); + + snprintf(bt_lpm.wake_lock_name, sizeof(bt_lpm.wake_lock_name), + "BTLowPower"); + wake_lock_init(&bt_lpm.wake_lock, WAKE_LOCK_SUSPEND, + bt_lpm.wake_lock_name); + return 0; +} + +static int bcm4330_bluetooth_probe(struct platform_device *pdev) +{ + int rc = 0; + int ret = 0; + + rc = gpio_request(BT_RESET_GPIO, "bcm4330_nreset_gpip"); + if (unlikely(rc)) { + return rc; + } + + rc = gpio_request(BT_REG_GPIO, "bcm4330_nshutdown_gpio"); + if (unlikely(rc)) { + gpio_free(BT_RESET_GPIO); + return rc; + } + + clk32kaudio_reg = regulator_get(0, "clk32kaudio"); + if (IS_ERR(clk32kaudio_reg)) { + pr_err("clk32kaudio reg not found!\n"); + clk32kaudio_reg = NULL; + } + + gpio_direction_output(BT_REG_GPIO, 1); + gpio_direction_output(BT_RESET_GPIO, 1); + + bt_rfkill = rfkill_alloc("bcm4330 Bluetooth", &pdev->dev, + RFKILL_TYPE_BLUETOOTH, &bcm4330_bt_rfkill_ops, + NULL); + + if (unlikely(!bt_rfkill)) { + gpio_free(BT_RESET_GPIO); + gpio_free(BT_REG_GPIO); + return -ENOMEM; + } + + rc = rfkill_register(bt_rfkill); + + if (unlikely(rc)) { + rfkill_destroy(bt_rfkill); + gpio_free(BT_RESET_GPIO); + gpio_free(BT_REG_GPIO); + return -1; + } + + rfkill_set_states(bt_rfkill, true, false); + bcm4330_bt_rfkill_set_power(NULL, true); + + ret = bcm_bt_lpm_init(pdev); + if (ret) { + rfkill_unregister(bt_rfkill); + rfkill_destroy(bt_rfkill); + + gpio_free(BT_RESET_GPIO); + gpio_free(BT_REG_GPIO); + } + + return ret; +} + +static int bcm4330_bluetooth_remove(struct platform_device *pdev) +{ + rfkill_unregister(bt_rfkill); + rfkill_destroy(bt_rfkill); + + gpio_free(BT_REG_GPIO); + gpio_free(BT_RESET_GPIO); + gpio_free(BT_WAKE_GPIO); + gpio_free(BT_HOST_WAKE_GPIO); + regulator_put(clk32kaudio_reg); + + wake_lock_destroy(&bt_lpm.wake_lock); + return 0; +} + +int bcm4430_bluetooth_suspend(struct platform_device *pdev, pm_message_t state) +{ + int irq = gpio_to_irq(BT_HOST_WAKE_GPIO); + int host_wake; + + disable_irq(irq); + host_wake = gpio_get_value(BT_HOST_WAKE_GPIO); + + if (host_wake) { + enable_irq(irq); + return -EBUSY; + } + + return 0; +} + +int bcm4430_bluetooth_resume(struct platform_device *pdev) +{ + int irq = gpio_to_irq(BT_HOST_WAKE_GPIO); + enable_irq(irq); + return 0; +} + +static struct platform_driver bcm4330_bluetooth_platform_driver = { + .probe = bcm4330_bluetooth_probe, + .remove = bcm4330_bluetooth_remove, + .suspend = bcm4430_bluetooth_suspend, + .resume = bcm4430_bluetooth_resume, + .driver = { + .name = "bcm4330_bluetooth", + .owner = THIS_MODULE, + }, +}; + +static int __init bcm4330_bluetooth_init(void) +{ + bt_enabled = false; + return platform_driver_register(&bcm4330_bluetooth_platform_driver); +} + +static void __exit bcm4330_bluetooth_exit(void) +{ + platform_driver_unregister(&bcm4330_bluetooth_platform_driver); +} + + +module_init(bcm4330_bluetooth_init); +module_exit(bcm4330_bluetooth_exit); + +MODULE_ALIAS("platform:bcm4330"); +MODULE_DESCRIPTION("bcm4330_bluetooth"); +MODULE_AUTHOR("Jaikumar Ganesh <jaikumar@google.com>"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-omap2/board-tuna-connector.c b/arch/arm/mach-omap2/board-tuna-connector.c new file mode 100644 index 0000000..7c09aef --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-connector.c @@ -0,0 +1,982 @@ +/* + * Copyright (C) 2011 Samsung, Inc. + * Copyright (C) 2011 Google Inc. + * + * Author: Adam Hampson <ahampson@sta.samsung.com> + * Dima Zavin <dima@android.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/device.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/platform_data/fsa9480.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/otg.h> +#include <linux/delay.h> +#include <linux/sii9234.h> +#include <linux/i2c/twl.h> +#include <linux/mutex.h> +#include <linux/switch.h> + +#include <plat/usb.h> + +#include "mux.h" +#include "board-tuna.h" + +#define GPIO_JACK_INT_N 4 +#define GPIO_CP_USB_ON 22 +#define GPIO_USB_OTG_ID 24 +#define GPIO_MHL_SEL 96 +#define GPIO_AP_SEL 97 +#define GPIO_MUX3_SEL0 139 +#define GPIO_MUX3_SEL1 140 +#define GPIO_USB_ID_SEL 191 +#define GPIO_IF_UART_SEL 101 + +#define GPIO_MHL_RST 161 +#define GPIO_MHL_WAKEUP 64 +#define GPIO_MHL_INT 175 +#define GPIO_HDMI_EN 100 + +#define MUX3_SEL0_AP 1 +#define MUX3_SEL1_AP 1 +#define MUX3_SEL0_MHL 1 +#define MUX3_SEL1_MHL 0 +#define MUX3_SEL0_FSA 0 +#define MUX3_SEL1_FSA 1 + +#define FSA3200_AP_SEL_AP 0 +#define FSA3200_MHL_SEL_AP 0 +#define FSA3200_AP_SEL_FSA 1 +#define FSA3200_MHL_SEL_FSA 0 +#define FSA3200_AP_SEL_MHL 1 +#define FSA3200_MHL_SEL_MHL 1 + +#define USB_ID_SEL_FSA 0 +#define USB_ID_SEL_MHL 1 + +#define IF_UART_SEL_DEFAULT 1 +#define IF_UART_SEL_AP 1 +#define IF_UART_SEL_CP 0 + +#define TUNA_MANUAL_USB_NONE 0 +#define TUNA_MANUAL_USB_MODEM 1 +#define TUNA_MANUAL_USB_AP 2 + +#define TUNA_MANUAL_UART_NONE 0 +#define TUNA_MANUAL_UART_MODEM 1 +#define TUNA_MANUAL_UART_LTE 2 +#define TUNA_MANUAL_UART_AP 3 + +#define CHARGERUSB_CTRL1 0x8 +#define CHARGERUSB_CTRL3 0xA +#define CHARGERUSB_CINLIMIT 0xE + +#define TWL6030_VBUS_IRQ (TWL6030_IRQ_BASE + USB_PRES_INTR_OFFSET) +#define TWL6030_VBUS_FLAGS (IRQF_TRIGGER_FALLING | IRQF_ONESHOT) + +#define TWL_REG_CONTROLLER_INT_MASK 0x00 +#define TWL_CONTROLLER_MVBUS_DET BIT(1) +#define TWL_CONTROLLER_RSVD BIT(5) + +#define TWL_REG_CONTROLLER_STAT1 0x03 +#define TWL_STAT1_VBUS_DET BIT(2) + +struct tuna_otg { + struct otg_transceiver otg; + struct device dev; + + struct regulator *vusb; + struct work_struct set_vbus_work; + struct mutex lock; + + bool reg_on; + bool need_vbus_drive; + int usb_manual_mode; + int uart_manual_mode; + int current_device; + + struct switch_dev dock_switch; +}; +static struct tuna_otg tuna_otg_xceiv; + +enum { + TUNA_USB_MUX_FSA = 0, + TUNA_USB_MUX_MHL, + TUNA_USB_MUX_AP, + NUM_TUNA_USB_MUX, + + TUNA_USB_MUX_DEFAULT = TUNA_USB_MUX_FSA, +}; + +static struct { + int mux3_sel0; + int mux3_sel1; +} tuna_usb_mux_states[] = { + [TUNA_USB_MUX_FSA] = { MUX3_SEL0_FSA, MUX3_SEL1_FSA }, + [TUNA_USB_MUX_MHL] = { MUX3_SEL0_MHL, MUX3_SEL1_MHL }, + [TUNA_USB_MUX_AP] = { MUX3_SEL0_AP, MUX3_SEL1_AP }, +}; + +static struct { + int ap_sel; + int mhl_sel; +} tuna_fsa3200_mux_pair_states[] = { + [TUNA_USB_MUX_FSA] = { FSA3200_AP_SEL_FSA, FSA3200_MHL_SEL_FSA }, + [TUNA_USB_MUX_MHL] = { FSA3200_AP_SEL_MHL, FSA3200_MHL_SEL_MHL }, + [TUNA_USB_MUX_AP] = { FSA3200_AP_SEL_AP, FSA3200_MHL_SEL_AP }, +}; + +static int tuna_usb_id_mux_states[] = { + [TUNA_USB_MUX_FSA] = USB_ID_SEL_FSA, + [TUNA_USB_MUX_MHL] = USB_ID_SEL_MHL, + [TUNA_USB_MUX_AP] = USB_ID_SEL_FSA, +}; + +static ssize_t tuna_otg_usb_sel_show(struct device *dev, + struct device_attribute *attr, + char *buf); +static ssize_t tuna_otg_usb_sel_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size); +static ssize_t tuna_otg_uart_switch_show(struct device *dev, + struct device_attribute *attr, + char *buf); +static ssize_t tuna_otg_uart_switch_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size); + +static DEVICE_ATTR(usb_sel, S_IRUSR | S_IWUSR, + tuna_otg_usb_sel_show, tuna_otg_usb_sel_store); +static DEVICE_ATTR(uart_sel, S_IRUSR | S_IWUSR, + tuna_otg_uart_switch_show, tuna_otg_uart_switch_store); + +static struct attribute *manual_mode_attributes[] = { + &dev_attr_usb_sel.attr, + &dev_attr_uart_sel.attr, + NULL, +}; + +static const struct attribute_group manual_mode_group = { + .attrs = manual_mode_attributes, +}; + +static bool tuna_twl_chgctrl_init; + +static void tuna_mux_usb(int state) +{ + BUG_ON(state >= NUM_TUNA_USB_MUX); + + pr_debug("mux to %d\n", state); + gpio_direction_output(GPIO_MUX3_SEL0, + tuna_usb_mux_states[state].mux3_sel0); + gpio_direction_output(GPIO_MUX3_SEL1, + tuna_usb_mux_states[state].mux3_sel1); +} + +static void tuna_mux_usb_id(int state) +{ + BUG_ON(state >= NUM_TUNA_USB_MUX); + + pr_debug("mux to %d\n", state); + gpio_direction_output(GPIO_USB_ID_SEL, tuna_usb_id_mux_states[state]); +} + +static void tuna_fsa3200_mux_pair(int state) +{ + BUG_ON(state >= NUM_TUNA_USB_MUX); + + pr_debug("mux to %d\n", state); + gpio_direction_output(GPIO_AP_SEL, + tuna_fsa3200_mux_pair_states[state].ap_sel); + gpio_direction_output(GPIO_MHL_SEL, + tuna_fsa3200_mux_pair_states[state].mhl_sel); +} + +static void tuna_mux_usb_to_fsa(bool enable) +{ + if (omap4_tuna_get_revision() >= 3) { + tuna_fsa3200_mux_pair(enable ? TUNA_USB_MUX_FSA : + TUNA_USB_MUX_DEFAULT); + } else { + tuna_mux_usb(enable ? TUNA_USB_MUX_FSA : TUNA_USB_MUX_DEFAULT); + + /* When switching ID away from FSA, we want to ensure we switch + * it off FSA, and force it to MHL. Ideally, we'd just say mux + * to default, but FSA is likely the default mux position and + * there's no way to force the ID pin to float to the FSA. + */ + tuna_mux_usb_id(enable ? TUNA_USB_MUX_FSA : TUNA_USB_MUX_MHL); + } +} + +static void tuna_mux_usb_to_mhl(bool enable) +{ + if (omap4_tuna_get_revision() >= 3) { + tuna_fsa3200_mux_pair(enable ? TUNA_USB_MUX_MHL : + TUNA_USB_MUX_DEFAULT); + } else { + tuna_mux_usb(enable ? TUNA_USB_MUX_MHL : TUNA_USB_MUX_DEFAULT); + tuna_mux_usb_id(enable ? TUNA_USB_MUX_MHL : TUNA_USB_MUX_DEFAULT); + } +} + +static void tuna_vusb_enable(struct tuna_otg *tuna_otg, bool enable) +{ + /* delay getting the regulator until later */ + if (IS_ERR_OR_NULL(tuna_otg->vusb)) { + tuna_otg->vusb = regulator_get(&tuna_otg->dev, "vusb"); + if (IS_ERR(tuna_otg->vusb)) { + dev_err(&tuna_otg->dev, "cannot get vusb regulator\n"); + return; + } + } + + if (enable) { + regulator_enable(tuna_otg->vusb); + tuna_otg->reg_on = true; + } else if (tuna_otg->reg_on) { + regulator_disable(tuna_otg->vusb); + tuna_otg->reg_on = false; + } +} + +static void tuna_set_vbus_drive(bool enable) +{ + if (enable) { + /* Set the VBUS current limit to 500mA */ + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x09, + CHARGERUSB_CINLIMIT); + + /* The TWL6030 has a feature to automatically turn on + * boost mode (VBUS Drive) when the ID signal is not + * grounded. This feature needs to be disabled on Tuna + * as the ID signal is not hooked up to the TWL6030. + */ + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x21, + CHARGERUSB_CTRL3); + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x40, + CHARGERUSB_CTRL1); + } else { + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x01, + CHARGERUSB_CTRL3); + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0, CHARGERUSB_CTRL1); + } +} + +static void tuna_ap_usb_attach(struct tuna_otg *tuna_otg) +{ + tuna_vusb_enable(tuna_otg, true); + + if (omap4_tuna_get_revision() >= 3) { + tuna_fsa3200_mux_pair(TUNA_USB_MUX_AP); + } else { + tuna_mux_usb(TUNA_USB_MUX_AP); + tuna_mux_usb_id(TUNA_USB_MUX_FSA); + } + + tuna_otg->otg.state = OTG_STATE_B_IDLE; + tuna_otg->otg.default_a = false; + tuna_otg->otg.last_event = USB_EVENT_VBUS; + atomic_notifier_call_chain(&tuna_otg->otg.notifier, + USB_EVENT_VBUS, + tuna_otg->otg.gadget); +} + +static void tuna_ap_usb_detach(struct tuna_otg *tuna_otg) +{ + tuna_vusb_enable(tuna_otg, false); + + tuna_otg->otg.state = OTG_STATE_B_IDLE; + tuna_otg->otg.default_a = false; + tuna_otg->otg.last_event = USB_EVENT_NONE; + atomic_notifier_call_chain(&tuna_otg->otg.notifier, + USB_EVENT_NONE, + tuna_otg->otg.gadget); +} + +static void tuna_cp_usb_attach(struct tuna_otg *tuna_otg) +{ + if (omap4_tuna_get_type() == TUNA_TYPE_MAGURO) + gpio_set_value(GPIO_CP_USB_ON, 1); + + tuna_mux_usb_to_fsa(true); +} + +static void tuna_cp_usb_detach(struct tuna_otg *tuna_otg) +{ + if (omap4_tuna_get_type() == TUNA_TYPE_MAGURO) + gpio_set_value(GPIO_CP_USB_ON, 0); +} + +static void tuna_usb_host_detach(struct tuna_otg *tuna_otg) +{ + /* Make sure the VBUS drive is turned off */ + tuna_set_vbus_drive(false); + + tuna_vusb_enable(tuna_otg, false); + + tuna_otg->otg.state = OTG_STATE_B_IDLE; + tuna_otg->otg.default_a = false; + tuna_otg->otg.last_event = USB_EVENT_NONE; + atomic_notifier_call_chain(&tuna_otg->otg.notifier, + USB_EVENT_NONE, + tuna_otg->otg.gadget); +} + +static void tuna_ap_uart_actions(struct tuna_otg *tuna_otg) +{ + tuna_mux_usb_to_fsa(true); + gpio_set_value(GPIO_IF_UART_SEL, IF_UART_SEL_AP); +} + +static void tuna_cp_uart_actions(struct tuna_otg *tuna_otg) +{ + tuna_mux_usb_to_fsa(true); + gpio_set_value(GPIO_IF_UART_SEL, IF_UART_SEL_CP); +} + +static void tuna_lte_uart_actions(struct tuna_otg *tuna_otg) +{ + tuna_mux_usb_to_fsa(true); + + /* The LTE modem's UART lines are connected to the V_AUDIO_L and + * V_AUDIO_R pins on the FSA9480. The RIL will configure the FSA9480 + * separately to set manual routing. + */ +} + +static void tuna_otg_mask_vbus_irq(void) +{ + twl6030_interrupt_mask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_LINE_C); + twl6030_interrupt_mask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_STS_C); +} + +static void tuna_otg_unmask_vbus_irq(void) +{ + if (!tuna_twl_chgctrl_init) { + int r; + + r = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + (u8) ~(TWL_CONTROLLER_RSVD | + TWL_CONTROLLER_MVBUS_DET), + TWL_REG_CONTROLLER_INT_MASK); + + if (r) + pr_err_once("%s: Error writing twl charge ctrl int mask\n", + __func__); + else + tuna_twl_chgctrl_init = true; + } + + twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_LINE_C); + twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_STS_C); +} + +static bool tuna_otg_vbus_present(void) +{ + u8 vbus_state; + + twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &vbus_state, + TWL_REG_CONTROLLER_STAT1); + + return !!(vbus_state & TWL_STAT1_VBUS_DET); +} + +static void tuna_fsa_usb_detected(int device) +{ + struct tuna_otg *tuna_otg = &tuna_otg_xceiv; + int old_device; + + mutex_lock(&tuna_otg->lock); + + old_device = tuna_otg->current_device; + tuna_otg->current_device = device; + + pr_debug("detected %x\n", device); + switch (device) { + case FSA9480_DETECT_AV_365K_CHARGER: + tuna_otg_set_dock_switch(1); + /* intentional fall-through */ + case FSA9480_DETECT_USB: + if (tuna_otg->usb_manual_mode == TUNA_MANUAL_USB_MODEM) + tuna_cp_usb_attach(tuna_otg); + else + tuna_ap_usb_attach(tuna_otg); + break; + case FSA9480_DETECT_CHARGER: + tuna_mux_usb_to_fsa(true); + + tuna_otg->otg.state = OTG_STATE_B_IDLE; + tuna_otg->otg.default_a = false; + tuna_otg->otg.last_event = USB_EVENT_CHARGER; + atomic_notifier_call_chain(&tuna_otg->otg.notifier, + USB_EVENT_CHARGER, + tuna_otg->otg.gadget); + break; + case FSA9480_DETECT_USB_HOST: + tuna_vusb_enable(tuna_otg, true); + + if (omap4_tuna_get_revision() >= 3) { + tuna_fsa3200_mux_pair(TUNA_USB_MUX_AP); + } else { + tuna_mux_usb(TUNA_USB_MUX_AP); + tuna_mux_usb_id(TUNA_USB_MUX_FSA); + } + + tuna_otg->otg.state = OTG_STATE_A_IDLE; + tuna_otg->otg.default_a = true; + tuna_otg->otg.last_event = USB_EVENT_ID; + atomic_notifier_call_chain(&tuna_otg->otg.notifier, + USB_EVENT_ID, + tuna_otg->otg.gadget); + break; + case FSA9480_DETECT_NONE: + tuna_mux_usb_to_fsa(true); + + switch (old_device) { + case FSA9480_DETECT_JIG: + if (tuna_otg->uart_manual_mode == TUNA_MANUAL_UART_NONE) + tuna_ap_uart_actions(tuna_otg); + break; + case FSA9480_DETECT_AV_365K_CHARGER: + tuna_otg_set_dock_switch(0); + /* intentional fall-through */ + case FSA9480_DETECT_USB: + if (tuna_otg->usb_manual_mode == TUNA_MANUAL_USB_MODEM) + tuna_cp_usb_detach(tuna_otg); + else + tuna_ap_usb_detach(tuna_otg); + break; + case FSA9480_DETECT_USB_HOST: + tuna_usb_host_detach(tuna_otg); + break; + case FSA9480_DETECT_CHARGER: + tuna_ap_usb_detach(tuna_otg); + break; + case FSA9480_DETECT_AV_365K: + tuna_otg_set_dock_switch(0); + break; + case FSA9480_DETECT_UART: + default: + break; + }; + break; + case FSA9480_DETECT_JIG: + switch (tuna_otg->uart_manual_mode) { + case TUNA_MANUAL_UART_AP: + tuna_ap_uart_actions(tuna_otg); + break; + case TUNA_MANUAL_UART_LTE: + tuna_lte_uart_actions(tuna_otg); + break; + case TUNA_MANUAL_UART_MODEM: + default: + tuna_cp_uart_actions(tuna_otg); + break; + }; + break; + case FSA9480_DETECT_AV_365K: + tuna_otg_set_dock_switch(1); + break; + case FSA9480_DETECT_UART: + default: + break; + } + + mutex_unlock(&tuna_otg->lock); +} + +static struct fsa9480_detect_set fsa_detect_sets[] = { + { + .prio = TUNA_OTG_ID_FSA9480_PRIO, + .mask = FSA9480_DETECT_ALL, + }, + { + .prio = TUNA_OTG_ID_FSA9480_LAST_PRIO, + .mask = 0, + .fallback = true, + }, +}; + +static struct fsa9480_platform_data tuna_fsa9480_pdata = { + .detect_time = 500, + .detect_sets = fsa_detect_sets, + .num_sets = ARRAY_SIZE(fsa_detect_sets), + + .enable = tuna_mux_usb_to_fsa, + .detected = tuna_fsa_usb_detected, + .external_id = GPIO_USB_OTG_ID, + + .external_vbus_irq = TWL6030_VBUS_IRQ, + .external_vbus_flags = TWL6030_VBUS_FLAGS, + .mask_vbus_irq = tuna_otg_mask_vbus_irq, + .unmask_vbus_irq = tuna_otg_unmask_vbus_irq, + .vbus_present = tuna_otg_vbus_present, +}; + +static struct i2c_board_info __initdata tuna_connector_i2c4_boardinfo[] = { + { + I2C_BOARD_INFO("fsa9480", 0x4A >> 1), + .irq = OMAP_GPIO_IRQ(GPIO_JACK_INT_N), + .platform_data = &tuna_fsa9480_pdata, + }, +}; + +static int tuna_otg_set_host(struct otg_transceiver *otg, struct usb_bus *host) +{ + otg->host = host; + if (!host) + otg->state = OTG_STATE_UNDEFINED; + return 0; +} + +static int tuna_otg_set_peripheral(struct otg_transceiver *otg, + struct usb_gadget *gadget) +{ + otg->gadget = gadget; + if (!gadget) + otg->state = OTG_STATE_UNDEFINED; + return 0; +} + +static void tuna_otg_work(struct work_struct *data) +{ + struct tuna_otg *tuna_otg = container_of(data, struct tuna_otg, + set_vbus_work); + + mutex_lock(&tuna_otg->lock); + + /* Only allow VBUS drive when in host mode. */ + if (tuna_otg->current_device != FSA9480_DETECT_USB_HOST) { + mutex_unlock(&tuna_otg->lock); + return; + } + + tuna_set_vbus_drive(tuna_otg->need_vbus_drive); + + mutex_unlock(&tuna_otg->lock); +} + +static int tuna_otg_set_vbus(struct otg_transceiver *otg, bool enabled) +{ + struct tuna_otg *tuna_otg = container_of(otg, struct tuna_otg, otg); + + dev_dbg(otg->dev, "vbus %s\n", enabled ? "on" : "off"); + + tuna_otg->need_vbus_drive = enabled; + schedule_work(&tuna_otg->set_vbus_work); + + return 0; +} + +static int tuna_otg_phy_init(struct otg_transceiver *otg) +{ + if (otg->last_event == USB_EVENT_ID) + omap4430_phy_power(otg->dev, 1, 1); + else + omap4430_phy_power(otg->dev, 0, 1); + return 0; +} + +static void tuna_otg_phy_shutdown(struct otg_transceiver *otg) +{ + omap4430_phy_power(otg->dev, 0, 0); +} + +static int tuna_otg_set_suspend(struct otg_transceiver *otg, int suspend) +{ + return omap4430_phy_suspend(otg->dev, suspend); +} + +static ssize_t tuna_otg_usb_sel_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tuna_otg *tuna_otg = dev_get_drvdata(dev); + const char* mode; + + switch (tuna_otg->usb_manual_mode) { + case TUNA_MANUAL_USB_AP: + mode = "PDA"; + break; + case TUNA_MANUAL_USB_MODEM: + mode = "MODEM"; + break; + default: + mode = "NONE"; + }; + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t tuna_otg_usb_sel_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct tuna_otg *tuna_otg = dev_get_drvdata(dev); + int old_mode; + + mutex_lock(&tuna_otg->lock); + + old_mode = tuna_otg->usb_manual_mode; + + if (!strncasecmp(buf, "PDA", 3)) { + tuna_otg->usb_manual_mode = TUNA_MANUAL_USB_AP; + + /* If we are transitioning from CP USB to AP USB then notify the + * USB stack that is now attached. + */ + if (tuna_otg->current_device == FSA9480_DETECT_USB && + old_mode == TUNA_MANUAL_USB_MODEM) { + tuna_cp_usb_detach(tuna_otg); + tuna_ap_usb_attach(tuna_otg); + } + } else if (!strncasecmp(buf, "MODEM", 5)) { + tuna_otg->usb_manual_mode = TUNA_MANUAL_USB_MODEM; + + /* If we are transitioning from AP USB to CP USB then notify the + * USB stack that is has been detached. + */ + if (tuna_otg->current_device == FSA9480_DETECT_USB && + (old_mode == TUNA_MANUAL_USB_AP || + old_mode == TUNA_MANUAL_USB_NONE)) { + tuna_ap_usb_detach(tuna_otg); + tuna_cp_usb_attach(tuna_otg); + } + } else if (!strncasecmp(buf, "NONE", 4)) { + tuna_otg->usb_manual_mode = TUNA_MANUAL_USB_NONE; + + /* If we are transitioning from CP USB to AP USB then notify the + * USB stack that it is now attached. + */ + if (tuna_otg->current_device == FSA9480_DETECT_USB && + old_mode == TUNA_MANUAL_USB_MODEM) { + tuna_cp_usb_detach(tuna_otg); + tuna_ap_usb_attach(tuna_otg); + } + } + + mutex_unlock(&tuna_otg->lock); + + return size; +} + +static ssize_t tuna_otg_uart_switch_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tuna_otg *tuna_otg = dev_get_drvdata(dev); + const char* mode; + + switch (tuna_otg->uart_manual_mode) { + case TUNA_MANUAL_UART_AP: + mode = "PDA"; + break; + case TUNA_MANUAL_UART_MODEM: + mode = "MODEM"; + break; + case TUNA_MANUAL_UART_LTE: + mode = "LTEMODEM"; + break; + default: + mode = "NONE"; + }; + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t tuna_otg_uart_switch_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct tuna_otg *tuna_otg = dev_get_drvdata(dev); + + mutex_lock(&tuna_otg->lock); + + if (!strncasecmp(buf, "PDA", 3)) { + tuna_otg->uart_manual_mode = TUNA_MANUAL_UART_AP; + + if (tuna_otg->current_device == FSA9480_DETECT_JIG) + tuna_ap_uart_actions(tuna_otg); + } else if (!strncasecmp(buf, "MODEM", 5)) { + tuna_otg->uart_manual_mode = TUNA_MANUAL_UART_MODEM; + + if (tuna_otg->current_device == FSA9480_DETECT_JIG) + tuna_cp_uart_actions(tuna_otg); + } else if (!strncasecmp(buf, "LTEMODEM", 8) && + omap4_tuna_get_type() == TUNA_TYPE_TORO) { + tuna_otg->uart_manual_mode = TUNA_MANUAL_UART_LTE; + + if (tuna_otg->current_device == FSA9480_DETECT_JIG) + tuna_lte_uart_actions(tuna_otg); + } else if (!strncasecmp(buf, "NONE", 4)) { + tuna_otg->uart_manual_mode = TUNA_MANUAL_UART_NONE; + + if (tuna_otg->current_device == FSA9480_DETECT_JIG) + tuna_ap_uart_actions(tuna_otg); + } + + mutex_unlock(&tuna_otg->lock); + + return size; +} + +#define OMAP_HDMI_HPD_ADDR 0x4A100098 +#define OMAP_HDMI_PULLTYPE_MASK 0x00000010 +static void sii9234_power(int on) +{ + struct omap_mux_partition *p = omap_mux_get("core"); + + u16 mux; + + mux = omap_mux_read(p, OMAP4_CTRL_MODULE_PAD_HDMI_HPD_OFFSET); + + if (on) { + gpio_set_value(GPIO_HDMI_EN, 1); + msleep(20); + gpio_set_value(GPIO_MHL_RST, 1); + + omap_mux_write(p, mux | OMAP_PULL_UP, + OMAP4_CTRL_MODULE_PAD_HDMI_HPD_OFFSET); + } else { + omap_mux_write(p, mux & ~OMAP_PULL_UP, + OMAP4_CTRL_MODULE_PAD_HDMI_HPD_OFFSET); + + gpio_set_value(GPIO_HDMI_EN, 0); + gpio_set_value(GPIO_MHL_RST, 0); + + } +} + +static void sii9234_enable_vbus(bool enable) +{ + +} + +static void sii9234_connect(bool on, u8 *devcap) +{ + struct tuna_otg *tuna_otg = &tuna_otg_xceiv; + unsigned long val; + int dock = 0; + + if (on) { + val = USB_EVENT_VBUS; + if (devcap) { + u16 adopter_id = + (devcap[MHL_DEVCAP_ADOPTER_ID_H] << 8) | + devcap[MHL_DEVCAP_ADOPTER_ID_L]; + u16 device_id = + (devcap[MHL_DEVCAP_DEVICE_ID_H] << 8) | + devcap[MHL_DEVCAP_DEVICE_ID_L]; + + if (adopter_id == 0x3333 || adopter_id == 321) { + if (devcap[MHL_DEVCAP_RESERVED] == 2) + val = USB_EVENT_CHARGER; + + if (device_id == 0x1234) + dock = 1; + } + } + } else { + val = USB_EVENT_NONE; + } + + tuna_otg->otg.state = OTG_STATE_B_IDLE; + tuna_otg->otg.default_a = false; + tuna_otg->otg.last_event = val; + + atomic_notifier_call_chain(&tuna_otg->otg.notifier, + val, tuna_otg->otg.gadget); + tuna_otg_set_dock_switch(dock); + +} + +void tuna_otg_pogo_charger(enum pogo_power_state pogo_state) +{ + struct tuna_otg *tuna_otg = &tuna_otg_xceiv; + unsigned long power_state; + + switch (pogo_state) { + case POGO_POWER_CHARGER: + power_state = USB_EVENT_CHARGER; + break; + case POGO_POWER_HOST: + power_state = USB_EVENT_VBUS; + break; + case POGO_POWER_DISCONNECTED: + default: + power_state = USB_EVENT_NONE; + break; + } + + tuna_otg->otg.state = OTG_STATE_B_IDLE; + tuna_otg->otg.default_a = false; + tuna_otg->otg.last_event = power_state; + atomic_notifier_call_chain(&tuna_otg->otg.notifier, power_state, + tuna_otg->otg.gadget); +} + +void tuna_otg_set_dock_switch(int enable) +{ + struct tuna_otg *tuna_otg = &tuna_otg_xceiv; + + switch_set_state(&tuna_otg->dock_switch, enable); +} + +static struct sii9234_platform_data sii9234_pdata = { + .prio = TUNA_OTG_ID_SII9234_PRIO, + .enable = tuna_mux_usb_to_mhl, + .power = sii9234_power, + .enable_vbus = sii9234_enable_vbus, + .connect = sii9234_connect, +}; + +static struct i2c_board_info __initdata tuna_i2c5_boardinfo[] = { + { + I2C_BOARD_INFO("sii9234_mhl_tx", 0x72>>1), + .irq = OMAP_GPIO_IRQ(GPIO_MHL_INT), + .platform_data = &sii9234_pdata, + }, + { + I2C_BOARD_INFO("sii9234_tpi", 0x7A>>1), + .platform_data = &sii9234_pdata, + }, + { + I2C_BOARD_INFO("sii9234_hdmi_rx", 0x92>>1), + .platform_data = &sii9234_pdata, + }, + { + I2C_BOARD_INFO("sii9234_cbus", 0xC8>>1), + .platform_data = &sii9234_pdata, + }, +}; + +int __init omap4_tuna_connector_init(void) +{ + struct tuna_otg *tuna_otg = &tuna_otg_xceiv; + int ret; + + if (omap4_tuna_get_revision() >= 3) { + gpio_request(GPIO_MHL_SEL, "fsa3200_mhl_sel"); + gpio_request(GPIO_AP_SEL, "fsa3200_ap_sel"); + + tuna_fsa3200_mux_pair(TUNA_USB_MUX_DEFAULT); + + omap_mux_init_gpio(GPIO_MHL_SEL, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(GPIO_AP_SEL, OMAP_PIN_OUTPUT); + } else { + gpio_request(GPIO_MUX3_SEL0, "usb_mux3_sel0"); + gpio_request(GPIO_MUX3_SEL1, "usb_mux3_sel1"); + gpio_request(GPIO_USB_ID_SEL, "usb_id_sel"); + + tuna_mux_usb(TUNA_USB_MUX_DEFAULT); + tuna_mux_usb_id(TUNA_USB_MUX_DEFAULT); + + omap_mux_init_gpio(GPIO_MUX3_SEL0, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(GPIO_MUX3_SEL1, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(GPIO_USB_ID_SEL, OMAP_PIN_OUTPUT); + } + + if (omap4_tuna_get_type() == TUNA_TYPE_MAGURO) { + gpio_request(GPIO_CP_USB_ON, "cp_usb_on"); + omap_mux_init_gpio(GPIO_CP_USB_ON, OMAP_PIN_OUTPUT); + gpio_direction_output(GPIO_CP_USB_ON, 0); + } + + omap_mux_init_gpio(GPIO_IF_UART_SEL, OMAP_PIN_OUTPUT); + gpio_request(GPIO_IF_UART_SEL, "uart_sel"); + gpio_direction_output(GPIO_IF_UART_SEL, IF_UART_SEL_DEFAULT); + + omap_mux_init_gpio(GPIO_USB_OTG_ID, OMAP_PIN_INPUT | + OMAP_WAKEUP_EN); + + omap_mux_init_gpio(GPIO_JACK_INT_N, + OMAP_PIN_INPUT_PULLUP | + OMAP_PIN_OFF_INPUT_PULLUP); + + mutex_init(&tuna_otg->lock); + + INIT_WORK(&tuna_otg->set_vbus_work, tuna_otg_work); + + device_initialize(&tuna_otg->dev); + dev_set_name(&tuna_otg->dev, "%s", "tuna_otg"); + ret = device_add(&tuna_otg->dev); + if (ret) { + pr_err("%s: cannot reg device '%s' (%d)\n", __func__, + dev_name(&tuna_otg->dev), ret); + return ret; + } + + dev_set_drvdata(&tuna_otg->dev, tuna_otg); + + tuna_otg->otg.dev = &tuna_otg->dev; + tuna_otg->otg.label = "tuna_otg_xceiv"; + tuna_otg->otg.set_host = tuna_otg_set_host; + tuna_otg->otg.set_peripheral = tuna_otg_set_peripheral; + tuna_otg->otg.set_suspend = tuna_otg_set_suspend; + tuna_otg->otg.set_vbus = tuna_otg_set_vbus; + tuna_otg->otg.init = tuna_otg_phy_init; + tuna_otg->otg.shutdown = tuna_otg_phy_shutdown; + + ATOMIC_INIT_NOTIFIER_HEAD(&tuna_otg->otg.notifier); + + ret = otg_set_transceiver(&tuna_otg->otg); + if (ret) + pr_err("tuna_otg: cannot set transceiver (%d)\n", ret); + + omap4430_phy_init(&tuna_otg->dev); + tuna_otg_set_suspend(&tuna_otg->otg, 0); + + i2c_register_board_info(4, tuna_connector_i2c4_boardinfo, + ARRAY_SIZE(tuna_connector_i2c4_boardinfo)); + + ret = sysfs_create_group(&tuna_otg->dev.kobj, &manual_mode_group); + if (ret) + pr_err("tuna_otg: Unable to create manual mode sysfs group" + "(%d)\n", ret); + + gpio_request(GPIO_HDMI_EN, NULL); + omap_mux_init_gpio(GPIO_HDMI_EN, OMAP_PIN_OUTPUT); + gpio_direction_output(GPIO_HDMI_EN, 0); + + gpio_request(GPIO_MHL_RST, NULL); + omap_mux_init_gpio(GPIO_MHL_RST, OMAP_PIN_OUTPUT); + gpio_direction_output(GPIO_MHL_RST, 0); + + gpio_request(GPIO_MHL_INT, NULL); + omap_mux_init_gpio(GPIO_MHL_INT, OMAP_PIN_INPUT); + gpio_direction_input(GPIO_MHL_INT); + + gpio_request(TUNA_GPIO_HDMI_HPD, NULL); + omap_mux_init_gpio(TUNA_GPIO_HDMI_HPD, OMAP_PIN_INPUT | OMAP_PULL_ENA); + gpio_direction_input(TUNA_GPIO_HDMI_HPD); + + i2c_register_board_info(5, tuna_i2c5_boardinfo, + ARRAY_SIZE(tuna_i2c5_boardinfo)); + + tuna_otg->dock_switch.name = "dock"; + switch_dev_register(&tuna_otg->dock_switch); + + return 0; +} diff --git a/arch/arm/mach-omap2/board-tuna-display.c b/arch/arm/mach-omap2/board-tuna-display.c new file mode 100644 index 0000000..d593a53 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-display.c @@ -0,0 +1,1126 @@ +/* Display panel support for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/omapfb.h> +#include <linux/regulator/consumer.h> + +#include <linux/platform_data/panel-s6e8aa0.h> + +#include <plat/vram.h> + +#include <video/omapdss.h> +#include <video/omap-panel-generic-dpi.h> + +#include "board-tuna.h" +#include "control.h" +#include "mux.h" + +#define TUNA_FB_RAM_SIZE SZ_16M /* ~1280*720*4 * 2 */ + +#define TUNA_GPIO_MLCD_RST 23 + +/* 4.65" Panel ID Info (D1h 1st Para) */ +#define M3 0xA1 +#define SM2 0x12 +#define SM2A2 0xA2 + +static unsigned int panel_id; +struct regulator *tuna_oled_reg; +struct regulator *tuna_oled_reg_iovcc; + + +static void tuna_oled_set_power(bool enable) +{ + if (IS_ERR_OR_NULL(tuna_oled_reg)) { + tuna_oled_reg = regulator_get(NULL, "vlcd"); + if (IS_ERR_OR_NULL(tuna_oled_reg)) { + pr_err("Can't get vlcd for display!\n"); + return; + } + } + + if (omap4_tuna_get_revision() >= 5) { + if (IS_ERR_OR_NULL(tuna_oled_reg_iovcc)) { + tuna_oled_reg_iovcc = regulator_get(NULL, "vlcd-iovcc"); + if (IS_ERR_OR_NULL(tuna_oled_reg_iovcc)) { + pr_err("Can't get vlcd for display!\n"); + return; + } + } + + if (enable) { + regulator_enable(tuna_oled_reg_iovcc); + regulator_enable(tuna_oled_reg); + } else { + regulator_disable(tuna_oled_reg); + regulator_disable(tuna_oled_reg_iovcc); + } + } else { + if (enable) + regulator_enable(tuna_oled_reg); + else + regulator_disable(tuna_oled_reg); + } +} + +static const struct s6e8aa0_acl_parameters tuna_oled_acl[] = { + { + .cd = 40, + .acl_val = 43, + .regs = { + 0xC1, /* ACL Control2 Register */ + 0x47, + 0x53, + 0x13, + 0x53, + 0x00, + 0x00, + 0x02, + 0xCF, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x07, + 0x0D, + 0x14, + 0x1A, + 0x20, + 0x26, + 0x2C, + 0x33, + 0x39, + 0x3F, + }, + }, + { + .cd = 300, + .acl_val = 45, + .regs = { + 0xC1, /* ACL Control2 Register */ + 0x47, + 0x53, + 0x13, + 0x53, + 0x00, + 0x00, + 0x02, + 0xCF, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x07, + 0x0E, + 0x14, + 0x1B, + 0x21, + 0x27, + 0x2E, + 0x34, + 0x3B, + 0x41, + }, + }, +}; + +static const struct s6e8aa0_elvss_parameters tuna_oled_elvss[] = { + { + .cd = 100, + .elvss_val = 0x11, + }, + { + .cd = 160, + .elvss_val = 0x0D, + }, + { + .cd = 200, + .elvss_val = 0x08, + }, + { + .cd = 300, + .elvss_val = 0x00, + }, +}; + +static const u8 tuna_oled_cmd_init_pre0[] = { + 0xF0, + 0x5A, + 0x5A, +}; + +static const u8 tuna_oled_cmd_init_pre1[] = { + 0xF1, + 0x5A, + 0x5A, +}; + +static const u8 tuna_oled_cmd_sleep_out[] = { + 0x11, +}; + +static const u8 tuna_oled_cmd_init_panel_m3[] = { + 0xF8, /* Panel Condition Set */ + 0x3D, /* DOTC[0:1], GTCON[2:4], SS, DOTC_H[6:7] */ + 0x35, /* FLTE[0:7] */ + 0x00, + 0x00, + 0x00, + 0x8D, + 0x00, + 0x4C, /* SCTE[0:7] */ + 0x6E, + 0x10, + 0x27, + 0x7D, /* INTE[0:7] */ + 0x3F, /* INWE[0:7] */ + 0x10, + 0x00, + 0x00, + 0x20, + 0x04, /* E_FLWE_H[0:7] */ + 0x08, /* E_SCTE[0:7] */ + 0x6E, /* E_SCWE[0:7] */ + 0x00, + 0x00, + 0x00, + 0x02, + 0x08, + 0x08, + 0x23, + 0x23, + 0xC0, + 0xC8, /* CLK2_CON[0:2], CLK1_CON[3:5], CLK2_DC, CLK1_DC */ + 0x08, /* INT2_CON[0:2], INT1_CON[3:5], INT2_DC, INT1_DC */ + 0x48, /* BICTLB_CON[0:2], BICTL_CON[3:5], BICTLB_DC, BICTL_DC */ + 0xC1, + 0x00, + 0xC3, /* EM_FLM_CON[0:2], ACL_FLM_CON[3:5], EM_FLM_DC, ACL_FLM_DC */ + 0xFF, /* EM_CLK1B_CON[0:2], EM_CLK1_CON[3:5], EM_CLK1B_DC, EM_CLK1_DC */ + 0xFF, /* EM_CLK2B_CON[0:2], EM_CLK2_CON[3:5], EM_CLK2B_DC, EM_CLK2_DC */ + 0xC8, /* EM_INT2_CON[0:2], EM_INT1_CON[3:5], EM_INT2_DC, EM_INT1_DC */ +}; + +static const u8 tuna_oled_cmd_init_panel_sm2[] = { + 0xF8, /* Panel Condition Set */ + 0x3D, /* DOTC[0:1], GTCON[2:4], SS, DOTC_H[6:7] */ + 0x31, /* FLTE[0:7] */ + 0x00, + 0x00, + 0x00, + 0x8C, /* FLWE */ + 0x00, + 0x39, /* SCTE[0:7] */ + 0x77, /* SCWE */ + 0x08, /* INTE */ + 0x25, + 0x77, /* INTE[0:7] */ + 0x3C, /* INWE[0:7] */ + 0x00, /* EMPS */ + 0x00, + 0x00, + 0x20, + 0x04, /* E_FLWE_H[0:7] */ + 0x08, /* E_SCTE[0:7] */ + 0x68, /* E_SCWE[0:7] */ + 0x00, + 0x00, + 0x00, + 0x02, + 0x07, + 0x07, + 0x21, + 0x21, + 0xC0, + 0xC8, /* CLK2_CON[0:2], CLK1_CON[3:5], CLK2_DC, CLK1_DC */ + 0x08, /* INT2_CON[0:2], INT1_CON[3:5], INT2_DC, INT1_DC */ + 0x48, /* BICTLB_CON[0:2], BICTL_CON[3:5], BICTLB_DC, BICTL_DC */ + 0xC1, + 0x00, + 0xC1, /* EM_FLM_CON[0:2], ACL_FLM_CON[3:5], EM_FLM_DC, ACL_FLM_DC */ + 0xFF, /* EM_CLK1B_CON[0:2], EM_CLK1_CON[3:5], EM_CLK1B_DC, EM_CLK1_DC */ + 0xFF, /* EM_CLK2B_CON[0:2], EM_CLK2_CON[3:5], EM_CLK2B_DC, EM_CLK2_DC */ + 0xC8, /* EM_INT2_CON[0:2], EM_INT1_CON[3:5], EM_INT2_DC, EM_INT1_DC */ +}; + +static const u8 tuna_oled_cmd_init_display[] = { + 0xF2, /* Display Condition set */ + 0x80, /* Display area */ + 0x03, /* VBP : 3 HsYNC */ + 0x0D, /* VFP : 13HSYNC */ +}; + +static const struct s6e8aa0_sequence_entry tuna_oled_seq_display_set_m3[] = { + { + .cmd = tuna_oled_cmd_init_pre0, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_pre0), + }, + { + .cmd = tuna_oled_cmd_sleep_out, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_sleep_out), + .msleep = 5, + }, + { + .cmd = tuna_oled_cmd_init_panel_m3, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_panel_m3), + }, + { + .cmd = tuna_oled_cmd_init_display, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_display), + }, +}; + +static const struct s6e8aa0_sequence_entry tuna_oled_seq_display_set_sm2[] = { + { + .cmd = tuna_oled_cmd_init_pre0, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_pre0), + }, + { + .cmd = tuna_oled_cmd_init_pre1, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_pre1), + }, + { + .cmd = tuna_oled_cmd_sleep_out, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_sleep_out), + .msleep = 5, + }, + { + .cmd = tuna_oled_cmd_init_panel_sm2, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_panel_sm2), + }, + { + .cmd = tuna_oled_cmd_init_display, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_display), + }, +}; + +static const u8 tuna_oled_cmd_gamma_ltps_update[] = { + 0xF7, + 0x03, /* Gamma/LTPS update */ +}; + +static const u8 tuna_oled_cmd_init_post0[] = { + 0xF6, + 0x00, + 0x02, + 0x00, +}; + +static const u8 tuna_oled_cmd_init_post1[] = { + 0xB6, + 0x0C, + 0x02, + 0x03, + 0x32, + 0xFF, + 0x44, + 0x44, + 0xC0, + 0x00, +}; + +static const u8 tuna_oled_cmd_init_post2_m3[] = { + 0xD9, + 0x14, + 0x40, + 0x0C, + 0xCB, + 0xCE, + 0x6E, + 0xC4, + 0x07, /* COLUMN_CHOP, FRAME_CHOP, LINE_CHOP, CHOP_EN */ + 0x40, + 0x40, /* ELVSS_CON : 0 */ + 0xD0, /* ELVSS -4.9V */ + 0x00, + 0x60, + 0x19, +}; + +static const u8 tuna_oled_cmd_power_ctrl_m3[] = { + 0xF4, /* Power Control */ + 0xCF, + 0x0A, + 0x0F, /* Vreg1 : 4.5V(default) */ + 0x10, /* VGH : 5.2v(default) */ + 0x19, /* VGL : -7.0v(default) */ + 0x33, + 0x02, +}; + +static const u8 tuna_oled_cmd_init_post2_sm2[] = { + 0xD9, + 0x14, + 0x40, + 0x0C, + 0xCB, + 0xCE, + 0x6E, + 0xC4, + 0x07, /* COLUMN_CHOP, FRAME_CHOP, LINE_CHOP, CHOP_EN */ + 0x40, + 0x41, /* ELVSS_CON : 1 */ + 0xD0, /* ELVSS -4.9V */ + 0x00, + 0x60, + 0x19, +}; + +static const u8 tuna_oled_cmd_power_ctrl_sm2[] = { + 0xF4, /* Power Control */ + 0xCF, + 0x0A, + 0x12, /* Vreg1 : 4.6V */ + 0x10, /* VGH : 5.2v(default) */ + 0x1E, /* VGL : -8.0v */ + 0x33, + 0x02, +}; + +static const u8 tuna_oled_cmd_display_on[] = { + 0x29, +}; + +static const struct s6e8aa0_sequence_entry tuna_oled_seq_etc_set_m3[] = { + { + .cmd = tuna_oled_cmd_gamma_ltps_update, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_gamma_ltps_update), + }, + { + .cmd = tuna_oled_cmd_init_post0, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_post0), + }, + { + .cmd = tuna_oled_cmd_init_post1, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_post1), + }, + { + .cmd = tuna_oled_cmd_init_post2_m3, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_post2_m3), + }, + { + .cmd = tuna_oled_cmd_power_ctrl_m3, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_power_ctrl_m3), + .msleep = 120, + }, + { + .cmd = tuna_oled_cmd_display_on, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_display_on), + }, +}; + +static const struct s6e8aa0_sequence_entry tuna_oled_seq_etc_set_sm2[] = { + { + .cmd = tuna_oled_cmd_gamma_ltps_update, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_gamma_ltps_update), + }, + { + .cmd = tuna_oled_cmd_init_post0, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_post0), + }, + { + .cmd = tuna_oled_cmd_init_post1, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_post1), + }, + { + .cmd = tuna_oled_cmd_init_post2_sm2, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_init_post2_sm2), + }, + { + .cmd = tuna_oled_cmd_power_ctrl_sm2, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_power_ctrl_sm2), + .msleep = 120, + }, + { + .cmd = tuna_oled_cmd_display_on, + .cmd_len = ARRAY_SIZE(tuna_oled_cmd_display_on), + }, +}; + +static const struct s6e8aa0_gamma_entry tuna_oled_gamma_table_m3[] = { + { BV_0, { 4500000, 4500000, 4500000, }, }, + { 0x00000001, { 4350000, 4350000, 4350000, }, }, + { 0x0001F8F0, { 4320166, 4338185, 4200000, }, }, + { 0x0002F71F, { 4305148, 4332238, 4102500, }, }, + { 0x00038485, { 4296793, 4328930, 4065000, }, }, + { 0x00053C55, { 4270807, 4318639, 3982500, }, }, + { 0x00060EEF, { 4258364, 4313711, 3960000, }, }, + { 0x00075444, { 4239141, 4306099, 3930000, }, }, + { 0x00095733, { 4208717, 4294050, 3892500, }, }, + { 0x000EC060, { 4126874, 4261640, 3810000, }, }, + { 0x00129EAB, { 4068363, 4238469, 3757500, }, }, + { 0x00179214, { 3993478, 4208813, 3720000, }, }, + { 0x001A47A0, { 3952500, 4192586, 3712500, }, }, + { 0x0025E12C, { 3802500, 4123103, 3682500, }, }, + { 0x002D413D, { 3756465, 4078926, 3673450, }, }, + { 0x0035D13F, { 3741586, 4027637, 3659844, }, }, + { 0x00400000, { 3726401, 3966643, 3645758, }, }, + { 0x004C1BF8, { 3710894, 3894110, 3631166, }, }, + { 0x005A827A, { 3695052, 3855649, 3616042, }, }, + { 0x006BA27E, { 3678859, 3823316, 3600360, }, }, + { 0x00800000, { 3662298, 3794488, 3584091, }, }, + { 0x009837F0, { 3645351, 3767841, 3567202, }, }, + { 0x00B504F3, { 3627999, 3742607, 3549662, }, }, + { 0x00D744FD, { 3610220, 3718295, 3531434, }, }, + { 0x01000000, { 3591992, 3694568, 3512481, }, }, + { 0x01306FE1, { 3573291, 3671183, 3492761, }, }, + { 0x016A09E6, { 3554089, 3647957, 3472232, }, }, + { 0x01AE89FA, { 3534358, 3624743, 3450847, }, }, + { 0x02000000, { 3514065, 3601422, 3428554, }, }, + { 0x0260DFC1, { 3493177, 3577893, 3405301, }, }, + { 0x02D413CD, { 3471654, 3554067, 3381029, }, }, + { 0x035D13F3, { 3449455, 3529864, 3355676, }, }, + { 0x04000000, { 3426534, 3505211, 3329174, }, }, + { 0x04C1BF83, { 3402839, 3480034, 3301451, }, }, + { 0x05A8279A, { 3378312, 3454264, 3272429, }, }, + { 0x06BA27E6, { 3352890, 3427831, 3242021, }, }, + { 0x08000000, { 3326501, 3400661, 3210138, }, }, + { 0x09837F05, { 3299062, 3372679, 3176678, }, }, + { 0x0B504F33, { 3270483, 3343804, 3141535, }, }, + { 0x0D744FCD, { 3240658, 3313948, 3104592, }, }, + { 0x10000000, { 3209466, 3283015, 3065722, }, }, + { 0x1306FE0A, { 3176767, 3250899, 3024787, }, }, + { 0x16A09E66, { 3142400, 3217481, 2981638, }, }, + { 0x1AE89F99, { 3106171, 3182624, 2936111, }, }, + { 0x20000000, { 3067855, 3146174, 2888031, }, }, + { 0x260DFC14, { 3027178, 3107951, 2837203, }, }, + { 0x2D413CCD, { 2983809, 3067744, 2783420, }, }, + { 0x35D13F32, { 2937340, 3025303, 2726454, }, }, + { 0x40000000, { 2887259, 2980328, 2666057, }, }, + { 0x4C1BF828, { 2832912, 2932454, 2601961, }, }, + { 0x5A82799A, { 2773444, 2881230, 2533878, }, }, + { 0x6BA27E65, { 2707706, 2826086, 2461495, }, }, + { 0x80000000, { 2639098, 2766291, 2384478, }, }, + { 0x9837F051, { 2560291, 2700878, 2302469, }, }, + { 0xB504F333, { 2472698, 2628531, 2215093, }, }, + { 0xD744FCCA, { 2375331, 2547389, 2121959, }, }, + { 0xFFFFFFFF, { 2266945, 2454682, 2022667, }, }, +}; + +static const struct s6e8aa0_gamma_entry tuna_oled_gamma_table_sm2[] = { + { BV_0, { 4600000, 4600000, 4600000, }, }, + { 0x00000001, { 4561667, 4561667, 4561667, }, }, + { 0x000004C2, { 4102930, 4561654, 4561115, }, }, + { 0x000005A8, { 4093308, 4561651, 3799195, }, }, + { 0x000006BA, { 4083466, 4561645, 3793888, }, }, + { 0x00000800, { 4073413, 4561639, 3788484, }, }, + { 0x00000983, { 4063166, 4561630, 3782992, }, }, + { 0x00000B50, { 4052685, 4561618, 3777391, }, }, + { 0x00000D74, { 4041989, 4561602, 3771689, }, }, + { 0x00001000, { 4031064, 4561582, 3765880, }, }, + { 0x00001307, { 4019915, 4561555, 3759964, }, }, + { 0x000016A1, { 4008527, 4561519, 3753935, }, }, + { 0x00001AE9, { 3996905, 4561472, 3747792, }, }, + { 0x00002000, { 3985042, 4561410, 3741533, }, }, + { 0x0000260E, { 3972926, 4561328, 3735148, }, }, + { 0x00002D41, { 3960557, 4561219, 3728639, }, }, + { 0x000035D1, { 3947926, 4561076, 3721998, }, }, + { 0x00004000, { 3935029, 4560888, 3715222, }, }, + { 0x00004C1C, { 3921862, 4560639, 3708307, }, }, + { 0x00005A82, { 3908420, 4560310, 3701250, }, }, + { 0x00006BA2, { 3894694, 4559877, 3694045, }, }, + { 0x00008000, { 3880678, 4559305, 3686685, }, }, + { 0x00009838, { 3866369, 4558550, 3679168, }, }, + { 0x0000B505, { 3851759, 4557554, 3671487, }, }, + { 0x0000D745, { 3836842, 4556240, 3663637, }, }, + { 0x00010000, { 3821612, 4554507, 3655612, }, }, + { 0x00013070, { 3806062, 4552219, 3647405, }, }, + { 0x00016A0A, { 3790185, 4549200, 3639010, }, }, + { 0x0001AE8A, { 3773975, 4545217, 3630420, }, }, + { 0x00020000, { 3757424, 4539961, 3621628, }, }, + { 0x000260E0, { 3740525, 4533026, 3612625, }, }, + { 0x0002D414, { 3723271, 4523876, 3603405, }, }, + { 0x00035D14, { 3705654, 4511801, 3593959, }, }, + { 0x00040000, { 3687668, 4495869, 3584277, }, }, + { 0x0004C1C0, { 3669303, 4474845, 3574351, }, }, + { 0x0005A828, { 3650553, 4447106, 3564170, }, }, + { 0x0006BA28, { 3631408, 4410503, 3553725, }, }, + { 0x00080000, { 3611862, 4362204, 3543003, }, }, + { 0x0009837F, { 3591904, 4298475, 3531993, }, }, + { 0x000B504F, { 3571527, 4214383, 3520683, }, }, + { 0x000D7450, { 3557593, 4103423, 3509060, }, }, + { 0x00100000, { 3544015, 4010592, 3497110, }, }, + { 0x001306FE, { 3530633, 3962306, 3484817, }, }, + { 0x0016A09E, { 3517328, 3926468, 3472166, }, }, + { 0x001AE8A0, { 3504007, 3896492, 3459141, }, }, + { 0x00200000, { 3490597, 3869832, 3445722, }, }, + { 0x00260DFC, { 3477036, 3845220, 3431893, }, }, + { 0x002D413D, { 3463269, 3821929, 3417630, }, }, + { 0x0035D13F, { 3449251, 3799494, 3402914, }, }, + { 0x00400000, { 3434937, 3777603, 3387721, }, }, + { 0x004C1BF8, { 3420287, 3756027, 3372025, }, }, + { 0x005A827A, { 3405262, 3734596, 3355799, }, }, + { 0x006BA27E, { 3389824, 3713176, 3339016, }, }, + { 0x00800000, { 3373936, 3691658, 3321643, }, }, + { 0x009837F0, { 3357560, 3669948, 3303646, }, }, + { 0x00B504F3, { 3340656, 3647968, 3284991, }, }, + { 0x00D744FD, { 3323186, 3625646, 3265636, }, }, + { 0x01000000, { 3305106, 3602915, 3245540, }, }, + { 0x01306FE1, { 3286372, 3579714, 3224657, }, }, + { 0x016A09E6, { 3266937, 3555983, 3202935, }, }, + { 0x01AE89FA, { 3246751, 3531662, 3180321, }, }, + { 0x02000000, { 3225759, 3506692, 3156754, }, }, + { 0x0260DFC1, { 3203902, 3481011, 3132167, }, }, + { 0x02D413CD, { 3181115, 3454554, 3106490, }, }, + { 0x035D13F3, { 3157329, 3427255, 3079643, }, }, + { 0x04000000, { 3132467, 3399041, 3051538, }, }, + { 0x04C1BF83, { 3106444, 3369833, 3022078, }, }, + { 0x05A8279A, { 3079166, 3339546, 2991155, }, }, + { 0x06BA27E6, { 3050528, 3308086, 2958650, }, }, + { 0x08000000, { 3020414, 3275348, 2924429, }, }, + { 0x09837F05, { 2988694, 3241215, 2888342, }, }, + { 0x0B504F33, { 2955220, 3205555, 2850219, }, }, + { 0x0D744FCD, { 2919827, 3168219, 2809871, }, }, + { 0x10000000, { 2882325, 3129034, 2767081, }, }, + { 0x1306FE0A, { 2842499, 3087803, 2721603, }, }, + { 0x16A09E66, { 2800102, 3044294, 2673154, }, }, + { 0x1AE89F99, { 2754846, 2998238, 2621408, }, }, + { 0x20000000, { 2706399, 2949314, 2565989, }, }, + { 0x260DFC14, { 2654372, 2897137, 2506458, }, }, + { 0x2D413CCD, { 2598304, 2841239, 2442298, }, }, + { 0x35D13F32, { 2537647, 2781048, 2372900, }, }, + { 0x40000000, { 2471743, 2715846, 2297536, }, }, + { 0x4C1BF828, { 2399793, 2644720, 2215328, }, }, + { 0x5A82799A, { 2320814, 2566484, 2125212, }, }, + { 0x6BA27E65, { 2233581, 2479554, 2025874, }, }, + { 0x80000000, { 2136547, 2381755, 1915679, }, }, + { 0x9837F051, { 2027719, 2269975, 1792556, }, }, + { 0xB504F333, { 1904479, 2139541, 1653843, }, }, + { 0xD744FCCA, { 1763299, 1982960, 1496041, }, }, + { 0xFFFFFFFF, { 1599291, 1787064, 1314455, }, }, +}; + +static const struct s6e8aa0_gamma_entry tuna_oled_gamma_table_sm2a2[] = { + { BV_0, { 4600000, 4600000, 4600000, }, }, + { 0x00000001, { 4561667, 4561667, 4561667, }, }, + { 0x000004C2, { 4320569, 4507119, 4172023, }, }, + { 0x000005A8, { 4313803, 4504412, 4164881, }, }, + { 0x000006BA, { 4306834, 4501566, 4157595, }, }, + { 0x00000800, { 4299666, 4498576, 4150172, }, }, + { 0x00000983, { 4292309, 4495443, 4142625, }, }, + { 0x00000B50, { 4284732, 4492149, 4134926, }, }, + { 0x00000D74, { 4276946, 4488692, 4127090, }, }, + { 0x00001000, { 4268937, 4485062, 4119106, }, }, + { 0x00001307, { 4260707, 4481254, 4110980, }, }, + { 0x000016A1, { 4252243, 4477254, 4102702, }, }, + { 0x00001AE9, { 4243544, 4473058, 4094275, }, }, + { 0x00002000, { 4234603, 4468654, 4085695, }, }, + { 0x0000260E, { 4225408, 4464030, 4076956, }, }, + { 0x00002D41, { 4215956, 4459176, 4068057, }, }, + { 0x000035D1, { 4206236, 4454081, 4058993, }, }, + { 0x00004000, { 4196243, 4448731, 4049762, }, }, + { 0x00004C1C, { 4185969, 4443116, 4040363, }, }, + { 0x00005A82, { 4175408, 4437223, 4030792, }, }, + { 0x00006BA2, { 4164549, 4431036, 4021044, }, }, + { 0x00008000, { 4153383, 4424541, 4011116, }, }, + { 0x00009838, { 4141905, 4417724, 4001007, }, }, + { 0x0000B505, { 4130104, 4410567, 3990713, }, }, + { 0x0000D745, { 4117971, 4403055, 3980229, }, }, + { 0x00010000, { 4105497, 4395170, 3969553, }, }, + { 0x00013070, { 4092672, 4386892, 3958681, }, }, + { 0x00016A0A, { 4079487, 4378203, 3947609, }, }, + { 0x0001AE8A, { 4065931, 4369081, 3936334, }, }, + { 0x00020000, { 4051994, 4359507, 3924852, }, }, + { 0x000260E0, { 4037665, 4349456, 3913159, }, }, + { 0x0002D414, { 4022934, 4338906, 3901251, }, }, + { 0x00035D14, { 4007788, 4327831, 3889125, }, }, + { 0x00040000, { 3992216, 4316205, 3876776, }, }, + { 0x0004C1C0, { 3976207, 4304001, 3864200, }, }, + { 0x0005A828, { 3959747, 4291191, 3851393, }, }, + { 0x0006BA28, { 3942825, 4277744, 3838351, }, }, + { 0x00080000, { 3925427, 4263628, 3825070, }, }, + { 0x0009837F, { 3907540, 4248811, 3811545, }, }, + { 0x000B504F, { 3889150, 4233257, 3797772, }, }, + { 0x000D7450, { 3873393, 4216929, 3783745, }, }, + { 0x00100000, { 3857665, 4199790, 3769461, }, }, + { 0x001306FE, { 3841894, 4181799, 3754915, }, }, + { 0x0016A09E, { 3826021, 4162913, 3740102, }, }, + { 0x001AE8A0, { 3809994, 4143088, 3725017, }, }, + { 0x00200000, { 3793770, 4122278, 3709654, }, }, + { 0x00260DFC, { 3777308, 4100433, 3694010, }, }, + { 0x002D413D, { 3760573, 4077502, 3677663, }, }, + { 0x0035D13F, { 3743530, 4053431, 3660904, }, }, + { 0x00400000, { 3726147, 4028163, 3643715, }, }, + { 0x004C1BF8, { 3708392, 4001639, 3626075, }, }, + { 0x005A827A, { 3690235, 3973797, 3607960, }, }, + { 0x006BA27E, { 3671644, 3953685, 3589346, }, }, + { 0x00800000, { 3652589, 3933053, 3570207, }, }, + { 0x009837F0, { 3633036, 3911876, 3550514, }, }, + { 0x00B504F3, { 3612951, 3890122, 3530237, }, }, + { 0x00D744FD, { 3592301, 3867762, 3509342, }, }, + { 0x01000000, { 3571046, 3844761, 3487793, }, }, + { 0x01306FE1, { 3549148, 3821082, 3465552, }, }, + { 0x016A09E6, { 3526565, 3796686, 3442574, }, }, + { 0x01AE89FA, { 3503250, 3771528, 3418813, }, }, + { 0x02000000, { 3479155, 3745560, 3394218, }, }, + { 0x0260DFC1, { 3454224, 3718731, 3368732, }, }, + { 0x02D413CD, { 3428401, 3690982, 3342292, }, }, + { 0x035D13F3, { 3401619, 3662250, 3314830, }, }, + { 0x04000000, { 3373808, 3632465, 3286268, }, }, + { 0x04C1BF83, { 3344890, 3601547, 3256522, }, }, + { 0x05A8279A, { 3314777, 3569411, 3225495, }, }, + { 0x06BA27E6, { 3283370, 3535958, 3193081, }, }, + { 0x08000000, { 3250562, 3501080, 3159159, }, }, + { 0x09837F05, { 3216227, 3464651, 3123590, }, }, + { 0x0B504F33, { 3180227, 3426533, 3086220, }, }, + { 0x0D744FCD, { 3142401, 3386564, 3046868, }, }, + { 0x10000000, { 3102567, 3344561, 3005329, }, }, + { 0x1306FE0A, { 3060513, 3300311, 2961362, }, }, + { 0x16A09E66, { 3015995, 3253568, 2914685, }, }, + { 0x1AE89F99, { 2968726, 3204040, 2864970, }, }, + { 0x20000000, { 2918367, 3151384, 2811819, }, }, + { 0x260DFC14, { 2864513, 3095191, 2754761, }, }, + { 0x2D413CCD, { 2806678, 3034964, 2693215, }, }, + { 0x35D13F32, { 2744269, 2970096, 2626472, }, }, + { 0x40000000, { 2676551, 2899833, 2553637, }, }, + { 0x4C1BF828, { 2602603, 2823224, 2473575, }, }, + { 0x5A82799A, { 2521246, 2739043, 2384804, }, }, + { 0x6BA27E65, { 2430943, 2645676, 2285353, }, }, + { 0x80000000, { 2329631, 2540937, 2172509, }, }, + { 0x9837F051, { 2214459, 2421765, 2042412, }, }, + { 0xB504F333, { 2081337, 2283682, 1889300, }, }, + { 0xD744FCCA, { 1924091, 2119771, 1704041, }, }, + { 0xFFFFFFFF, { 1732795, 1918520, 1470915, }, }, +}; + +static struct s6e8aa0_factory_calibration_info tuna_oled_factory_info_old = { + .regs = { + [1][0][6] = 0x090, + [1][1][6] = 0x081, + [1][2][6] = 0x0c5, + }, + .brightness = { + [1][6] = BV_255, /* 300 cd/m2 */ + }, + .color_adj = { + /* Convert from 8500K to D65, assuming: + * Rx 0.66950, Ry 0.33100 + * Gx 0.18800, Gy 0.74350 + * Bx 0.14142, By 0.04258 + */ + .mult = { + 2318372099U, + 2117262806U, + 1729744557U, + }, + .rshift = 31, + }, +}; + +static struct s6e8aa0_factory_calibration_info tuna_oled_factory_info_m2t1 = { + .regs = { + [1][0][6] = 0x090, + [1][1][6] = 0x081, + [1][2][6] = 0x0c5, + }, + .brightness = { + [1][6] = BV_255, /* 300 cd/m2 */ + }, +}; + +static struct s6e8aa0_factory_calibration_info tuna_oled_factory_info_8500k = { + .regs = { + [1][0][0] = 0x0f, + [1][1][0] = 0x0f, + [1][2][0] = 0x0f, + + [1][0][1] = 0xcc, + [1][1][1] = 0x9c, + [1][2][1] = 0xd7, + + [1][0][2] = 0xc1, + [1][1][2] = 0xba, + [1][2][2] = 0xc1, + + [1][0][3] = 0xcf, + [1][1][3] = 0xcd, + [1][2][3] = 0xcf, + + [1][0][4] = 0xaa, + [1][1][4] = 0xaa, + [1][2][4] = 0xa7, + + [1][0][5] = 0xbe, + [1][1][5] = 0xbe, + [1][2][5] = 0xba, + + [1][0][6] = 0x090, + [1][1][6] = 0x081, + [1][2][6] = 0x0c5, + }, + .brightness = { + [1][4] = 403193777, /* 28.16275996 cd/m2 */ + [1][6] = BV_255, /* 300 cd/m2 */ + }, + .color_adj = { + /* Convert from 8500K to D65, assuming: + * Rx 0.66950, Ry 0.33100 + * Gx 0.18800, Gy 0.74350 + * Bx 0.14142, By 0.04258 + * + * These values are adjusted down by x 0.9333 to bring + * maximum brightness down from 300 cd/m2 to 280. + */ + .mult = { + 2163736680U, + 1976041377U, + 1614370595U, + }, + .rshift = 31, + }, +}; + +static struct s6e8aa0_factory_calibration_info tuna_oled_factory_info_6500k = { + .regs = { + [1][0][0] = 0x7c, /* sRGB Gamma 300cd, 6500K */ + [1][1][0] = 0x3c, + [1][2][0] = 0x87, + + [1][0][1] = 0xbb, + [1][1][1] = 0xd4, + [1][2][1] = 0xa8, + + [1][0][2] = 0xac, + [1][1][2] = 0xc0, + [1][2][2] = 0xa1, + + [1][0][3] = 0xb8, + [1][1][3] = 0xc8, + [1][2][3] = 0xb2, + + [1][0][4] = 0x8c, + [1][1][4] = 0x9f, + [1][2][4] = 0x84, + + [1][0][5] = 0xa7, + [1][1][5] = 0xb2, + [1][2][5] = 0xa3, + + [1][0][6] = 0x0d8, + [1][1][6] = 0x0bd, + [1][2][6] = 0x0f7, + }, + .brightness = { + [1][1] = BV_15, /* 1.43 cd/m2 */ + [1][2] = BV_35, /* 5.04 cd/m2 */ + [1][3] = BV_59, /* 13.12 cd/m2 */ + [1][4] = BV_87, /* 28.59 cd/m2 */ + [1][5] = BV_171, /* 122.17 cd/m2 */ + [1][6] = BV_255, /* 300 cd/m2 */ + }, + .color_adj = { + /* + * These values are adjusted down by x 0.9333 to bring + * maximum brightness down from 300 cd/m2 to 280. + */ + .mult = { + 2004318071U, + 2004318071U, + 2004318071U, + }, + .rshift = 31, + }, +}; + +static struct s6e8aa0_factory_calibration_info tuna_oled_factory_info_sm2a2 = { + .regs = { + [1][0][0] = 0x52, /* sRGB Gamma 300cd, 6500K, A2 Line */ + [1][1][0] = 0x24, + [1][2][0] = 0x5d, + + [1][0][1] = 0xba, + [1][1][1] = 0xcd, + [1][2][1] = 0xb3, + + [1][0][2] = 0xad, + [1][1][2] = 0xc0, + [1][2][2] = 0xb1, + + [1][0][3] = 0xbf, + [1][1][3] = 0xc7, + [1][2][3] = 0xbc, + + [1][0][4] = 0x90, + [1][1][4] = 0x97, + [1][2][4] = 0x8a, + + [1][0][5] = 0xaa, + [1][1][5] = 0xae, + [1][2][5] = 0xa5, + + [1][0][6] = 0x0c2, + [1][1][6] = 0x0a8, + [1][2][6] = 0x0d7, + }, + .brightness = { + [1][1] = BV_15, /* 1.43 cd/m2 */ + [1][2] = BV_35, /* 5.04 cd/m2 */ + [1][3] = BV_59, /* 13.12 cd/m2 */ + [1][4] = BV_87, /* 28.59 cd/m2 */ + [1][5] = BV_171, /* 122.17 cd/m2 */ + [1][6] = BV_255, /* 300 cd/m2 */ + }, + .color_adj = { + /* + * These values are adjusted down by x 0.9333 to bring + * maximum brightness down from 300 cd/m2 to 280. + */ + .mult = { + 2004318071U, + 2004318071U, + 2004318071U, + }, + .rshift = 31, + }, +}; + +static struct panel_s6e8aa0_data tuna_oled_data_m3 = { + .reset_gpio = TUNA_GPIO_MLCD_RST, + .set_power = tuna_oled_set_power, + .seq_display_set = tuna_oled_seq_display_set_m3, + .seq_display_set_size = ARRAY_SIZE(tuna_oled_seq_display_set_m3), + .seq_etc_set = tuna_oled_seq_etc_set_m3, + .seq_etc_set_size = ARRAY_SIZE(tuna_oled_seq_etc_set_m3), + .gamma_table = tuna_oled_gamma_table_m3, + .gamma_table_size = ARRAY_SIZE(tuna_oled_gamma_table_m3), + .factory_info = &tuna_oled_factory_info_8500k, + .acl_table = tuna_oled_acl, + .acl_table_size = ARRAY_SIZE(tuna_oled_acl), + .acl_average = 6, /* use 20 frame Y average accumulation count */ +}; + +static struct panel_s6e8aa0_data tuna_oled_data_sm2 = { + .reset_gpio = TUNA_GPIO_MLCD_RST, + .set_power = tuna_oled_set_power, + .seq_display_set = tuna_oled_seq_display_set_sm2, + .seq_display_set_size = ARRAY_SIZE(tuna_oled_seq_display_set_sm2), + .seq_etc_set = tuna_oled_seq_etc_set_sm2, + .seq_etc_set_size = ARRAY_SIZE(tuna_oled_seq_etc_set_sm2), + .gamma_table = tuna_oled_gamma_table_sm2, + .gamma_table_size = ARRAY_SIZE(tuna_oled_gamma_table_sm2), + .factory_info = &tuna_oled_factory_info_6500k, + .acl_table = tuna_oled_acl, + .acl_table_size = ARRAY_SIZE(tuna_oled_acl), + .acl_average = 6, /* use 20 frame Y average accumulation count */ + .elvss_table = tuna_oled_elvss, + .elvss_table_size = ARRAY_SIZE(tuna_oled_elvss), +}; + +static struct panel_s6e8aa0_data tuna_oled_data_sm2a2 = { + .reset_gpio = TUNA_GPIO_MLCD_RST, + .set_power = tuna_oled_set_power, + .seq_display_set = tuna_oled_seq_display_set_sm2, + .seq_display_set_size = ARRAY_SIZE(tuna_oled_seq_display_set_sm2), + .seq_etc_set = tuna_oled_seq_etc_set_sm2, + .seq_etc_set_size = ARRAY_SIZE(tuna_oled_seq_etc_set_sm2), + .gamma_table = tuna_oled_gamma_table_sm2a2, + .gamma_table_size = ARRAY_SIZE(tuna_oled_gamma_table_sm2a2), + .factory_info = &tuna_oled_factory_info_sm2a2, + .acl_table = tuna_oled_acl, + .acl_table_size = ARRAY_SIZE(tuna_oled_acl), + .acl_average = 6, /* use 20 frame Y average accumulation count */ + .elvss_table = tuna_oled_elvss, + .elvss_table_size = ARRAY_SIZE(tuna_oled_elvss), +}; + +static struct omap_dss_device tuna_oled_device = { + .name = "lcd", + .driver_name = "s6e8aa0", + .type = OMAP_DISPLAY_TYPE_DSI, + .phy.dsi = { + .type = OMAP_DSS_DSI_TYPE_VIDEO_MODE, + .clk_lane = 1, + .clk_pol = 0, + .data1_lane = 2, + .data1_pol = 0, + .data2_lane = 3, + .data2_pol = 0, + .data3_lane = 4, + .data3_pol = 0, + .data4_lane = 5, + .data4_pol = 0, + }, + .panel = { + .width_in_um = 58000, + .height_in_um = 102000, + }, + .clocks = { + .dispc = { + .channel = { + .lck_div = 1, /* LCD */ + .pck_div = 2, /* PCD */ + .lcd_clk_src + = OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC, + }, + .dispc_fclk_src = OMAP_DSS_CLK_SRC_FCK, + }, + .dsi = { + .regn = 19, /* DSI_PLL_REGN */ + .regm = 236, /* DSI_PLL_REGM */ + + .regm_dispc = 6, /* PLL_CLK1 (M4) */ + .regm_dsi = 6, /* PLL_CLK2 (M5) */ + .lp_clk_div = 8, /* LPDIV */ + .offset_ddr_clk = 122, /* DDR PRE & DDR POST + * offset increase + */ + + .dsi_fclk_src = OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI, + }, + }, + + .channel = OMAP_DSS_CHANNEL_LCD, +#ifdef CONFIG_FB_OMAP_BOOTLOADER_INIT + .skip_init = true, +#else + .skip_init = false, +#endif +}; + +static void tuna_hdmi_mux_init(void) +{ + u32 r; + + /* PAD0_HDMI_HPD_PAD1_HDMI_CEC */ + omap_mux_init_signal("hdmi_hpd.hdmi_hpd", + OMAP_PIN_INPUT_PULLDOWN); + omap_mux_init_signal("gpmc_wait2.gpio_100", + OMAP_PIN_INPUT_PULLDOWN); + omap_mux_init_signal("hdmi_cec.hdmi_cec", + OMAP_PIN_INPUT_PULLUP); + /* PAD0_HDMI_DDC_SCL_PAD1_HDMI_DDC_SDA */ + omap_mux_init_signal("hdmi_ddc_scl.hdmi_ddc_scl", + OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("hdmi_ddc_sda.hdmi_ddc_sda", + OMAP_PIN_INPUT_PULLUP); + + /* strong pullup on DDC lines using unpublished register */ + r = ((1 << 24) | (1 << 28)) ; + omap4_ctrl_pad_writel(r, OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_I2C_1); + +} + +static struct omap_dss_device tuna_hdmi_device = { + .name = "hdmi", + .driver_name = "hdmi_panel", + .type = OMAP_DISPLAY_TYPE_HDMI, + .clocks = { + .dispc = { + .dispc_fclk_src = OMAP_DSS_CLK_SRC_FCK, + }, + .hdmi = { + .regn = 15, + .regm2 = 1, + .max_pixclk_khz = 75000, + }, + }, + .hpd_gpio = TUNA_GPIO_HDMI_HPD, + .channel = OMAP_DSS_CHANNEL_DIGIT, +}; + +static struct omap_dss_device *tuna_dss_devices[] = { + &tuna_oled_device, + &tuna_hdmi_device, +}; + +static struct omap_dss_board_info tuna_dss_data = { + .num_devices = ARRAY_SIZE(tuna_dss_devices), + .devices = tuna_dss_devices, + .default_device = &tuna_oled_device, +}; + +static struct omapfb_platform_data tuna_fb_pdata = { + .mem_desc = { + .region_cnt = 1, + .region = { + [0] = { + .size = TUNA_FB_RAM_SIZE, + }, + }, + }, +}; + +void __init omap4_tuna_display_init(void) +{ + struct panel_s6e8aa0_data *panel; + + if (omap4_tuna_get_revision() == + (omap4_tuna_get_type() == TUNA_TYPE_MAGURO ? 2 : 1)) { + /* + * Older devices were not calibrated the same way as newer + * devices. These values are probably not correct, but the older + * devices tested look closer to the newer devices with these + * values than they do using the same register values as the + * newer devices. + */ + tuna_oled_data_m3.factory_info = &tuna_oled_factory_info_m2t1; + } else if (omap4_tuna_get_revision() <= 1) { + tuna_oled_data_m3.factory_info = &tuna_oled_factory_info_old; + } + + switch (panel_id) { + case SM2: + panel = &tuna_oled_data_sm2; + break; + case SM2A2: + panel = &tuna_oled_data_sm2a2; + break; + default: + panel = &tuna_oled_data_m3; + break; + } + + tuna_oled_device.data = panel; + + omap4_ctrl_pad_writel(0x1FF80000, + OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_DSIPHY); + omap_mux_init_gpio(panel->reset_gpio, OMAP_PIN_OUTPUT); + + pr_info("Using %ps\n", panel->factory_info); + + omap_vram_set_sdram_vram(TUNA_FB_RAM_SIZE, 0); + omapfb_set_platform_data(&tuna_fb_pdata); + tuna_hdmi_mux_init(); + omap_display_init(&tuna_dss_data); +} + +static int __init get_panel_id(char *str) +{ + long value; + int ret; + + ret = strict_strtol(str, 0, &value); + if (ret < 0) + return ret; + + panel_id = (unsigned int)value; + return 0; +} +__setup("mms_ts.panel_id=", get_panel_id); + diff --git a/arch/arm/mach-omap2/board-tuna-emif.c b/arch/arm/mach-omap2/board-tuna-emif.c new file mode 100644 index 0000000..84819ce --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-emif.c @@ -0,0 +1,108 @@ +/* + * LPDDR2 data as per SAMSUNG data sheet + * + * Copyright (C) 2011 Texas Instruments, Inc. + * + * Santosh Shilimkar <santosh.shilimkar@ti.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/init.h> + +#include <mach/emif.h> +#include "board-tuna.h" + +const struct lpddr2_timings lpddr2_samsung_timings_400_mhz = { + .max_freq = 400000000, + .RL = 6, + .tRPab = 21, + .tRCD = 18, + .tWR = 15, + .tRASmin = 42, + .tRRD = 10, + .tWTRx2 = 15, + .tXSR = 140, + .tXPx2 = 15, + .tRFCab = 130, + .tRTPx2 = 15, + .tCKE = 3, + .tCKESR = 15, + .tZQCS = 90, + .tZQCL = 360, + .tZQINIT = 1000, + .tDQSCKMAXx2 = 11, + .tRASmax = 70, + .tFAW = 50, +}; + +const struct lpddr2_timings lpddr2_samsung_timings_200_mhz = { + .max_freq = 200000000, + .RL = 3, + .tRPab = 21, + .tRCD = 18, + .tWR = 15, + .tRASmin = 42, + .tRRD = 10, + .tWTRx2 = 20, + .tXSR = 140, + .tXPx2 = 15, + .tRFCab = 130, + .tRTPx2 = 15, + .tCKE = 3, + .tCKESR = 15, + .tZQCS = 90, + .tZQCL = 360, + .tZQINIT = 1000, + .tDQSCKMAXx2 = 11, + .tRASmax = 70, + .tFAW = 50, +}; + +const struct lpddr2_min_tck lpddr2_samsung_min_tck = { + .tRL = 3, + .tRP_AB = 3, + .tRCD = 3, + .tWR = 3, + .tRAS_MIN = 3, + .tRRD = 2, + .tWTR = 2, + .tXP = 2, + .tRTP = 2, + .tCKE = 3, + .tCKESR = 3, + .tFAW = 8 +}; + +struct lpddr2_device_info lpddr2_samsung_4G_S4_dev = { + .device_timings = { + &lpddr2_samsung_timings_200_mhz, + &lpddr2_samsung_timings_400_mhz + }, + .min_tck = &lpddr2_samsung_min_tck, + .type = LPDDR2_TYPE_S4, + .density = LPDDR2_DENSITY_4Gb, + .io_width = LPDDR2_IO_WIDTH_32, + .emif_ddr_selfrefresh_cycles = 262144, +}; + +/* + * LPDDR2 Configuration Data: + * The memory organisation is as below : + * EMIF1 - CS0 - 4 Gb + * EMIF2 - CS0 - 4 Gb + * -------------------- + * TOTAL - 8 Gb + * + * Same devices installed on EMIF1 and EMIF2 + */ +static __initdata struct emif_device_details emif_devices = { + .cs0_device = &lpddr2_samsung_4G_S4_dev, +}; + +void __init omap4_tuna_emif_init(void) +{ + omap_emif_setup_device_details(&emif_devices, &emif_devices); +} diff --git a/arch/arm/mach-omap2/board-tuna-input.c b/arch/arm/mach-omap2/board-tuna-input.c new file mode 100644 index 0000000..6c12dd0 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-input.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/keyreset.h> +#include <linux/gpio_event.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c/atmel_mxt_ts.h> +#include <linux/platform_data/mms_ts.h> +#include <asm/mach-types.h> +#include <plat/omap4-keypad.h> + +#include "board-tuna.h" +#include "mux.h" + +#define GPIO_TOUCH_EN 19 +#define GPIO_TOUCH_IRQ 46 + +/* touch is on i2c3 */ +#define GPIO_TOUCH_SCL 130 +#define GPIO_TOUCH_SDA 131 + +static int mms_ts_panel_id; + +static struct gpio_event_direct_entry tuna_gpio_keypad_keys_map_high[] = { + { + .code = KEY_POWER, + .gpio = 3, + }, +}; + +static struct gpio_event_input_info tuna_gpio_keypad_keys_info_high = { + .info.func = gpio_event_input_func, + .info.no_suspend = true, + .type = EV_KEY, + .keymap = tuna_gpio_keypad_keys_map_high, + .keymap_size = ARRAY_SIZE(tuna_gpio_keypad_keys_map_high), + .flags = GPIOEDF_ACTIVE_HIGH, + .debounce_time.tv64 = 2 * NSEC_PER_MSEC, +}; + +static struct gpio_event_direct_entry tuna_gpio_keypad_keys_map_low[] = { + { + .code = KEY_VOLUMEDOWN, + .gpio = 8, + }, + { + .code = KEY_VOLUMEUP, + .gpio = 30, + }, +}; + +static struct gpio_event_input_info tuna_gpio_keypad_keys_info_low = { + .info.func = gpio_event_input_func, + .info.no_suspend = true, + .type = EV_KEY, + .keymap = tuna_gpio_keypad_keys_map_low, + .keymap_size = ARRAY_SIZE(tuna_gpio_keypad_keys_map_low), + .debounce_time.tv64 = 2 * NSEC_PER_MSEC, +}; + +static struct gpio_event_info *tuna_gpio_keypad_info[] = { + &tuna_gpio_keypad_keys_info_high.info, + &tuna_gpio_keypad_keys_info_low.info, +}; + +static struct gpio_event_platform_data tuna_gpio_keypad_data = { + .name = "tuna-gpio-keypad", + .info = tuna_gpio_keypad_info, + .info_count = ARRAY_SIZE(tuna_gpio_keypad_info) +}; + +static struct platform_device tuna_gpio_keypad_device = { + .name = GPIO_EVENT_DEV_NAME, + .id = 0, + .dev = { + .platform_data = &tuna_gpio_keypad_data, + }, +}; + +static int melfas_mux_fw_flash(bool to_gpios) +{ + /* TOUCH_EN is always an output */ + if (to_gpios) { + gpio_direction_output(GPIO_TOUCH_IRQ, 0); + omap_mux_set_gpio( + OMAP_PIN_INPUT | OMAP_MUX_MODE3, + GPIO_TOUCH_IRQ); + + gpio_direction_output(GPIO_TOUCH_SCL, 0); + omap_mux_set_gpio(OMAP_PIN_INPUT | OMAP_MUX_MODE3, + GPIO_TOUCH_SCL); + + gpio_direction_output(GPIO_TOUCH_SDA, 0); + omap_mux_set_gpio(OMAP_PIN_INPUT | OMAP_MUX_MODE3, + GPIO_TOUCH_SDA); + } else { + gpio_direction_output(GPIO_TOUCH_IRQ, 1); + gpio_direction_input(GPIO_TOUCH_IRQ); + omap_mux_set_gpio( + OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE3, + GPIO_TOUCH_IRQ); + + gpio_direction_output(GPIO_TOUCH_SCL, 1); + gpio_direction_input(GPIO_TOUCH_SCL); + omap_mux_set_gpio(OMAP_PIN_INPUT | OMAP_MUX_MODE0, + GPIO_TOUCH_SCL); + + gpio_direction_output(GPIO_TOUCH_SDA, 1); + gpio_direction_input(GPIO_TOUCH_SDA); + omap_mux_set_gpio(OMAP_PIN_INPUT | OMAP_MUX_MODE0, + GPIO_TOUCH_SDA); + } + + return 0; +} + +static int __init mms_ts_panel_id_setup(char *str) +{ + mms_ts_panel_id = simple_strtol(str, NULL, 0); + return 1; +} +__setup("mms_ts.panel_id=", mms_ts_panel_id_setup); + +static struct mms_ts_platform_data mms_ts_pdata = { + .max_x = 720, + .max_y = 1280, + .mux_fw_flash = melfas_mux_fw_flash, + .gpio_resetb = GPIO_TOUCH_IRQ, + .gpio_vdd_en = GPIO_TOUCH_EN, + .gpio_scl = GPIO_TOUCH_SCL, + .gpio_sda = GPIO_TOUCH_SDA, +}; + +static struct i2c_board_info __initdata tuna_i2c3_boardinfo_final[] = { + { + I2C_BOARD_INFO("mms_ts", 0x48), + .flags = I2C_CLIENT_WAKE, + .platform_data = &mms_ts_pdata, + .irq = OMAP_GPIO_IRQ(GPIO_TOUCH_IRQ), + }, +}; + +void __init omap4_tuna_input_init(void) +{ + gpio_request(GPIO_TOUCH_IRQ, "tsp_int_n"); + gpio_direction_input(GPIO_TOUCH_IRQ); + omap_mux_init_gpio(GPIO_TOUCH_IRQ, + OMAP_PIN_INPUT_PULLUP); + + gpio_request(GPIO_TOUCH_EN, "tsp_en"); + gpio_direction_output(GPIO_TOUCH_EN, 1); + omap_mux_init_gpio(GPIO_TOUCH_EN, OMAP_PIN_OUTPUT); + gpio_request(GPIO_TOUCH_SCL, "ap_i2c3_scl"); + gpio_request(GPIO_TOUCH_SDA, "ap_i2c3_sda"); + + /* 0x12 == FPCB 3.2 + * 0xa1 == FPCB 3.1 + */ + if (mms_ts_panel_id == 0x12) + mms_ts_pdata.fw_name = "mms144_ts_rev32.fw"; + else + mms_ts_pdata.fw_name = "mms144_ts_rev31.fw"; + + i2c_register_board_info(3, tuna_i2c3_boardinfo_final, + ARRAY_SIZE(tuna_i2c3_boardinfo_final)); + + omap_mux_init_gpio(8, OMAP_PIN_INPUT); + omap_mux_init_gpio(30, OMAP_PIN_INPUT); + + platform_device_register(&tuna_gpio_keypad_device); +} diff --git a/arch/arm/mach-omap2/board-tuna-jack.c b/arch/arm/mach-omap2/board-tuna-jack.c new file mode 100644 index 0000000..d3dc6ac --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-jack.c @@ -0,0 +1,152 @@ +/* Board support file for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2010 Texas Instruments + * + * Based on mach-omap2/board-omap4panda.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/input.h> +#include <linux/i2c/twl6030-madc.h> +#include <linux/sec_jack.h> + +#include "mux.h" +#include "board-tuna.h" + +#define GPIO_EAR_MICBIAS_EN 49 +#define GPIO_DET_35 0 +#define GPIO_EAR_SEND_END 1 + +#define ADC_CHANNEL_JACK 2 + +static void sec_jack_set_micbias_state(bool on) +{ + gpio_set_value(GPIO_EAR_MICBIAS_EN, 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) +{ + int value; + + value = twl6030_get_madc_conversion(ADC_CHANNEL_JACK); + return (int)(1800*value) / 1024; +} + +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, + .send_end_gpio = GPIO_EAR_SEND_END, +}; + +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 omap4_tuna_jack_init(void) +{ + omap_mux_init_signal("sim_io.gpio_wk0", OMAP_PIN_INPUT); + gpio_request(GPIO_DET_35, "det_35_en"); + gpio_direction_input(GPIO_DET_35); + + omap_mux_init_signal("sim_clk.gpio_wk1", OMAP_PIN_INPUT); + gpio_request(GPIO_EAR_SEND_END, "ear_send_end"); + gpio_direction_input(GPIO_EAR_SEND_END); + + omap_mux_init_signal("gpmc_a25.gpio_49", OMAP_PIN_OUTPUT | OMAP_MUX_MODE3); + gpio_request(GPIO_EAR_MICBIAS_EN, "ear_micbias_en"); + gpio_direction_output(GPIO_EAR_MICBIAS_EN, 0); + + gpio_free(GPIO_DET_35); + gpio_free(GPIO_EAR_SEND_END); + platform_device_register(&sec_device_jack); +} diff --git a/arch/arm/mach-omap2/board-tuna-modems.c b/arch/arm/mach-omap2/board-tuna-modems.c new file mode 100755 index 0000000..410e3da --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-modems.c @@ -0,0 +1,760 @@ +/* linux/arch/arm/mach-xxxx/board-tuna-modems.c + * Copyright (C) 2010 Samsung Electronics. 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/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/delay.h> + +#include <mach/omap4-common.h> +#include <linux/platform_data/modem.h> +#include "board-tuna.h" +#include "mux.h" + +#define OMAP_GPIO_MIPI_HSI_CP_ON 53 +#define OMAP_GPIO_MIPI_HSI_RESET_REQ_N 50 +#define OMAP_GPIO_MIPI_HSI_CP_RST 15 +#define OMAP_GPIO_MIPI_HSI_PDA_ACTIVE 119 +#define OMAP_GPIO_MIPI_HSI_PHONE_ACTIVE 120 +#define OMAP_GPIO_MIPI_HSI_CP_DUMP_INT 95 +#define OMAP_GPIO_MIPI_HSI_GPS_UART_SEL 164 + +#define OMAP_GPIO_DPRAM_VIA_RST 15 +#define OMAP_GPIO_DPRAM_PDA_ACTIVE 119 +#define OMAP_GPIO_DPRAM_PHONE_ACTIVE 120 +#define OMAP_GPIO_DPRAM_PWRHOLD_OFF 53 +#define OMAP_GPIO_DPRAM_INT_N 62 + +#define OMAP_GPIO_CMC_SPI_CLK_ACK 178 +#define OMAP_GPIO_CMC_SPI_CLK_REQ 164 +#define OMAP_GPIO_CMC_SPI_WAKEUP_INT 134 +#define OMAP_GPIO_LTE_ACTIVE 47 +#define OMAP_GPIO_CMC2AP_INT1 61 +#define OMAP_GPIO_CMC2AP_INT2 160 +#define OMAP_GPIO_AP2CMC_INT1 18 +#define OMAP_GPIO_AP2CMC_INT2 28 +#define OMAP_GPIO_221_PMIC_PWRON 41 +#define OMAP_GPIO_CMC_RST 50 +#define OMAP_GPIO_221_PMIC_PWRHOLD_OFF 163 + +#define DPRAM_START_ADDRESS 0x04000000 +#define DPRAM_SIZE 0x4000 +#define DPRAM_END_ADDRESS (DPRAM_START_ADDRESS + DPRAM_SIZE - 1) + +#define GPMC_CONTROL_BASE_ADDR 0x50000000 +#define GPMC_CONFIG1_1 (GPMC_CONTROL_BASE_ADDR + 0x90) +#define GPMC_CONFIG2_1 (GPMC_CONTROL_BASE_ADDR + 0x94) +#define GPMC_CONFIG3_1 (GPMC_CONTROL_BASE_ADDR + 0x98) +#define GPMC_CONFIG4_1 (GPMC_CONTROL_BASE_ADDR + 0x9C) +#define GPMC_CONFIG5_1 (GPMC_CONTROL_BASE_ADDR + 0xA0) +#define GPMC_CONFIG6_1 (GPMC_CONTROL_BASE_ADDR + 0xA4) +#define GPMC_CONFIG7_1 (GPMC_CONTROL_BASE_ADDR + 0xA8) + +#define DPRAM_GPMC_CONFIG1 0x00001201 +#define DPRAM_GPMC_CONFIG2 0x000f1200 +#define DPRAM_GPMC_CONFIG3 0x44040400 +#define DPRAM_GPMC_CONFIG4 0x0e05f155 +#define DPRAM_GPMC_CONFIG5 0x000e1016 +#define DPRAM_GPMC_CONFIG6 0x060603c3 +#define DPRAM_GPMC_CONFIG7 0x00000F44 + +/* umts target platform data */ +static struct modem_io_t umts_io_devices[] = { + [0] = { + .name = "umts_ipc0", + .id = 0x1, + .format = IPC_FMT, + .io_type = IODEV_MISC, + .link = LINKDEV_MIPI, + }, + [1] = { + .name = "umts_rfs0", + .id = 0x41, + .format = IPC_RFS, + .io_type = IODEV_MISC, + .link = LINKDEV_MIPI, + }, + [2] = { + .name = "rmnet0", + .id = 0x2A, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_MIPI, + }, + [3] = { + .name = "umts_boot0", + .id = 0x0, + .format = IPC_BOOT, + .io_type = IODEV_MISC, + .link = LINKDEV_MIPI, + }, + [4] = { + .name = "rmnet1", + .id = 0x2B, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_MIPI, + }, + [5] = { + .name = "rmnet2", + .id = 0x2C, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_MIPI, + }, + [6] = { + .name = "multipdp", + .id = 0x1, + .format = IPC_MULTI_RAW, + .io_type = IODEV_DUMMY, + .link = LINKDEV_MIPI, + }, + [7] = { + .name = "umts_ramdump0", + .id = 0x0, + .format = IPC_RAMDUMP, + .io_type = IODEV_MISC, + .link = LINKDEV_MIPI, + }, + [8] = { + .name = "umts_boot1", + .id = 0x1, + .format = IPC_BOOT, + .io_type = IODEV_MISC, + .link = LINKDEV_MIPI, + }, +}; + +static struct modem_data umts_modem_data = { + .name = "xmm6260", + + .gpio_cp_on = OMAP_GPIO_MIPI_HSI_CP_ON, + .gpio_reset_req_n = OMAP_GPIO_MIPI_HSI_RESET_REQ_N, + .gpio_cp_reset = OMAP_GPIO_MIPI_HSI_CP_RST, + .gpio_pda_active = OMAP_GPIO_MIPI_HSI_PDA_ACTIVE, + .gpio_phone_active = OMAP_GPIO_MIPI_HSI_PHONE_ACTIVE, + .gpio_cp_dump_int = OMAP_GPIO_MIPI_HSI_CP_DUMP_INT, + .gpio_flm_uart_sel = OMAP_GPIO_MIPI_HSI_GPS_UART_SEL, + .gpio_cp_warm_reset = 0, + + .modem_type = IMC_XMM6260, + .link_type = LINKDEV_MIPI, + .modem_net = UMTS_NETWORK, + + .num_iodevs = ARRAY_SIZE(umts_io_devices), + .iodevs = umts_io_devices, +}; + +static void umts_modem_cfg_gpio(void) +{ + unsigned gpio_reset_req_n = umts_modem_data.gpio_reset_req_n; + unsigned gpio_cp_on = umts_modem_data.gpio_cp_on; + unsigned gpio_cp_rst = umts_modem_data.gpio_cp_reset; + unsigned gpio_pda_active = umts_modem_data.gpio_pda_active; + unsigned gpio_phone_active = umts_modem_data.gpio_phone_active; + unsigned gpio_cp_dump_int = umts_modem_data.gpio_cp_dump_int; + unsigned gpio_flm_uart_sel = umts_modem_data.gpio_flm_uart_sel; + + /* gpio mux setting */ + omap_mux_init_signal("gpmc_ncs0.gpio_50", OMAP_PIN_OUTPUT | + OMAP_OFF_EN | OMAP_OFFOUT_VAL); + omap_mux_init_signal("gpmc_ncs3.gpio_53", OMAP_PIN_OUTPUT | + OMAP_OFF_EN); + omap_mux_init_signal("dpm_emu4.gpio_15", OMAP_PIN_OUTPUT | + OMAP_OFF_EN | OMAP_OFFOUT_VAL); + omap_mux_init_signal("abe_dmic_clk1.gpio_119", OMAP_PIN_OUTPUT | + OMAP_OFF_EN); + omap_mux_init_signal("abe_dmic_din1.gpio_120", OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("usbb1_ulpitll_dat7.gpio_95", OMAP_PIN_INPUT); + omap_mux_init_signal("usbb2_ulpitll_dat3.gpio_164", OMAP_PIN_OUTPUT | + OMAP_OFF_EN | OMAP_OFFOUT_VAL); + omap_mux_init_signal("uart3_cts_rctx.uart1_tx", OMAP_PIN_INPUT); + omap_mux_init_signal("mcspi1_cs1.uart1_rx", OMAP_PIN_INPUT); + + if (gpio_reset_req_n) { + gpio_request(gpio_reset_req_n, "RESET_REQ_N"); + gpio_direction_output(gpio_reset_req_n, 0); + } + + if (gpio_cp_on) { + gpio_request(gpio_cp_on, "CP_ON"); + gpio_direction_output(gpio_cp_on, 0); + } + + if (gpio_cp_rst) { + gpio_request(gpio_cp_rst, "CP_RST"); + gpio_direction_output(gpio_cp_rst, 0); + } + + if (gpio_pda_active) { + gpio_request(gpio_pda_active, "PDA_ACTIVE"); + gpio_direction_output(gpio_pda_active, 0); + } + + if (gpio_phone_active) { + gpio_request(gpio_phone_active, "PHONE_ACTIVE"); + gpio_direction_input(gpio_phone_active); + } + + if (gpio_cp_dump_int) { + gpio_request(gpio_cp_dump_int, "CP_DUMP_INT"); + gpio_direction_input(gpio_cp_dump_int); + } + + if (gpio_flm_uart_sel) { + gpio_request(gpio_flm_uart_sel, "GPS_UART_SEL"); + gpio_direction_output(gpio_flm_uart_sel, 1); + } + + if (gpio_phone_active) + irq_set_irq_type( + OMAP_GPIO_IRQ(OMAP_GPIO_MIPI_HSI_PHONE_ACTIVE), + IRQ_TYPE_LEVEL_HIGH); + + pr_debug("umts_modem_cfg_gpio done\n"); +} + +/* To get modem state, register phone active irq using resource */ +static struct resource umts_modem_res[] = { + [0] = { + .name = "umts_phone_active", + .start = OMAP_GPIO_IRQ(OMAP_GPIO_MIPI_HSI_PHONE_ACTIVE), + .end = OMAP_GPIO_IRQ(OMAP_GPIO_MIPI_HSI_PHONE_ACTIVE), + .flags = IORESOURCE_IRQ, + }, +}; + +/* if use more than one modem device, then set id num */ +static struct platform_device umts_modem = { + .name = "modem_if", + .id = -1, + .num_resources = ARRAY_SIZE(umts_modem_res), + .resource = umts_modem_res, + .dev = { + .platform_data = &umts_modem_data, + }, +}; + +static struct modem_io_t cdma_io_devices[] = { + [0] = { + .name = "multipdp", + .id = 0x1, + .format = IPC_MULTI_RAW, + .io_type = IODEV_DUMMY, + .link = LINKDEV_DPRAM, + }, + [1] = { + .name = "cdma_ipc0", + .id = 0x1, + .format = IPC_FMT, + .io_type = IODEV_MISC, + .link = LINKDEV_DPRAM, + }, + [2] = { + .name = "cdma_boot0", + .id = 0x1, + .format = IPC_BOOT, + .io_type = IODEV_MISC, + .link = LINKDEV_DPRAM, + }, + [3] = { + .name = "cdma_rmnet0", + .id = 0x2A, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_DPRAM, + }, + [4] = { + .name = "cdma_rmnet1", + .id = 0x2B, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_DPRAM, + }, + [5] = { + .name = "cdma_rmnet2", + .id = 0x2C, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_DPRAM, + }, + [6] = { + .name = "cdma_rmnet3", + .id = 0x2D, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_DPRAM, + }, + [7] = { + .name = "cdma_rmnet4", + .id = 0x27, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_DPRAM, + }, + [8] = { + .name = "cdma_rmnet5", /* DM Port io-device */ + .id = 0x3A, + .format = IPC_RAW, + .io_type = IODEV_MISC, + .link = LINKDEV_DPRAM, + }, + [9] = { + .name = "cdma_ramdump0", + .id = 0x1, + .format = IPC_RAMDUMP, + .io_type = IODEV_MISC, + .link = LINKDEV_DPRAM, + }, + [10] = { + .name = "cdma_rmnet6", /* AT CMD io-device */ + .id = 0x31, + .format = IPC_RAW, + .io_type = IODEV_MISC, + .link = LINKDEV_DPRAM, + }, +}; + +/* cdma target platform data */ +static struct modem_data cdma_modem_data = { + .name = "cbp7.1", + + /*ToDo: always power on vbat 3.3v it is not cennected GPIO*/ + .gpio_cp_on = 0, + .gpio_reset_req_n = 0, + .gpio_cp_reset = OMAP_GPIO_DPRAM_VIA_RST, + .gpio_pda_active = OMAP_GPIO_DPRAM_PDA_ACTIVE, + .gpio_phone_active = OMAP_GPIO_DPRAM_PHONE_ACTIVE, + .gpio_cp_dump_int = 0, /*ToDo:*/ + .gpio_cp_warm_reset = 0, + .gpio_cp_off = OMAP_GPIO_DPRAM_PWRHOLD_OFF, + + .modem_type = VIA_CBP71, + .link_type = LINKDEV_DPRAM, + .modem_net = CDMA_NETWORK, + + .num_iodevs = ARRAY_SIZE(cdma_io_devices), + .iodevs = cdma_io_devices, +}; + +static void dpram_cfg_gpio(void) +{ + int nRetry = 0; + int resetdone; + + omap_mux_init_signal("gpmc_ad0", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad1", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad2", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad3", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad4", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad5", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad6", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad7", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad8", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad9", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad10", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad11", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad12", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad13", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad14", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + omap_mux_init_signal("gpmc_ad15", OMAP_PULL_ENA|OMAP_PULL_UP|OMAP_INPUT_EN); + + omap_mux_init_signal("gpmc_nadv_ale", 0); + omap_mux_init_signal("gpmc_nwe", 0); + omap_mux_init_signal("gpmc_nbe0_cle", 0); + omap_mux_init_signal("gpmc_ncs1", 0); + omap_mux_init_signal("gpmc_nbe1", 0); + + omap_mux_init_signal("gpmc_wait1.gpio_62", OMAP_WAKEUP_EN | OMAP_INPUT_EN); + omap_mux_init_signal("dpm_emu3", OMAP_MUX_MODE3); + omap_mux_init_signal("gpmc_ncs3.gpio_53", OMAP_PIN_OUTPUT); + + gpio_request(OMAP_GPIO_DPRAM_INT_N, "dpram_int"); + gpio_direction_input(OMAP_GPIO_DPRAM_INT_N); + irq_set_irq_type(OMAP_GPIO_IRQ(OMAP_GPIO_DPRAM_INT_N), + IRQ_TYPE_LEVEL_LOW); + + /*dpram platform init setting*/ + __raw_writel(0x02, OMAP4_GPMC_IO_ADDRESS((OMAP44XX_GPMC_BASE + 0x10))); + + while (nRetry < 100) { + msleep(20); + resetdone = __raw_readl(OMAP4_GPMC_IO_ADDRESS(OMAP44XX_GPMC_BASE + + 0x14)); + if (resetdone == 0x1) + break; + + nRetry++; + } + + __raw_writel(0x10, OMAP4_GPMC_IO_ADDRESS((OMAP44XX_GPMC_BASE + 0x10))); + + __raw_writel((u32)DPRAM_GPMC_CONFIG1, + OMAP4_GPMC_IO_ADDRESS(GPMC_CONFIG1_1)); + __raw_writel((u32)DPRAM_GPMC_CONFIG2, + OMAP4_GPMC_IO_ADDRESS(GPMC_CONFIG2_1)); + __raw_writel((u32)DPRAM_GPMC_CONFIG3, + OMAP4_GPMC_IO_ADDRESS(GPMC_CONFIG3_1)); + __raw_writel((u32)DPRAM_GPMC_CONFIG4, + OMAP4_GPMC_IO_ADDRESS(GPMC_CONFIG4_1)); + __raw_writel((u32)DPRAM_GPMC_CONFIG5, + OMAP4_GPMC_IO_ADDRESS(GPMC_CONFIG5_1)); + __raw_writel((u32)DPRAM_GPMC_CONFIG6, + OMAP4_GPMC_IO_ADDRESS(GPMC_CONFIG6_1)); + + __raw_writel((u32)0xF04, OMAP4_GPMC_IO_ADDRESS(GPMC_CONFIG7_1)); + msleep(50); + __raw_writel((u32)(0xF04 | 0x040), + OMAP4_GPMC_IO_ADDRESS(GPMC_CONFIG7_1)); +} + +static int dpram_cfg_gpmc_clk(void) +{ + struct clk *dpram_gpmc_ck; + struct clk *dpram_gpmc_ick; + + dpram_gpmc_ck = clk_get(NULL, "gpmc_ck"); + if (IS_ERR(dpram_gpmc_ck)) { + pr_err("Could not get GPMC clock gpmc_ck\n"); + return -ENOENT; + } + clk_enable(dpram_gpmc_ck); + + dpram_gpmc_ick = clk_get(NULL, "gpmc_ick"); + if (IS_ERR(dpram_gpmc_ick)) { + clk_disable(dpram_gpmc_ck); + pr_err("Could not get GPMC clock gpmc_ick\n"); + return -ENOENT; + } + clk_enable(dpram_gpmc_ick); + + return 0; +} + +static void cdma_modem_cfg_gpio(void) +{ + unsigned gpio_cp_rst = cdma_modem_data.gpio_cp_reset; + unsigned gpio_pda_active = cdma_modem_data.gpio_pda_active; + unsigned gpio_phone_active = cdma_modem_data.gpio_phone_active; + unsigned gpio_cp_off = cdma_modem_data.gpio_cp_off; + + dpram_cfg_gpio(); + if (dpram_cfg_gpmc_clk()) { + pr_err("fail to enable GPMC clock\n"); + return; + } + + omap_mux_init_signal("abe_dmic_din1.gpio_120", OMAP_PIN_INPUT); + omap_mux_init_signal("abe_dmic_clk1.gpio_119", OMAP_PIN_OUTPUT | + OMAP_PIN_OFF_OUTPUT_LOW); + + /* gpio mux setting */ + if (gpio_cp_rst) { + gpio_request(gpio_cp_rst, "CP_RST"); + gpio_direction_output(gpio_cp_rst, 0); + } + + if (gpio_pda_active) { + gpio_request(gpio_pda_active, "PDA_ACTIVE"); + gpio_direction_output(gpio_pda_active, 0); + } + + if (gpio_phone_active) { + gpio_request(gpio_phone_active, "PHONE_ACTIVE"); + gpio_direction_input(gpio_phone_active); + } + + if (gpio_cp_off) { + gpio_request(gpio_cp_off, "VIA_OFF"); + gpio_direction_output(gpio_cp_off, 1); + } + + if (gpio_phone_active) + irq_set_irq_type( + OMAP_GPIO_IRQ(OMAP_GPIO_DPRAM_PHONE_ACTIVE), + IRQ_TYPE_LEVEL_HIGH); +} + +static struct resource cdma_modem_res[] = { + [0] = { + .name = "cdma_phone_active", + .start = OMAP_GPIO_IRQ(OMAP_GPIO_DPRAM_PHONE_ACTIVE), + .end = OMAP_GPIO_IRQ(OMAP_GPIO_DPRAM_PHONE_ACTIVE), + .flags = IORESOURCE_IRQ, + }, + [1] = { + .name = "cdma_dpram_int", + .start = OMAP_GPIO_IRQ(OMAP_GPIO_DPRAM_INT_N), + .end = OMAP_GPIO_IRQ(OMAP_GPIO_DPRAM_INT_N), + .flags = IORESOURCE_IRQ, + }, + [2] = { + .name = "cdma_dpram", + .start = DPRAM_START_ADDRESS, + .end = DPRAM_END_ADDRESS, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device cdma_modem = { + .name = "modem_if", + .id = 1, + .num_resources = ARRAY_SIZE(cdma_modem_res), + .resource = cdma_modem_res, + .dev = { + .platform_data = &cdma_modem_data, + }, +}; + +/* lte target platform data */ +static struct modem_io_t lte_io_devices[] = { + [0] = { + .name = "lte_ipc0", + .id = 0x1, + .format = IPC_FMT, + .io_type = IODEV_MISC, + .link = LINKDEV_USB, + }, + [1] = { + .name = "lte_rmnet0", + .id = 0x2A, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_USB, + }, + [2] = { + .name = "lte_rfs0", + .id = 0x0, + .format = IPC_RFS, + .io_type = IODEV_MISC, + .link = LINKDEV_USB, + }, + [3] = { + .name = "lte_boot0", + .id = 0x0, + .format = IPC_BOOT, + .io_type = IODEV_MISC, + .link = LINKDEV_USB, + }, + [4] = { + .name = "lte_rmnet1", + .id = 0x2B, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_USB, + }, + [5] = { + .name = "lte_rmnet2", + .id = 0x2C, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_USB, + }, + [6] = { + .name = "lte_rmnet3", + .id = 0x2D, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_USB, + }, + [7] = { + .name = "lte_multipdp", + .id = 0x1, + .format = IPC_MULTI_RAW, + .io_type = IODEV_DUMMY, + .link = LINKDEV_USB, + }, + [8] = { + .name = "lte_rmnet4", /* DM Port io-device */ + .id = 0x3F, + .format = IPC_RAW, + .io_type = IODEV_MISC, + .link = LINKDEV_USB, + }, + [9] = { + .name = "lte_ramdump0", + .id = 0x0, + .format = IPC_RAMDUMP, + .io_type = IODEV_MISC, + .link = LINKDEV_USB, + }, +}; + +/* +Prime vs P4 usage +CMC2AP_INT1 vs CMC2AP_STATUS +AP2CMC_INT1 vs AP2CMC_STATUS +CMC2AP_INT2 vs CMC2AP_WAKEUP +AP2CMC_INT2 vs AP2CMC_WAKEUP +*/ +static struct modem_data lte_modem_data = { + .name = "cmc221", + + .gpio_cp_on = OMAP_GPIO_221_PMIC_PWRON, + .gpio_reset_req_n = 0, + .gpio_cp_reset = OMAP_GPIO_CMC_RST, + .gpio_pda_active = 0,/*NOT YET CONNECTED*/ + .gpio_phone_active = OMAP_GPIO_LTE_ACTIVE, + .gpio_cp_dump_int = OMAP_GPIO_LTE_ACTIVE,/*TO BE CHECKED*/ + + .gpio_cp_warm_reset = 0, +#ifdef CONFIG_LTE_MODEM_CMC221 + .gpio_cp_off = OMAP_GPIO_221_PMIC_PWRHOLD_OFF, + .gpio_slave_wakeup = OMAP_GPIO_AP2CMC_INT2, + .gpio_host_wakeup = OMAP_GPIO_CMC2AP_INT2, + .gpio_host_active = OMAP_GPIO_AP2CMC_INT1, +#endif + + .modem_type = SEC_CMC221, + .link_type = LINKDEV_USB, + .modem_net = LTE_NETWORK, + + .num_iodevs = ARRAY_SIZE(lte_io_devices), + .iodevs = lte_io_devices, +}; + +static void omap_lte_mux_init(void) +{ + pr_debug("[MODEM_IF] %s IN!\n", __func__); + + omap_mux_init_signal("gpmc_a17.gpio_41", OMAP_PIN_OUTPUT); + omap_mux_init_signal("usbb2_ulpitll_dat2.gpio_163", OMAP_PIN_OUTPUT); + omap_mux_init_signal("gpmc_ncs0.gpio_50", OMAP_PIN_OUTPUT); + omap_mux_init_signal("dpm_emu7.gpio_18", OMAP_PIN_OUTPUT); + omap_mux_init_signal("usbb2_ulpitll_nxt.gpio_160", + OMAP_PIN_INPUT | OMAP_PIN_OFF_WAKEUPENABLE); + omap_mux_init_signal("dpm_emu17.gpio_28", OMAP_PIN_OUTPUT); + omap_mux_init_signal("gpmc_a23.gpio_47", OMAP_PIN_INPUT_PULLDOWN); +} + +static void lte_modem_cfg_gpio(void) +{ + unsigned gpio_cp_on = lte_modem_data.gpio_cp_on; + unsigned gpio_cp_rst = lte_modem_data.gpio_cp_reset; + unsigned gpio_phone_active = lte_modem_data.gpio_phone_active; +#ifdef CONFIG_LTE_MODEM_CMC221 + unsigned gpio_cp_off = lte_modem_data.gpio_cp_off; + unsigned gpio_slave_wakeup = lte_modem_data.gpio_slave_wakeup; + unsigned gpio_host_wakeup = lte_modem_data.gpio_host_wakeup; + unsigned gpio_host_active = lte_modem_data.gpio_host_active; +#endif + + omap_lte_mux_init(); + if (gpio_cp_on) { + gpio_request(gpio_cp_on, "LTE_ON"); + gpio_direction_output(gpio_cp_on, 0); + } + + if (gpio_cp_rst) { + gpio_request(gpio_cp_rst, "LTE_RST"); + gpio_direction_output(gpio_cp_rst, 0); + } + + if (gpio_phone_active) { + gpio_request(gpio_phone_active, "LTE_ACTIVE"); + gpio_direction_input(gpio_phone_active); + } + +#ifdef CONFIG_LTE_MODEM_CMC221 + if (gpio_cp_off) { + gpio_request(gpio_cp_off, "LTE_OFF"); + gpio_direction_output(gpio_cp_off, 1); + } + + if (gpio_slave_wakeup) { + gpio_request(gpio_slave_wakeup, "LTE_SLAVE_WAKEUP"); + gpio_direction_output(gpio_slave_wakeup, 0); + } + + if (gpio_host_wakeup) { + gpio_request(gpio_host_wakeup, "LTE_HOST_WAKEUP"); + gpio_direction_input(gpio_host_wakeup); + } + + if (gpio_host_active) { + gpio_request(gpio_host_active, "LTE_HOST_ACTIVE"); + gpio_direction_output(gpio_host_active, 1); + } +#endif +} + +static struct resource lte_modem_res[] = { + [0] = { + .name = "lte_phone_active", + /* phone active irq */ + .start = OMAP_GPIO_IRQ(OMAP_GPIO_LTE_ACTIVE), + .end = OMAP_GPIO_IRQ(OMAP_GPIO_LTE_ACTIVE), + .flags = IORESOURCE_IRQ, + }, + [1] = { + .name = "lte_host_wakeup", + /* host wakeup irq */ + .start = OMAP_GPIO_IRQ(OMAP_GPIO_CMC2AP_INT2), + .end = OMAP_GPIO_IRQ(OMAP_GPIO_CMC2AP_INT2), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device lte_modem_wake = { + .name = "modem_lte_wake", + .id = -1, +}; + +static struct platform_device lte_modem = { + .name = "modem_if", + .id = 2, + .num_resources = ARRAY_SIZE(lte_modem_res), + .resource = lte_modem_res, + .dev = { + .platform_data = <e_modem_data, + }, +}; + +/* lte_modem_wake must be registered before the ehci driver */ +void __init modem_toro_init(void) +{ + lte_modem_wake.dev.platform_data = <e_modem_data; + platform_device_register(<e_modem_wake); +} + +static int __init init_modem(void) +{ + pr_debug("[MODEM_IF] init_modem\n"); + + switch (omap4_tuna_get_type()) { + case TUNA_TYPE_MAGURO: /* HSPA */ + /* umts gpios configuration */ + umts_modem_cfg_gpio(); + platform_device_register(&umts_modem); + break; + + case TUNA_TYPE_TORO: /* LTE */ + /* cdma gpios configuration */ + cdma_modem_cfg_gpio(); + platform_device_register(&cdma_modem); + + /* lte gpios configuration */ + lte_modem_cfg_gpio(); + platform_device_register(<e_modem); + break; + + default: + break; + } + return 0; +} +late_initcall(init_modem); diff --git a/arch/arm/mach-omap2/board-tuna-nfc.c b/arch/arm/mach-omap2/board-tuna-nfc.c new file mode 100644 index 0000000..032ceae --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-nfc.c @@ -0,0 +1,148 @@ +/* Control power to pn544 + * + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/types.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/wakelock.h> +#include <plat/serial.h> + +#include "mux.h" + +#define GPIO_NFC_EN 173 +#define GPIO_NFC_FW 172 +#define GPIO_NFC_IRQ 17 + +#define PWR_OFF 0 +#define PWR_ON 1 +#define PWR_ON_FW 2 + +#define NFC_UART_NUM 4 /* omap_uart_wake() counts from 1 */ + +static unsigned int nfc_power; +static struct wake_lock nfc_wake_lock; + +static void nfc_power_apply(void) { + switch (nfc_power) { + case PWR_OFF: + pr_info("%s OFF\n", __func__); + gpio_set_value(GPIO_NFC_FW, 0); + gpio_set_value(GPIO_NFC_EN, 0); + msleep(60); + break; + case PWR_ON: + pr_info("%s ON\n", __func__); + gpio_set_value(GPIO_NFC_FW, 0); + gpio_set_value(GPIO_NFC_EN, 1); + msleep(20); + break; + case PWR_ON_FW: + pr_info("%s ON (firmware download)\n", __func__); + gpio_set_value(GPIO_NFC_FW, 1); + gpio_set_value(GPIO_NFC_EN, 1); + msleep(20); + gpio_set_value(GPIO_NFC_EN, 0); /* fw mode requires reset */ + msleep(60); + gpio_set_value(GPIO_NFC_EN, 1); + msleep(20); + break; + } +} + +static ssize_t nfc_power_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", nfc_power); +} + +static ssize_t nfc_power_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int rc; + unsigned int val; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + if (val > PWR_ON_FW) + return -EINVAL; + nfc_power = val; + nfc_power_apply(); + return count; +} + +static DEVICE_ATTR(nfc_power, S_IWUSR | S_IRUGO, nfc_power_show, + nfc_power_store); + +static irqreturn_t nfc_irq_isr(int irq, void *dev) +{ + omap_uart_wake(NFC_UART_NUM); + + /* + * take a 500ms wakelock, to give time for higher layers + * to either take their own wakelock or finish processing + */ + wake_lock_timeout(&nfc_wake_lock, msecs_to_jiffies(500)); + + return IRQ_HANDLED; +} + +void __init omap4_tuna_nfc_init(void) +{ + struct platform_device *pdev; + int irq; + + gpio_request(GPIO_NFC_FW, "nfc_fw"); + gpio_direction_output(GPIO_NFC_FW, 0); + omap_mux_init_gpio(GPIO_NFC_FW, OMAP_PIN_OUTPUT); + + gpio_request(GPIO_NFC_EN, "nfc_en"); + gpio_direction_output(GPIO_NFC_EN, 0); + omap_mux_init_gpio(GPIO_NFC_EN, OMAP_PIN_OUTPUT); + + gpio_request(GPIO_NFC_IRQ, "nfc_irq"); + gpio_direction_input(GPIO_NFC_IRQ); + omap_mux_init_gpio(GPIO_NFC_IRQ, OMAP_PIN_INPUT_PULLUP | + OMAP_PIN_OFF_WAKEUPENABLE); + + wake_lock_init(&nfc_wake_lock, WAKE_LOCK_SUSPEND, "nfc"); + + irq = gpio_to_irq(GPIO_NFC_IRQ); + if (request_irq(irq, nfc_irq_isr, IRQF_TRIGGER_RISING, "nfc_irq", + NULL)) { + pr_err("%s: request_irq() failed\n", __func__); + return; + } + + if (enable_irq_wake(irq)) { + pr_err("%s: irq_set_irq_wake() failed\n", __func__); + return; + } + + nfc_power = PWR_OFF; + + pdev = platform_device_register_simple("nfc-power", -1, NULL, 0); + if (IS_ERR(pdev)) { + pr_err("%s: platform_device_register_simple() failed\n", __func__); + return; + } + if (device_create_file(&pdev->dev, &dev_attr_nfc_power)) + pr_err("%s: device_create_file() failed\n", __func__); +} diff --git a/arch/arm/mach-omap2/board-tuna-pogo.c b/arch/arm/mach-omap2/board-tuna-pogo.c new file mode 100644 index 0000000..e9021f0 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-pogo.c @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2011 Samsung, Inc. + * + * Author: Adam Hampson <ahampson@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/kernel.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/switch.h> +#include <linux/wakelock.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include <linux/usb/otg.h> +#include <linux/usb/otg_id.h> + +#include <asm/div64.h> + +#include "mux.h" +#include "control.h" +#include "board-tuna.h" + +#define GPIO_POGO_DATA 121 +#define GPIO_POGO_DET 169 + +/* The below constants are in milliseconds */ +#define POGO_WAKE_PERIOD 100 +#define POGO_ID_PERIOD_TIMEOUT 750 +#define POGO_ID_DESKDOCK 50 +#define POGO_ID_CARDOCK 100 +#define POGO_ID_CHARGER 50 +#define POGO_ID_USB 100 +#define POGO_ID_AUDIO 50 +#define POGO_ID_NO_AUDIO 100 +#define POGO_ENTER_SPDIF_WAIT_PERIOD 100 +#define POGO_ID_PERIOD_TOLERANCE 20 +#define POGO_DET_DEBOUNCE 80 + +#define POGO_DOCK_ID_MAX_RETRY 10 + +#define POGO_AUDIO_DISCONNECTED 0 +#define POGO_AUDIO_CONNECTED 2 + +enum { + POGO_DOCK_DOCKED = BIT(0), + POGO_DOCK_DESK = BIT(1), /* 0 = Car */ + POGO_DOCK_CHARGER = BIT(2), + POGO_DOCK_AUDIO_CONN = BIT(3), +}; + +enum debounce_state { + POGO_DET_DOCKED, /* interrupt enabled, timer stopped */ + POGO_DET_UNSTABLE, /* interrupt disabled, timer running */ + POGO_DET_WAIT_STABLE, /* interrupt enabled, timer running */ + POGO_DET_UNDOCKED, /* interrupt disabled, timer stopped */ +}; + +struct tuna_pogo { + struct otg_id_notifier_block otg_id_nb; + struct switch_dev audio_switch; + struct wake_lock wake_lock; + struct completion completion; + struct timespec timestamps[4]; + int ts_index; + int det_irq; + int data_irq; + unsigned int dock_type; + struct mutex mutex; + struct timer_list det_timer; + struct work_struct det_work; + spinlock_t det_irq_lock; + enum debounce_state debounce_state; +}; +static struct tuna_pogo tuna_pogo; + +static void pogo_send_pulse(unsigned int duration_in_ms) +{ + gpio_direction_output(GPIO_POGO_DATA, 1); + msleep(duration_in_ms); + gpio_direction_output(GPIO_POGO_DATA, 0); +} + +static int pogo_read_id_period(struct tuna_pogo *pogo, + unsigned int timeout_in_ms) +{ + int ret; + + memset(pogo->timestamps, 0, sizeof(pogo->timestamps)); + pogo->ts_index = 0; + + gpio_direction_input(GPIO_POGO_DATA); + + irq_set_irq_type(pogo->data_irq, IRQF_TRIGGER_HIGH); + + enable_irq(pogo->data_irq); + + ret = wait_for_completion_timeout(&pogo->completion, + msecs_to_jiffies(timeout_in_ms)); + if (ret <= 0) { + if (pogo->ts_index != ARRAY_SIZE(pogo->timestamps)) + pr_debug("No response to wake within timeout\n"); + else + pr_debug("ID period did not conclude within timeout\n"); + disable_irq(pogo->data_irq); + return -1; + } + + return 0; +} + +static void pogo_dock_change(struct tuna_pogo *pogo) +{ + char *dock_type_str; + char *dock_audio_str; + char *dock_charger_str; + + if (!(pogo->dock_type & POGO_DOCK_DOCKED)) { + tuna_otg_set_dock_switch(0); + switch_set_state(&pogo->audio_switch, POGO_AUDIO_DISCONNECTED); + tuna_otg_pogo_charger(POGO_POWER_DISCONNECTED); + pr_info("Undocked\n"); + return; + } else { + tuna_otg_set_dock_switch(pogo->dock_type & POGO_DOCK_DESK ? 1 : 2); + } + + dock_type_str = pogo->dock_type & POGO_DOCK_DESK ? "Desk" : "Car"; + if (pogo->dock_type & POGO_DOCK_AUDIO_CONN) { + switch_set_state(&pogo->audio_switch, POGO_AUDIO_CONNECTED); + dock_audio_str = ""; + } else { + switch_set_state(&pogo->audio_switch, POGO_AUDIO_DISCONNECTED); + dock_audio_str = " no"; + } + if (pogo->dock_type & POGO_DOCK_CHARGER) { + tuna_otg_pogo_charger(POGO_POWER_CHARGER); + dock_charger_str = "charger"; + } else { + tuna_otg_pogo_charger(POGO_POWER_HOST); + dock_charger_str = "host"; + } + + pr_info("%s dock,%s audio, USB %s\n", dock_type_str, dock_audio_str, + dock_charger_str); +} + +static int pogo_detect_callback(struct otg_id_notifier_block *nb) +{ + struct tuna_pogo *pogo = container_of(nb, struct tuna_pogo, otg_id_nb); + int dock_type_period; + int power_type_period; + int audio_cable_period; + struct timespec temp; + unsigned int retry = 0; + unsigned long irqflags; + + if (gpio_get_value(GPIO_POGO_DET)) { + wake_lock(&pogo->wake_lock); + + while (!(pogo->dock_type & POGO_DOCK_DOCKED)) { + + if (!gpio_get_value(GPIO_POGO_DET)) { + wake_unlock(&pogo->wake_lock); + return OTG_ID_UNHANDLED; + } + + if (retry++ > POGO_DOCK_ID_MAX_RETRY) { + wake_unlock(&pogo->wake_lock); + pr_err("Unable to identify pogo dock\n"); + return OTG_ID_UNHANDLED; + } + + /* Start the detection process by sending a wake pulse + * to the dock. + */ + pogo_send_pulse(POGO_WAKE_PERIOD); + + if (pogo_read_id_period(pogo, POGO_ID_PERIOD_TIMEOUT)) + continue; + + temp = timespec_sub(pogo->timestamps[1], + pogo->timestamps[0]); + dock_type_period = temp.tv_nsec / NSEC_PER_MSEC; + + temp = timespec_sub(pogo->timestamps[2], + pogo->timestamps[1]); + power_type_period = temp.tv_nsec / NSEC_PER_MSEC; + + temp = timespec_sub(pogo->timestamps[3], + pogo->timestamps[2]); + audio_cable_period = temp.tv_nsec / NSEC_PER_MSEC; + + /* The length of the ID period will indicate the type of + * dock that is attached. + */ + if (abs(dock_type_period - POGO_ID_DESKDOCK) <= + POGO_ID_PERIOD_TOLERANCE) { + pogo->dock_type |= POGO_DOCK_DESK; + } else if (abs(dock_type_period - POGO_ID_CARDOCK) > + POGO_ID_PERIOD_TOLERANCE) { + continue; + } + + if (abs(power_type_period - POGO_ID_CHARGER) <= + POGO_ID_PERIOD_TOLERANCE) { + pogo->dock_type |= POGO_DOCK_CHARGER; + } else if (abs(power_type_period - POGO_ID_USB) > + POGO_ID_PERIOD_TOLERANCE) { + continue; + } + + if (abs(audio_cable_period - POGO_ID_AUDIO) <= + POGO_ID_PERIOD_TOLERANCE) { + pogo->dock_type |= POGO_DOCK_AUDIO_CONN; + pogo->dock_type |= POGO_DOCK_DOCKED; + } else if (abs(audio_cable_period - + POGO_ID_NO_AUDIO) <= + POGO_ID_PERIOD_TOLERANCE) { + pogo->dock_type |= POGO_DOCK_DOCKED; + } + } + + msleep(POGO_ENTER_SPDIF_WAIT_PERIOD); + + mutex_lock(&pogo->mutex); + omap_mux_set_gpio(OMAP_MUX_MODE2 | OMAP_PIN_OUTPUT, + GPIO_POGO_DATA); + + pogo_dock_change(pogo); + wake_lock_timeout(&pogo->wake_lock, msecs_to_jiffies(1000)); + + spin_lock_irqsave(&pogo->det_irq_lock, irqflags); + pogo->debounce_state = POGO_DET_DOCKED; + enable_irq(pogo->det_irq); + enable_irq_wake(pogo->det_irq); + spin_unlock_irqrestore(&pogo->det_irq_lock, irqflags); + mutex_unlock(&pogo->mutex); + + return OTG_ID_HANDLED; + } + + return OTG_ID_UNHANDLED; +} + +static void pogo_dock_undock(struct tuna_pogo *pogo) +{ + mutex_lock(&pogo->mutex); + if ((pogo->dock_type & POGO_DOCK_DOCKED) && + pogo->debounce_state == POGO_DET_UNDOCKED) { + pogo->dock_type = 0; + pogo_dock_change(pogo); + + omap_mux_set_gpio(OMAP_MUX_MODE3 | OMAP_PIN_INPUT_PULLDOWN, + GPIO_POGO_DATA); + } + mutex_unlock(&pogo->mutex); +} + +/* This callback is used to cancel any ownership of the chain */ +static void pogo_cancel_callback(struct otg_id_notifier_block *nb) +{ + struct tuna_pogo *pogo = container_of(nb, struct tuna_pogo, otg_id_nb); + unsigned long irqflags; + + /* Disable the POGO_DET IRQ and cancel any pending timer if needed */ + spin_lock_irqsave(&pogo->det_irq_lock, irqflags); + switch (pogo->debounce_state) { + case POGO_DET_UNDOCKED: + break; + case POGO_DET_UNSTABLE: + del_timer(&pogo->det_timer); + break; + case POGO_DET_WAIT_STABLE: + del_timer(&pogo->det_timer); + /* fall through */ + case POGO_DET_DOCKED: + disable_irq_wake(pogo->det_irq); + disable_irq_nosync(pogo->det_irq); + break; + } + pogo->debounce_state = POGO_DET_UNDOCKED; + spin_unlock_irqrestore(&pogo->det_irq_lock, irqflags); + + /* Change the state to undocked */ + pogo_dock_undock(pogo); +} + +static void det_work_func(struct work_struct *work) +{ + struct tuna_pogo *pogo = container_of(work, struct tuna_pogo, det_work); + + pogo_dock_undock(pogo); + + /* Notify the otg_id chain that a change has occurred */ + otg_id_notify(); +} + +static void pogo_det_timer_func(unsigned long arg) +{ + struct tuna_pogo *pogo = (struct tuna_pogo *)arg; + unsigned long irqflags; + + spin_lock_irqsave(&pogo->det_irq_lock, irqflags); + switch (pogo->debounce_state) { + case POGO_DET_DOCKED: + break; + case POGO_DET_UNSTABLE: + /* + * The detect gpio changed in one the previous two 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. + */ + pogo->debounce_state = POGO_DET_WAIT_STABLE; + enable_irq(pogo->det_irq); + enable_irq_wake(pogo->det_irq); + mod_timer(&pogo->det_timer, + jiffies + msecs_to_jiffies(POGO_DET_DEBOUNCE)); + break; + case POGO_DET_WAIT_STABLE: + if (gpio_get_value(GPIO_POGO_DET) == 0) { + pogo->debounce_state = POGO_DET_UNDOCKED; + disable_irq_wake(pogo->det_irq); + disable_irq_nosync(pogo->det_irq); + wake_lock_timeout(&pogo->wake_lock, + msecs_to_jiffies(1000)); + schedule_work(&pogo->det_work); + } else { + /* The device appears to be back in the dock */ + pogo->debounce_state = POGO_DET_DOCKED; + wake_unlock(&pogo->wake_lock); + } + break; + case POGO_DET_UNDOCKED: + break; + } + spin_unlock_irqrestore(&pogo->det_irq_lock, irqflags); +} + +static irqreturn_t pogo_det_irq(int irq, void *data) +{ + struct tuna_pogo *pogo = data; + unsigned long irqflags; + + spin_lock_irqsave(&pogo->det_irq_lock, irqflags); + switch (pogo->debounce_state) { + case POGO_DET_DOCKED: + wake_lock(&pogo->wake_lock); + mod_timer(&pogo->det_timer, + jiffies + msecs_to_jiffies(POGO_DET_DEBOUNCE)); + /* fall through */ + case POGO_DET_WAIT_STABLE: + /* + * Disable IRQ line in case there is noise. It will be + * re-enabled when the timer expires + */ + pogo->debounce_state = POGO_DET_UNSTABLE; + disable_irq_wake(pogo->det_irq); + disable_irq_nosync(pogo->det_irq); + break; + case POGO_DET_UNSTABLE: + case POGO_DET_UNDOCKED: + break; + } + spin_unlock_irqrestore(&pogo->det_irq_lock, irqflags); + + return IRQ_HANDLED; +} + +static irqreturn_t pogo_data_irq(int irq, void *data) +{ + struct tuna_pogo *pogo = data; + + ktime_get_ts(&pogo->timestamps[pogo->ts_index++]); + irq_set_irq_type(pogo->data_irq, gpio_get_value(GPIO_POGO_DATA) ? + IRQF_TRIGGER_LOW : + IRQF_TRIGGER_HIGH); + if (pogo->ts_index >= ARRAY_SIZE(pogo->timestamps)) { + complete(&pogo->completion); + disable_irq_nosync(pogo->data_irq); + } + return IRQ_HANDLED; +} + +void __init omap4_tuna_pogo_init(void) +{ + struct tuna_pogo *pogo = &tuna_pogo; + unsigned int r; + int ret; + + omap_mux_init_signal("usbb2_hsic_data.gpio_169", + OMAP_PIN_INPUT_PULLDOWN | OMAP_MUX_MODE3 | + OMAP_WAKEUP_EN); + + /* The pullup/pulldown controls in the mux register are not the controls + * that you are looking for. The usbb2_hsic_data signal has a separate + * special control in the CONTROL_USBB_HSIC register. + */ + r = omap4_ctrl_pad_readl(OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_USBB_HSIC); + r &= ~OMAP4_USBB2_HSIC_DATA_WD_MASK; + omap4_ctrl_pad_writel(r, OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_USBB_HSIC); + + ret = gpio_request(GPIO_POGO_DET, "pogo_det"); + if (ret < 0) + pr_err("request for pogo_det gpio failed, err %d\n", ret); + + gpio_direction_input(GPIO_POGO_DET); + + omap_mux_init_signal("abe_dmic_din2.gpio_121", + OMAP_PIN_INPUT_PULLDOWN | OMAP_MUX_MODE3); + ret = gpio_request(GPIO_POGO_DATA, "pogo_data"); + if (ret < 0) + pr_err("request for pogo_data gpio failed, err %d\n", ret); + + gpio_direction_output(GPIO_POGO_DATA, 0); + + /* The POGO dock does not involve USB but we are reusing the existing + * usb audio switch report the availabilty of SPDIF audio through the + * POGO dock. + */ + pogo->audio_switch.name = "usb_audio"; + + switch_dev_register(&pogo->audio_switch); + + wake_lock_init(&pogo->wake_lock, WAKE_LOCK_SUSPEND, "pogo"); + + init_completion(&pogo->completion); + + pogo->otg_id_nb.detect = pogo_detect_callback; + pogo->otg_id_nb.proxy_wait = NULL; + pogo->otg_id_nb.cancel = pogo_cancel_callback; + pogo->otg_id_nb.priority = TUNA_OTG_ID_POGO_PRIO; + + ret = otg_id_register_notifier(&pogo->otg_id_nb); + if (ret < 0) + pr_err("Unable to register notifier\n"); + + setup_timer(&pogo->det_timer, pogo_det_timer_func, (unsigned long)pogo); + spin_lock_init(&pogo->det_irq_lock); + mutex_init(&pogo->mutex); + INIT_WORK(&pogo->det_work, det_work_func); + pogo->debounce_state = POGO_DET_UNDOCKED; + + pogo->det_irq = gpio_to_irq(GPIO_POGO_DET); + + ret = request_irq(pogo->det_irq, pogo_det_irq, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "pogo_det", pogo); + if (ret < 0) + pr_err("Unable to register pogo_det interrupt (%d)\n", ret); + + disable_irq(pogo->det_irq); + + pogo->data_irq = gpio_to_irq(GPIO_POGO_DATA); + + ret = request_irq(pogo->data_irq, pogo_data_irq, + IRQF_TRIGGER_HIGH, + "pogo_data", pogo); + if (ret < 0) + pr_err("Unable to register pogo_data interrupt (%d)\n", ret); + + disable_irq(pogo->data_irq); +} diff --git a/arch/arm/mach-omap2/board-tuna-power.c b/arch/arm/mach-omap2/board-tuna-power.c new file mode 100644 index 0000000..53a6338 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-power.c @@ -0,0 +1,533 @@ +/* Power support for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/max17040_battery.h> +#include <linux/moduleparam.h> +#include <linux/pda_power.h> +#include <linux/platform_device.h> +#include <linux/i2c/twl6030-madc.h> +#include <linux/delay.h> + +#include <plat/cpu.h> +#include <plat/omap-pm.h> + +#include "board-tuna.h" +#include "mux.h" +#include "pm.h" + +/* These will be different on pre-lunchbox, lunchbox, and final */ +#define GPIO_CHARGING_N 83 +#define GPIO_TA_NCONNECTED 142 +#define GPIO_CHARGE_N 13 +#define GPIO_CHG_CUR_ADJ 102 +#define GPIO_FUEL_ALERT 44 + +#define TPS62361_GPIO 7 +#define ADC_NUM_SAMPLES 5 +#define ADC_LIMIT_ERR_COUNT 5 +#define ISET_ADC_CHANNEL 3 +#define TEMP_ADC_CHANNEL 1 + +#define CHARGE_FULL_ADC 150 + +#define HIGH_BLOCK_TEMP_MAGURO 500 +#define HIGH_RECOVER_TEMP_MAGURO 420 +#define LOW_BLOCK_TEMP_MAGURO (-50) +#define LOW_RECOVER_TEMP_MAGURO 0 + +#define HIGH_BLOCK_TEMP_TORO 490 +#define HIGH_RECOVER_TEMP_TORO 420 +#define LOW_BLOCK_TEMP_TORO (-50) +#define LOW_RECOVER_TEMP_TORO 0 + +/** +** temp_adc_table_data +** @adc_value : thermistor adc value +** @temperature : temperature(C) * 10 +**/ +struct temp_adc_table_data { + int adc_value; + int temperature; +}; + +static DEFINE_SPINLOCK(charge_en_lock); +static int charger_state; +static bool is_charging_mode; + +static struct temp_adc_table_data temper_table_maguro[] = { + /* ADC, Temperature (C/10) */ + { 75, 700 }, + { 78, 690 }, + { 82, 680 }, + { 84, 670 }, + { 87, 660 }, + { 89, 650 }, + { 92, 640 }, + { 95, 630 }, + { 99, 620 }, + { 102, 610 }, + { 105, 600 }, + { 109, 590 }, + { 113, 580 }, + { 117, 570 }, + { 121, 560 }, + { 124, 550 }, + { 127, 540 }, + { 135, 530 }, + { 139, 520 }, + { 143, 510 }, + { 147, 500 }, + { 153, 490 }, + { 158, 480 }, + { 163, 470 }, + { 169, 460 }, + { 175, 450 }, + { 181, 440 }, + { 187, 430 }, + { 193, 420 }, + { 199, 410 }, + { 205, 400 }, + { 212, 390 }, + { 218, 380 }, + { 227, 370 }, + { 233, 360 }, + { 240, 350 }, + { 249, 340 }, + { 258, 330 }, + { 267, 320 }, + { 276, 310 }, + { 285, 300 }, + { 299, 290 }, + { 308, 280 }, + { 313, 270 }, + { 322, 260 }, + { 331, 250 }, + { 342, 240 }, + { 355, 230 }, + { 363, 220 }, + { 373, 210 }, + { 383, 200 }, + { 394, 190 }, + { 407, 180 }, + { 417, 170 }, + { 427, 160 }, + { 437, 150 }, + { 450, 140 }, + { 465, 130 }, + { 475, 120 }, + { 487, 110 }, + { 500, 100 }, + { 514, 90 }, + { 526, 80 }, + { 540, 70 }, + { 552, 60 }, + { 565, 50 }, + { 577, 40 }, + { 589, 30 }, + { 603, 20 }, + { 614, 10 }, + { 628, 0 }, + { 639, (-10) }, + { 664, (-20) }, + { 689, (-30) }, + { 717, (-40) }, + { 744, (-50) }, + { 754, (-60) }, + { 765, (-70) }, + { 776, (-80) }, + { 787, (-90) }, + { 798, (-100) }, +}; + +static struct temp_adc_table_data temper_table_toro[] = { + /* ADC, Temperature (C/10) */ + { 70, 700 }, + { 72, 690 }, + { 75, 680 }, + { 77, 670 }, + { 80, 660 }, + { 82, 650 }, + { 85, 640 }, + { 88, 630 }, + { 91, 620 }, + { 94, 610 }, + { 97, 600 }, + { 101, 590 }, + { 104, 580 }, + { 108, 570 }, + { 111, 560 }, + { 115, 550 }, + { 119, 540 }, + { 124, 530 }, + { 129, 520 }, + { 133, 510 }, + { 137, 500 }, + { 141, 490 }, + { 146, 480 }, + { 150, 470 }, + { 155, 460 }, + { 160, 450 }, + { 166, 440 }, + { 171, 430 }, + { 177, 420 }, + { 184, 410 }, + { 190, 400 }, + { 196, 390 }, + { 203, 380 }, + { 212, 370 }, + { 219, 360 }, + { 226, 350 }, + { 234, 340 }, + { 242, 330 }, + { 250, 320 }, + { 258, 310 }, + { 266, 300 }, + { 275, 290 }, + { 284, 280 }, + { 294, 270 }, + { 303, 260 }, + { 312, 250 }, + { 322, 240 }, + { 333, 230 }, + { 344, 220 }, + { 354, 210 }, + { 364, 200 }, + { 375, 190 }, + { 387, 180 }, + { 399, 170 }, + { 410, 160 }, + { 422, 150 }, + { 431, 140 }, + { 443, 130 }, + { 456, 120 }, + { 468, 110 }, + { 480, 100 }, + { 493, 90 }, + { 506, 80 }, + { 519, 70 }, + { 532, 60 }, + { 545, 50 }, + { 558, 40 }, + { 571, 30 }, + { 582, 20 }, + { 595, 10 }, + { 608, 0 }, + { 620, (-10) }, + { 632, (-20) }, + { 645, (-30) }, + { 658, (-40) }, + { 670, (-50) }, + { 681, (-60) }, + { 696, (-70) }, + { 708, (-80) }, + { 720, (-90) }, + { 732, (-100) }, +}; + +static struct temp_adc_table_data *temper_table = temper_table_maguro; +static int temper_table_size = ARRAY_SIZE(temper_table_maguro); + +static bool enable_sr = true; +module_param(enable_sr, bool, S_IRUSR | S_IRGRP | S_IROTH); + +static struct gpio charger_gpios[] = { + { .gpio = GPIO_CHARGING_N, .flags = GPIOF_IN, .label = "charging_n" }, + { .gpio = GPIO_TA_NCONNECTED, .flags = GPIOF_IN, .label = "charger_n" }, + { .gpio = GPIO_CHARGE_N, .flags = GPIOF_OUT_INIT_HIGH, .label = "charge_n" }, + { .gpio = GPIO_CHG_CUR_ADJ, .flags = GPIOF_OUT_INIT_LOW, .label = "charge_cur_adj" }, +}; + +static int twl6030_get_adc_data(int ch) +{ + int adc_data; + int adc_max = -1; + int adc_min = 1 << 11; + int adc_total = 0; + int i, j; + + for (i = 0; i < ADC_NUM_SAMPLES; i++) { + adc_data = twl6030_get_madc_conversion(ch); + if (adc_data == -EAGAIN) { + for (j = 0; j < ADC_LIMIT_ERR_COUNT; j++) { + msleep(20); + adc_data = twl6030_get_madc_conversion(ch); + if (adc_data > 0) + break; + } + if (j >= ADC_LIMIT_ERR_COUNT) { + pr_err("%s: Retry count exceeded[ch:%d]\n", + __func__, ch); + return adc_data; + } + } else if (adc_data < 0) { + pr_err("%s: Failed read adc value : %d [ch:%d]\n", + __func__, adc_data, ch); + return adc_data; + } + + if (adc_data > adc_max) + adc_max = adc_data; + if (adc_data < adc_min) + adc_min = adc_data; + + adc_total += adc_data; + } + return (adc_total - adc_max - adc_min) / (ADC_NUM_SAMPLES - 2); +} + +static int iset_adc_value(void) +{ + return twl6030_get_adc_data(ISET_ADC_CHANNEL); +} + +static int temp_adc_value(void) +{ + return twl6030_get_adc_data(TEMP_ADC_CHANNEL); +} + +static bool check_charge_full(void) +{ + int ret; + + ret = iset_adc_value(); + if (ret < 0) { + pr_err("%s: invalid iset adc value [%d]\n", + __func__, ret); + return false; + } + pr_debug("%s : iset adc value : %d\n", __func__, ret); + + return ret < CHARGE_FULL_ADC; +} + +static int get_bat_temp_by_adc(int *batt_temp) +{ + int array_size = temper_table_size; + int temp_adc = temp_adc_value(); + int mid; + int left_side = 0; + int right_side = array_size - 1; + int temp = 0; + + if (temp_adc < 0) { + pr_err("%s : Invalid temperature adc value [%d]\n", + __func__, temp_adc); + return temp_adc; + } + + while (left_side <= right_side) { + mid = (left_side + right_side) / 2; + if (mid == 0 || mid == array_size - 1 || + (temper_table[mid].adc_value <= temp_adc && + temper_table[mid+1].adc_value > temp_adc)) { + temp = temper_table[mid].temperature; + break; + } else if (temp_adc - temper_table[mid].adc_value > 0) { + left_side = mid + 1; + } else { + right_side = mid - 1; + } + } + + pr_debug("%s: temp adc : %d, temp : %d\n", __func__, temp_adc, temp); + *batt_temp = temp; + return 0; +} + +static int charger_init(struct device *dev) +{ + return gpio_request_array(charger_gpios, ARRAY_SIZE(charger_gpios)); +} + +static void charger_exit(struct device *dev) +{ + gpio_free_array(charger_gpios, ARRAY_SIZE(charger_gpios)); +} + +static void set_charge_en(int state) +{ + gpio_set_value(GPIO_CHARGE_N, !state); +} + +static void charger_set_charge(int state) +{ + unsigned long flags; + + spin_lock_irqsave(&charge_en_lock, flags); + gpio_set_value(GPIO_CHG_CUR_ADJ, !!(state & PDA_POWER_CHARGE_AC)); + charger_state = state; + set_charge_en(state); + spin_unlock_irqrestore(&charge_en_lock, flags); +} + +static void charger_set_only_charge(int state) +{ + unsigned long flags; + + spin_lock_irqsave(&charge_en_lock, flags); + if (charger_state) + set_charge_en(state); + spin_unlock_irqrestore(&charge_en_lock, flags); + /* CHG_ING_N level changed after set charge_en and 150ms */ + msleep(150); +} + +static int charger_is_online(void) +{ + return !gpio_get_value(GPIO_TA_NCONNECTED); +} + +static int charger_is_charging(void) +{ + return !gpio_get_value(GPIO_CHARGING_N); +} + +static char *tuna_charger_supplied_to[] = { + "battery", +}; + +static const __initdata struct pda_power_pdata charger_pdata = { + .init = charger_init, + .exit = charger_exit, + .set_charge = charger_set_charge, + .wait_for_status = 500, + .wait_for_charger = 500, + .supplied_to = tuna_charger_supplied_to, + .num_supplicants = ARRAY_SIZE(tuna_charger_supplied_to), + .use_otg_notifier = true, +}; + +static struct max17040_platform_data max17043_pdata = { + .charger_online = charger_is_online, + .charger_enable = charger_is_charging, + .allow_charging = charger_set_only_charge, + .skip_reset = true, + .min_capacity = 3, + .is_full_charge = check_charge_full, + .get_bat_temp = get_bat_temp_by_adc, + .high_block_temp = HIGH_BLOCK_TEMP_MAGURO, + .high_recover_temp = HIGH_RECOVER_TEMP_MAGURO, + .low_block_temp = LOW_BLOCK_TEMP_MAGURO, + .low_recover_temp = LOW_RECOVER_TEMP_MAGURO, + .fully_charged_vol = 4150000, + .recharge_vol = 4140000, + .limit_charging_time = 21600, /* 6 hours */ + .limit_recharging_time = 5400, /* 90 min */ +}; + +static const __initdata struct i2c_board_info max17043_i2c[] = { + { + I2C_BOARD_INFO("max17040", (0x6C >> 1)), + .platform_data = &max17043_pdata, + .irq = OMAP_GPIO_IRQ(GPIO_FUEL_ALERT), + } +}; + +static int __init tuna_charger_mode_setup(char *str) +{ + if (!str) /* No mode string */ + return 0; + + is_charging_mode = !strcmp(str, "charger"); + + pr_debug("Charge mode string = \"%s\" charger mode = %d\n", str, + is_charging_mode); + + return 1; +} + +__setup("androidboot.mode=", tuna_charger_mode_setup); + +void __init omap4_tuna_power_init(void) +{ + struct platform_device *pdev; + int status; + + /* Vsel0 = gpio, vsel1 = gnd */ + status = omap_tps6236x_board_setup(true, TPS62361_GPIO, -1, + OMAP_PIN_OFF_OUTPUT_HIGH, -1); + if (status) + pr_err("TPS62361 initialization failed: %d\n", status); + /* + * Some Tuna devices have a 4430 chip on a 4460 board, manually + * tweak the power tree to the 4460 style with the TPS regulator. + */ + if (cpu_is_omap443x()) { + /* Disable 4430 mapping */ + omap_twl_pmic_update("mpu", CHIP_IS_OMAP443X, 0x0); + omap_twl_pmic_update("core", CHIP_IS_OMAP443X, 0x0); + /* make 4460 map usable for 4430 */ + omap_twl_pmic_update("core", CHIP_IS_OMAP446X, CHIP_IS_OMAP443X); + omap_tps6236x_update("mpu", CHIP_IS_OMAP446X, CHIP_IS_OMAP443X); + } + + /* Update temperature data from board type */ + if (omap4_tuna_get_type() == TUNA_TYPE_TORO) { + temper_table = temper_table_toro; + temper_table_size = ARRAY_SIZE(temper_table_toro); + + max17043_pdata.high_block_temp = HIGH_BLOCK_TEMP_TORO; + max17043_pdata.high_recover_temp = HIGH_RECOVER_TEMP_TORO; + max17043_pdata.low_block_temp = LOW_BLOCK_TEMP_TORO; + max17043_pdata.low_recover_temp = LOW_RECOVER_TEMP_TORO; + } + + /* Update oscillator information */ + if (omap4_tuna_get_revision() <= 0x3) { + /* + * until sample 4 (Toro and Maguro), we used KC2520B38: + * ST = 10ms + * Output Disable time = 100ns + * Output enable time = 5ms + * tstart = 10ms + 5ms = 15ms. + * tshut = 1us (rounded) + */ + omap_pm_set_osc_lp_time(15000, 1); + } else { + /* + * sample 5 onwards (Toro and Maguro), we use SQ200384: + * ST = 10ms + * Output Disable time = 100ns + * Output enable time = 10ms + * tstart = 10ms + 10ms = 20ms. + * tshut = 1us (rounded) + */ + omap_pm_set_osc_lp_time(20000, 1); + } + + omap_mux_init_gpio(charger_gpios[0].gpio, OMAP_PIN_INPUT); + omap_mux_init_gpio(charger_gpios[1].gpio, OMAP_PIN_INPUT); + omap_mux_init_gpio(charger_gpios[2].gpio, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(charger_gpios[3].gpio, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(GPIO_FUEL_ALERT, OMAP_PIN_INPUT); + + pdev = platform_device_register_resndata(NULL, "pda-power", -1, + NULL, 0, &charger_pdata, sizeof(charger_pdata)); + if (IS_ERR_OR_NULL(pdev)) + pr_err("cannot register pda-power\n"); + + max17043_pdata.use_fuel_alert = !is_charging_mode; + i2c_register_board_info(4, max17043_i2c, ARRAY_SIZE(max17043_i2c)); + + if (enable_sr) + omap_enable_smartreflex_on_init(); + + omap_pm_enable_off_mode(); +} diff --git a/arch/arm/mach-omap2/board-tuna-sensors.c b/arch/arm/mach-omap2/board-tuna-sensors.c new file mode 100755 index 0000000..f818aad --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-sensors.c @@ -0,0 +1,212 @@ +/* Sensor support for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/mpu.h> +#include <linux/gp2a.h> +#include <linux/i2c/twl6030-madc.h> + +#include "mux.h" +#include "board-tuna.h" + +#define GPIO_GYRO_INT 45 +#define GPIO_ACC_INT 122 +#define GPIO_MAG_INT 176 +#define GPIO_PS_ON 25 +#define GPIO_PS_VOUT 21 +#define GPIO_MSENSE_IRQ 157 + +#define GP2A_LIGHT_ADC_CHANNEL 4 + +static int gp2a_light_adc_value(void) +{ + return twl6030_get_madc_conversion(GP2A_LIGHT_ADC_CHANNEL); +} + +static void gp2a_power(bool on) +{ + /* this controls the power supply rail to the gp2a IC */ + gpio_set_value(GPIO_PS_ON, on); +} + +static void gp2a_gpio_init(void) +{ + int ret = gpio_request(GPIO_PS_ON, "gp2a_power_supply_on"); + if (ret) { + pr_err("%s Failed to request gpio gp2a power supply\n", + __func__); + return; + } + /* set power pin to output, initially powered off*/ + ret = gpio_direction_output(GPIO_PS_ON, 0); + if (ret) { + pr_err("%s Failed in gpio_direction_output, value 0 with error %d\n", + __func__, ret); + } +} + +static s8 orientation_back[] = { + -1, 0, 0, + 0, 1, 0, + 0, 0, -1, +}; + +static s8 orientation_back_right_90[] = { + 0, -1, 0, + -1, 0, 0, + 0, 0, -1, +}; + +static s8 orientation_back_left_90[] = { + 0, 1, 0, + 1, 0, 0, + 0, 0, -1, +}; + +static s8 orientation_back_180[] = { + 1, 0, 0, + 0, -1, 0, + 0, 0, -1, +}; + +/* + * A correction matrix for YAS530 + * which takes care of soft iron effect in TORO + */ +static s32 compass_correction_matrix_toro[] = { + 1072, -51, -22, + -30, 910, -4, + -23, -63, 1024, +}; + +static void rotcpy(s8 dst[3 * 3], const s8 src[3 * 3]) +{ + memcpy(dst, src, 3 * 3); +} + +static struct mpu_platform_data mpu_data = { + .int_config = 0x10, + .orientation = { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 }, + /* accel */ + .accel = { + .irq = OMAP_GPIO_IRQ(GPIO_ACC_INT), + .adapt_num = 4, + .bus = EXT_SLAVE_BUS_SECONDARY, + .address = 0x18, + .orientation = { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 }, + }, + /* compass */ + .compass = { + .irq = OMAP_GPIO_IRQ(GPIO_MAG_INT), + .adapt_num = 4, + .bus = EXT_SLAVE_BUS_PRIMARY, + .address = 0x2E, + .orientation = { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 }, + }, +}; + +static struct gp2a_platform_data gp2a_pdata = { + .power = gp2a_power, + .p_out = GPIO_PS_VOUT, + .light_adc_value = gp2a_light_adc_value, +}; + +static struct i2c_board_info __initdata tuna_sensors_i2c4_boardinfo[] = { + { + I2C_BOARD_INFO("mpu3050", 0x68), + .irq = OMAP_GPIO_IRQ(GPIO_GYRO_INT), + .platform_data = &mpu_data, + }, + { + I2C_BOARD_INFO("bma250", 0x18), + .irq = OMAP_GPIO_IRQ(GPIO_ACC_INT), + .platform_data = &mpu_data.accel, + }, + { + I2C_BOARD_INFO("yas530", 0x2e), + .irq = OMAP_GPIO_IRQ(GPIO_MAG_INT), + .platform_data = &mpu_data.compass, + }, + { + I2C_BOARD_INFO("gp2a", 0x44), + .platform_data = &gp2a_pdata, + }, + { + I2C_BOARD_INFO("bmp180", 0x77), + }, +}; + +static void omap4_tuna_fixup_orientations_maguro(int revision) +{ + if (revision >= 3) { + rotcpy(mpu_data.orientation, orientation_back_right_90); + rotcpy(mpu_data.accel.orientation, orientation_back_left_90); + } else if (revision >= 2) { + rotcpy(mpu_data.orientation, orientation_back_right_90); + rotcpy(mpu_data.accel.orientation, orientation_back_180); + } else if (revision == 1) { + rotcpy(mpu_data.accel.orientation, orientation_back_left_90); + } +} + +static void omap4_tuna_fixup_orientations_toro(int revision) +{ + if (revision >= 2) { + rotcpy(mpu_data.orientation, orientation_back_left_90); + rotcpy(mpu_data.accel.orientation, orientation_back); + rotcpy(mpu_data.compass.orientation, orientation_back_180); + } else if (revision >= 1) { + rotcpy(mpu_data.orientation, orientation_back_left_90); + rotcpy(mpu_data.accel.orientation, orientation_back_180); + rotcpy(mpu_data.compass.orientation, orientation_back_left_90); + } +} + +void __init omap4_tuna_sensors_init(void) +{ + omap_mux_init_gpio(GPIO_GYRO_INT, OMAP_PIN_INPUT); + omap_mux_init_gpio(GPIO_ACC_INT, OMAP_PIN_INPUT); + omap_mux_init_gpio(GPIO_MAG_INT, OMAP_PIN_INPUT); + omap_mux_init_gpio(GPIO_PS_ON, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(GPIO_PS_VOUT, OMAP_WAKEUP_EN | OMAP_PIN_INPUT); + + gpio_request(GPIO_GYRO_INT, "GYRO_INT"); + gpio_direction_input(GPIO_GYRO_INT); + gpio_request(GPIO_ACC_INT, "ACC_INT"); + gpio_direction_input(GPIO_ACC_INT); + gpio_request(GPIO_MAG_INT, "MAG_INT"); + gpio_direction_input(GPIO_MAG_INT); + gpio_request(GPIO_MSENSE_IRQ, "MSENSE_IRQ"); + gpio_direction_output(GPIO_MSENSE_IRQ, 1); + /* optical sensor */ + gp2a_gpio_init(); + + if (omap4_tuna_get_type() == TUNA_TYPE_MAGURO) { + omap4_tuna_fixup_orientations_maguro(omap4_tuna_get_revision()); + } else if (omap4_tuna_get_type() == TUNA_TYPE_TORO) { + omap4_tuna_fixup_orientations_toro(omap4_tuna_get_revision()); + mpu_data.compass.private_data = compass_correction_matrix_toro; + } + + i2c_register_board_info(4, tuna_sensors_i2c4_boardinfo, + ARRAY_SIZE(tuna_sensors_i2c4_boardinfo)); +} diff --git a/arch/arm/mach-omap2/board-tuna-usbhost.c b/arch/arm/mach-omap2/board-tuna-usbhost.c new file mode 100644 index 0000000..e11e954 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-usbhost.c @@ -0,0 +1,85 @@ +/* USB Host (EHCI) support for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <plat/usb.h> + +#include "board-tuna.h" +#include "mux.h" + +#define GPIO_USB3333_RESETB 159 + +static struct usbhs_omap_board_data usbhs_bdata = { + .port_mode[0] = OMAP_EHCI_PORT_MODE_PHY, + .port_mode[1] = OMAP_USBHS_PORT_MODE_UNUSED, + .port_mode[2] = OMAP_USBHS_PORT_MODE_UNUSED, + .phy_reset = false, + .reset_gpio_port[0] = -EINVAL, + .reset_gpio_port[1] = -EINVAL, + .reset_gpio_port[2] = -EINVAL +}; + +void __init omap4_ehci_init(void) +{ + int ret = 0; + struct clk *phy_ref_clk; + + omap_mux_init_gpio(GPIO_USB3333_RESETB, OMAP_PIN_OUTPUT | + OMAP_PIN_OFF_NONE); + + ret = gpio_request(GPIO_USB3333_RESETB, "usb3333_resetb"); + if (ret) { + pr_err("omap: ehci: Cannot request GPIO %d", + GPIO_USB3333_RESETB); + return; + } + gpio_direction_output(GPIO_USB3333_RESETB, 0); + gpio_set_value(GPIO_USB3333_RESETB, 0); + + /* FREF_CLK3 provides the 19.2 MHz reference clock to the PHY */ + omap_mux_init_signal("fref_clk3_out", OMAP_PIN_OUTPUT | OMAP_MUX_MODE0); + + phy_ref_clk = clk_get(NULL, "auxclk3_ck"); + if (IS_ERR(phy_ref_clk)) { + pr_err("omap: ehci: Cannot request auxclk3"); + return; + } + ret = clk_set_rate(phy_ref_clk, 19200000); + if (ret < 0) { + pr_err("omap: ehci: Cannot clk_set_rate auxclk3 err %d", ret); + return; + } + ret = clk_enable(phy_ref_clk); + if (ret < 0) { + pr_err("omap: ehci: Cannot clk_enable auxclk3 err %d", ret); + return; + } + + udelay(100); + gpio_set_value(GPIO_USB3333_RESETB, 1); + + /* Everything went well with phy clock, pass it to ehci driver for + * low power managment now + */ + usbhs_bdata.transceiver_clk[0] = phy_ref_clk; + + usbhs_init(&usbhs_bdata); + + pr_info("usb:ehci initialized"); + return; +} diff --git a/arch/arm/mach-omap2/board-tuna-vibrator.c b/arch/arm/mach-omap2/board-tuna-vibrator.c new file mode 100755 index 0000000..de1d5e7 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-vibrator.c @@ -0,0 +1,185 @@ +/* arch/arm/mach-omap2/board-tuna-vibrator.c + * + * Copyright (C) 2011 Samsung Electronics Co. Ltd. All Rights Reserved. + * Author: Rom Lemarchand <rlemarchand@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. + * + */ + +#include <linux/hrtimer.h> +#include <linux/gpio.h> +#include <linux/wakelock.h> +#include <linux/mutex.h> +#include <asm/mach-types.h> +#include <plat/dmtimer.h> + +#include <../../../drivers/staging/android/timed_output.h> + +#include "mux.h" +#include "board-tuna.h" + +/* Vibrator enable pin is changed on Rev 05 to block not intended vibration. */ +#define GPIO_MOTOR_EN 162 +#define GPIO_MOTOR_EN_REV05 54 + +#define VIB_GPTIMER_NUM 10 +#define PWM_DUTY_MAX 1450 +#define MAX_TIMEOUT 10000 /* 10s */ + +static struct vibrator { + struct wake_lock wklock; + struct hrtimer timer; + struct mutex lock; + struct omap_dm_timer *gptimer; + bool enabled; + unsigned gpio_en; +} vibdata; + +static void vibrator_off(void) +{ + if (!vibdata.enabled) + return; + omap_dm_timer_stop(vibdata.gptimer); + gpio_set_value(vibdata.gpio_en, 0); + vibdata.enabled = false; + wake_unlock(&vibdata.wklock); +} + +static int vibrator_get_time(struct timed_output_dev *dev) +{ + if (hrtimer_active(&vibdata.timer)) { + ktime_t r = hrtimer_get_remaining(&vibdata.timer); + return ktime_to_ms(r); + } + + return 0; +} + +static void vibrator_enable(struct timed_output_dev *dev, int value) +{ + mutex_lock(&vibdata.lock); + + /* cancel previous timer and set GPIO according to value */ + hrtimer_cancel(&vibdata.timer); + + if (value) { + wake_lock(&vibdata.wklock); + + gpio_set_value(vibdata.gpio_en, 1); + omap_dm_timer_start(vibdata.gptimer); + + vibdata.enabled = true; + + if (value > 0) { + if (value > MAX_TIMEOUT) + value = MAX_TIMEOUT; + + hrtimer_start(&vibdata.timer, + ns_to_ktime((u64)value * NSEC_PER_MSEC), + HRTIMER_MODE_REL); + } + } else { + vibrator_off(); + } + + mutex_unlock(&vibdata.lock); +} + +static struct timed_output_dev to_dev = { + .name = "vibrator", + .get_time = vibrator_get_time, + .enable = vibrator_enable, +}; + +static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer) +{ + vibrator_off(); + return HRTIMER_NORESTART; +} + +static int __init vibrator_init(void) +{ + int ret; + + vibdata.enabled = false; + + hrtimer_init(&vibdata.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + vibdata.timer.function = vibrator_timer_func; + + vibdata.gptimer = omap_dm_timer_request_specific(VIB_GPTIMER_NUM); + if (vibdata.gptimer == NULL) + return -1; + + ret = omap_dm_timer_set_source(vibdata.gptimer, + OMAP_TIMER_SRC_SYS_CLK); + if (ret < 0) + goto err_dm_timer_src; + + omap_dm_timer_set_load(vibdata.gptimer, 1, -PWM_DUTY_MAX); + omap_dm_timer_set_match(vibdata.gptimer, 1, -PWM_DUTY_MAX+10); + omap_dm_timer_set_pwm(vibdata.gptimer, 0, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_enable(vibdata.gptimer); + omap_dm_timer_write_counter(vibdata.gptimer, -2); + omap_dm_timer_disable(vibdata.gptimer); + + wake_lock_init(&vibdata.wklock, WAKE_LOCK_SUSPEND, "vibrator"); + mutex_init(&vibdata.lock); + + ret = timed_output_dev_register(&to_dev); + if (ret < 0) + goto err_to_dev_reg; + + return 0; + +err_to_dev_reg: + mutex_destroy(&vibdata.lock); + wake_lock_destroy(&vibdata.wklock); + +err_dm_timer_src: + omap_dm_timer_free(vibdata.gptimer); + vibdata.gptimer = NULL; + + return -1; +} + +static int __init omap4_tuna_vibrator_init(void) +{ + int ret; + + if (!machine_is_tuna()) + return 0; + + vibdata.gpio_en = (omap4_tuna_get_revision() >= 5) ? + GPIO_MOTOR_EN_REV05 : GPIO_MOTOR_EN; + + omap_mux_init_gpio(vibdata.gpio_en, OMAP_PIN_OUTPUT | + OMAP_PIN_OFF_OUTPUT_LOW); + omap_mux_init_signal("dpm_emu18.dmtimer10_pwm_evt", OMAP_PIN_OUTPUT); + + ret = gpio_request(vibdata.gpio_en, "vibrator-en"); + if (ret) + return ret; + + gpio_direction_output(vibdata.gpio_en, 0); + + ret = vibrator_init(); + if (ret < 0) + gpio_free(vibdata.gpio_en); + + return ret; +} + +/* + * This is needed because the vibrator is dependent on omap_dm_timers which get + * initialized at device_init time + */ +late_initcall(omap4_tuna_vibrator_init); diff --git a/arch/arm/mach-omap2/board-tuna-wifi.c b/arch/arm/mach-omap2/board-tuna-wifi.c new file mode 100644 index 0000000..65d4fbd --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-wifi.c @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/err.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/pm_runtime.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/fixed.h> +#include <plat/mmc.h> + +#include <linux/random.h> +#include <linux/jiffies.h> + +#include "hsmmc.h" +#include "control.h" +#include "mux.h" +#include "board-tuna.h" + +#define GPIO_WLAN_PMENA 104 +#define GPIO_WLAN_IRQ 2 + +#define ATAG_TUNA_MAC 0x57464d41 +/* #define ATAG_TUNA_MAC_DEBUG */ + +#ifdef CONFIG_DHD_USE_STATIC_BUF +#define PREALLOC_WLAN_NUMBER_OF_SECTIONS 4 +#define PREALLOC_WLAN_NUMBER_OF_BUFFERS 160 +#define PREALLOC_WLAN_SECTION_HEADER 24 + +#define WLAN_SECTION_SIZE_0 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 128) +#define WLAN_SECTION_SIZE_1 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 128) +#define WLAN_SECTION_SIZE_2 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 512) +#define WLAN_SECTION_SIZE_3 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 1024) + +#define WLAN_SKB_BUF_NUM 16 + +static struct sk_buff *wlan_static_skb[WLAN_SKB_BUF_NUM]; + +typedef struct wifi_mem_prealloc_struct { + void *mem_ptr; + unsigned long size; +} wifi_mem_prealloc_t; + +static wifi_mem_prealloc_t wifi_mem_array[PREALLOC_WLAN_NUMBER_OF_SECTIONS] = { + { NULL, (WLAN_SECTION_SIZE_0 + PREALLOC_WLAN_SECTION_HEADER) }, + { NULL, (WLAN_SECTION_SIZE_1 + PREALLOC_WLAN_SECTION_HEADER) }, + { NULL, (WLAN_SECTION_SIZE_2 + PREALLOC_WLAN_SECTION_HEADER) }, + { NULL, (WLAN_SECTION_SIZE_3 + PREALLOC_WLAN_SECTION_HEADER) } +}; + +static void *tuna_wifi_mem_prealloc(int section, unsigned long size) +{ + if (section == PREALLOC_WLAN_NUMBER_OF_SECTIONS) + return wlan_static_skb; + if ((section < 0) || (section > PREALLOC_WLAN_NUMBER_OF_SECTIONS)) + return NULL; + if (wifi_mem_array[section].size < size) + return NULL; + return wifi_mem_array[section].mem_ptr; +} +#endif + +int __init tuna_init_wifi_mem(void) +{ +#ifdef CONFIG_DHD_USE_STATIC_BUF + int i; + + for(i=0;( i < WLAN_SKB_BUF_NUM );i++) { + if (i < (WLAN_SKB_BUF_NUM/2)) + wlan_static_skb[i] = dev_alloc_skb(4096); + else + wlan_static_skb[i] = dev_alloc_skb(8192); + } + for(i=0;( i < PREALLOC_WLAN_NUMBER_OF_SECTIONS );i++) { + wifi_mem_array[i].mem_ptr = kmalloc(wifi_mem_array[i].size, + GFP_KERNEL); + if (wifi_mem_array[i].mem_ptr == NULL) + return -ENOMEM; + } +#endif + return 0; +} + +static struct resource tuna_wifi_resources[] = { + [0] = { + .name = "bcmdhd_wlan_irq", + .start = OMAP_GPIO_IRQ(GPIO_WLAN_IRQ), + .end = OMAP_GPIO_IRQ(GPIO_WLAN_IRQ), + .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE, + }, +}; + +#if 0 +/* BCM4329 returns wrong sdio_vsn(1) when we read cccr, + * we use predefined value (sdio_vsn=2) here to initial sdio driver well + */ +static struct embedded_sdio_data tuna_wifi_emb_data = { + .cccr = { + .sdio_vsn = 2, + .multi_block = 1, + .low_speed = 0, + .wide_bus = 0, + .high_power = 1, + .high_speed = 1, + }, +}; +#endif + +static int tuna_wifi_cd = 0; /* WIFI virtual 'card detect' status */ +static void (*wifi_status_cb)(int card_present, void *dev_id); +static void *wifi_status_cb_devid; +static struct regulator *clk32kaudio_reg; + +static int tuna_wifi_status_register( + void (*callback)(int card_present, void *dev_id), + void *dev_id) +{ + if (wifi_status_cb) + return -EAGAIN; + wifi_status_cb = callback; + wifi_status_cb_devid = dev_id; + return 0; +} + +static unsigned int tuna_wifi_status(struct device *dev) +{ + return tuna_wifi_cd; +} + +struct mmc_platform_data tuna_wifi_data = { + .ocr_mask = MMC_VDD_165_195 | MMC_VDD_20_21, + .built_in = 1, + .status = tuna_wifi_status, + .card_present = 0, + .register_status_notify = tuna_wifi_status_register, +}; + +static int tuna_wifi_set_carddetect(int val) +{ + pr_debug("%s: %d\n", __func__, val); + tuna_wifi_cd = val; + if (wifi_status_cb) { + wifi_status_cb(val, wifi_status_cb_devid); + } else + pr_warning("%s: Nobody to notify\n", __func__); + return 0; +} + +static int tuna_wifi_power_state; + +struct fixed_voltage_data { + struct regulator_desc desc; + struct regulator_dev *dev; + int microvolts; + int gpio; + unsigned startup_delay; + bool enable_high; + bool is_enabled; +}; + +static struct regulator_consumer_supply tuna_vmmc5_supply = { + .supply = "vmmc", + .dev_name = "omap_hsmmc.4", +}; + +static struct regulator_init_data tuna_vmmc5 = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + .consumer_supplies = &tuna_vmmc5_supply, +}; + +static struct fixed_voltage_config tuna_vwlan = { + .supply_name = "vwl1271", + .microvolts = 2000000, /* 2.0V */ + .gpio = GPIO_WLAN_PMENA, + .startup_delay = 70000, /* 70msec */ + .enable_high = 1, + .enabled_at_boot = 0, + .init_data = &tuna_vmmc5, +}; + +static struct platform_device omap_vwlan_device = { + .name = "reg-fixed-voltage", + .id = 1, + .dev = { + .platform_data = &tuna_vwlan, + }, +}; + +static int tuna_wifi_power(int on) +{ + int ret = 0; + + pr_debug("%s: %d\n", __func__, on); + if (!clk32kaudio_reg) { + clk32kaudio_reg = regulator_get(0, "clk32kaudio"); + if (IS_ERR(clk32kaudio_reg)) { + pr_err("%s: clk32kaudio reg not found!\n", __func__); + clk32kaudio_reg = NULL; + } + } + + if (clk32kaudio_reg && on && !tuna_wifi_power_state) { + ret = regulator_enable(clk32kaudio_reg); + if (ret) + pr_err("%s: regulator_enable failed: %d\n", __func__, + ret); + } + msleep(300); + gpio_set_value(GPIO_WLAN_PMENA, on); + msleep(200); + + if (clk32kaudio_reg && !on && tuna_wifi_power_state) { + ret = regulator_disable(clk32kaudio_reg); + if (ret) + pr_err("%s: regulator_disable failed: %d\n", __func__, + ret); + } + tuna_wifi_power_state = on; + return ret; +} + +static int tuna_wifi_reset_state; + +static int tuna_wifi_reset(int on) +{ + pr_debug("%s: do nothing\n", __func__); + tuna_wifi_reset_state = on; + return 0; +} + +static unsigned char tuna_mac_addr[IFHWADDRLEN] = { 0,0x90,0x4c,0,0,0 }; + +static int __init tuna_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 = strict_strtoul(token, 0x10, &val); + if (res < 0) + return 0; + tuna_mac_addr[i++] = (u8)val; + } + + return 1; +} + +__setup("androidboot.macaddr=", tuna_mac_addr_setup); + +static int tuna_wifi_get_mac_addr(unsigned char *buf) +{ + int type = omap4_tuna_get_type(); + uint rand_mac; + + if (type != TUNA_TYPE_TORO) + return -EINVAL; + + if (!buf) + return -EFAULT; + + if ((tuna_mac_addr[4] == 0) && (tuna_mac_addr[5] == 0)) { + srandom32((uint)jiffies); + rand_mac = random32(); + tuna_mac_addr[3] = (unsigned char)rand_mac; + tuna_mac_addr[4] = (unsigned char)(rand_mac >> 8); + tuna_mac_addr[5] = (unsigned char)(rand_mac >> 16); + } + memcpy(buf, tuna_mac_addr, IFHWADDRLEN); + return 0; +} + +/* Customized Locale table : OPTIONAL feature */ +#define WLC_CNTRY_BUF_SZ 4 +typedef struct cntry_locales_custom { + char iso_abbrev[WLC_CNTRY_BUF_SZ]; + char custom_locale[WLC_CNTRY_BUF_SZ]; + int custom_locale_rev; +} cntry_locales_custom_t; + +static cntry_locales_custom_t tuna_wifi_translate_custom_table[] = { +/* Table should be filled out based on custom platform regulatory requirement */ + {"", "XY", 4}, /* universal */ + {"US", "US", 69}, /* input ISO "US" to : US regrev 69 */ + {"CA", "US", 69}, /* input ISO "CA" to : US regrev 69 */ + {"EU", "EU", 5}, /* European union countries */ + {"AT", "EU", 5}, + {"BE", "EU", 5}, + {"BG", "EU", 5}, + {"CY", "EU", 5}, + {"CZ", "EU", 5}, + {"DK", "EU", 5}, + {"EE", "EU", 5}, + {"FI", "EU", 5}, + {"FR", "EU", 5}, + {"DE", "EU", 5}, + {"GR", "EU", 5}, + {"HU", "EU", 5}, + {"IE", "EU", 5}, + {"IT", "EU", 5}, + {"LV", "EU", 5}, + {"LI", "EU", 5}, + {"LT", "EU", 5}, + {"LU", "EU", 5}, + {"MT", "EU", 5}, + {"NL", "EU", 5}, + {"PL", "EU", 5}, + {"PT", "EU", 5}, + {"RO", "EU", 5}, + {"SK", "EU", 5}, + {"SI", "EU", 5}, + {"ES", "EU", 5}, + {"SE", "EU", 5}, + {"GB", "EU", 5}, /* input ISO "GB" to : EU regrev 05 */ + {"IL", "IL", 0}, + {"CH", "CH", 0}, + {"TR", "TR", 0}, + {"NO", "NO", 0}, + {"KR", "KR", 25}, + {"AU", "XY", 3}, + {"CN", "CN", 0}, + {"TW", "XY", 3}, + {"AR", "XY", 3}, + {"MX", "XY", 3}, + {"JP", "EU", 0}, + {"BR", "KR", 25} +}; + +static void *tuna_wifi_get_country_code(char *ccode) +{ + int size = ARRAY_SIZE(tuna_wifi_translate_custom_table); + int i; + + if (!ccode) + return NULL; + + for (i = 0; i < size; i++) + if (strcmp(ccode, tuna_wifi_translate_custom_table[i].iso_abbrev) == 0) + return &tuna_wifi_translate_custom_table[i]; + return &tuna_wifi_translate_custom_table[0]; +} + +static struct wifi_platform_data tuna_wifi_control = { + .set_power = tuna_wifi_power, + .set_reset = tuna_wifi_reset, + .set_carddetect = tuna_wifi_set_carddetect, +#ifdef CONFIG_DHD_USE_STATIC_BUF + .mem_prealloc = tuna_wifi_mem_prealloc, +#else + .mem_prealloc = NULL, +#endif + .get_mac_addr = tuna_wifi_get_mac_addr, + .get_country_code = tuna_wifi_get_country_code, +}; + +static struct platform_device tuna_wifi_device = { + .name = "bcmdhd_wlan", + .id = 1, + .num_resources = ARRAY_SIZE(tuna_wifi_resources), + .resource = tuna_wifi_resources, + .dev = { + .platform_data = &tuna_wifi_control, + }, +}; + +static void __init tuna_wlan_gpio(void) +{ + pr_debug("%s: start\n", __func__); + + /* WLAN SDIO: MMC5 CMD */ + omap_mux_init_signal("sdmmc5_cmd", OMAP_PIN_INPUT_PULLUP); + /* WLAN SDIO: MMC5 CLK */ + omap_mux_init_signal("sdmmc5_clk", OMAP_PIN_INPUT_PULLUP); + /* WLAN SDIO: MMC5 DAT[0-3] */ + omap_mux_init_signal("sdmmc5_dat0", OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("sdmmc5_dat1", OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("sdmmc5_dat2", OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("sdmmc5_dat3", OMAP_PIN_INPUT_PULLUP); + /* WLAN OOB - BCM4330 - GPIO 16 or GPIO 2 */ + omap_mux_init_signal("sim_reset.gpio_wk2", OMAP_PIN_INPUT); + omap_mux_init_signal("kpd_row1.safe_mode", 0); + /* WLAN PMENA - GPIO 104 */ + omap_mux_init_signal("gpmc_ncs7.gpio_104", OMAP_PIN_OUTPUT); + /* Enable power to gpio_wk0-gpio_wk2 */ + omap4_ctrl_wk_pad_writel(0xb0000000, + OMAP4_CTRL_MODULE_PAD_WKUP_CONTROL_USIMIO); + + /* gpio_enable(GPIO_WLAN_IRQ); */ + gpio_request(GPIO_WLAN_IRQ, "wlan_irq"); + gpio_direction_input(GPIO_WLAN_IRQ); +} + +int __init tuna_wlan_init(void) +{ + pr_debug("%s: start\n", __func__); + + tuna_wlan_gpio(); + tuna_init_wifi_mem(); + platform_device_register(&omap_vwlan_device); + return platform_device_register(&tuna_wifi_device); +} diff --git a/arch/arm/mach-omap2/board-tuna.c b/arch/arm/mach-omap2/board-tuna.c new file mode 100644 index 0000000..ba69f87 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna.c @@ -0,0 +1,1436 @@ +/* Board support file for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2010 Texas Instruments + * + * Based on mach-omap2/board-omap4panda.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/ion.h> +#include <linux/leds.h> +#include <linux/gpio.h> +#include <linux/memblock.h> +#include <linux/omap_ion.h> +#include <linux/usb/otg.h> +#include <linux/i2c/twl.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/fixed.h> +#include <linux/wl12xx.h> +#include <linux/reboot.h> +#include <linux/memblock.h> +#include <linux/sysfs.h> +#include <linux/uaccess.h> +#include <linux/proc_fs.h> +#include <linux/spi/spi.h> +#include <linux/spi/flash.h> +#include <linux/platform_data/lte_modem_bootloader.h> +#include <linux/platform_data/ram_console.h> +#include <plat/mcspi.h> +#include <linux/i2c-gpio.h> + +#include <mach/hardware.h> +#include <mach/omap4-common.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include <plat/board-tuna-bluetooth.h> +#include <plat/omap-serial.h> +#include <plat/board.h> +#include <plat/common.h> +#include <plat/cpu.h> +#include <plat/usb.h> +#include <plat/mmc.h> +#include <plat/remoteproc.h> +#include <plat/omap-serial.h> + +#include <mach/omap_fiq_debugger.h> + +#include <mach/id.h> +#include "timer-gp.h" + +#include "omap4-sar-layout.h" +#include "hsmmc.h" +#include "control.h" +#include "mux.h" +#include "board-tuna.h" +#include "resetreason.h" +#include <mach/dmm.h> + +#define TUNA_RAMCONSOLE_START (PLAT_PHYS_OFFSET + SZ_512M) +#define TUNA_RAMCONSOLE_SIZE SZ_2M + +struct class *sec_class; +EXPORT_SYMBOL(sec_class); + +/* For LTE(CMC221) */ +#define OMAP_GPIO_LTE_ACTIVE 47 +#define OMAP_GPIO_CMC2AP_INT1 61 + +#define GPIO_AUD_PWRON 127 +#define GPIO_AUD_PWRON_TORO_V1 20 + +/* GPS GPIO Setting */ +#define GPIO_AP_AGPS_TSYNC 18 +#define GPIO_GPS_nRST 136 +#define GPIO_GPS_PWR_EN 137 +#define GPIO_GPS_UART_SEL 164 + +#define GPIO_MHL_SCL_18V 99 +#define GPIO_MHL_SDA_18V 98 + +#define REBOOT_FLAG_RECOVERY 0x52564352 +#define REBOOT_FLAG_FASTBOOT 0x54534146 +#define REBOOT_FLAG_NORMAL 0x4D524F4E +#define REBOOT_FLAG_POWER_OFF 0x46464F50 + +#define UART_NUM_FOR_GPS 0 + +static int tuna_hw_rev; + +static struct gpio tuna_hw_rev_gpios[] = { + {76, GPIOF_IN, "hw_rev0"}, + {75, GPIOF_IN, "hw_rev1"}, + {74, GPIOF_IN, "hw_rev2"}, + {73, GPIOF_IN, "hw_rev3"}, + {170, GPIOF_IN, "hw_rev4"}, +}; + +static const char const *omap4_tuna_hw_name_maguro[] = { + [0x00] = "Toro Lunchbox #1", + [0x01] = "Maguro 1st Sample", + [0x02] = "Maguro 2nd Sample", + [0x03] = "Maguro 4th Sample", + [0x05] = "Maguro 5th Sample", + [0x07] = "Maguro 6th Sample", + [0x08] = "Maguro 7th Sample", + [0x09] = "Maguro 8th Sample", +}; + +static const char const *omap4_tuna_hw_name_toro[] = { + [0x00] = "Toro Lunchbox #2", + [0x01] = "Toro 1st Sample", + [0x02] = "Toro 2nd Sample", + [0x03] = "Toro 4th Sample", + [0x05] = "Toro 5th Sample", + [0x06] = "Toro 8th Sample", + [0x08] = "Toro 8th Sample", + [0x09] = "Toro 8-1th Sample", +}; + +int omap4_tuna_get_revision(void) +{ + return tuna_hw_rev & TUNA_REV_MASK; +} + +int omap4_tuna_get_type(void) +{ + return tuna_hw_rev & TUNA_TYPE_MASK; +} + + +static const char *omap4_tuna_hw_rev_name(void) { + const char *ret; + const char **names; + int num; + int rev; + + if (omap4_tuna_get_type() == TUNA_TYPE_MAGURO) { + names = omap4_tuna_hw_name_maguro; + num = ARRAY_SIZE(omap4_tuna_hw_name_maguro); + ret = "Maguro unknown"; + } else { + names = omap4_tuna_hw_name_toro; + num = ARRAY_SIZE(omap4_tuna_hw_name_toro); + ret = "Toro unknown"; + } + + rev = omap4_tuna_get_revision(); + if (rev >= num || !names[rev]) + return ret; + + return names[rev]; +} + +/* + * Store a handy board information string which we can use elsewhere like + * like in panic situation + */ +static char omap4_tuna_bd_info_string[255]; +static void omap4_tuna_init_hw_rev(void) +{ + int ret; + int i; + u32 r; + + /* Disable weak driver pulldown on usbb2_hsic_strobe */ + r = omap4_ctrl_pad_readl(OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_USBB_HSIC); + r &= ~OMAP4_USBB2_HSIC_STROBE_WD_MASK; + omap4_ctrl_pad_writel(r, OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_USBB_HSIC); + + ret = gpio_request_array(tuna_hw_rev_gpios, + ARRAY_SIZE(tuna_hw_rev_gpios)); + + BUG_ON(ret); + + for (i = 0; i < ARRAY_SIZE(tuna_hw_rev_gpios); i++) + tuna_hw_rev |= gpio_get_value(tuna_hw_rev_gpios[i].gpio) << i; + + snprintf(omap4_tuna_bd_info_string, + ARRAY_SIZE(omap4_tuna_bd_info_string), + "Tuna HW revision: %02x (%s), cpu %s ES%d.%d ", + tuna_hw_rev, + omap4_tuna_hw_rev_name(), + cpu_is_omap443x() ? "OMAP4430" : "OMAP4460", + (GET_OMAP_REVISION() >> 4) & 0xf, + GET_OMAP_REVISION() & 0xf); + + pr_info("%s\n", omap4_tuna_bd_info_string); + mach_panic_string = omap4_tuna_bd_info_string; +} + +/* wl127x BT, FM, GPS connectivity chip */ +static int wl1271_gpios[] = {46, -1, -1}; +static struct platform_device wl1271_device = { + .name = "kim", + .id = -1, + .dev = { + .platform_data = &wl1271_gpios, + }, +}; + +static struct resource ramconsole_resources[] = { + { + .flags = IORESOURCE_MEM, + .start = TUNA_RAMCONSOLE_START, + .end = TUNA_RAMCONSOLE_START + TUNA_RAMCONSOLE_SIZE - 1, + }, +}; + +static struct ram_console_platform_data ramconsole_pdata; + +static struct platform_device ramconsole_device = { + .name = "ram_console", + .id = -1, + .num_resources = ARRAY_SIZE(ramconsole_resources), + .resource = ramconsole_resources, + .dev = { + .platform_data = &ramconsole_pdata, + }, +}; + +static struct platform_device bcm4330_bluetooth_device = { + .name = "bcm4330_bluetooth", + .id = -1, +}; + +static void __init tuna_bt_init(void) +{ + /* BT_EN - GPIO 104 */ + omap_mux_init_signal("gpmc_ncs6.gpio_103", OMAP_PIN_OUTPUT); + /*BT_nRST - GPIO 42 */ + omap_mux_init_signal("gpmc_a18.gpio_42", OMAP_PIN_OUTPUT); + /* BT_WAKE - GPIO 27 */ + omap_mux_init_signal("dpm_emu16.gpio_27", OMAP_PIN_OUTPUT); + /* BT_HOST_WAKE - GPIO 177 */ + omap_mux_init_signal("kpd_row5.gpio_177", OMAP_WAKEUP_EN | OMAP_PIN_INPUT); + + platform_device_register(&bcm4330_bluetooth_device); +} + +static struct twl4030_madc_platform_data twl6030_madc = { + .irq_line = -1, +}; + +static struct platform_device twl6030_madc_device = { + .name = "twl6030_madc", + .id = -1, + .dev = { + .platform_data = &twl6030_madc, + }, +}; + + +static struct i2c_gpio_platform_data tuna_gpio_i2c5_pdata = { + .sda_pin = GPIO_MHL_SDA_18V, + .scl_pin = GPIO_MHL_SCL_18V, + .udelay = 3, + .timeout = 0, +}; + +static struct platform_device tuna_gpio_i2c5_device = { + .name = "i2c-gpio", + .id = 5, + .dev = { + .platform_data = &tuna_gpio_i2c5_pdata, + } +}; + +#define PHYS_ADDR_SMC_SIZE (SZ_1M * 3) +#define PHYS_ADDR_DUCATI_SIZE (SZ_1M * 105) +#define OMAP_TUNA_ION_HEAP_SECURE_INPUT_SIZE (SZ_1M * 90) +#define OMAP_TUNA_ION_HEAP_TILER_SIZE (SZ_1M * 81) +#define OMAP_TUNA_ION_HEAP_NONSECURE_TILER_SIZE (SZ_1M * 15) + +#define PHYS_ADDR_SMC_MEM (0x80000000 + SZ_1G - PHYS_ADDR_SMC_SIZE) +#define PHYS_ADDR_DUCATI_MEM (PHYS_ADDR_SMC_MEM - \ + PHYS_ADDR_DUCATI_SIZE - \ + OMAP_TUNA_ION_HEAP_SECURE_INPUT_SIZE) + +static struct ion_platform_data tuna_ion_data = { + .nr = 3, + .heaps = { + { + .type = ION_HEAP_TYPE_CARVEOUT, + .id = OMAP_ION_HEAP_SECURE_INPUT, + .name = "secure_input", + .base = PHYS_ADDR_SMC_MEM - + OMAP_TUNA_ION_HEAP_SECURE_INPUT_SIZE, + .size = OMAP_TUNA_ION_HEAP_SECURE_INPUT_SIZE, + }, + { .type = OMAP_ION_HEAP_TYPE_TILER, + .id = OMAP_ION_HEAP_TILER, + .name = "tiler", + .base = PHYS_ADDR_DUCATI_MEM - + OMAP_TUNA_ION_HEAP_TILER_SIZE, + .size = OMAP_TUNA_ION_HEAP_TILER_SIZE, + }, + { .type = OMAP_ION_HEAP_TYPE_TILER, + .id = OMAP_ION_HEAP_NONSECURE_TILER, + .name = "nonsecure_tiler", + .base = PHYS_ADDR_DUCATI_MEM - + OMAP_TUNA_ION_HEAP_TILER_SIZE - + OMAP_TUNA_ION_HEAP_NONSECURE_TILER_SIZE, + .size = OMAP_TUNA_ION_HEAP_NONSECURE_TILER_SIZE, + }, + }, +}; + +static struct platform_device tuna_ion_device = { + .name = "ion-omap4", + .id = -1, + .dev = { + .platform_data = &tuna_ion_data, + }, +}; + +static struct platform_device tuna_spdif_dit_device = { + .name = "spdif-dit", + .id = 0, +}; + +static struct platform_device *tuna_devices[] __initdata = { + &ramconsole_device, + &wl1271_device, + &twl6030_madc_device, + &tuna_ion_device, + &tuna_gpio_i2c5_device, + &tuna_spdif_dit_device, +}; + +/* + * The UART1 is for GPS, and CSR GPS chip should control uart1 rts level + * for gps firmware download. + */ +static int uart1_rts_ctrl_write(struct file *file, const char __user *buffer, + size_t count, loff_t *offs) +{ + char buf[10] = {0,}; + + if (omap4_tuna_get_revision() < TUNA_REV_SAMPLE_4) + return -ENXIO; + if (count > sizeof(buf) - 1) + return -EINVAL; + if (copy_from_user(buf, buffer, count)) + return -EFAULT; + + if (!strncmp(buf, "1", 1)) { + omap_rts_mux_write(OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE7, + UART_NUM_FOR_GPS); + } else if (!strncmp(buf, "0", 1)) { + omap_rts_mux_write(OMAP_PIN_OUTPUT | OMAP_MUX_MODE1, + UART_NUM_FOR_GPS); + } + + return count; +} + +static const struct file_operations uart1_rts_ctrl_proc_fops = { + .write = uart1_rts_ctrl_write, +}; + +static int __init tuna_gps_rts_ctrl_init(void) +{ + struct proc_dir_entry *p_entry; + + p_entry = proc_create("mcspi1_cs3_ctrl", 0666, NULL, + &uart1_rts_ctrl_proc_fops); + + if (!p_entry) + return -ENOMEM; + + return 0; +} + +static void tuna_gsd4t_gps_gpio(void) +{ + /* AP_AGPS_TSYNC - GPIO 18 */ + omap_mux_init_signal("dpm_emu7.gpio_18", OMAP_PIN_OUTPUT); + /* GPS_nRST - GPIO 136 */ + omap_mux_init_signal("mcspi1_simo.gpio_136", OMAP_PIN_OUTPUT); + /* GPS_PWR_EN - GPIO 137 */ + omap_mux_init_signal("mcspi1_cs0.gpio_137", OMAP_PIN_OUTPUT); + /* GPS_UART_SEL - GPIO 164 */ + omap_mux_init_signal("usbb2_ulpitll_dat3.gpio_164", OMAP_PIN_OUTPUT); +} + +static void tuna_gsd4t_gps_init(void) +{ + struct device *gps_dev; + + gps_dev = device_create(sec_class, NULL, 0, NULL, "gps"); + if (IS_ERR(gps_dev)) { + pr_err("Failed to create device(gps)!\n"); + goto err; + } + tuna_gsd4t_gps_gpio(); + + gpio_request(GPIO_AP_AGPS_TSYNC, "AP_AGPS_TSYNC"); + gpio_direction_output(GPIO_AP_AGPS_TSYNC, 0); + + gpio_request(GPIO_GPS_nRST, "GPS_nRST"); + gpio_direction_output(GPIO_GPS_nRST, 1); + + gpio_request(GPIO_GPS_PWR_EN, "GPS_PWR_EN"); + gpio_direction_output(GPIO_GPS_PWR_EN, 0); + + gpio_request(GPIO_GPS_UART_SEL , "GPS_UART_SEL"); + gpio_direction_output(GPIO_GPS_UART_SEL , 0); + + gpio_export(GPIO_GPS_nRST, 1); + gpio_export(GPIO_GPS_PWR_EN, 1); + + gpio_export_link(gps_dev, "GPS_nRST", GPIO_GPS_nRST); + gpio_export_link(gps_dev, "GPS_PWR_EN", GPIO_GPS_PWR_EN); + + tuna_gps_rts_ctrl_init(); + +err: + return; +} + +static int __init sec_common_init(void) +{ + sec_class = class_create(THIS_MODULE, "sec"); + if (IS_ERR(sec_class)) + pr_err("Failed to create class(sec)!\n"); + + return 0; +} + +static void __init tuna_init_early(void) +{ + omap2_init_common_infrastructure(); + omap2_init_common_devices(NULL, NULL); +} + +static struct omap_musb_board_data musb_board_data = { + .interface_type = MUSB_INTERFACE_UTMI, +#ifdef CONFIG_USB_MUSB_OTG + .mode = MUSB_OTG, +#else + .mode = MUSB_PERIPHERAL, +#endif + .power = 500, +}; + +static struct omap2_hsmmc_info mmc[] = { + { + .mmc = 1, + .nonremovable = true, + .caps = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA, + .ocr_mask = MMC_VDD_165_195, + .gpio_wp = -EINVAL, + .gpio_cd = -EINVAL, + }, + { + .name = "omap_wlan", + .mmc = 5, + .caps = MMC_CAP_4_BIT_DATA, + .gpio_wp = -EINVAL, + .gpio_cd = -EINVAL, + .ocr_mask = MMC_VDD_165_195 | MMC_VDD_20_21, + .nonremovable = false, + .mmc_data = &tuna_wifi_data, + }, + {} /* Terminator */ +}; + +static struct regulator_consumer_supply tuna_vmmc_supply[] = { + { + .supply = "vmmc", + .dev_name = "omap_hsmmc.0", + }, + { + .supply = "vmmc", + .dev_name = "omap_hsmmc.1", + }, +}; + +static struct regulator_init_data tuna_vaux1 = { + .constraints = { + .min_uV = 3000000, + .max_uV = 3000000, + .apply_uV = true, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + .always_on = true, + }, +}; + +static struct regulator_init_data tuna_vaux2 = { + .constraints = { + .min_uV = 1200000, + .max_uV = 2800000, + .apply_uV = true, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + .state_mem = { + .disabled = true, + }, + .initial_state = PM_SUSPEND_MEM, + }, +}; + +static struct regulator_consumer_supply tuna_vaux3_supplies[] = { + { + .supply = "vlcd", + }, +}; + +static struct regulator_init_data tuna_vaux3 = { + .constraints = { + .min_uV = 3100000, + .max_uV = 3100000, + .apply_uV = true, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(tuna_vaux3_supplies), + .consumer_supplies = tuna_vaux3_supplies, +}; + +static struct regulator_init_data tuna_vmmc = { + .constraints = { + .min_uV = 1800000, + .max_uV = 1800000, + .apply_uV = true, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 2, + .consumer_supplies = tuna_vmmc_supply, +}; + +static struct regulator_init_data tuna_vpp = { + .constraints = { + .min_uV = 1800000, + .max_uV = 2500000, + .apply_uV = true, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + .state_mem = { + .disabled = true, + }, + .initial_state = PM_SUSPEND_MEM, + }, +}; + +static struct regulator_consumer_supply tuna_vusim_supplies[] = { + { + .supply = "vlcd-iovcc", + }, +}; + +static struct regulator_init_data tuna_vusim = { + .constraints = { + .min_uV = 2200000, + .max_uV = 2200000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(tuna_vusim_supplies), + .consumer_supplies = tuna_vusim_supplies, +}; + +static struct regulator_init_data tuna_vana = { + .constraints = { + .min_uV = 2100000, + .max_uV = 2100000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + .always_on = true, + }, +}; + +static struct regulator_consumer_supply tuna_vcxio_supply[] = { + REGULATOR_SUPPLY("vdds_dsi", "omapdss_dss"), + REGULATOR_SUPPLY("vdds_dsi", "omapdss_dsi1"), +}; + +static struct regulator_init_data tuna_vcxio = { + .constraints = { + .min_uV = 1800000, + .max_uV = 1800000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(tuna_vcxio_supply), + .consumer_supplies = tuna_vcxio_supply, + +}; + +static struct regulator_consumer_supply tuna_vdac_supply[] = { + { + .supply = "hdmi_vref", + }, +}; + +static struct regulator_init_data tuna_vdac = { + .constraints = { + .min_uV = 1800000, + .max_uV = 1800000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(tuna_vdac_supply), + .consumer_supplies = tuna_vdac_supply, +}; + +static struct regulator_consumer_supply tuna_vusb_supply[] = { + REGULATOR_SUPPLY("vusb", "tuna_otg"), +}; + +static struct regulator_init_data tuna_vusb = { + .constraints = { + .min_uV = 3300000, + .max_uV = 3300000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + .state_mem = { + .disabled = true, + }, + .initial_state = PM_SUSPEND_MEM, + }, + .num_consumer_supplies = ARRAY_SIZE(tuna_vusb_supply), + .consumer_supplies = tuna_vusb_supply, +}; + +/* clk32kg is a twl6030 32khz clock modeled as a regulator, used by GPS */ +static struct regulator_init_data tuna_clk32kg = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .always_on = true, + }, +}; + +static struct regulator_consumer_supply tuna_clk32kaudio_supply[] = { + { + .supply = "clk32kaudio", + }, + { + .supply = "twl6040_clk32k", + } +}; + +static struct regulator_init_data tuna_clk32kaudio = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .boot_on = true, + }, + .num_consumer_supplies = ARRAY_SIZE(tuna_clk32kaudio_supply), + .consumer_supplies = tuna_clk32kaudio_supply, +}; + + +static struct regulator_init_data tuna_vdd3 = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .state_mem = { + .disabled = true, + }, + .initial_state = PM_SUSPEND_MEM, + }, +}; + +/* + * VMEM is unused. Register it to regulator framework and let it + * be in disabled state. + */ +static struct regulator_init_data tuna_vmem = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .state_mem = { + .disabled = true, + }, + .initial_state = PM_SUSPEND_MEM, + }, +}; + +static struct regulator_init_data tuna_v2v1 = { + .constraints = { + .min_uV = 2100000, + .max_uV = 2100000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + .always_on = true, + }, +}; + +static struct twl4030_codec_audio_data twl6040_audio = { + /* single-step ramp for headset and handsfree */ + .hs_left_step = 0x0f, + .hs_right_step = 0x0f, + .hf_left_step = 0x1d, + .hf_right_step = 0x1d, + .ep_step = 0x0f, +}; + +static struct regulator *twl6040_clk32kreg; + +static int tuna_twl6040_get_ext_clk32k(void) +{ + int ret = 0; + + twl6040_clk32kreg = regulator_get(NULL, "twl6040_clk32k"); + if (IS_ERR(twl6040_clk32kreg)) { + ret = PTR_ERR(twl6040_clk32kreg); + pr_err("failed to get CLK32K %d\n", ret); + } + + return ret; +} + +static void tuna_twl6040_put_ext_clk32k(void) +{ + regulator_put(twl6040_clk32kreg); +} + +static int tuna_twl6040_set_ext_clk32k(bool on) +{ + int ret; + + if (IS_ERR_OR_NULL(twl6040_clk32kreg)) + return -EINVAL; + + if (on) + ret = regulator_enable(twl6040_clk32kreg); + else + ret = regulator_disable(twl6040_clk32kreg); + + if (ret) + pr_err("failed to enable TWL6040 CLK32K %d\n", ret); + + return ret; +} + +static struct twl4030_codec_data twl6040_codec = { + .audio = &twl6040_audio, + .naudint_irq = OMAP44XX_IRQ_SYS_2N, + .irq_base = TWL6040_CODEC_IRQ_BASE, + .get_ext_clk32k = tuna_twl6040_get_ext_clk32k, + .put_ext_clk32k = tuna_twl6040_put_ext_clk32k, + .set_ext_clk32k = tuna_twl6040_set_ext_clk32k, +}; + +static struct twl4030_platform_data tuna_twldata = { + .irq_base = TWL6030_IRQ_BASE, + .irq_end = TWL6030_IRQ_END, + + /* Regulators */ + .vmmc = &tuna_vmmc, + .vpp = &tuna_vpp, + .vusim = &tuna_vusim, + .vana = &tuna_vana, + .vcxio = &tuna_vcxio, + .vdac = &tuna_vdac, + .vusb = &tuna_vusb, + .vaux1 = &tuna_vaux1, + .vaux2 = &tuna_vaux2, + .vaux3 = &tuna_vaux3, + .clk32kg = &tuna_clk32kg, + .clk32kaudio = &tuna_clk32kaudio, + + /* children */ + .codec = &twl6040_codec, + .madc = &twl6030_madc, + + /* SMPS */ + .vdd3 = &tuna_vdd3, + .vmem = &tuna_vmem, + .v2v1 = &tuna_v2v1, +}; + +static void tuna_audio_init(void) +{ + unsigned int aud_pwron; + + /* twl6040 naudint */ + omap_mux_init_signal("sys_nirq2.sys_nirq2", \ + OMAP_PIN_INPUT_PULLUP); + + /* aud_pwron */ + if (omap4_tuna_get_type() == TUNA_TYPE_TORO && + omap4_tuna_get_revision() >= 1) + aud_pwron = GPIO_AUD_PWRON_TORO_V1; + else + aud_pwron = GPIO_AUD_PWRON; + omap_mux_init_gpio(aud_pwron, OMAP_PIN_OUTPUT); + twl6040_codec.audpwron_gpio = aud_pwron; + + omap_mux_init_signal("gpmc_a24.gpio_48", OMAP_PIN_OUTPUT | OMAP_MUX_MODE3); + omap_mux_init_signal("kpd_col3.gpio_171", OMAP_PIN_OUTPUT | OMAP_MUX_MODE3); +} + +static struct i2c_board_info __initdata tuna_i2c1_boardinfo[] = { + { + I2C_BOARD_INFO("twl6030", 0x48), + .flags = I2C_CLIENT_WAKE, + .irq = OMAP44XX_IRQ_SYS_1N, + .platform_data = &tuna_twldata, + }, +}; + +static struct i2c_board_info __initdata tuna_i2c2_boardinfo[] = { + { + I2C_BOARD_INFO("ducati", 0x20), + .irq = OMAP44XX_IRQ_I2C2, + .ext_master = true, + }, +}; + +static struct i2c_board_info __initdata tuna_i2c4_boardinfo[] = { + { + I2C_BOARD_INFO("an30259a", 0x30), + }, +}; + +static int __init tuna_i2c_init(void) +{ + u32 r; + + omap_mux_init_signal("sys_nirq1", OMAP_PIN_INPUT_PULLUP | + OMAP_WAKEUP_EN); + omap_mux_init_signal("i2c1_scl.i2c1_scl", OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("i2c1_sda.i2c1_sda", OMAP_PIN_INPUT_PULLUP); + + /* + * This will allow unused regulator to be shutdown. This flag + * should be set in the board file. Before regulators are registered. + */ + regulator_has_full_constraints(); + + /* + * Phoenix Audio IC needs I2C1 to + * start with 400 KHz or less + */ + omap_register_i2c_bus(1, 400, tuna_i2c1_boardinfo, + ARRAY_SIZE(tuna_i2c1_boardinfo)); + omap_register_i2c_bus(2, 400, tuna_i2c2_boardinfo, + ARRAY_SIZE(tuna_i2c2_boardinfo)); + omap_register_i2c_bus(3, 400, NULL, 0); + + /* Disable internal pullup on i2c.4 line: + * as external 2.2K is already present + */ + r = omap4_ctrl_pad_readl(OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_I2C_0); + r |= (1 << OMAP4_I2C4_SDA_PULLUPRESX_SHIFT); + omap4_ctrl_pad_writel(r, OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_I2C_0); + + omap_register_i2c_bus(4, 400, NULL, 0); + + /* + * Drive MSECURE high for TWL6030 write access. + */ + omap_mux_init_signal("fref_clk0_out.gpio_wk6", OMAP_PIN_OUTPUT); + gpio_request(6, "msecure"); + gpio_direction_output(6, 1); + + return 0; +} + +#ifdef CONFIG_OMAP_MUX +static struct omap_board_mux board_mux[] __initdata = { + /* camera gpios */ + OMAP4_MUX(MCSPI1_SOMI, + OMAP_MUX_MODE3 | OMAP_PIN_INPUT_PULLDOWN), /* gpio_135 */ + OMAP4_MUX(KPD_COL0, + OMAP_MUX_MODE3 | OMAP_PIN_INPUT_PULLDOWN), /* gpio_173 */ + OMAP4_MUX(GPMC_A19, + OMAP_MUX_MODE3 | OMAP_PIN_INPUT_PULLDOWN), /* gpio_43 */ + /* hwrev */ + OMAP4_MUX(CSI21_DY4, OMAP_MUX_MODE3 | OMAP_PIN_INPUT), + OMAP4_MUX(CSI21_DX4, OMAP_MUX_MODE3 | OMAP_PIN_INPUT), + OMAP4_MUX(CSI21_DY3, OMAP_MUX_MODE3 | OMAP_PIN_INPUT), + OMAP4_MUX(CSI21_DX3, OMAP_MUX_MODE3 | OMAP_PIN_INPUT), + OMAP4_MUX(USBB2_HSIC_STROBE, OMAP_MUX_MODE3 | OMAP_PIN_INPUT), + /* fRom */ + OMAP4_MUX(USBB2_ULPITLL_DAT4, + OMAP_MUX_MODE4 | OMAP_PIN_INPUT), /* mcpsi3_somi */ + OMAP4_MUX(USBB2_ULPITLL_DAT5, + OMAP_MUX_MODE4 | OMAP_PIN_INPUT_PULLDOWN), /* mcpsi3_cs0 */ + OMAP4_MUX(USBB2_ULPITLL_DAT6, + OMAP_MUX_MODE4 | OMAP_PIN_INPUT), /* mcpsi3_simo */ + OMAP4_MUX(USBB2_ULPITLL_DAT7, + OMAP_MUX_MODE4 | OMAP_PIN_INPUT), /* mcpsi3_clk */ + { .reg_offset = OMAP_MUX_TERMINATOR }, +}; + +static struct omap_board_mux board_wkup_mux[] __initdata = { + /* power button */ + OMAP4_MUX(SIM_CD, OMAP_MUX_MODE3 | OMAP_PIN_INPUT), + { .reg_offset = OMAP_MUX_TERMINATOR }, +}; + +#else +#define board_mux NULL +#define board_wkup_mux NULL +#endif + +/* sample4+ adds gps rts/cts lines */ +static struct omap_device_pad tuna_uart1_pads_sample4[] __initdata = { + { + .name = "mcspi1_cs3.uart1_rts", + .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE1, + }, + { + .name = "mcspi1_cs2.uart1_cts", + .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE1, + }, + { + .name = "uart3_cts_rctx.uart1_tx", + .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE1, + }, + { + .name = "mcspi1_cs1.uart1_rx", + .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, + .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE1, + .idle = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE1, + }, +}; + +static struct omap_device_pad tuna_uart1_pads[] __initdata = { + { + .name = "uart3_cts_rctx.uart1_tx", + .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE1, + }, + { + .name = "mcspi1_cs1.uart1_rx", + .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, + .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE1, + .idle = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE1, + }, +}; + +static struct omap_device_pad tuna_uart2_pads[] __initdata = { + { + .name = "uart2_cts.uart2_cts", + .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0, + }, + { + .name = "uart2_rts.uart2_rts", + .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, + }, + { + .name = "uart2_tx.uart2_tx", + .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, + }, + { + .name = "uart2_rx.uart2_rx", + .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0, + }, +}; + +static struct omap_device_pad tuna_uart3_pads[] __initdata = { + { + .name = "uart3_tx_irtx.uart3_tx_irtx", + .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, + }, + { + .name = "uart3_rx_irrx.uart3_rx_irrx", + .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, + .enable = OMAP_PIN_INPUT | OMAP_MUX_MODE0, + .idle = OMAP_PIN_INPUT | OMAP_MUX_MODE0, + }, +}; + +static struct omap_device_pad tuna_uart4_pads[] __initdata = { + { + .name = "uart4_tx.uart4_tx", + .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, + }, + { + .name = "uart4_rx.uart4_rx", + .enable = OMAP_PIN_INPUT | OMAP_MUX_MODE0, + }, +}; + +static struct omap_uart_port_info tuna_uart2_info __initdata = { + .use_dma = 0, + .dma_rx_buf_size = DEFAULT_RXDMA_BUFSIZE, + .dma_rx_poll_rate = DEFAULT_RXDMA_POLLRATE, + .dma_rx_timeout = DEFAULT_RXDMA_TIMEOUT, + .auto_sus_timeout = 0, + .wake_peer = bcm_bt_lpm_exit_lpm_locked, + .rts_mux_driver_control = 1, +}; + +static inline void __init board_serial_init(void) +{ + struct omap_device_pad *uart1_pads; + int uart1_pads_sz; + + if (omap4_tuna_get_revision() >= TUNA_REV_SAMPLE_4) { + uart1_pads = tuna_uart1_pads_sample4; + uart1_pads_sz = ARRAY_SIZE(tuna_uart1_pads_sample4); + } else { + uart1_pads = tuna_uart1_pads; + uart1_pads_sz = ARRAY_SIZE(tuna_uart1_pads); + } + + omap_serial_init_port_pads(0, uart1_pads, uart1_pads_sz, NULL); + omap_serial_init_port_pads(1, tuna_uart2_pads, + ARRAY_SIZE(tuna_uart2_pads), &tuna_uart2_info); + omap_serial_init_port_pads(3, tuna_uart4_pads, + ARRAY_SIZE(tuna_uart4_pads), NULL); +} + +/* fiq_debugger initializes really early but OMAP resource mgmt + * is not yet ready @ arch_init, so init the serial debugger later */ +static int __init board_serial_debug_init(void) +{ + return omap_serial_debug_init(2, false, true, + tuna_uart3_pads, ARRAY_SIZE(tuna_uart3_pads)); +} +device_initcall(board_serial_debug_init); + +/* SPI flash memory in camera module */ +#define F_ROM_SPI_BUS_NUM 3 +#define F_ROM_SPI_CS 0 +#define F_ROM_SPI_SPEED_HZ 24000000 + +static const struct flash_platform_data w25q80_pdata = { + .name = "w25q80", + .type = "w25q80", +}; + +static struct omap2_mcspi_device_config f_rom_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, /* 0: slave, 1: master */ + .swap_datalines = 1, +}; + +static struct spi_board_info tuna_f_rom[] __initdata = { + { + .modalias = "m25p80", + .controller_data = &f_rom_mcspi_config, + .platform_data = &w25q80_pdata, + .bus_num = F_ROM_SPI_BUS_NUM, + .chip_select = F_ROM_SPI_CS, + .max_speed_hz = F_ROM_SPI_SPEED_HZ, + .mode = SPI_MODE_0, + }, +}; + +static void tuna_from_init(void) +{ + int err; + + if (tuna_hw_rev >= 0x07) + f_rom_mcspi_config.swap_datalines = 0; + + err = spi_register_board_info(tuna_f_rom, ARRAY_SIZE(tuna_f_rom)); + if (err) + pr_err("failed to register SPI F-ROM\n"); +} + +/*SPI for LTE modem bootloader*/ +#define LTE_MODEM_SPI_BUS_NUM 4 +#define LTE_MODEM_SPI_CS 0 +#define LTE_MODEM_SPI_MAX_HZ 1500000 + +struct lte_modem_bootloader_platform_data lte_modem_bootloader_pdata = { + .name = "lte_modem_int", + .gpio_lte2ap_status = OMAP_GPIO_CMC2AP_INT1, +}; + +static struct omap2_mcspi_device_config lte_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, /* 0: slave, 1: master */ +}; + +static struct spi_board_info tuna_lte_modem[] __initdata = { + { + .modalias = "lte_modem_spi", + .controller_data = <e_mcspi_config, + .platform_data = <e_modem_bootloader_pdata, + .max_speed_hz = LTE_MODEM_SPI_MAX_HZ, + .bus_num = LTE_MODEM_SPI_BUS_NUM, + .chip_select = LTE_MODEM_SPI_CS, + .mode = SPI_MODE_0, + }, +}; + +static int tuna_notifier_call(struct notifier_block *this, + unsigned long code, void *_cmd) +{ + void __iomem *sar_base; + unsigned int flag = REBOOT_FLAG_NORMAL; + + sar_base = omap4_get_sar_ram_base(); + + if (!sar_base) + return notifier_from_errno(-ENOMEM); + + if (code == SYS_RESTART) { + if (_cmd) { + if (!strcmp(_cmd, "recovery")) + flag = REBOOT_FLAG_RECOVERY; + else if (!strcmp(_cmd, "bootloader")) + flag = REBOOT_FLAG_FASTBOOT; + } + } else if (code == SYS_POWER_OFF) { + flag = REBOOT_FLAG_POWER_OFF; + } + + /* The Samsung LOKE bootloader will look for the boot flag at a fixed + * offset from the end of the 1st SAR bank. + */ + writel(flag, sar_base + SAR_BANK2_OFFSET - 0xC); + + return NOTIFY_DONE; +} + +static struct notifier_block tuna_reboot_notifier = { + .notifier_call = tuna_notifier_call, +}; + +static ssize_t tuna_soc_family_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "OMAP%04x\n", GET_OMAP_TYPE); +} + +static ssize_t tuna_soc_revision_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "ES%d.%d\n", (GET_OMAP_REVISION() >> 4) & 0xf, + GET_OMAP_REVISION() & 0xf); +} + +static ssize_t tuna_soc_die_id_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct omap_die_id oid; + omap_get_die_id(&oid); + return sprintf(buf, "%08X-%08X-%08X-%08X\n", oid.id_3, oid.id_2, + oid.id_1, oid.id_0); +} + +static ssize_t tuna_soc_prod_id_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct omap_die_id oid; + omap_get_production_id(&oid); + return sprintf(buf, "%08X-%08X\n", oid.id_1, oid.id_0); +} + +static ssize_t tuna_soc_msv_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%08X\n", omap_ctrl_readl(0x013c)); +} + +static const char *omap_types[] = { + [OMAP2_DEVICE_TYPE_TEST] = "TST", + [OMAP2_DEVICE_TYPE_EMU] = "EMU", + [OMAP2_DEVICE_TYPE_SEC] = "HS", + [OMAP2_DEVICE_TYPE_GP] = "GP", + [OMAP2_DEVICE_TYPE_BAD] = "BAD", +}; + +static ssize_t tuna_soc_type_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", omap_types[omap_type()]); +} + +#define TUNA_ATTR_RO(_type, _name, _show) \ + struct kobj_attribute tuna_##_type##_prop_attr_##_name = \ + __ATTR(_name, S_IRUGO, _show, NULL) + +static TUNA_ATTR_RO(soc, family, tuna_soc_family_show); +static TUNA_ATTR_RO(soc, revision, tuna_soc_revision_show); +static TUNA_ATTR_RO(soc, type, tuna_soc_type_show); +static TUNA_ATTR_RO(soc, die_id, tuna_soc_die_id_show); +static TUNA_ATTR_RO(soc, production_id, tuna_soc_prod_id_show); +static TUNA_ATTR_RO(soc, msv, tuna_soc_msv_show); + +static struct attribute *tuna_soc_prop_attrs[] = { + &tuna_soc_prop_attr_family.attr, + &tuna_soc_prop_attr_revision.attr, + &tuna_soc_prop_attr_type.attr, + &tuna_soc_prop_attr_die_id.attr, + &tuna_soc_prop_attr_production_id.attr, + &tuna_soc_prop_attr_msv.attr, + NULL, +}; + +static struct attribute_group tuna_soc_prop_attr_group = { + .attrs = tuna_soc_prop_attrs, +}; + +static ssize_t tuna_board_revision_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%s (0x%02x)\n", omap4_tuna_hw_rev_name(), + tuna_hw_rev); +} + +static TUNA_ATTR_RO(board, revision, tuna_board_revision_show); +static struct attribute *tuna_board_prop_attrs[] = { + &tuna_board_prop_attr_revision.attr, + NULL, +}; + +static struct attribute_group tuna_board_prop_attr_group = { + .attrs = tuna_board_prop_attrs, +}; + +static void __init omap4_tuna_create_board_props(void) +{ + struct kobject *board_props_kobj; + struct kobject *soc_kobj; + int ret = 0; + + board_props_kobj = kobject_create_and_add("board_properties", NULL); + if (!board_props_kobj) + goto err_board_obj; + + soc_kobj = kobject_create_and_add("soc", board_props_kobj); + if (!soc_kobj) + goto err_soc_obj; + + ret = sysfs_create_group(board_props_kobj, &tuna_board_prop_attr_group); + if (ret) + goto err_board_sysfs_create; + + ret = sysfs_create_group(soc_kobj, &tuna_soc_prop_attr_group); + if (ret) + goto err_soc_sysfs_create; + + return; + +err_soc_sysfs_create: + sysfs_remove_group(board_props_kobj, &tuna_board_prop_attr_group); +err_board_sysfs_create: + kobject_put(soc_kobj); +err_soc_obj: + kobject_put(board_props_kobj); +err_board_obj: + if (!board_props_kobj || !soc_kobj || ret) + pr_err("failed to create board_properties\n"); +} + +#define HSMMC2_MUX (OMAP_MUX_MODE1 | OMAP_PIN_INPUT_PULLUP) +#define HSMMC1_MUX OMAP_PIN_INPUT_PULLUP + +static void __init omap4_tuna_led_init(void) +{ + i2c_register_board_info(4, tuna_i2c4_boardinfo, + ARRAY_SIZE(tuna_i2c4_boardinfo)); +} + +/* always reboot into charger mode on "power-off". Let the bootloader + * figure out if we should truly power-off or not. + */ +static void tuna_power_off(void) +{ + printk(KERN_EMERG "Rebooting into bootloader for power-off.\n"); + arm_pm_restart('c', NULL); +} + +static void __init tuna_init(void) +{ + int package = OMAP_PACKAGE_CBS; + + if (omap_rev() == OMAP4430_REV_ES1_0) + package = OMAP_PACKAGE_CBL; + omap4_mux_init(board_mux, board_wkup_mux, package); + + omap4_tuna_init_hw_rev(); + + omap4_tuna_emif_init(); + + pm_power_off = tuna_power_off; + + register_reboot_notifier(&tuna_reboot_notifier); + + /* hsmmc d0-d7 */ + omap_mux_init_signal("sdmmc1_dat0.sdmmc1_dat0", HSMMC1_MUX); + omap_mux_init_signal("sdmmc1_dat1.sdmmc1_dat1", HSMMC1_MUX); + omap_mux_init_signal("sdmmc1_dat2.sdmmc1_dat2", HSMMC1_MUX); + omap_mux_init_signal("sdmmc1_dat3.sdmmc1_dat3", HSMMC1_MUX); + omap_mux_init_signal("sdmmc1_dat4.sdmmc1_dat4", HSMMC1_MUX); + omap_mux_init_signal("sdmmc1_dat5.sdmmc1_dat5", HSMMC1_MUX); + omap_mux_init_signal("sdmmc1_dat6.sdmmc1_dat6", HSMMC1_MUX); + omap_mux_init_signal("sdmmc1_dat7.sdmmc1_dat7", HSMMC1_MUX); + /* hsmmc cmd */ + omap_mux_init_signal("sdmmc1_cmd.sdmmc1_cmd", HSMMC1_MUX); + /* hsmmc clk */ + omap_mux_init_signal("sdmmc1_clk.sdmmc1_clk", HSMMC1_MUX); + + gpio_request(158, "emmc_en"); + gpio_direction_output(158, 1); + omap_mux_init_gpio(158, OMAP_PIN_INPUT_PULLUP); + + omap_mux_init_gpio(GPIO_MHL_SDA_18V, OMAP_PIN_INPUT_PULLUP); + omap_mux_init_gpio(GPIO_MHL_SCL_18V, OMAP_PIN_INPUT_PULLUP); + + sec_common_init(); + + if (TUNA_TYPE_TORO == omap4_tuna_get_type()) { + omap_mux_init_signal("gpmc_wait0", + OMAP_MUX_MODE3 | OMAP_PIN_INPUT_PULLDOWN); + gpio_request(OMAP_GPIO_CMC2AP_INT1, "gpio_61"); + gpio_direction_input(OMAP_GPIO_CMC2AP_INT1); + + omap_mux_init_signal("mcspi4_clk", OMAP_MUX_MODE0); + omap_mux_init_signal("mcspi4_simo", OMAP_MUX_MODE0); + omap_mux_init_signal("mcspi4_somi", OMAP_MUX_MODE0); + omap_mux_init_signal("mcspi4_cs0", OMAP_MUX_MODE0); + } + + tuna_wlan_init(); + tuna_audio_init(); + tuna_i2c_init(); + tuna_gsd4t_gps_init(); + ramconsole_pdata.bootinfo = omap4_get_resetreason(); + platform_add_devices(tuna_devices, ARRAY_SIZE(tuna_devices)); + board_serial_init(); + tuna_bt_init(); + omap2_hsmmc_init(mmc); + usb_musb_init(&musb_board_data); + omap4_tuna_create_board_props(); + if (TUNA_TYPE_TORO == omap4_tuna_get_type()) { + spi_register_board_info(tuna_lte_modem, + ARRAY_SIZE(tuna_lte_modem)); + } + tuna_from_init(); + omap_dmm_init(); + omap4_tuna_display_init(); + omap4_tuna_input_init(); + omap4_tuna_nfc_init(); + omap4_tuna_power_init(); + omap4_tuna_jack_init(); + omap4_tuna_sensors_init(); + omap4_tuna_led_init(); + omap4_tuna_connector_init(); + omap4_tuna_pogo_init(); +#ifdef CONFIG_OMAP_HSI_DEVICE + if (TUNA_TYPE_MAGURO == omap4_tuna_get_type()) + omap_hsi_init(); +#endif +#ifdef CONFIG_USB_EHCI_HCD_OMAP + if (TUNA_TYPE_TORO == omap4_tuna_get_type()) { +#ifdef CONFIG_SEC_MODEM + modem_toro_init(); +#endif + omap4_ehci_init(); + } +#endif +} + +static void __init tuna_map_io(void) +{ + omap2_set_globals_443x(); + omap44xx_map_common_io(); +} + +static void __init tuna_reserve(void) +{ + int i; + int ret; + + /* do the static reservations first */ + memblock_remove(TUNA_RAMCONSOLE_START, TUNA_RAMCONSOLE_SIZE); + memblock_remove(PHYS_ADDR_SMC_MEM, PHYS_ADDR_SMC_SIZE); + memblock_remove(PHYS_ADDR_DUCATI_MEM, PHYS_ADDR_DUCATI_SIZE); + + for (i = 0; i < tuna_ion_data.nr; i++) + if (tuna_ion_data.heaps[i].type == ION_HEAP_TYPE_CARVEOUT || + tuna_ion_data.heaps[i].type == OMAP_ION_HEAP_TYPE_TILER) { + ret = memblock_remove(tuna_ion_data.heaps[i].base, + tuna_ion_data.heaps[i].size); + if (ret) + pr_err("memblock remove of %x@%lx failed\n", + tuna_ion_data.heaps[i].size, + tuna_ion_data.heaps[i].base); + } + + /* ipu needs to recognize secure input buffer area as well */ + omap_ipu_set_static_mempool(PHYS_ADDR_DUCATI_MEM, PHYS_ADDR_DUCATI_SIZE + + OMAP_TUNA_ION_HEAP_SECURE_INPUT_SIZE); + omap_reserve(); +} + +MACHINE_START(TUNA, "Tuna") + /* Maintainer: Google, Inc */ + .boot_params = 0x80000100, + .reserve = tuna_reserve, + .map_io = tuna_map_io, + .init_early = tuna_init_early, + .init_irq = gic_init_irq, + .init_machine = tuna_init, + .timer = &omap_timer, +MACHINE_END diff --git a/arch/arm/mach-omap2/board-tuna.h b/arch/arm/mach-omap2/board-tuna.h new file mode 100644 index 0000000..619da15 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MACH_OMAP2_BOARD_TUNA_H_ +#define _MACH_OMAP2_BOARD_TUNA_H_ + +#define TUNA_REV_MASK 0xf +#define TUNA_REV_03 0x3 +#define TUNA_REV_SAMPLE_4 0x3 + +#define TUNA_TYPE_TORO 0x10 +#define TUNA_TYPE_MAGURO 0x00 +#define TUNA_TYPE_MASK 0x10 + +#define TUNA_GPIO_HDMI_HPD 63 + +#define TUNA_OTG_ID_POGO_PRIO INT_MIN +#define TUNA_OTG_ID_FSA9480_PRIO (INT_MIN + 1) +#define TUNA_OTG_ID_SII9234_PRIO (INT_MIN + 2) +#define TUNA_OTG_ID_FSA9480_LAST_PRIO INT_MAX + +int omap4_tuna_get_revision(void); +int omap4_tuna_get_type(void); +bool omap4_tuna_final_gpios(void); +void omap4_tuna_display_init(void); +void omap4_tuna_input_init(void); +void omap4_tuna_jack_init(void); +void omap4_tuna_nfc_init(void); +void omap4_tuna_power_init(void); +void omap4_tuna_sensors_init(void); +void omap4_tuna_pogo_init(void); +int omap4_tuna_connector_init(void); +int tuna_wlan_init(void); +int omap_hsi_init(void); +void omap4_tuna_emif_init(void); +void omap4_ehci_init(void); +void modem_toro_init(void); + +enum pogo_power_state { + POGO_POWER_DISCONNECTED, + POGO_POWER_CHARGER, + POGO_POWER_HOST, +}; +void tuna_otg_pogo_charger(enum pogo_power_state); +void tuna_otg_set_dock_switch(int enable); + +extern struct mmc_platform_data tuna_wifi_data; +extern struct class *sec_class; + +#endif diff --git a/arch/arm/mach-omap2/dpll3xxx.c b/arch/arm/mach-omap2/dpll3xxx.c index 7fb0d21..ff4a10b 100644 --- a/arch/arm/mach-omap2/dpll3xxx.c +++ b/arch/arm/mach-omap2/dpll3xxx.c @@ -96,7 +96,6 @@ retry: /* Try Error Recovery: for failing usbdpll locking */ if (!strcmp(clk->name, "dpll_usb_ck")) { - reg = __raw_readl(dd->mult_div1_reg); /* Put in MN bypass */ @@ -132,6 +131,17 @@ retry: first_time = false; goto retry; } + + pr_info("\n========== USB DPLL DUMP ===========\n"); + pr_info("CM_CLKMODE_DPLL_USB :%08x\n", omap_readl(0x4A008180)); + pr_info("CM_IDLEST_DPLL_USB :%08x\n", omap_readl(0x4A008184)); + pr_info("CM_AUTOIDLE_DPLL_USB :%08x\n", omap_readl(0x4A008188)); + pr_info("CM_CLKSEL_DPLL_USB :%08x\n", omap_readl(0x4A00818C)); + pr_info("CM_DIV_M2_DPLL_USB :%08x\n", omap_readl(0x4A008190)); + pr_info("CM_SSC_DELTAMSTEP_DPLL_USB :%08x\n", omap_readl(0x4A0081A8)); + pr_info("CM_SSC_MODFREQDIV_DPLL_USB :%08x\n", omap_readl(0x4A0081AC)); + pr_info("CM_CLKDCOLDO_DPLL_USB :%08x\n", omap_readl(0x4A0081B4)); + pr_info("========== USB DPLL DUMP: End ===========\n"); } } else { pr_debug("clock: %s transition to '%s' in %d loops\n", diff --git a/arch/arm/mach-omap2/dpll44xx.c b/arch/arm/mach-omap2/dpll44xx.c index bc6d418..7e509e7 100644 --- a/arch/arm/mach-omap2/dpll44xx.c +++ b/arch/arm/mach-omap2/dpll44xx.c @@ -175,6 +175,12 @@ int omap4_prcm_freq_update(void) if (i == MAX_FREQ_UPDATE_TIMEOUT) { pr_err("%s: Frequency update failed (call from %pF)\n", __func__, (void *)_RET_IP_); + pr_err("CLKCTRL: EMIF_1=0x%x EMIF_2=0x%x DMM=0x%x\n", + __raw_readl(OMAP4430_CM_MEMIF_EMIF_1_CLKCTRL), + __raw_readl(OMAP4430_CM_MEMIF_EMIF_2_CLKCTRL), + __raw_readl(OMAP4430_CM_MEMIF_DMM_CLKCTRL)); + emif_dump(0); + emif_dump(1); return -1; } diff --git a/arch/arm/mach-omap2/emif.c b/arch/arm/mach-omap2/emif.c index 50f1128..968bcde 100644 --- a/arch/arm/mach-omap2/emif.c +++ b/arch/arm/mach-omap2/emif.c @@ -60,6 +60,20 @@ static struct omap_device_pm_latency omap_emif_latency[] = { }, }; +static u32 get_temperature_level(u32 emif_nr); + +void emif_dump(int emif_nr) +{ + void __iomem *base = emif[emif_nr].base; + + printk("EMIF%d s=0x%x is_sys=0x%x is_ll=0x%x temp=0x%02x\n", + emif_nr + 1, + __raw_readl(base + OMAP44XX_EMIF_STATUS), + __raw_readl(base + OMAP44XX_EMIF_IRQSTATUS_SYS), + __raw_readl(base + OMAP44XX_EMIF_IRQSTATUS_LL), + get_temperature_level(emif_nr)); +} + static void do_cancel_out(u32 *num, u32 *den, u32 factor) { while (1) { diff --git a/arch/arm/mach-omap2/include/mach/emif.h b/arch/arm/mach-omap2/include/mach/emif.h index 3a65b20..99a7c69 100644 --- a/arch/arm/mach-omap2/include/mach/emif.h +++ b/arch/arm/mach-omap2/include/mach/emif.h @@ -263,4 +263,5 @@ int omap_emif_setup_device_details( const struct emif_device_details *emif2_devices); void emif_clear_irq(int emif_id); +void emif_dump(int emif_nr); #endif diff --git a/arch/arm/mach-omap2/include/mach/tf_mshield.h b/arch/arm/mach-omap2/include/mach/tf_mshield.h new file mode 100644 index 0000000..52f98bf --- /dev/null +++ b/arch/arm/mach-omap2/include/mach/tf_mshield.h @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2010 Trusted Logic S.A. + * + * 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. + */ + +#ifdef CONFIG_SECURITY_MIDDLEWARE_COMPONENT +void tf_allocate_workspace(void); +#endif diff --git a/arch/arm/mach-omap2/io.c b/arch/arm/mach-omap2/io.c index b5c8e80..6694dfb 100644 --- a/arch/arm/mach-omap2/io.c +++ b/arch/arm/mach-omap2/io.c @@ -44,6 +44,7 @@ #include "clockdomain.h" #include <plat/omap_hwmod.h> #include <plat/multi.h> +#include <mach/tf_mshield.h> /* * The machine specific code may provide the extra mapping besides the @@ -251,6 +252,9 @@ static void __init _omap2_map_common_io(void) omap2_check_revision(); omap_sram_init(); +#ifdef CONFIG_SECURITY_MIDDLEWARE_COMPONENT + tf_allocate_workspace(); +#endif } #ifdef CONFIG_SOC_OMAP2420 diff --git a/arch/arm/mach-omap2/omap2plus-cpufreq.c b/arch/arm/mach-omap2/omap2plus-cpufreq.c index 7c5a6f9..6c1261c 100644 --- a/arch/arm/mach-omap2/omap2plus-cpufreq.c +++ b/arch/arm/mach-omap2/omap2plus-cpufreq.c @@ -27,6 +27,7 @@ #include <linux/io.h> #include <linux/opp.h> #include <linux/cpu.h> +#include <linux/earlysuspend.h> #include <linux/platform_device.h> #include <asm/system.h> @@ -59,8 +60,10 @@ static struct device *mpu_dev; static DEFINE_MUTEX(omap_cpufreq_lock); static unsigned int max_thermal; +static unsigned int max_capped; static unsigned int max_freq; static unsigned int current_target_freq; +static unsigned int screen_off_max_freq; static bool omap_cpufreq_ready; static bool omap_cpufreq_suspended; @@ -91,6 +94,9 @@ static int omap_cpufreq_scale(unsigned int target_freq, unsigned int cur_freq) if (freqs.new > max_thermal) freqs.new = max_thermal; + if (max_capped && freqs.new > max_capped) + freqs.new = max_capped; + if ((freqs.old == freqs.new) && (cur_freq = freqs.new)) return 0; @@ -256,6 +262,46 @@ static int omap_target(struct cpufreq_policy *policy, return ret; } +static void omap_cpu_early_suspend(struct early_suspend *h) +{ + unsigned int cur; + + mutex_lock(&omap_cpufreq_lock); + + if (screen_off_max_freq) { + max_capped = screen_off_max_freq; + + cur = omap_getspeed(0); + if (cur > max_capped) + omap_cpufreq_scale(max_capped, cur); + } + + mutex_unlock(&omap_cpufreq_lock); +} + +static void omap_cpu_late_resume(struct early_suspend *h) +{ + unsigned int cur; + + mutex_lock(&omap_cpufreq_lock); + + if (max_capped) { + max_capped = 0; + + cur = omap_getspeed(0); + if (cur != current_target_freq) + omap_cpufreq_scale(current_target_freq, cur); + } + + mutex_unlock(&omap_cpufreq_lock); +} + +static struct early_suspend omap_cpu_early_suspend_handler = { + .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN, + .suspend = omap_cpu_early_suspend, + .resume = omap_cpu_late_resume, +}; + static inline void freq_table_free(void) { if (atomic_dec_and_test(&freq_table_users)) @@ -332,8 +378,52 @@ static int omap_cpu_exit(struct cpufreq_policy *policy) return 0; } +static ssize_t show_screen_off_freq(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%u\n", screen_off_max_freq); +} + +static ssize_t store_screen_off_freq(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int freq = 0; + int ret; + int index; + + if (!freq_table) + return -EINVAL; + + ret = sscanf(buf, "%u", &freq); + if (ret != 1) + return -EINVAL; + + mutex_lock(&omap_cpufreq_lock); + + ret = cpufreq_frequency_table_target(policy, freq_table, freq, + CPUFREQ_RELATION_H, &index); + if (ret) + goto out; + + screen_off_max_freq = freq_table[index].frequency; + + ret = count; + +out: + mutex_unlock(&omap_cpufreq_lock); + return ret; +} + +struct freq_attr omap_cpufreq_attr_screen_off_freq = { + .attr = { .name = "screen_off_max_freq", + .mode = 0644, + }, + .show = show_screen_off_freq, + .store = store_screen_off_freq, +}; + static struct freq_attr *omap_cpufreq_attr[] = { &cpufreq_freq_attr_scaling_available_freqs, + &omap_cpufreq_attr_screen_off_freq, NULL, }; @@ -407,6 +497,8 @@ static int __init omap_cpufreq_init(void) return -EINVAL; } + register_early_suspend(&omap_cpu_early_suspend_handler); + ret = cpufreq_register_driver(&omap_driver); omap_cpufreq_ready = !ret; @@ -429,6 +521,8 @@ static int __init omap_cpufreq_init(void) static void __exit omap_cpufreq_exit(void) { cpufreq_unregister_driver(&omap_driver); + + unregister_early_suspend(&omap_cpu_early_suspend_handler); platform_driver_unregister(&omap_cpufreq_platform_driver); platform_device_unregister(&omap_cpufreq_device); } diff --git a/arch/arm/mach-omap2/omap4-common.c b/arch/arm/mach-omap2/omap4-common.c index 7bf033c..488a73f 100644 --- a/arch/arm/mach-omap2/omap4-common.c +++ b/arch/arm/mach-omap2/omap4-common.c @@ -274,6 +274,7 @@ static int __init omap_barriers_init(void) } core_initcall(omap_barriers_init); +#ifndef CONFIG_SECURITY_MIDDLEWARE_COMPONENT /* * omap4_sec_dispatcher: Routine to dispatch low power secure * service routines @@ -324,3 +325,4 @@ u32 omap4_secure_dispatcher(u32 idx, u32 flag, u32 nargs, u32 arg1, u32 arg2, return ret; } +#endif diff --git a/arch/arm/mach-omap2/omap_hsi.c b/arch/arm/mach-omap2/omap_hsi.c index ce8fa54..3597e4c 100644..100755 --- a/arch/arm/mach-omap2/omap_hsi.c +++ b/arch/arm/mach-omap2/omap_hsi.c @@ -25,8 +25,6 @@ #include <linux/notifier.h> #include <linux/hsi_driver_if.h> -#include <asm/clkdev.h> - #include <plat/omap_hsi.h> #include <plat/omap_hwmod.h> #include <plat/omap_device.h> @@ -35,6 +33,8 @@ #include "clock.h" #include "mux.h" #include "control.h" +#include "pm.h" +#include "dvfs.h" static int omap_hsi_wakeup_enable(int hsi_port); static int omap_hsi_wakeup_disable(int hsi_port); @@ -42,8 +42,6 @@ static int omap_hsi_wakeup_disable(int hsi_port); #define OMAP_HSI_PLATFORM_DEVICE_NAME "omap_hsi.0" #define OMAP_HSI_HWMOD_NAME "hsi" #define OMAP_HSI_HWMOD_CLASSNAME "hsi" -#define OMAP_HSI_PADCONF_CAWAKE_PIN "usbb1_ulpitll_clk.hsi1_cawake" -#define OMAP_HSI_PADCONF_CAWAKE_MODE OMAP_MUX_MODE1 #define OMAP_MUX_MODE_MASK 0x7 @@ -85,8 +83,9 @@ static int omap_mux_disable_wakeup(const char *muxname) */ -static struct port_ctx hsi_port_ctx[] = { +static struct hsi_port_ctx omap_hsi_port_ctx[] = { [0] = { + .port_number = 1, .hst.mode = HSI_MODE_FRAME, .hst.flow = HSI_FLOW_SYNCHRONIZED, .hst.frame_size = HSI_FRAMESIZE_DEFAULT, @@ -101,24 +100,28 @@ static struct port_ctx hsi_port_ctx[] = { .hsr.counters = HSI_COUNTERS_FT_DEFAULT | HSI_COUNTERS_TB_DEFAULT | HSI_COUNTERS_FB_DEFAULT, + .cawake_padconf_name = "usbb1_ulpitll_clk.hsi1_cawake", + .cawake_padconf_hsi_mode = OMAP_MUX_MODE1, }, }; -static struct ctrl_ctx hsi_ctx = { +static struct hsi_ctrl_ctx omap_hsi_ctrl_ctx = { .sysconfig = 0, .gdd_gcr = 0, .dll = 0, - .pctx = hsi_port_ctx, + .pctx = omap_hsi_port_ctx, }; static struct hsi_platform_data omap_hsi_platform_data = { - .num_ports = ARRAY_SIZE(hsi_port_ctx), + .num_ports = ARRAY_SIZE(omap_hsi_port_ctx), .hsi_gdd_chan_count = HSI_HSI_DMA_CHANNEL_MAX, .default_hsi_fclk = HSI_DEFAULT_FCLK, - .ctx = &hsi_ctx, + .fifo_mapping_strategy = HSI_FIFO_MAPPING_ALL_PORT1, + .ctx = &omap_hsi_ctrl_ctx, .device_enable = omap_device_enable, .device_idle = omap_device_idle, .device_shutdown = omap_device_shutdown, + .device_scale = omap_device_scale, .wakeup_enable = omap_hsi_wakeup_enable, .wakeup_disable = omap_hsi_wakeup_disable, .wakeup_is_from_hsi = omap_hsi_is_io_wakeup_from_hsi, @@ -126,6 +129,19 @@ static struct hsi_platform_data omap_hsi_platform_data = { }; +static u32 omap_hsi_configure_errata(void) +{ + u32 errata = 0; + + if (cpu_is_omap44xx()) { + SET_HSI_ERRATA(errata, HSI_ERRATUM_i696_SW_RESET_FSM_STUCK); + SET_HSI_ERRATA(errata, HSI_ERRATUM_ixxx_3WIRES_NO_SWAKEUP); + SET_HSI_ERRATA(errata, HSI_ERRATUM_i702_PM_HSI_SWAKEUP); + } + + return errata; +} + static struct platform_device *hsi_get_hsi_platform_device(void) { struct device *dev; @@ -167,23 +183,49 @@ static struct hsi_dev *hsi_get_hsi_controller_data(struct platform_device *pd) } /** +* hsi_get_hsi_port_ctx_data - Returns a pointer on the port context +* +* @hsi_port - port number to obtain context. Range [1, 2] +* +* Return value :* If success: pointer on the HSI port context requested +* * else NULL +*/ +static struct hsi_port_ctx *hsi_get_hsi_port_ctx_data(int hsi_port) +{ + int i; + + for (i = 0; i < omap_hsi_platform_data.num_ports; i++) + if (omap_hsi_platform_data.ctx->pctx[i].port_number == hsi_port) + return &omap_hsi_platform_data.ctx->pctx[i]; + + return NULL; +} + +/** * omap_hsi_is_io_pad_hsi - Indicates if IO Pad has been muxed for HSI CAWAKE * +* @hsi_port - port number to check for HSI muxing. Range [1, 2] +* * Return value :* 0 if CAWAKE Padconf has not been found or CAWAKE not muxed for * CAWAKE * * else 1 */ -static int omap_hsi_is_io_pad_hsi(void) +static int omap_hsi_is_io_pad_hsi(int hsi_port) { + struct hsi_port_ctx *port_ctx; u16 val; + port_ctx = hsi_get_hsi_port_ctx_data(hsi_port); + if (!port_ctx) + return 0; + /* Check for IO pad */ - val = omap_mux_read_signal(OMAP_HSI_PADCONF_CAWAKE_PIN); + val = omap_mux_read_signal(port_ctx->cawake_padconf_name); if (val == -ENODEV) return 0; /* Continue only if CAWAKE is muxed */ - if ((val & OMAP_MUX_MODE_MASK) != OMAP_HSI_PADCONF_CAWAKE_MODE) + if ((val & OMAP_MUX_MODE_MASK) != port_ctx->cawake_padconf_hsi_mode) return 0; return 1; @@ -192,49 +234,66 @@ static int omap_hsi_is_io_pad_hsi(void) /** * omap_hsi_is_io_wakeup_from_hsi - Indicates an IO wakeup from HSI CAWAKE * -* Return value :* 0 if CAWAKE Padconf has not been found or no IOWAKEUP event -* occured for CAWAKE -* * else 1 -* TODO : return value should indicate the HSI port which has awaken +* @hsi_port - returns port number which triggered wakeup. Range [1, 2]. +* Only valid if return value is 1 (HSI wakeup detected) +* +* Return value :* false if CAWAKE Padconf has not been found or no IOWAKEUP event +* occured for CAWAKE. +* * true if HSI wakeup detected on port *hsi_port */ -int omap_hsi_is_io_wakeup_from_hsi(void) +bool omap_hsi_is_io_wakeup_from_hsi(int *hsi_port) { + struct hsi_port_ctx *port_ctx; u16 val; + int i; + + for (i = 0; i < omap_hsi_platform_data.num_ports; i++) { + port_ctx = &omap_hsi_platform_data.ctx->pctx[i]; /* Check for IO pad wakeup */ - val = omap_mux_read_signal(OMAP_HSI_PADCONF_CAWAKE_PIN); + val = omap_mux_read_signal(port_ctx->cawake_padconf_name); if (val == -ENODEV) - return 0; + continue; /* Continue only if CAWAKE is muxed */ - if ((val & OMAP_MUX_MODE_MASK) != OMAP_HSI_PADCONF_CAWAKE_MODE) - return 0; + if ((val & OMAP_MUX_MODE_MASK) != + port_ctx->cawake_padconf_hsi_mode) + continue; + + if (val & OMAP44XX_PADCONF_WAKEUPEVENT0) { + *hsi_port = port_ctx->port_number; + return true; + } + } - if (val & OMAP44XX_PADCONF_WAKEUPEVENT0) - return 1; + *hsi_port = 0; - return 0; + return false; } /** * omap_hsi_wakeup_enable - Enable HSI wakeup feature from RET/OFF mode * * @hsi_port - reference to the HSI port onto which enable wakeup feature. +* Range [1, 2] * * Return value :* 0 if CAWAKE has been configured to wakeup platform * * -ENODEV if CAWAKE is not muxed on padconf */ static int omap_hsi_wakeup_enable(int hsi_port) { + struct hsi_port_ctx *port_ctx; int ret = -ENODEV; - if (omap_hsi_is_io_pad_hsi()) - ret = omap_mux_enable_wakeup(OMAP_HSI_PADCONF_CAWAKE_PIN); - else - pr_debug("Trying to enable HSI IO wakeup on non HSI board\n"); - + if (omap_hsi_is_io_pad_hsi(hsi_port)) { + port_ctx = hsi_get_hsi_port_ctx_data(hsi_port); + ret = omap_mux_enable_wakeup(port_ctx->cawake_padconf_name); + omap4_trigger_ioctrl(); + } else { + pr_debug("HSI port %d not muxed, failed to enable IO wakeup\n", + hsi_port); + } - /* TODO: handle hsi_port param and use it to find the correct Pad */ return ret; } @@ -242,21 +301,24 @@ static int omap_hsi_wakeup_enable(int hsi_port) * omap_hsi_wakeup_disable - Disable HSI wakeup feature from RET/OFF mode * * @hsi_port - reference to the HSI port onto which disable wakeup feature. +* Range [1, 2] * * Return value :* 0 if CAWAKE has been configured to not wakeup platform * * -ENODEV if CAWAKE is not muxed on padconf */ static int omap_hsi_wakeup_disable(int hsi_port) { + struct hsi_port_ctx *port_ctx; int ret = -ENODEV; - if (omap_hsi_is_io_pad_hsi()) - ret = omap_mux_disable_wakeup(OMAP_HSI_PADCONF_CAWAKE_PIN); - else - pr_debug("Trying to disable HSI IO wakeup on non HSI board\n"); - - - /* TODO: handle hsi_port param and use it to find the correct Pad */ + if (omap_hsi_is_io_pad_hsi(hsi_port)) { + port_ctx = hsi_get_hsi_port_ctx_data(hsi_port); + ret = omap_mux_disable_wakeup(port_ctx->cawake_padconf_name); + omap4_trigger_ioctrl(); + } else { + pr_debug("HSI port %d not muxed, failed to disable IO wakeup\n", + hsi_port); + } return ret; } @@ -264,6 +326,9 @@ static int omap_hsi_wakeup_disable(int hsi_port) /** * omap_hsi_prepare_suspend - Prepare HSI for suspend mode * +* @hsi_port - reference to the HSI port. Range [1, 2] +* @dev_may_wakeup - value of sysfs flag indicating device wakeup capability +* * Return value :* 0 if CAWAKE padconf has been configured properly * * -ENODEV if CAWAKE is not muxed on padconf. * @@ -281,42 +346,79 @@ int omap_hsi_prepare_suspend(int hsi_port, bool dev_may_wakeup) } /** +* omap_hsi_io_wakeup_check - Check if IO wakeup is from HSI and schedule HSI +* processing tasklet +* +* Return value : * 0 if HSI tasklet scheduled. +* * negative value else. +*/ +int omap_hsi_io_wakeup_check(void) +{ + int hsi_port, ret = -1; + + /* Modem HSI wakeup */ + if (omap_hsi_is_io_wakeup_from_hsi(&hsi_port)) + ret = omap_hsi_wakeup(hsi_port); + + return ret; +} + +/** * omap_hsi_wakeup - Prepare HSI for wakeup from suspend mode (RET/OFF) * -* Return value : 1 if IO wakeup source is HSI -* 0 if IO wakeup source is not HSI. +* @hsi_port - reference to the HSI port which triggered wakeup. +* Range [1, 2] +* +* Return value : * 0 if HSI tasklet scheduled. +* * negative value else. */ int omap_hsi_wakeup(int hsi_port) { static struct platform_device *pdev; static struct hsi_dev *hsi_ctrl; + int i; if (!pdev) { - pdev = hsi_get_hsi_platform_device(); + pdev = hsi_get_hsi_platform_device(); if (!pdev) - return -ENODEV; -} + return -ENODEV; + } if (!device_may_wakeup(&pdev->dev)) { - dev_info(&pdev->dev, "Modem not allowed to wakeup platform"); + dev_info(&pdev->dev, "Modem not allowed to wakeup platform\n"); return -EPERM; } if (!hsi_ctrl) { - hsi_ctrl = hsi_get_hsi_controller_data(pdev); - if (!hsi_ctrl) - return -ENODEV; + hsi_ctrl = hsi_get_hsi_controller_data(pdev); + if (!hsi_ctrl) + return -ENODEV; } - dev_dbg(hsi_ctrl->dev, "Modem wakeup detected from HSI CAWAKE Pad"); + for (i = 0; i < omap_hsi_platform_data.num_ports; i++) { + if (omap_hsi_platform_data.ctx->pctx[i].port_number == hsi_port) + break; + } + + if (i == omap_hsi_platform_data.num_ports) + return -ENODEV; + + + /* Check no other interrupt handler has already scheduled the tasklet */ + if (test_and_set_bit(HSI_FLAGS_TASKLET_LOCK, + &hsi_ctrl->hsi_port[i].flags)) + return -EBUSY; + + dev_dbg(hsi_ctrl->dev, "Modem wakeup detected from HSI CAWAKE Pad port " + "%d\n", hsi_port); /* CAWAKE falling or rising edge detected */ - hsi_ctrl->hsi_port->cawake_off_event = true; - tasklet_hi_schedule(&hsi_ctrl->hsi_port->hsi_tasklet); + hsi_ctrl->hsi_port[i].cawake_off_event = true; + tasklet_hi_schedule(&hsi_ctrl->hsi_port[i].hsi_tasklet); /* Disable interrupt until Bottom Half has cleared */ /* the IRQ status register */ - disable_irq_nosync(hsi_ctrl->hsi_port->irq); + disable_irq_nosync(hsi_ctrl->hsi_port[i].irq); return 0; } @@ -344,6 +446,8 @@ static int __init omap_hsi_register(struct omap_hwmod *oh, void *user) return -EEXIST; } + omap_hsi_platform_data.errata = omap_hsi_configure_errata(); + od = omap_device_build(OMAP_HSI_PLATFORM_DEVICE_DRIVER_NAME, 0, oh, pdata, sizeof(*pdata), omap_hsi_latency, ARRAY_SIZE(omap_hsi_latency), false); @@ -376,19 +480,19 @@ static void __init omap_4430hsi_pad_conf(void) /* hsi1_acready */ omap_mux_init_signal("usbb1_ulpitll_nxt.hsi1_acready", \ OMAP_PIN_OUTPUT | \ - OMAP_PIN_OFF_OUTPUT_LOW); + OMAP_OFF_EN); /* hsi1_acwake */ omap_mux_init_signal("usbb1_ulpitll_dat0.hsi1_acwake", \ OMAP_PIN_OUTPUT | \ - OMAP_PIN_OFF_NONE); + OMAP_OFF_EN); /* hsi1_acdata */ omap_mux_init_signal("usbb1_ulpitll_dat1.hsi1_acdata", \ OMAP_PIN_OUTPUT | \ - OMAP_PIN_OFF_NONE); + OMAP_OFF_EN); /* hsi1_acflag */ omap_mux_init_signal("usbb1_ulpitll_dat2.hsi1_acflag", \ OMAP_PIN_OUTPUT | \ - OMAP_PIN_OFF_NONE); + OMAP_OFF_EN); /* hsi1_caready */ omap_mux_init_signal("usbb1_ulpitll_dat3.hsi1_caready", \ OMAP_PIN_INPUT | \ diff --git a/arch/arm/mach-omap2/omap_hwmod.c b/arch/arm/mach-omap2/omap_hwmod.c index fac4aec..aea82b7 100644 --- a/arch/arm/mach-omap2/omap_hwmod.c +++ b/arch/arm/mach-omap2/omap_hwmod.c @@ -152,6 +152,7 @@ #include "prm44xx.h" #include "mux.h" #include "pm.h" +#include "board-tuna.h" /* Maximum microseconds to wait for OMAP module to softreset */ #define MAX_MODULE_SOFTRESET_WAIT 10000 diff --git a/arch/arm/mach-omap2/omap_hwmod_44xx_data.c b/arch/arm/mach-omap2/omap_hwmod_44xx_data.c index b5c5b10..a650628 100644 --- a/arch/arm/mach-omap2/omap_hwmod_44xx_data.c +++ b/arch/arm/mach-omap2/omap_hwmod_44xx_data.c @@ -1059,7 +1059,7 @@ static struct omap_hwmod_class omap44xx_ctrl_module_hwmod_class = { static struct omap_hwmod omap44xx_ctrl_module_core_hwmod; static struct omap_hwmod_irq_info omap44xx_ctrl_module_core_irqs[] = { { .name = "sec_evts", .irq = 8 + OMAP44XX_IRQ_GIC_START }, - { .name = "thermal_alert", .irq = 126 + OMAP44XX_IRQ_GIC_START }, + { .name = "thermal_alert", .irq = 127 + OMAP44XX_IRQ_GIC_START }, }; static struct omap_hwmod_addr_space omap44xx_ctrl_module_core_addrs[] = { @@ -1106,7 +1106,7 @@ static struct omap_hwmod_class omap44xx_thermal_sensor_hwmod_class = { }; static struct omap_hwmod_irq_info omap44xx_thermal_sensor_irqs[] = { - { .name = "thermal_alert", .irq = 126 + OMAP44XX_IRQ_GIC_START }, + { .name = "thermal_alert", .irq = 127 + OMAP44XX_IRQ_GIC_START }, }; static struct omap_hwmod_addr_space omap44xx_thermal_sensor_addrs[] = { @@ -5573,7 +5573,7 @@ static struct omap_hwmod_ocp_if *omap44xx_uart3_slaves[] = { static struct omap_hwmod omap44xx_uart3_hwmod = { .name = "uart3", .class = &omap44xx_uart_hwmod_class, - .flags = (HWMOD_INIT_NO_IDLE | HWMOD_INIT_NO_RESET), + .flags = (HWMOD_SWSUP_SIDLE | HWMOD_INIT_NO_IDLE | HWMOD_INIT_NO_RESET), .mpu_irqs = omap44xx_uart3_irqs, .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_uart3_irqs), .sdma_reqs = omap44xx_uart3_sdma_reqs, @@ -5941,7 +5941,7 @@ static struct omap_hwmod_addr_space omap44xx_usbhs_ohci_addrs[] = { .name = "ohci", .pa_start = 0x4A064800, .pa_end = 0x4A064BFF, - .flags = ADDR_MAP_ON_INIT + .flags = ADDR_MAP_ON_INIT | ADDR_TYPE_RT } }; diff --git a/arch/arm/mach-omap2/omap_l3_noc.c b/arch/arm/mach-omap2/omap_l3_noc.c index 9d152de..0d6aaae 100644 --- a/arch/arm/mach-omap2/omap_l3_noc.c +++ b/arch/arm/mach-omap2/omap_l3_noc.c @@ -28,30 +28,10 @@ #include <linux/slab.h> #include "omap_l3_noc.h" +#include "board-tuna.h" #define NUM_OF_L3_MASTERS ARRAY_SIZE(l3_masters) -static void l3_dump_targ_context(u32 baseaddr) -{ - pr_err("COREREG : 0x%08x\n", readl(baseaddr + L3_COREREG)); - pr_err("VERSIONREG : 0x%08x\n", readl(baseaddr + L3_VERSIONREG)); - pr_err("MAINCTLREG : 0x%08x\n", readl(baseaddr + L3_MAINCTLREG)); - pr_err("NTTPADDR_0 : 0x%08x\n", readl(baseaddr + L3_NTTPADDR_0)); - pr_err("SVRTSTDLVL : 0x%08x\n", readl(baseaddr + L3_SVRTSTDLVL)); - pr_err("SVRTCUSTOMLVL: 0x%08x\n", readl(baseaddr + L3_SVRTCUSTOMLVL)); - pr_err("MAIN : 0x%08x\n", readl(baseaddr + L3_MAIN)); - pr_err("HDR : 0x%08x\n", readl(baseaddr + L3_HDR)); - pr_err("MSTADDR : 0x%08x\n", readl(baseaddr + L3_MSTADDR)); - pr_err("SLVADDR : 0x%08x\n", readl(baseaddr + L3_SLVADDR)); - pr_err("INFO : 0x%08x\n", readl(baseaddr + L3_INFO)); - pr_err("SLVOFSLSB : 0x%08x\n", readl(baseaddr + L3_SLVOFSLSB)); - pr_err("SLVOFSMSB : 0x%08x\n", readl(baseaddr + L3_SLVOFSMSB)); - pr_err("CUSTOMINFO_INFO : 0x%08x\n", readl(baseaddr + L3_CUSTOMINFO_INFO)); - pr_err("CUSTOMINFO_MSTADDR: 0x%08x\n", readl(baseaddr + L3_CUSTOMINFO_MSTADDR)); - pr_err("CUSTOMINFO_OPCODE : 0x%08x\n", readl(baseaddr + L3_CUSTOMINFO_OPCODE)); - pr_err("ADDRSPACESIZELOG : 0x%08x\n", readl(baseaddr + L3_ADDRSPACESIZELOG)); -} - /* * Interrupt Handler for L3 error detection. * 1) Identify the L3 clockdomain partition to which the error belongs to. @@ -116,10 +96,23 @@ static irqreturn_t l3_interrupt_handler(int irq, void *_l3) slave_addr = std_err_main_addr + L3_SLAVE_ADDRESS_OFFSET; - WARN(true, "L3 standard error: SOURCE:%s at address 0x%x\n", - source_name, readl(slave_addr)); - - l3_dump_targ_context(base + regoffset); + pr_err("L3 standard error: SOURCE:%s at address 0x%x MSTADDR=0x%x hdr=0x%x\n", + source_name, readl(slave_addr), + readl(base + regoffset + L3_MSTADDR), + readl(base + regoffset + L3_HDR)); + WARN_ONCE(true, "L3 standard error"); + + /* Disable ABE L3 Interrupt on LTE boards */ + if ((readl(base + regoffset + L3_MSTADDR) == 0xc0) && + (readl(base + regoffset + L3_SLVADDR) == 0x3) && + (omap4_tuna_get_type() == TUNA_TYPE_TORO)) { + pr_err("** Disabling ABE L3 interrupt for now....\n"); + writel(0x1, base + regoffset + L3_MAINCTLREG); + writel(0x0, base + regoffset + L3_SVRTSTDLVL); + writel(0x0, base + regoffset + L3_SVRTCUSTOMLVL); + writel(0x0, base + regoffset + L3_MAIN); + writel(0x1F, base + regoffset + L3_ADDRSPACESIZELOG); + } /* clear the std error log*/ clear = std_err_main | CLEAR_STDERR_LOG; @@ -131,8 +124,10 @@ static irqreturn_t l3_interrupt_handler(int irq, void *_l3) l3_targ_stderrlog_main_name[i][err_src]; regoffset = targ_reg_offset[i][err_src]; - WARN(true, "CUSTOM SRESP error with SOURCE:%s\n", - source_name); + pr_err("L3 CUSTOM SRESP error with SOURCE:%s info=0x%x\n", + source_name, + readl(base + regoffset + L3_CUSTOMINFO_INFO)); + WARN_ONCE(true, "L3 custom sresp error"); masterid = readl(base + regoffset + L3_CUSTOMINFO_MSTADDR); diff --git a/arch/arm/mach-omap2/opp4xxx_data.c b/arch/arm/mach-omap2/opp4xxx_data.c index 255f132..c0fd100 100644 --- a/arch/arm/mach-omap2/opp4xxx_data.c +++ b/arch/arm/mach-omap2/opp4xxx_data.c @@ -324,8 +324,9 @@ int __init omap4_opp_init(void) if (!r) { if (omap4_has_mpu_1_2ghz()) omap4_mpu_opp_enable(1200000000); - if (omap4_has_mpu_1_5ghz()) - omap4_mpu_opp_enable(1500000000); + /* The tuna PCB doesn't support 1.5GHz, so disable it for now */ + /*if (omap4_has_mpu_1_5ghz()) + omap4_mpu_opp_enable(1500000000);*/ } return r; diff --git a/arch/arm/mach-omap2/pm44xx.c b/arch/arm/mach-omap2/pm44xx.c index b9e2bf1..52ee078 100644..100755 --- a/arch/arm/mach-omap2/pm44xx.c +++ b/arch/arm/mach-omap2/pm44xx.c @@ -23,7 +23,10 @@ #include <linux/irq.h> #include <asm/hardware/gic.h> +#include <asm/mach-types.h> + #include <mach/omap4-common.h> + #include <plat/omap_hsi.h> #include <plat/common.h> #include <plat/temperature_sensor.h> @@ -80,6 +83,12 @@ static struct clockdomain *emif_clkdm, *mpuss_clkdm; /* Yet un-named erratum which requires AUTORET to be disabled for IVA PD */ #define OMAP4_PM_ERRATUM_IVA_AUTO_RET_iXXX BIT(1) +/* +* HSI - OMAP4430-2.2BUG00055: +* HSI: DSP Swakeup generated is the same than MPU Swakeup. +* System can’t enter in off mode due to the DSP. +*/ +#define OMAP4_PM_ERRATUM_HSI_SWAKEUP_iXXX BIT(2) /* Dynamic dependendency Cannot be enabled due to i688 erratum ID for 443x */ #define OMAP4_PM_ERRATUM_MPU_EMIF_NO_DYNDEP_i688 BIT(3) @@ -98,9 +107,22 @@ static struct clockdomain *emif_clkdm, *mpuss_clkdm; */ #define OMAP4_PM_ERRATUM_MPU_EMIF_NO_DYNDEP_IDLE_iXXX BIT(4) -static u8 pm44xx_errata; +u8 pm44xx_errata; #define is_pm44xx_erratum(erratum) (pm44xx_errata & OMAP4_PM_ERRATUM_##erratum) +/* HACK: check CAWAKE wakeup event */ +#define USBB1_ULPITLL_CLK 0x4A1000C0 +#define CONTROL_PADCONF_WAKEUPEVENT_2 0x4A1001E0 +static int cawake_event_flag = 0; +void check_cawake_wakeup_event(void) +{ + if ((omap_readl(USBB1_ULPITLL_CLK) & 0x80000000) || + (omap_readl(CONTROL_PADCONF_WAKEUPEVENT_2) & 0x2)) { + pr_info("[HSI] PORT 1 CAWAKE WAKEUP EVENT\n"); + cawake_event_flag = 1; + } +} + #define MAX_IOPAD_LATCH_TIME 1000 void omap4_trigger_ioctrl(void) { @@ -706,6 +728,10 @@ static int omap4_pm_suspend(void) * More details can be found in OMAP4430 TRM section 4.3.4.2. */ omap4_enter_sleep(0, PWRDM_POWER_OFF, true); + + /* HACK: check CAWAKE wakeup event */ + check_cawake_wakeup_event(); + omap4_print_wakeirq(); prcmdebug_dump(PRCMDEBUG_LASTSLEEP); @@ -1021,7 +1047,7 @@ no_32k: * * Bug ref is HSI-C1BUG00106 : dsp swakeup generated by HSI same as mpu swakeup */ -static void omap_pm_clear_dsp_wake_up(void) +void omap_pm_clear_dsp_wake_up(void) { int ret; int timeout = 10; @@ -1083,6 +1109,7 @@ static void omap_pm_clear_dsp_wake_up(void) static irqreturn_t prcm_interrupt_handler (int irq, void *dev_id) { u32 irqenable_mpu, irqstatus_mpu; + int hsi_port; irqenable_mpu = omap4_prm_read_inst_reg(OMAP4430_PRM_OCP_SOCKET_INST, OMAP4_PRM_IRQENABLE_MPU_OFFSET); @@ -1092,12 +1119,19 @@ static irqreturn_t prcm_interrupt_handler (int irq, void *dev_id) /* Check if a IO_ST interrupt */ if (irqstatus_mpu & OMAP4430_IO_ST_MASK) { /* Check if HSI caused the IO wakeup */ - if (omap_hsi_is_io_wakeup_from_hsi()) { - omap_pm_clear_dsp_wake_up(); - omap_hsi_wakeup(0); - } + + /* HACK: check CAWAKE wakeup event */ + if (cawake_event_flag) { + hsi_port = 1; + cawake_event_flag = 0; + omap_hsi_wakeup(hsi_port); + } else + if (omap_hsi_is_io_wakeup_from_hsi(&hsi_port)) + omap_hsi_wakeup(hsi_port); + omap_uart_resume_idle(); - usbhs_wakeup(); + if (!machine_is_tuna()) + usbhs_wakeup(); omap_debug_uart_resume_idle(); omap4_trigger_ioctrl(); } @@ -1201,7 +1235,8 @@ static void __init omap4_pm_setup_errata(void) * all OMAP4 silica */ if (cpu_is_omap44xx()) - pm44xx_errata |= OMAP4_PM_ERRATUM_IVA_AUTO_RET_iXXX; + pm44xx_errata |= OMAP4_PM_ERRATUM_IVA_AUTO_RET_iXXX | + OMAP4_PM_ERRATUM_HSI_SWAKEUP_iXXX; /* Dynamic Dependency errata for all silicon !=443x */ if (cpu_is_omap443x()) pm44xx_errata |= OMAP4_PM_ERRATUM_MPU_EMIF_NO_DYNDEP_i688; diff --git a/arch/arm/mach-omap2/usb-host.c b/arch/arm/mach-omap2/usb-host.c index 7b422b5..046f690 100644 --- a/arch/arm/mach-omap2/usb-host.c +++ b/arch/arm/mach-omap2/usb-host.c @@ -75,9 +75,7 @@ static struct omap_device_pad port1_phy_pads[] __initdata = { }, { .name = "usbb1_ulpitll_dir.usbb1_ulpiphy_dir", - .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, - .enable = (OMAP_PIN_INPUT_PULLDOWN | OMAP_MUX_MODE4) & ~OMAP_WAKEUP_EN, - .idle = OMAP_PIN_INPUT_PULLDOWN | OMAP_MUX_MODE4, + .enable = OMAP_PIN_INPUT_PULLDOWN | OMAP_MUX_MODE4, }, { .name = "usbb1_ulpitll_nxt.usbb1_ulpiphy_nxt", @@ -85,9 +83,7 @@ static struct omap_device_pad port1_phy_pads[] __initdata = { }, { .name = "usbb1_ulpitll_dat0.usbb1_ulpiphy_dat0", - .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, - .enable = (OMAP_PIN_INPUT_PULLDOWN | OMAP_MUX_MODE4) & ~OMAP_WAKEUP_EN, - .idle = OMAP_PIN_INPUT_PULLDOWN | OMAP_MUX_MODE4, + .enable = OMAP_PIN_INPUT_PULLDOWN | OMAP_MUX_MODE4, }, { .name = "usbb1_ulpitll_dat1.usbb1_ulpiphy_dat1", diff --git a/arch/arm/plat-omap/include/plat/board-tuna-bluetooth.h b/arch/arm/plat-omap/include/plat/board-tuna-bluetooth.h new file mode 100644 index 0000000..c74de05 --- /dev/null +++ b/arch/arm/plat-omap/include/plat/board-tuna-bluetooth.h @@ -0,0 +1,30 @@ +/* + * Bluetooth Broadcomm and low power control via GPIO + * + * Copyright (C) 2011 Samsung, Inc. + * Copyright (C) 2011 Google, Inc. + * + * 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 __BOARD_TUNA_BLUETOOTH_H__ +#define __BOARD_TUNA_BLUETOOTH_H__ + +#include <linux/serial_core.h> + +extern void bcm_bt_lpm_exit_lpm_locked(struct uart_port *uport); + +#endif /* __BOARD_TUNA_BLUETOOTH_H__ */ diff --git a/arch/arm/plat-omap/include/plat/memory.h b/arch/arm/plat-omap/include/plat/memory.h index e6720aa..0c7260a 100644 --- a/arch/arm/plat-omap/include/plat/memory.h +++ b/arch/arm/plat-omap/include/plat/memory.h @@ -86,17 +86,7 @@ #endif /* CONFIG_ARCH_OMAP15XX */ /* Override the ARM default */ -#ifdef CONFIG_FB_OMAP_CONSISTENT_DMA_SIZE - -#if (CONFIG_FB_OMAP_CONSISTENT_DMA_SIZE == 0) -#undef CONFIG_FB_OMAP_CONSISTENT_DMA_SIZE -#define CONFIG_FB_OMAP_CONSISTENT_DMA_SIZE 2 -#endif - -#define CONSISTENT_DMA_SIZE \ - (((CONFIG_FB_OMAP_CONSISTENT_DMA_SIZE + 1) & ~1) * 1024 * 1024) - -#endif +#define CONSISTENT_DMA_SIZE (14 * SZ_1M) #endif diff --git a/arch/arm/plat-omap/include/plat/omap_hsi.h b/arch/arm/plat-omap/include/plat/omap_hsi.h index 1a75ed4..ce9dadf 100644 --- a/arch/arm/plat-omap/include/plat/omap_hsi.h +++ b/arch/arm/plat-omap/include/plat/omap_hsi.h @@ -135,10 +135,11 @@ #define HSI_SSI_WAKE_MASK 0xff /* for SSI */ #define HSI_WAKE_MASK 0xffff /* for HSI */ #define HSI_SET_WAKE_4_WIRES (0 << 16) +#define HSI_SET_WAKE_3_WIRES (1 << 16) +#define HSI_SET_WAKE_3_WIRES_MASK 0xfffcffff /* 3-wires + ACREADY to 1 */ #define HSI_SET_WAKE_READY_LVL_0 (0 << 17) -#define HSI_SET_WAKE(channel) (1 << (channel) |\ - HSI_SET_WAKE_4_WIRES |\ - HSI_SET_WAKE_READY_LVL_0) +#define HSI_SET_WAKE_READY_LVL_1 (1 << 17) +#define HSI_SET_WAKE(channel) (1 << (channel)) #define HSI_CLEAR_WAKE(channel) (1 << (channel)) #define HSI_WAKE(channel) (1 << (channel)) @@ -259,6 +260,11 @@ #define HSI_HSR_ERROR_TBE (1 << 4) /* HSI only */ #define HSI_HSR_ERROR_RME (1 << 7) /* HSI only */ #define HSI_HSR_ERROR_TME (1 << 11) /* HSI only */ +#define HSI_HSR_ERROR_ALL (HSI_HSR_ERROR_SIG | \ + HSI_HSR_ERROR_FTE | \ + HSI_HSR_ERROR_TBE | \ + HSI_HSR_ERROR_RME | \ + HSI_HSR_ERROR_TME) #define HSI_HSR_ERRORACK_REG(port) (HSI_HSR_BASE(port) + 0x0024) @@ -268,6 +274,7 @@ #define HSI_HSR_OVERRUNACK_REG(port) (HSI_HSR_BASE(port) + 0x0030) +/* HSR_COUNTERS_Pp is former SSI_TIMEOUT_REG */ #define HSI_HSR_COUNTERS_REG(port) (HSI_HSR_BASE(port) + 0x0034) #define SSI_TIMEOUT_REG(port) (HSI_HSR_COUNTERS_REG(port)) #define HSI_TIMEOUT_DEFAULT 0 /* SSI only */ @@ -287,6 +294,7 @@ (((FB << HSI_COUNTERS_FB_OFFSET) & HSI_COUNTERS_FB_MASK) \ ((TB << HSI_COUNTERS_TB_OFFSET) & HSI_COUNTERS_TB_MASK) \ ((FT << HSI_COUNTERS_FT_OFFSET) & HSI_COUNTERS_FT_MASK)) +/* For SSI */ #define SSI_SSR_COMBINE_COUNTERS(FT) \ ((FT << HSI_SSI_RX_TIMEOUT_OFFSET) & HSI_SSI_RX_TIMEOUT_MASK) @@ -454,9 +462,34 @@ HSI_SYS_MPU_U_ENABLE_REG(port, irq)) #define HSI_SYS_MPU_STATUS_CH_REG(port, irq, channel) \ - ((channel < HSI_SSI_CHANNELS_MAX) ? \ + (((channel) < HSI_SSI_CHANNELS_MAX) ? \ HSI_SYS_MPU_STATUS_REG(port, irq) : \ HSI_SYS_MPU_U_STATUS_REG(port, irq)) + + +/* HSI errata handling */ +#define IS_HSI_ERRATA(errata, id) (errata & (id)) +#define SET_HSI_ERRATA(errata, id) (errata |= (id)) + +/* HSI-C1BUG00088: i696: HSI: Issue with SW reset + * No recovery from SW reset under specific circumstances + * If a SW RESET is done while some HSI errors are still not + * acknowledged, the HSR FSM is stucked. */ +#define HSI_ERRATUM_i696_SW_RESET_FSM_STUCK BIT(0) + +/* HSI-C1BUG00085: ixxx: HSI wakeup issue in 3 wires mode + * HSI will NOT generate the Swakeup for 2nd frame if it entered + * IDLE after 1st received frame */ +#define HSI_ERRATUM_ixxx_3WIRES_NO_SWAKEUP BIT(1) + +/* +* HSI - OMAP4430-2.2BUG00055: i702 +* HSI: DSP Swakeup generated is the same than MPU Swakeup. +* System cannot enter in off mode due to the DSP. +*/ +#define HSI_ERRATUM_i702_PM_HSI_SWAKEUP BIT(2) + + /** * struct omap_ssi_config - SSI board configuration * @num_ports: Number of ports in use @@ -479,16 +512,15 @@ extern int omap_hsi_config(struct omap_hsi_board_config *hsi_config); #ifdef CONFIG_OMAP_HSI extern int omap_hsi_prepare_suspend(int hsi_port, bool dev_may_wakeup); -extern int omap_hsi_prepare_idle(void); +extern int omap_hsi_io_wakeup_check(void); extern int omap_hsi_wakeup(int hsi_port); -extern int omap_hsi_is_io_wakeup_from_hsi(void); +extern bool omap_hsi_is_io_wakeup_from_hsi(int *hsi_port); #else inline int omap_hsi_prepare_suspend(int hsi_port, bool dev_may_wakeup) { return -ENOSYS; } -inline int omap_hsi_prepare_idle(void) { return -ENOSYS; } +inline int omap_hsi_io_wakeup_check(void) { return -ENOSYS; } inline int omap_hsi_wakeup(int hsi_port) { return -ENOSYS; } -inline int omap_hsi_is_io_wakeup_from_hsi(void) { return -ENOSYS; } - +inline bool omap_hsi_is_io_wakeup_from_hsi(int *hsi_port) { return false; } #endif #endif /* __OMAP_HSI_H__ */ diff --git a/arch/arm/plat-omap/include/plat/uncompress.h b/arch/arm/plat-omap/include/plat/uncompress.h index ac4b60d..cf07178 100644 --- a/arch/arm/plat-omap/include/plat/uncompress.h +++ b/arch/arm/plat-omap/include/plat/uncompress.h @@ -164,6 +164,7 @@ static inline void __arch_decomp_setup(unsigned long arch_id) /* omap4 based boards using UART3 */ DEBUG_LL_OMAP4(3, omap_4430sdp); DEBUG_LL_OMAP4(3, omap4_panda); + DEBUG_LL_OMAP4(3, tuna); /* zoom2/3 external uart */ DEBUG_LL_ZOOM(omap_zoom2); diff --git a/arch/arm/plat-omap/omap_rpmsg.c b/arch/arm/plat-omap/omap_rpmsg.c index 3653833..269f206 100644 --- a/arch/arm/plat-omap/omap_rpmsg.c +++ b/arch/arm/plat-omap/omap_rpmsg.c @@ -47,6 +47,7 @@ struct omap_rpmsg_vproc { char *mbox_name; char *rproc_name; struct omap_mbox *mbox; + struct mutex lock; struct rproc *rproc; struct notifier_block nb; struct notifier_block rproc_nb; @@ -145,13 +146,25 @@ static void omap_rpmsg_notify(struct virtqueue *vq) { struct omap_rpmsg_vq_info *rpvq = vq->priv; int ret; + int count = 5; pr_debug("sending mailbox msg: %d\n", rpvq->vq_id); - rproc_last_busy(rpvq->rpdev->rproc); + do { + rproc_last_busy(rpvq->rpdev->rproc); + mutex_lock(&rpvq->rpdev->lock); + if (rpvq->rpdev->mbox) + break; + mutex_unlock(&rpvq->rpdev->lock); + } while (--count); + if (!count) { + pr_err("mbox handle is NULL\n"); + return; + } /* send the index of the triggered virtqueue as the mailbox payload */ ret = omap_mbox_msg_send(rpvq->rpdev->mbox, rpvq->vq_id); if (ret) pr_err("ugh, omap_mbox_msg_send() failed: %d\n", ret); + mutex_unlock(&rpvq->rpdev->lock); } static int omap_rpmsg_mbox_callback(struct notifier_block *this, @@ -228,18 +241,22 @@ static int rpmsg_rproc_suspend(struct omap_rpmsg_vproc *rpdev) static int rpmsg_rproc_pos_suspend(struct omap_rpmsg_vproc *rpdev) { + mutex_lock(&rpdev->lock); if (rpdev->mbox) { omap_mbox_put(rpdev->mbox, &rpdev->nb); rpdev->mbox = NULL; } + mutex_unlock(&rpdev->lock); return NOTIFY_DONE; } static int rpmsg_rproc_resume(struct omap_rpmsg_vproc *rpdev) { + mutex_lock(&rpdev->lock); if (!rpdev->mbox) rpdev->mbox = omap_mbox_get(rpdev->mbox_name, &rpdev->nb); + mutex_unlock(&rpdev->lock); return NOTIFY_DONE; } @@ -315,6 +332,7 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev, /* system-wide unique id for this virtqueue */ rpvq->vq_id = rpdev->base_vq_id + index; rpvq->rpdev = rpdev; + mutex_init(&rpdev->lock); return vq; diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c index ead17f9..0088b8c 100644 --- a/drivers/i2c/busses/i2c-omap.c +++ b/drivers/i2c/busses/i2c-omap.c @@ -42,6 +42,11 @@ #include <linux/pm_runtime.h> #include <linux/pm_qos_params.h> +#ifdef CONFIG_ARCH_OMAP4 +#include "../../../arch/arm/mach-omap2/cm2_44xx.h" +#include "../../../arch/arm/mach-omap2/cm-regbits-44xx.h" +#endif + /* I2C controller revisions */ #define OMAP_I2C_REV_2 0x20 @@ -264,6 +269,51 @@ static inline u16 omap_i2c_read_reg(struct omap_i2c_dev *i2c_dev, int reg) (i2c_dev->regs[reg] << i2c_dev->reg_shift)); } +static void omap_i2c_dump(struct omap_i2c_dev *dev) +{ + struct clk *fclk; + unsigned long fclk_rate; + + dev_info(dev->dev, "sysc=0x%04x stat=0x%04x syss=0x%04x con=0x%04x\n", + omap_i2c_read_reg(dev, OMAP_I2C_SYSC_REG), + omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG), + omap_i2c_read_reg(dev, OMAP_I2C_SYSS_REG), + omap_i2c_read_reg(dev, OMAP_I2C_CON_REG)); + + pr_info("we=0x%04x sa=0x%04x cnt=%d buf=0x%04x bufstat=0x%04x\n", + omap_i2c_read_reg(dev, OMAP_I2C_WE_REG), + omap_i2c_read_reg(dev, OMAP_I2C_SA_REG), + omap_i2c_read_reg(dev, OMAP_I2C_CNT_REG), + omap_i2c_read_reg(dev, OMAP_I2C_BUF_REG), + omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG)); + + fclk = clk_get(dev->dev, "fck"); + fclk_rate = clk_get_rate(fclk); + clk_put(fclk); + + pr_info("fclk=%lu" +#ifdef CONFIG_ARCH_OMAP4 + " gated=%s" +#endif + " psc=0x%04x scll=0x%04x sclh=0x%04x\n", + fclk_rate, +#ifdef CONFIG_ARCH_OMAP4 + __raw_readl(OMAP4430_CM_L4PER_CLKSTCTRL) & + OMAP4430_CLKACTIVITY_PER_96M_GFCLK_MASK ? "no" : "yes", +#endif + omap_i2c_read_reg(dev, OMAP_I2C_PSC_REG), + omap_i2c_read_reg(dev, OMAP_I2C_SCLL_REG), + omap_i2c_read_reg(dev, OMAP_I2C_SCLH_REG)); + +#ifdef CONFIG_ARCH_OMAP4 + pr_info("CLKCTRL: 1:0x%08x 2:0x%08x 3:0x%08x 4:0x%08x\n", + __raw_readl(OMAP4430_CM_L4PER_I2C1_CLKCTRL), + __raw_readl(OMAP4430_CM_L4PER_I2C2_CLKCTRL), + __raw_readl(OMAP4430_CM_L4PER_I2C3_CLKCTRL), + __raw_readl(OMAP4430_CM_L4PER_I2C4_CLKCTRL)); +#endif +} + static void omap_i2c_unidle(struct omap_i2c_dev *dev) { struct platform_device *pdev; @@ -501,6 +551,7 @@ static int omap_i2c_wait_for_bb(struct omap_i2c_dev *dev) while (omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG) & OMAP_I2C_STAT_BB) { if (time_after(jiffies, timeout)) { dev_warn(dev->dev, "timeout waiting for bus ready\n"); + omap_i2c_dump(dev); return -ETIMEDOUT; } msleep(1); @@ -591,6 +642,9 @@ static int omap_i2c_xfer_msg(struct i2c_adapter *adap, return r; if (r == 0) { dev_err(dev->dev, "controller timed out\n"); + pr_info("addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n", + msg->addr, msg->len, msg->flags, stop); + omap_i2c_dump(dev); omap_i2c_init(dev); return -ETIMEDOUT; } diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 6f4ad1a..1f8e6d5 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -494,4 +494,11 @@ config INPUT_XEN_KBDDEV_FRONTEND To compile this driver as a module, choose M here: the module will be called xen-kbdfront. +config OPTICAL_GP2A + depends on I2C && GENERIC_GPIO + tristate "GP2A ambient light and proximity input device" + default n + help + This option enables proximity & light sensors using gp2a driver. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index eb73834..059ce58 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -47,4 +47,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o - +obj-$(CONFIG_OPTICAL_GP2A) += gp2a.o
\ No newline at end of file diff --git a/drivers/input/misc/gp2a.c b/drivers/input/misc/gp2a.c new file mode 100644 index 0000000..472da90 --- /dev/null +++ b/drivers/input/misc/gp2a.c @@ -0,0 +1,639 @@ +/* linux/driver/input/misc/gp2a.c + * Copyright (C) 2010 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 + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/gpio.h> +#include <linux/wakelock.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/workqueue.h> +#include <linux/uaccess.h> +#include <linux/gp2a.h> + + +/* Note about power vs enable/disable: + * The chip has two functions, proximity and ambient light sensing. + * There is no separate power enablement to the two functions (unlike + * the Capella CM3602/3623). + * This module implements two drivers: /dev/proximity and /dev/light. + * When either driver is enabled (via sysfs attributes), we give power + * to the chip. When both are disabled, we remove power from the chip. + * In suspend, we remove power if light is disabled but not if proximity is + * enabled (proximity is allowed to wakeup from suspend). + * + * There are no ioctls for either driver interfaces. Output is via + * input device framework and control via sysfs attributes. + */ + + +#define gp2a_dbgmsg(str, args...) pr_debug("%s: " str, __func__, ##args) + +/* ADDSEL is LOW */ +#define REGS_PROX 0x0 /* Read Only */ +#define REGS_GAIN 0x1 /* Write Only */ +#define REGS_HYS 0x2 /* Write Only */ +#define REGS_CYCLE 0x3 /* Write Only */ +#define REGS_OPMOD 0x4 /* Write Only */ + +/* sensor type */ +#define LIGHT 0 +#define PROXIMITY 1 +#define ALL 2 + +#define DELAY_LOWBOUND (5 * NSEC_PER_MSEC) + +/* start time delay for light sensor in nano seconds */ +#define LIGHT_SENSOR_START_TIME_DELAY 50000000 + +static u8 reg_defaults[5] = { + 0x00, /* PROX: read only register */ + 0x08, /* GAIN: large LED drive level */ + 0xC2, /* HYS: receiver sensitivity */ + 0x04, /* CYCLE: */ + 0x01, /* OPMOD: normal operating mode */ +}; + +enum { + LIGHT_ENABLED = BIT(0), + PROXIMITY_ENABLED = BIT(1), +}; + +/* driver data */ +struct gp2a_data { + struct input_dev *proximity_input_dev; + struct input_dev *light_input_dev; + struct gp2a_platform_data *pdata; + struct i2c_client *i2c_client; + int irq; + struct work_struct work_light; + struct hrtimer timer; + ktime_t light_poll_delay; + bool on; + u8 power_state; + struct mutex power_lock; + struct wake_lock prx_wake_lock; + struct workqueue_struct *wq; +}; + +int gp2a_i2c_write(struct gp2a_data *gp2a, u8 reg, u8 *val) +{ + int err = 0; + struct i2c_msg msg[1]; + unsigned char data[2]; + int retry = 10; + struct i2c_client *client = gp2a->i2c_client; + + if ((client == NULL) || (!client->adapter)) + return -ENODEV; + + while (retry--) { + data[0] = reg; + data[1] = *val; + + msg->addr = client->addr; + msg->flags = 0; /* write */ + msg->len = 2; + msg->buf = data; + + err = i2c_transfer(client->adapter, msg, 1); + + if (err >= 0) + return 0; + } + return err; +} + +static void gp2a_light_enable(struct gp2a_data *gp2a) +{ + gp2a_dbgmsg("starting poll timer, delay %lldns\n", + ktime_to_ns(gp2a->light_poll_delay)); + /* + * Set far out of range ABS_MISC value, -1024, to enable real value to + * go through next. + */ + input_abs_set_val(gp2a->light_input_dev, ABS_MISC, -1024); + hrtimer_start(&gp2a->timer, ktime_set(0, LIGHT_SENSOR_START_TIME_DELAY), + HRTIMER_MODE_REL); +} + +static void gp2a_light_disable(struct gp2a_data *gp2a) +{ + gp2a_dbgmsg("cancelling poll timer\n"); + hrtimer_cancel(&gp2a->timer); + cancel_work_sync(&gp2a->work_light); +} + +static ssize_t poll_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + return sprintf(buf, "%lld\n", ktime_to_ns(gp2a->light_poll_delay)); +} + + +static ssize_t poll_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + int64_t new_delay; + int err; + + err = strict_strtoll(buf, 10, &new_delay); + if (err < 0) + return err; + + gp2a_dbgmsg("new delay = %lldns, old delay = %lldns\n", + new_delay, ktime_to_ns(gp2a->light_poll_delay)); + + if (new_delay < DELAY_LOWBOUND) { + gp2a_dbgmsg("new delay less than low bound, so set delay " + "to %lld\n", (int64_t)DELAY_LOWBOUND); + new_delay = DELAY_LOWBOUND; + } + + mutex_lock(&gp2a->power_lock); + if (new_delay != ktime_to_ns(gp2a->light_poll_delay)) { + gp2a->light_poll_delay = ns_to_ktime(new_delay); + if (gp2a->power_state & LIGHT_ENABLED) { + gp2a_light_disable(gp2a); + gp2a_light_enable(gp2a); + } + } + mutex_unlock(&gp2a->power_lock); + + return size; +} + +static ssize_t light_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + (gp2a->power_state & LIGHT_ENABLED) ? 1 : 0); +} + +static ssize_t proximity_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + (gp2a->power_state & PROXIMITY_ENABLED) ? 1 : 0); +} + +static ssize_t light_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + bool new_value; + + if (sysfs_streq(buf, "1")) + new_value = true; + else if (sysfs_streq(buf, "0")) + new_value = false; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + mutex_lock(&gp2a->power_lock); + gp2a_dbgmsg("new_value = %d, old state = %d\n", + new_value, (gp2a->power_state & LIGHT_ENABLED) ? 1 : 0); + if (new_value && !(gp2a->power_state & LIGHT_ENABLED)) { + if (!gp2a->power_state) + gp2a->pdata->power(true); + gp2a->power_state |= LIGHT_ENABLED; + gp2a_light_enable(gp2a); + } else if (!new_value && (gp2a->power_state & LIGHT_ENABLED)) { + gp2a_light_disable(gp2a); + gp2a->power_state &= ~LIGHT_ENABLED; + if (!gp2a->power_state) + gp2a->pdata->power(false); + } + mutex_unlock(&gp2a->power_lock); + return size; +} + +static ssize_t proximity_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + bool new_value; + + if (sysfs_streq(buf, "1")) + new_value = true; + else if (sysfs_streq(buf, "0")) + new_value = false; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + mutex_lock(&gp2a->power_lock); + gp2a_dbgmsg("new_value = %d, old state = %d\n", + new_value, (gp2a->power_state & PROXIMITY_ENABLED) ? 1 : 0); + if (new_value && !(gp2a->power_state & PROXIMITY_ENABLED)) { + if (!gp2a->power_state) + gp2a->pdata->power(true); + gp2a->power_state |= PROXIMITY_ENABLED; + enable_irq(gp2a->irq); + enable_irq_wake(gp2a->irq); + gp2a_i2c_write(gp2a, REGS_GAIN, ®_defaults[1]); + gp2a_i2c_write(gp2a, REGS_HYS, ®_defaults[2]); + gp2a_i2c_write(gp2a, REGS_CYCLE, ®_defaults[3]); + gp2a_i2c_write(gp2a, REGS_OPMOD, ®_defaults[4]); + } else if (!new_value && (gp2a->power_state & PROXIMITY_ENABLED)) { + disable_irq_wake(gp2a->irq); + disable_irq(gp2a->irq); + gp2a_i2c_write(gp2a, REGS_OPMOD, ®_defaults[0]); + gp2a->power_state &= ~PROXIMITY_ENABLED; + if (!gp2a->power_state) + gp2a->pdata->power(false); + } + mutex_unlock(&gp2a->power_lock); + return size; +} + +static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP, + poll_delay_show, poll_delay_store); + +static struct device_attribute dev_attr_light_enable = + __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, + light_enable_show, light_enable_store); + +static struct device_attribute dev_attr_proximity_enable = + __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, + proximity_enable_show, proximity_enable_store); + +static struct attribute *light_sysfs_attrs[] = { + &dev_attr_light_enable.attr, + &dev_attr_poll_delay.attr, + NULL +}; + +static struct attribute_group light_attribute_group = { + .attrs = light_sysfs_attrs, +}; + +static struct attribute *proximity_sysfs_attrs[] = { + &dev_attr_proximity_enable.attr, + NULL +}; + +static struct attribute_group proximity_attribute_group = { + .attrs = proximity_sysfs_attrs, +}; + +static void gp2a_work_func_light(struct work_struct *work) +{ + struct gp2a_data *gp2a = container_of(work, struct gp2a_data, + work_light); + int adc = gp2a->pdata->light_adc_value(); + if (adc < 0) { + pr_err("adc returned error %d\n", adc); + return; + } + gp2a_dbgmsg("adc returned light value %d\n", adc); + input_report_abs(gp2a->light_input_dev, ABS_MISC, adc); + input_sync(gp2a->light_input_dev); +} + +/* This function is for light sensor. It operates every a few seconds. + * It asks for work to be done on a thread because i2c needs a thread + * context (slow and blocking) and then reschedules the timer to run again. + */ +static enum hrtimer_restart gp2a_timer_func(struct hrtimer *timer) +{ + struct gp2a_data *gp2a = container_of(timer, struct gp2a_data, timer); + queue_work(gp2a->wq, &gp2a->work_light); + hrtimer_forward_now(&gp2a->timer, gp2a->light_poll_delay); + return HRTIMER_RESTART; +} + +/* interrupt happened due to transition/change of near/far proximity state */ +irqreturn_t gp2a_irq_handler(int irq, void *data) +{ + struct gp2a_data *ip = data; + int val = gpio_get_value(ip->pdata->p_out); + if (val < 0) { + pr_err("%s: gpio_get_value error %d\n", __func__, val); + return IRQ_HANDLED; + } + + gp2a_dbgmsg("gp2a: proximity val=%d\n", val); + + /* 0 is close, 1 is far */ + input_report_abs(ip->proximity_input_dev, ABS_DISTANCE, val); + input_sync(ip->proximity_input_dev); + wake_lock_timeout(&ip->prx_wake_lock, 3*HZ); + return IRQ_HANDLED; +} + +static int gp2a_setup_irq(struct gp2a_data *gp2a) +{ + int rc = -EIO; + struct gp2a_platform_data *pdata = gp2a->pdata; + int irq; + + gp2a_dbgmsg("start\n"); + + rc = gpio_request(pdata->p_out, "gpio_proximity_out"); + if (rc < 0) { + pr_err("%s: gpio %d request failed (%d)\n", + __func__, pdata->p_out, rc); + return rc; + } + + rc = gpio_direction_input(pdata->p_out); + if (rc < 0) { + pr_err("%s: failed to set gpio %d as input (%d)\n", + __func__, pdata->p_out, rc); + goto err_gpio_direction_input; + } + + irq = gpio_to_irq(pdata->p_out); + rc = request_irq(irq, + gp2a_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "proximity_int", + gp2a); + if (rc < 0) { + pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n", + __func__, irq, + pdata->p_out, rc); + goto err_request_irq; + } + + /* start with interrupts disabled */ + disable_irq(irq); + gp2a->irq = irq; + + /* sync input device with proximity gpio pin default value */ + gp2a_irq_handler(gp2a->irq, gp2a); + + gp2a_dbgmsg("success\n"); + + goto done; + +err_request_irq: +err_gpio_direction_input: + gpio_free(pdata->p_out); +done: + return rc; +} + +static int gp2a_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = -ENODEV; + struct input_dev *input_dev; + struct gp2a_data *gp2a; + struct gp2a_platform_data *pdata = client->dev.platform_data; + + if (!pdata) { + pr_err("%s: missing pdata!\n", __func__); + return ret; + } + if (!pdata->power || !pdata->light_adc_value) { + pr_err("%s: incomplete pdata!\n", __func__); + return ret; + } + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("%s: i2c functionality check failed!\n", __func__); + return ret; + } + + gp2a = kzalloc(sizeof(struct gp2a_data), GFP_KERNEL); + if (!gp2a) { + pr_err("%s: failed to alloc memory for module data\n", + __func__); + return -ENOMEM; + } + + gp2a->pdata = pdata; + gp2a->i2c_client = client; + i2c_set_clientdata(client, gp2a); + + + wake_lock_init(&gp2a->prx_wake_lock, WAKE_LOCK_SUSPEND, + "prx_wake_lock"); + mutex_init(&gp2a->power_lock); + + /* allocate proximity input_device */ + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + goto err_input_allocate_device_proximity; + } + gp2a->proximity_input_dev = input_dev; + input_set_drvdata(input_dev, gp2a); + input_dev->name = "proximity"; + input_set_capability(input_dev, EV_ABS, ABS_DISTANCE); + input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0); + + ret = gp2a_setup_irq(gp2a); + if (ret) { + pr_err("%s: could not setup irq\n", __func__); + input_free_device(input_dev); + goto err_setup_irq; + } + + gp2a_dbgmsg("registering proximity input device\n"); + ret = input_register_device(input_dev); + if (ret < 0) { + pr_err("%s: could not register input device\n", __func__); + input_free_device(input_dev); + goto err_input_register_device_proximity; + } + ret = sysfs_create_group(&input_dev->dev.kobj, + &proximity_attribute_group); + if (ret) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_sysfs_create_group_proximity; + } + + /* hrtimer settings. we poll for light values using a timer. */ + hrtimer_init(&gp2a->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + gp2a->light_poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC); + gp2a->timer.function = gp2a_timer_func; + + /* the timer just fires off a work queue request. we need a thread + * to read the i2c (can be slow and blocking) + */ + gp2a->wq = create_singlethread_workqueue("gp2a_wq"); + if (!gp2a->wq) { + ret = -ENOMEM; + pr_err("%s: could not create workqueue\n", __func__); + goto err_create_workqueue; + } + /* this is the thread function we run on the work queue */ + INIT_WORK(&gp2a->work_light, gp2a_work_func_light); + + /* allocate lightsensor-level input_device */ + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + ret = -ENOMEM; + goto err_input_allocate_device_light; + } + input_set_drvdata(input_dev, gp2a); + input_dev->name = "lightsensor-level"; + input_set_capability(input_dev, EV_ABS, ABS_MISC); + input_set_abs_params(input_dev, ABS_MISC, 0, 1023, 8, 0); + + gp2a_dbgmsg("registering lightsensor-level input device\n"); + ret = input_register_device(input_dev); + if (ret < 0) { + pr_err("%s: could not register input device\n", __func__); + input_free_device(input_dev); + goto err_input_register_device_light; + } + gp2a->light_input_dev = input_dev; + ret = sysfs_create_group(&input_dev->dev.kobj, + &light_attribute_group); + if (ret) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_sysfs_create_group_light; + } + goto done; + + /* error, unwind it all */ +err_sysfs_create_group_light: + input_unregister_device(gp2a->light_input_dev); +err_input_register_device_light: +err_input_allocate_device_light: + destroy_workqueue(gp2a->wq); +err_create_workqueue: + sysfs_remove_group(&gp2a->proximity_input_dev->dev.kobj, + &proximity_attribute_group); +err_sysfs_create_group_proximity: + input_unregister_device(gp2a->proximity_input_dev); +err_input_register_device_proximity: + free_irq(gp2a->irq, gp2a); + gpio_free(gp2a->pdata->p_out); +err_setup_irq: +err_input_allocate_device_proximity: + mutex_destroy(&gp2a->power_lock); + wake_lock_destroy(&gp2a->prx_wake_lock); + kfree(gp2a); +done: + return ret; +} + +static int gp2a_suspend(struct device *dev) +{ + /* We disable power only if proximity is disabled. If proximity + * is enabled, we leave power on because proximity is allowed + * to wake up device. We remove power without changing + * gp2a->power_state because we use that state in resume + */ + struct i2c_client *client = to_i2c_client(dev); + struct gp2a_data *gp2a = i2c_get_clientdata(client); + if (gp2a->power_state & LIGHT_ENABLED) + gp2a_light_disable(gp2a); + if (gp2a->power_state == LIGHT_ENABLED) + gp2a->pdata->power(false); + return 0; +} + +static int gp2a_resume(struct device *dev) +{ + /* Turn power back on if we were before suspend. */ + struct i2c_client *client = to_i2c_client(dev); + struct gp2a_data *gp2a = i2c_get_clientdata(client); + if (gp2a->power_state == LIGHT_ENABLED) + gp2a->pdata->power(true); + if (gp2a->power_state & LIGHT_ENABLED) + gp2a_light_enable(gp2a); + return 0; +} + +static int gp2a_i2c_remove(struct i2c_client *client) +{ + struct gp2a_data *gp2a = i2c_get_clientdata(client); + sysfs_remove_group(&gp2a->light_input_dev->dev.kobj, + &light_attribute_group); + sysfs_remove_group(&gp2a->proximity_input_dev->dev.kobj, + &proximity_attribute_group); + free_irq(gp2a->irq, gp2a); + destroy_workqueue(gp2a->wq); + input_unregister_device(gp2a->light_input_dev); + input_unregister_device(gp2a->proximity_input_dev); + gpio_free(gp2a->pdata->p_out); + if (gp2a->power_state) { + gp2a->power_state = 0; + if (gp2a->power_state & LIGHT_ENABLED) + gp2a_light_disable(gp2a); + gp2a->pdata->power(false); + } + mutex_destroy(&gp2a->power_lock); + wake_lock_destroy(&gp2a->prx_wake_lock); + kfree(gp2a); + return 0; +} + +static const struct i2c_device_id gp2a_device_id[] = { + {"gp2a", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, gp2a_device_id); + +static const struct dev_pm_ops gp2a_pm_ops = { + .suspend = gp2a_suspend, + .resume = gp2a_resume +}; + +static struct i2c_driver gp2a_i2c_driver = { + .driver = { + .name = "gp2a", + .owner = THIS_MODULE, + .pm = &gp2a_pm_ops + }, + .probe = gp2a_i2c_probe, + .remove = gp2a_i2c_remove, + .id_table = gp2a_device_id, +}; + + +static int __init gp2a_init(void) +{ + return i2c_add_driver(&gp2a_i2c_driver); +} + +static void __exit gp2a_exit(void) +{ + i2c_del_driver(&gp2a_i2c_driver); +} + +module_init(gp2a_init); +module_exit(gp2a_exit); + +MODULE_AUTHOR("mjchen@sta.samsung.com"); +MODULE_DESCRIPTION("Optical Sensor driver for gp2ap002a00f"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 4104103..86bc7a7 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -272,6 +272,18 @@ config TOUCHSCREEN_MCS5000 To compile this driver as a module, choose M here: the module will be called mcs5000_ts. +config TOUCHSCREEN_MMS + tristate "MELFAS MMS-series touchscreen" + depends on I2C + help + Say Y here if you have a MELFAS MMS-series touchscreen controller + chip in your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mms_ts. + config TOUCHSCREEN_MTOUCH tristate "MicroTouch serial touchscreens" select SERIO diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 0738f19..ce1fbe2 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_TOUCHSCREEN_LPC32XX) += lpc32xx_ts.o obj-$(CONFIG_TOUCHSCREEN_MAX11801) += max11801_ts.o obj-$(CONFIG_TOUCHSCREEN_MC13783) += mc13783_ts.o obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.o +obj-$(CONFIG_TOUCHSCREEN_MMS) += mms_ts.o obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c index 1e61387..d4e1515 100644 --- a/drivers/input/touchscreen/atmel_mxt_ts.c +++ b/drivers/input/touchscreen/atmel_mxt_ts.c @@ -829,6 +829,10 @@ static int mxt_initialize(struct mxt_data *data) MXT_COMMAND_RESET, 1); msleep(MXT_RESET_TIME); + error = mxt_make_highchg(data); + if (error) + return error; + /* Update matrix size at info struct */ error = mxt_read_reg(client, MXT_MATRIX_X_SIZE, &val); if (error) diff --git a/drivers/input/touchscreen/mms_ts.c b/drivers/input/touchscreen/mms_ts.c new file mode 100755 index 0000000..a6ec6eb --- /dev/null +++ b/drivers/input/touchscreen/mms_ts.c @@ -0,0 +1,961 @@ +/* + * mms_ts.c - Touchscreen driver for Melfas MMS-series touch controllers + * + * Copyright (C) 2011 Google Inc. + * Author: Dima Zavin <dima@android.com> + * Simon Wilson <simonwilson@google.com> + * + * ISP reflashing code based on original code from Melfas. + * + * 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. + * + */ + +//#define DEBUG +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/earlysuspend.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include <linux/platform_data/mms_ts.h> + +#include <asm/unaligned.h> + +#define MAX_FINGERS 10 +#define MAX_WIDTH 30 +#define MAX_PRESSURE 255 + +/* Registers */ +#define MMS_MODE_CONTROL 0x01 +#define MMS_XYRES_HI 0x02 +#define MMS_XRES_LO 0x03 +#define MMS_YRES_LO 0x04 + +#define MMS_INPUT_EVENT_PKT_SZ 0x0F +#define MMS_INPUT_EVENT0 0x10 +#define FINGER_EVENT_SZ 6 + +#define MMS_TSP_REVISION 0xF0 +#define MMS_HW_REVISION 0xF1 +#define MMS_COMPAT_GROUP 0xF2 +#define MMS_FW_VERSION 0xF3 + +enum { + ISP_MODE_FLASH_ERASE = 0x59F3, + ISP_MODE_FLASH_WRITE = 0x62CD, + ISP_MODE_FLASH_READ = 0x6AC9, +}; + +/* each address addresses 4-byte words */ +#define ISP_MAX_FW_SIZE (0x1F00 * 4) +#define ISP_IC_INFO_ADDR 0x1F00 + +static bool mms_force_reflash = false; +module_param_named(force_reflash, mms_force_reflash, bool, S_IWUSR | S_IRUGO); + +static bool mms_flash_from_probe; +module_param_named(flash_from_probe, mms_flash_from_probe, bool, + S_IWUSR | S_IRUGO); + +static bool mms_die_on_flash_fail = true; +module_param_named(die_on_flash_fail, mms_die_on_flash_fail, bool, + S_IWUSR | S_IRUGO); + +struct mms_ts_info { + struct i2c_client *client; + struct input_dev *input_dev; + char phys[32]; + + int max_x; + int max_y; + + bool invert_x; + bool invert_y; + + int irq; + + struct mms_ts_platform_data *pdata; + + char *fw_name; + struct completion init_done; + struct early_suspend early_suspend; + + /* protects the enabled flag */ + struct mutex lock; + bool enabled; +}; + +struct mms_fw_image { + __le32 hdr_len; + __le32 data_len; + __le32 fw_ver; + __le32 hdr_ver; + u8 data[0]; +} __attribute__ ((packed)); + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mms_ts_early_suspend(struct early_suspend *h); +static void mms_ts_late_resume(struct early_suspend *h); +#endif + +static irqreturn_t mms_ts_interrupt(int irq, void *dev_id) +{ + struct mms_ts_info *info = dev_id; + struct i2c_client *client = info->client; + u8 buf[MAX_FINGERS*FINGER_EVENT_SZ] = { 0 }; + int ret; + int i; + int sz; + u8 reg = MMS_INPUT_EVENT0; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .buf = ®, + .len = 1, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .buf = buf, + }, + }; + + sz = i2c_smbus_read_byte_data(client, MMS_INPUT_EVENT_PKT_SZ); + if (sz < 0) { + dev_err(&client->dev, "%s bytes=%d\n", __func__, sz); + goto out; + } + dev_dbg(&client->dev, "bytes available: %d\n", sz); + BUG_ON(sz > MAX_FINGERS*FINGER_EVENT_SZ); + if (sz == 0) + goto out; + + msg[1].len = sz; + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(&client->dev, + "failed to read %d bytes of touch data (%d)\n", + sz, ret); + goto out; + } + +#if defined(VERBOSE_DEBUG) + print_hex_dump(KERN_DEBUG, "mms_ts raw: ", + DUMP_PREFIX_OFFSET, 32, 1, buf, sz, false); +#endif + for (i = 0; i < sz; i += FINGER_EVENT_SZ) { + u8 *tmp = &buf[i]; + int id = (tmp[0] & 0xf) - 1; + int x = tmp[2] | ((tmp[1] & 0xf) << 8); + int y = tmp[3] | (((tmp[1] >> 4) & 0xf) << 8); + + if (info->invert_x) { + x = info->max_x - x; + if (x < 0) + x = 0; + } + if (info->invert_y) { + y = info->max_y - y; + if (y < 0) + y = 0; + } + + if ((tmp[0] & 0x80) == 0) { + dev_dbg(&client->dev, "finger %d up\n", id); + input_mt_slot(info->input_dev, id); + input_mt_report_slot_state(info->input_dev, + MT_TOOL_FINGER, false); + continue; + } + + input_mt_slot(info->input_dev, id); + input_mt_report_slot_state(info->input_dev, + MT_TOOL_FINGER, true); + input_report_abs(info->input_dev, ABS_MT_TOUCH_MAJOR, tmp[4]); + input_report_abs(info->input_dev, ABS_MT_PRESSURE, tmp[5]); + input_report_abs(info->input_dev, ABS_MT_POSITION_X, x); + input_report_abs(info->input_dev, ABS_MT_POSITION_Y, y); + + dev_dbg(&client->dev, + "finger %d: x=%d y=%d p=%d w=%d\n", id, x, y, tmp[5], + tmp[4]); + } + + input_sync(info->input_dev); + +out: + return IRQ_HANDLED; +} + +static void hw_reboot(struct mms_ts_info *info, bool bootloader) +{ + gpio_direction_output(info->pdata->gpio_vdd_en, 0); + gpio_direction_output(info->pdata->gpio_sda, bootloader ? 0 : 1); + gpio_direction_output(info->pdata->gpio_scl, bootloader ? 0 : 1); + gpio_direction_output(info->pdata->gpio_resetb, 0); + msleep(30); + gpio_set_value(info->pdata->gpio_vdd_en, 1); + msleep(30); + + if (bootloader) { + gpio_set_value(info->pdata->gpio_scl, 0); + gpio_set_value(info->pdata->gpio_sda, 1); + } else { + gpio_set_value(info->pdata->gpio_resetb, 1); + gpio_direction_input(info->pdata->gpio_resetb); + gpio_direction_input(info->pdata->gpio_scl); + gpio_direction_input(info->pdata->gpio_sda); + } + msleep(40); +} + +static inline void hw_reboot_bootloader(struct mms_ts_info *info) +{ + hw_reboot(info, true); +} + +static inline void hw_reboot_normal(struct mms_ts_info *info) +{ + hw_reboot(info, false); +} + +static inline void mms_pwr_on_reset(struct mms_ts_info *info) +{ + struct i2c_adapter *adapter = to_i2c_adapter(info->client->dev.parent); + + if (!info->pdata->mux_fw_flash) { + dev_info(&info->client->dev, + "missing platform data, can't do power-on-reset\n"); + return; + } + + i2c_lock_adapter(adapter); + info->pdata->mux_fw_flash(true); + + gpio_direction_output(info->pdata->gpio_vdd_en, 0); + gpio_direction_output(info->pdata->gpio_sda, 1); + gpio_direction_output(info->pdata->gpio_scl, 1); + gpio_direction_output(info->pdata->gpio_resetb, 1); + msleep(50); + gpio_direction_output(info->pdata->gpio_vdd_en, 1); + msleep(50); + + info->pdata->mux_fw_flash(false); + i2c_unlock_adapter(adapter); + + /* TODO: Seems long enough for the firmware to boot. + * Find the right value */ + msleep(250); +} + +static void isp_toggle_clk(struct mms_ts_info *info, int start_lvl, int end_lvl, + int hold_us) +{ + gpio_set_value(info->pdata->gpio_scl, start_lvl); + udelay(hold_us); + gpio_set_value(info->pdata->gpio_scl, end_lvl); + udelay(hold_us); +} + +/* 1 <= cnt <= 32 bits to write */ +static void isp_send_bits(struct mms_ts_info *info, u32 data, int cnt) +{ + gpio_direction_output(info->pdata->gpio_resetb, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_sda, 0); + + /* clock out the bits, msb first */ + while (cnt--) { + gpio_set_value(info->pdata->gpio_sda, (data >> cnt) & 1); + udelay(3); + isp_toggle_clk(info, 1, 0, 3); + } +} + +/* 1 <= cnt <= 32 bits to read */ +static u32 isp_recv_bits(struct mms_ts_info *info, int cnt) +{ + u32 data = 0; + + gpio_direction_output(info->pdata->gpio_resetb, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_set_value(info->pdata->gpio_sda, 0); + gpio_direction_input(info->pdata->gpio_sda); + + /* clock in the bits, msb first */ + while (cnt--) { + isp_toggle_clk(info, 0, 1, 1); + data = (data << 1) | (!!gpio_get_value(info->pdata->gpio_sda)); + } + + gpio_direction_output(info->pdata->gpio_sda, 0); + return data; +} + +static void isp_enter_mode(struct mms_ts_info *info, u32 mode) +{ + int cnt; + unsigned long flags; + + local_irq_save(flags); + gpio_direction_output(info->pdata->gpio_resetb, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_sda, 1); + + mode &= 0xffff; + for (cnt = 15; cnt >= 0; cnt--) { + gpio_set_value(info->pdata->gpio_resetb, (mode >> cnt) & 1); + udelay(3); + isp_toggle_clk(info, 1, 0, 3); + } + + gpio_set_value(info->pdata->gpio_resetb, 0); + local_irq_restore(flags); +} + +static void isp_exit_mode(struct mms_ts_info *info) +{ + int i; + unsigned long flags; + + local_irq_save(flags); + gpio_direction_output(info->pdata->gpio_resetb, 0); + udelay(3); + + for (i = 0; i < 10; i++) + isp_toggle_clk(info, 1, 0, 3); + local_irq_restore(flags); +} + +static void flash_set_address(struct mms_ts_info *info, u16 addr) +{ + /* Only 13 bits of addr are valid. + * The addr is in bits 13:1 of cmd */ + isp_send_bits(info, (u32)(addr & 0x1fff) << 1, 18); +} + +static void flash_erase(struct mms_ts_info *info) +{ + isp_enter_mode(info, ISP_MODE_FLASH_ERASE); + + gpio_direction_output(info->pdata->gpio_resetb, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_sda, 1); + + /* 4 clock cycles with different timings for the erase to + * get processed, clk is already 0 from above */ + udelay(7); + isp_toggle_clk(info, 1, 0, 3); + udelay(7); + isp_toggle_clk(info, 1, 0, 3); + usleep_range(25000, 35000); + isp_toggle_clk(info, 1, 0, 3); + usleep_range(150, 200); + isp_toggle_clk(info, 1, 0, 3); + + gpio_set_value(info->pdata->gpio_sda, 0); + + isp_exit_mode(info); +} + +static u32 flash_readl(struct mms_ts_info *info, u16 addr) +{ + int i; + u32 val; + unsigned long flags; + + local_irq_save(flags); + isp_enter_mode(info, ISP_MODE_FLASH_READ); + flash_set_address(info, addr); + + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_sda, 0); + udelay(40); + + /* data load cycle */ + for (i = 0; i < 6; i++) + isp_toggle_clk(info, 1, 0, 10); + + val = isp_recv_bits(info, 32); + isp_exit_mode(info); + local_irq_restore(flags); + + return val; +} + +static void flash_writel(struct mms_ts_info *info, u16 addr, u32 val) +{ + unsigned long flags; + + local_irq_save(flags); + isp_enter_mode(info, ISP_MODE_FLASH_WRITE); + flash_set_address(info, addr); + isp_send_bits(info, val, 32); + + gpio_direction_output(info->pdata->gpio_sda, 1); + /* 6 clock cycles with different timings for the data to get written + * into flash */ + isp_toggle_clk(info, 0, 1, 3); + isp_toggle_clk(info, 0, 1, 3); + isp_toggle_clk(info, 0, 1, 6); + isp_toggle_clk(info, 0, 1, 12); + isp_toggle_clk(info, 0, 1, 3); + isp_toggle_clk(info, 0, 1, 3); + + isp_toggle_clk(info, 1, 0, 1); + + gpio_direction_output(info->pdata->gpio_sda, 0); + isp_exit_mode(info); + local_irq_restore(flags); + usleep_range(300, 400); +} + +static bool flash_is_erased(struct mms_ts_info *info) +{ + struct i2c_client *client = info->client; + u32 val; + u16 addr; + + for (addr = 0; addr < (ISP_MAX_FW_SIZE / 4); addr++) { + udelay(40); + val = flash_readl(info, addr); + + if (val != 0xffffffff) { + dev_dbg(&client->dev, + "addr 0x%x not erased: 0x%08x != 0xffffffff\n", + addr, val); + return false; + } + } + return true; +} + +static int fw_write_image(struct mms_ts_info *info, const u8 *data, size_t len) +{ + struct i2c_client *client = info->client; + u16 addr = 0; + + for (addr = 0; addr < (len / 4); addr++, data += 4) { + u32 val = get_unaligned_le32(data); + u32 verify_val; + int retries = 3; + + while (retries--) { + flash_writel(info, addr, val); + verify_val = flash_readl(info, addr); + if (val == verify_val) + break; + dev_err(&client->dev, + "mismatch @ addr 0x%x: 0x%x != 0x%x\n", + addr, verify_val, val); + hw_reboot_bootloader(info); + continue; + } + if (retries < 0) + return -ENXIO; + } + + return 0; +} + +static int fw_download(struct mms_ts_info *info, const u8 *data, size_t len) +{ + struct i2c_client *client = info->client; + u32 val; + int ret = 0; + + if (len % 4) { + dev_err(&client->dev, + "fw image size (%d) must be a multiple of 4 bytes\n", + len); + return -EINVAL; + } else if (len > ISP_MAX_FW_SIZE) { + dev_err(&client->dev, + "fw image is too big, %d > %d\n", len, ISP_MAX_FW_SIZE); + return -EINVAL; + } + + dev_info(&client->dev, "fw download start\n"); + + gpio_direction_output(info->pdata->gpio_vdd_en, 0); + gpio_direction_output(info->pdata->gpio_sda, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_resetb, 0); + + hw_reboot_bootloader(info); + + val = flash_readl(info, ISP_IC_INFO_ADDR); + dev_info(&client->dev, "IC info: 0x%02x (%x)\n", val & 0xff, val); + + dev_info(&client->dev, "fw erase...\n"); + flash_erase(info); + if (!flash_is_erased(info)) { + ret = -ENXIO; + goto err; + } + + dev_info(&client->dev, "fw write...\n"); + /* XXX: what does this do?! */ + flash_writel(info, ISP_IC_INFO_ADDR, 0xffffff00 | (val & 0xff)); + usleep_range(1000, 1500); + ret = fw_write_image(info, data, len); + if (ret) + goto err; + usleep_range(1000, 1500); + + hw_reboot_normal(info); + usleep_range(1000, 1500); + dev_info(&client->dev, "fw download done...\n"); + return 0; + +err: + dev_err(&client->dev, "fw download failed...\n"); + hw_reboot_normal(info); + return ret; +} + +static int get_fw_version(struct mms_ts_info *info) +{ + int ret; + int retries = 3; + + /* this seems to fail sometimes after a reset.. retry a few times */ + do { + ret = i2c_smbus_read_byte_data(info->client, MMS_FW_VERSION); + } while (ret < 0 && retries-- > 0); + + return ret; +} + +static int mms_ts_enable(struct mms_ts_info *info) +{ + mutex_lock(&info->lock); + if (info->enabled) + goto out; + /* wake up the touch controller. */ + i2c_smbus_write_byte_data(info->client, 0, 0); + usleep_range(3000, 5000); + info->enabled = true; + enable_irq(info->irq); +out: + mutex_unlock(&info->lock); + return 0; +} + +static int mms_ts_disable(struct mms_ts_info *info) +{ + mutex_lock(&info->lock); + if (!info->enabled) + goto out; + disable_irq(info->irq); + i2c_smbus_write_byte_data(info->client, MMS_MODE_CONTROL, 0); + usleep_range(10000, 12000); + info->enabled = false; +out: + mutex_unlock(&info->lock); + return 0; +} + +static int mms_ts_input_open(struct input_dev *dev) +{ + struct mms_ts_info *info = input_get_drvdata(dev); + int ret; + + ret = wait_for_completion_interruptible_timeout(&info->init_done, + msecs_to_jiffies(90 * MSEC_PER_SEC)); + + if (ret > 0) { + if (info->irq != -1) + ret = mms_ts_enable(info); + else + ret = -ENXIO; + } else if (ret < 0) { + dev_err(&dev->dev, + "error while waiting for device to init (%d)\n", ret); + ret = -ENXIO; + } else if (ret == 0) { + dev_err(&dev->dev, + "timedout while waiting for device to init\n"); + ret = -ENXIO; + } + + return ret; +} + +static void mms_ts_input_close(struct input_dev *dev) +{ + struct mms_ts_info *info = input_get_drvdata(dev); + mms_ts_disable(info); +} + +static int mms_ts_finish_config(struct mms_ts_info *info) +{ + struct i2c_client *client = info->client; + int ret; + + ret = request_threaded_irq(client->irq, NULL, mms_ts_interrupt, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "mms_ts", info); + if (ret < 0) { + dev_err(&client->dev, "Failed to register interrupt\n"); + goto err_req_irq; + } + disable_irq(client->irq); + + info->irq = client->irq; + barrier(); + + dev_info(&client->dev, + "Melfas MMS-series touch controller initialized\n"); + + complete_all(&info->init_done); + return 0; + +err_req_irq: + return ret; +} + +static void mms_ts_fw_load(const struct firmware *fw, void *context) +{ + struct mms_ts_info *info = context; + struct i2c_client *client = info->client; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret = 0; + int ver; + int retries = 3; + struct mms_fw_image *fw_img; + + ver = get_fw_version(info); + if (ver < 0) { + ver = 0; + dev_err(&client->dev, + "can't read version, controller dead? forcing reflash"); + } + + if (!fw) { + dev_info(&client->dev, "could not find firmware file '%s'\n", + info->fw_name); + goto done; + } + + fw_img = (struct mms_fw_image *)fw->data; + if (fw_img->hdr_len != sizeof(struct mms_fw_image) || + fw_img->data_len + fw_img->hdr_len != fw->size || + fw_img->hdr_ver != 0x1) { + dev_err(&client->dev, + "firmware image '%s' invalid, may continue\n", + info->fw_name); + goto err; + } + + if (ver == fw_img->fw_ver && !mms_force_reflash) { + dev_info(&client->dev, + "fw version 0x%02x already present\n", ver); + goto done; + } + + dev_info(&client->dev, "need fw update (0x%02x != 0x%02x)\n", + ver, fw_img->fw_ver); + + if (!info->pdata || !info->pdata->mux_fw_flash) { + dev_err(&client->dev, + "fw cannot be updated, missing platform data\n"); + goto err; + } + + while (retries--) { + i2c_lock_adapter(adapter); + info->pdata->mux_fw_flash(true); + + ret = fw_download(info, fw_img->data, fw_img->data_len); + + info->pdata->mux_fw_flash(false); + i2c_unlock_adapter(adapter); + + if (ret < 0) { + dev_err(&client->dev, + "error updating firmware to version 0x%02x\n", + fw_img->fw_ver); + if (retries) + dev_err(&client->dev, "retrying flashing\n"); + continue; + } + + ver = get_fw_version(info); + if (ver == fw_img->fw_ver) { + dev_info(&client->dev, + "fw update done. ver = 0x%02x\n", ver); + goto done; + } else { + dev_err(&client->dev, + "ERROR: fw update succeeded, but fw version is still wrong (0x%x != 0x%x)\n", + ver, fw_img->fw_ver); + } + if (retries) + dev_err(&client->dev, "retrying flashing\n"); + } + + dev_err(&client->dev, "could not flash firmware, ran out of retries\n"); + BUG_ON(mms_die_on_flash_fail); + +err: + /* complete anyway, so open() doesn't get blocked */ + complete_all(&info->init_done); + goto out; + +done: + mms_ts_finish_config(info); +out: + release_firmware(fw); +} + +static int __devinit mms_ts_config(struct mms_ts_info *info, bool nowait) +{ + struct i2c_client *client = info->client; + int ret = 0; + const char *filename = info->pdata->fw_name ?: "mms144_ts.fw"; + + mms_pwr_on_reset(info); + + if (nowait) { + const struct firmware *fw; + info->fw_name = kasprintf(GFP_KERNEL, "melfas/%s", filename); + ret = request_firmware(&fw, info->fw_name, &client->dev); + if (ret) { + dev_err(&client->dev, + "error requesting built-in firmware\n"); + goto out; + } + mms_ts_fw_load(fw, info); + } else { + info->fw_name = kstrdup(filename, GFP_KERNEL); + ret = request_firmware_nowait(THIS_MODULE, true, info->fw_name, + &client->dev, GFP_KERNEL, + info, mms_ts_fw_load); + if (ret) + dev_err(&client->dev, + "cannot schedule firmware update (%d)\n", ret); + } + +out: + return ret; +} + +static int __devinit mms_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct mms_ts_info *info; + struct input_dev *input_dev; + int ret = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return -EIO; + + info = kzalloc(sizeof(struct mms_ts_info), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!info || !input_dev) { + dev_err(&client->dev, "Failed to allocate memory\n"); + goto err_alloc; + } + + info->client = client; + info->input_dev = input_dev; + info->pdata = client->dev.platform_data; + init_completion(&info->init_done); + info->irq = -1; + mutex_init(&info->lock); + + if (info->pdata) { + info->max_x = info->pdata->max_x; + info->max_y = info->pdata->max_y; + info->invert_x = info->pdata->invert_x; + info->invert_y = info->pdata->invert_y; + } else { + info->max_x = 720; + info->max_y = 1280; + } + + input_mt_init_slots(input_dev, MAX_FINGERS); + + snprintf(info->phys, sizeof(info->phys), + "%s/input0", dev_name(&client->dev)); + input_dev->name = "Melfas MMSxxx Touchscreen"; + input_dev->phys = info->phys; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + input_dev->open = mms_ts_input_open; + input_dev->close = mms_ts_input_close; + + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, MAX_WIDTH, 0, 0); + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, MAX_PRESSURE, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + 0, info->max_x, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + 0, info->max_y, 0, 0); + + input_set_drvdata(input_dev, info); + + ret = input_register_device(input_dev); + if (ret) { + dev_err(&client->dev, "failed to register input dev (%d)\n", + ret); + goto err_reg_input_dev; + } + + i2c_set_clientdata(client, info); + + ret = mms_ts_config(info, mms_flash_from_probe); + if (ret) { + dev_err(&client->dev, "failed to initialize (%d)\n", ret); + goto err_config; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + info->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + info->early_suspend.suspend = mms_ts_early_suspend; + info->early_suspend.resume = mms_ts_late_resume; + register_early_suspend(&info->early_suspend); +#endif + + return 0; + +err_config: + input_unregister_device(input_dev); + input_dev = NULL; +err_reg_input_dev: +err_alloc: + input_free_device(input_dev); + kfree(info->fw_name); + kfree(info); + return ret; +} + +static int __devexit mms_ts_remove(struct i2c_client *client) +{ + struct mms_ts_info *info = i2c_get_clientdata(client); + + if (info->irq >= 0) + free_irq(info->irq, info); + input_unregister_device(info->input_dev); + kfree(info->fw_name); + kfree(info); + + return 0; +} + +#if defined(CONFIG_PM) || defined(CONFIG_HAS_EARLYSUSPEND) +static int mms_ts_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mms_ts_info *info = i2c_get_clientdata(client); + int i; + + /* TODO: turn off the power (set vdd_en to 0) to the touchscreen + * on suspend + */ + + mutex_lock(&info->input_dev->mutex); + if (!info->input_dev->users) + goto out; + + mms_ts_disable(info); + for (i = 0; i < MAX_FINGERS; i++) { + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, MT_TOOL_FINGER, + false); + } + input_sync(info->input_dev); + +out: + mutex_unlock(&info->input_dev->mutex); + return 0; +} + +static int mms_ts_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mms_ts_info *info = i2c_get_clientdata(client); + int ret = 0; + + mutex_lock(&info->input_dev->mutex); + if (info->input_dev->users) + ret = mms_ts_enable(info); + mutex_unlock(&info->input_dev->mutex); + + return ret; +} +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mms_ts_early_suspend(struct early_suspend *h) +{ + struct mms_ts_info *info; + info = container_of(h, struct mms_ts_info, early_suspend); + mms_ts_suspend(&info->client->dev); +} + +static void mms_ts_late_resume(struct early_suspend *h) +{ + struct mms_ts_info *info; + info = container_of(h, struct mms_ts_info, early_suspend); + mms_ts_resume(&info->client->dev); +} +#endif + +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) +static const struct dev_pm_ops mms_ts_pm_ops = { + .suspend = mms_ts_suspend, + .resume = mms_ts_resume, +}; +#endif + +static const struct i2c_device_id mms_ts_id[] = { + { "mms_ts", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mms_ts_id); + +static struct i2c_driver mms_ts_driver = { + .probe = mms_ts_probe, + .remove = __devexit_p(mms_ts_remove), + .driver = { + .name = "mms_ts", +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) + .pm = &mms_ts_pm_ops, +#endif + }, + .id_table = mms_ts_id, +}; + +static int __init mms_ts_init(void) +{ + return i2c_add_driver(&mms_ts_driver); +} + +static void __exit mms_ts_exit(void) +{ + i2c_del_driver(&mms_ts_driver); +} + +module_init(mms_ts_init); +module_exit(mms_ts_exit); + +/* Module information */ +MODULE_DESCRIPTION("Touchscreen driver for Melfas MMS-series controllers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c index 212a338..130d950 100644 --- a/drivers/mfd/omap-usb-host.c +++ b/drivers/mfd/omap-usb-host.c @@ -762,7 +762,8 @@ static void omap_usbhs_init(struct device *dev) reg |= (OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN | OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN | OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN); - reg |= OMAP4_UHH_HOSTCONFIG_APP_START_CLK; + + /* Keep ENA_INCR_ALIGN = 0: Known to cause OCP delays */ reg &= ~OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN; if (is_omap_usbhs_rev1(omap)) { diff --git a/drivers/mfd/twl6030-irq.c b/drivers/mfd/twl6030-irq.c index aa77f02..9b3b987 100644 --- a/drivers/mfd/twl6030-irq.c +++ b/drivers/mfd/twl6030-irq.c @@ -92,6 +92,8 @@ static struct task_struct *task; static struct completion irq_event; static atomic_t twl6030_wakeirqs = ATOMIC_INIT(0); +static u8 vbatmin_hi_threshold; + static int twl6030_irq_pm_notifier(struct notifier_block *notifier, unsigned long pm_event, void *unused) { @@ -223,8 +225,15 @@ static irqreturn_t handle_twl6030_pih(int irq, void *devid) */ static irqreturn_t handle_twl6030_vlow(int irq, void *unused) { - pr_info("handle_twl6030_vlow: kernel_power_off()\n"); + pr_err("twl6030: BAT_VLOW interrupt; threshold=%dmV\n", + 2300 + (vbatmin_hi_threshold - 0b110) * 50); + +#if 1 /* temporary */ + WARN_ON_ONCE(1); +#else + pr_emerg("handle_twl6030_vlow: kernel_power_off()\n"); kernel_power_off(); +#endif return IRQ_HANDLED; } @@ -407,6 +416,9 @@ int twl6030_vlow_init(int vlow_irq) return status; } + twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &vbatmin_hi_threshold, + TWL6030_VBATMIN_HI_THRESHOLD); + /* install an irq handler for vlow */ status = request_threaded_irq(vlow_irq, NULL, handle_twl6030_vlow, IRQF_ONESHOT, diff --git a/drivers/mfd/twl6030-madc.c b/drivers/mfd/twl6030-madc.c index f537ba5..ebff3d7 100644 --- a/drivers/mfd/twl6030-madc.c +++ b/drivers/mfd/twl6030-madc.c @@ -264,6 +264,13 @@ static int __devinit twl6030_madc_probe(struct platform_device *pdev) madc, DEBUG_FOPS); wake_lock_init(&madc->wakelock, WAKE_LOCK_SUSPEND, "twl6030 adc"); twl6030_madc = madc; + + if (twl_i2c_write_u8(TWL_MODULE_MADC, TWL6030_MADC_TEMP1_EN | + TWL6030_MADC_SCALER_EN_CH2, + TWL6030_MADC_CTRL)) + dev_err(twl6030_madc->dev, "unable to write to register 0x%X\n", + TWL6030_MADC_CTRL); + return 0; } diff --git a/drivers/mfd/twl6040-codec.c b/drivers/mfd/twl6040-codec.c index 4633d70..68dec57 100644 --- a/drivers/mfd/twl6040-codec.c +++ b/drivers/mfd/twl6040-codec.c @@ -329,7 +329,7 @@ static int twl6040_power_up_completion(struct twl6040 *twl6040, do { gpio_set_value(twl6040->audpwron, 1); time_left = wait_for_completion_timeout(&twl6040->ready, - msecs_to_jiffies(144)); + msecs_to_jiffies(700)); if (!time_left) { intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID); if (!(intid & TWL6040_READYINT)) { diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 42ecf16..984aacb 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -313,6 +313,15 @@ config SGI_GRU_DEBUG 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 + 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 @@ -482,6 +491,16 @@ config BMP085 To compile this driver as a module, choose M here: the module will be called bmp085. +config BMP180 + tristate "BMP180 digital pressure sensor" + depends on I2C && SYSFS + help + If you say yes here you get support for the Bosch Sensortec + BMP180 digital pressure sensor. + + To compile this driver as a module, choose M here: the + module will be called bmp180. + config PCH_PHUB tristate "Intel EG20T PCH / OKI SEMICONDUCTOR IOH(ML7213/ML7223) PHUB" depends on PCI @@ -524,6 +543,15 @@ config APANIC_PLABEL If your platform uses a different flash partition label for storing crashdumps, enter it here. +config USB_SWITCH_FSA9480 + tristate "FSA9480 USB Switch" + depends on I2C + help + The FSA9480 is a USB port accessory detector and switch. + The FSA9480 is fully controlled using I2C and enables USB data, + stereo and mono audio, video, microphone and UART data to use + a common connector port. + config OMAP_DIE_TEMP_SENSOR bool "OMAP On-Die temp sensor support" depends on OMAP_TEMP_SENSOR @@ -531,12 +559,23 @@ config OMAP_DIE_TEMP_SENSOR Enabling this config will give support for the on-die temp sensor for the OMAP platform. +config LEDS_AN30259A + tristate "LED driver for panasonic AN30259A RGB LED" + depends on I2C + help + This option enables support for AN30259A RGB LED chips + accessed via the I2C bus. + AN30259A has three channel LED driver. By synchronous clock + function, combined operation is possible. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" +source "drivers/misc/inv_mpu/Kconfig" source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" +source "drivers/misc/modem_if/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index ff217f2..9b0ece8 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o obj-$(CONFIG_BMP085) += bmp085.o +obj-$(CONFIG_BMP180) += bmp180.o obj-$(CONFIG_ICS932S401) += ics932s401.o obj-$(CONFIG_LKDTM) += lkdtm.o obj-$(CONFIG_TIFM_CORE) += tifm_core.o @@ -19,6 +20,7 @@ obj-$(CONFIG_PHANTOM) += phantom.o obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o +obj-$(CONFIG_SAMSUNG_JACK) += sec_jack.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o obj-$(CONFIG_KGDB_TESTS) += kgdbts.o @@ -50,4 +52,8 @@ obj-y += carma/ obj-$(CONFIG_WL127X_RFKILL) += wl127x-rfkill.o obj-$(CONFIG_APANIC) += apanic.o obj-$(CONFIG_SENSORS_AK8975) += akm8975.o +obj-y += inv_mpu/ +obj-$(CONFIG_SEC_MODEM) += modem_if/ +obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o obj-$(CONFIG_OMAP_DIE_TEMP_SENSOR) += omap_temp_sensor.o +obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o diff --git a/drivers/misc/bmp180.c b/drivers/misc/bmp180.c new file mode 100755 index 0000000..867e5d9 --- /dev/null +++ b/drivers/misc/bmp180.c @@ -0,0 +1,644 @@ +/* + * Copyright (C) 2011 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 + */ + +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#define BMP180_DRV_NAME "bmp180" +#define DRIVER_VERSION "1.0" + +/* Register definitions */ +#define BMP180_TAKE_MEAS_REG 0xf4 +#define BMP180_READ_MEAS_REG_U 0xf6 +#define BMP180_READ_MEAS_REG_L 0xf7 +#define BMP180_READ_MEAS_REG_XL 0xf8 + +/* + * Bytes defined by the spec to take measurements + * Temperature will take 4.5ms before EOC + */ +#define BMP180_MEAS_TEMP 0x2e +/* 4.5ms wait for measurement */ +#define BMP180_MEAS_PRESS_OVERSAMP_0 0x34 +/* 7.5ms wait for measurement */ +#define BMP180_MEAS_PRESS_OVERSAMP_1 0x74 +/* 13.5ms wait for measurement */ +#define BMP180_MEAS_PRESS_OVERSAMP_2 0xb4 +/* 25.5ms wait for measurement */ +#define BMP180_MEAS_PRESS_OVERSAMP_3 0xf4 + +/* + * EEPROM registers each is a two byte value so there is + * an upper byte and a lower byte + */ +#define BMP180_EEPROM_AC1_U 0xaa +#define BMP180_EEPROM_AC1_L 0xab +#define BMP180_EEPROM_AC2_U 0xac +#define BMP180_EEPROM_AC2_L 0xad +#define BMP180_EEPROM_AC3_U 0xae +#define BMP180_EEPROM_AC3_L 0xaf +#define BMP180_EEPROM_AC4_U 0xb0 +#define BMP180_EEPROM_AC4_L 0xb1 +#define BMP180_EEPROM_AC5_U 0xb2 +#define BMP180_EEPROM_AC5_L 0xb3 +#define BMP180_EEPROM_AC6_U 0xb4 +#define BMP180_EEPROM_AC6_L 0xb5 +#define BMP180_EEPROM_B1_U 0xb6 +#define BMP180_EEPROM_B1_L 0xb7 +#define BMP180_EEPROM_B2_U 0xb8 +#define BMP180_EEPROM_B2_L 0xb9 +#define BMP180_EEPROM_MB_U 0xba +#define BMP180_EEPROM_MB_L 0xbb +#define BMP180_EEPROM_MC_U 0xbc +#define BMP180_EEPROM_MC_L 0xbd +#define BMP180_EEPROM_MD_U 0xbe +#define BMP180_EEPROM_MD_L 0xbf + +#define I2C_TRIES 5 +#define AUTO_INCREMENT 0x80 + +#define DELAY_LOWBOUND (50 * NSEC_PER_MSEC) +#define DELAY_DEFAULT (200 * NSEC_PER_MSEC) + +#define PRESSURE_MAX 125000 +#define PRESSURE_MIN 95000 +#define PRESSURE_FUZZ 5 +#define PRESSURE_FLAT 5 + +struct bmp180_eeprom_data { + s16 AC1, AC2, AC3; + u16 AC4, AC5, AC6; + s16 B1, B2; + s16 MB, MC, MD; +}; + +struct bmp180_data { + struct i2c_client *client; + struct mutex lock; + struct workqueue_struct *wq; + struct work_struct work_pressure; + struct input_dev *input_dev; + struct hrtimer timer; + ktime_t poll_delay; + u8 oversampling_rate; + struct bmp180_eeprom_data bmp180_eeprom_vals; + bool enabled; + bool on_before_suspend; +}; + +static int bmp180_i2c_read(const struct i2c_client *client, u8 cmd, + u8 *buf, int len) +{ + int err; + int tries = 0; + + do { + err = i2c_smbus_read_i2c_block_data(client, cmd, len, buf); + if (err == len) + return 0; + } while (++tries < I2C_TRIES); + + return err; +} + +static int bmp180_i2c_write(const struct i2c_client *client, u8 cmd, u8 data) +{ + int err; + int tries = 0; + + do { + err = i2c_smbus_write_byte_data(client, cmd, data); + if (!err) + return 0; + } while (++tries < I2C_TRIES); + + return err; +} + +static void bmp180_enable(struct bmp180_data *barom) +{ + pr_debug("%s: bmp180_enable\n", __func__); + if (!barom->enabled) { + barom->enabled = true; + pr_debug("%s: start timer\n", __func__); + hrtimer_start(&barom->timer, barom->poll_delay, + HRTIMER_MODE_REL); + } +} + +static void bmp180_disable(struct bmp180_data *barom) +{ + pr_debug("%s: bmp180_disable\n", __func__); + if (barom->enabled) { + barom->enabled = false; + pr_debug("%s: stop timer\n", __func__); + hrtimer_cancel(&barom->timer); + cancel_work_sync(&barom->work_pressure); + } +} + +static int bmp180_get_raw_temperature(struct bmp180_data *barom, + u16 *raw_temperature) +{ + int err; + u16 buf; + + pr_debug("%s: read uncompensated temperature value\n", __func__); + err = bmp180_i2c_write(barom->client, BMP180_TAKE_MEAS_REG, + BMP180_MEAS_TEMP); + if (err) { + pr_err("%s: can't write BMP180_TAKE_MEAS_REG\n", __func__); + return err; + } + + msleep(5); + + err = bmp180_i2c_read(barom->client, BMP180_READ_MEAS_REG_U, + (u8 *)&buf, 2); + if (err) { + pr_err("%s: Fail to read uncompensated temperature\n", + __func__); + return err; + } + *raw_temperature = be16_to_cpu(buf); + pr_debug("%s: uncompensated temperature: %d\n", + __func__, *raw_temperature); + return err; +} + +static int bmp180_get_raw_pressure(struct bmp180_data *barom, + u32 *raw_pressure) +{ + int err; + u32 buf = 0; + + pr_debug("%s: read uncompensated pressure value\n", __func__); + + err = bmp180_i2c_write(barom->client, BMP180_TAKE_MEAS_REG, + BMP180_MEAS_PRESS_OVERSAMP_0 | + (barom->oversampling_rate << 6)); + if (err) { + pr_err("%s: can't write BMP180_TAKE_MEAS_REG\n", __func__); + return err; + } + + msleep(2+(3 << barom->oversampling_rate)); + + err = bmp180_i2c_read(barom->client, BMP180_READ_MEAS_REG_U, + ((u8 *)&buf)+1, 3); + if (err) { + pr_err("%s: Fail to read uncompensated pressure\n", __func__); + return err; + } + + *raw_pressure = be32_to_cpu(buf); + *raw_pressure >>= (8 - barom->oversampling_rate); + pr_debug("%s: uncompensated pressure: %d\n", + __func__, *raw_pressure); + + return err; +} + +static void bmp180_get_pressure_data(struct work_struct *work) +{ + u16 raw_temperature; + u32 raw_pressure; + long x1, x2, x3, b3, b5, b6; + unsigned long b4, b7; + long p; + int pressure; + int err; + + struct bmp180_data *barom = + container_of(work, struct bmp180_data, work_pressure); + + if (bmp180_get_raw_temperature(barom, &raw_temperature)) { + pr_err("%s: can't read uncompensated temperature\n", __func__); + return; + } + + if (bmp180_get_raw_pressure(barom, &raw_pressure)) { + pr_err("%s: Fail to read uncompensated pressure\n", __func__); + return; + } + + x1 = ((raw_temperature - barom->bmp180_eeprom_vals.AC6) * + barom->bmp180_eeprom_vals.AC5) >> 15; + x2 = (barom->bmp180_eeprom_vals.MC << 11) / + (x1 + barom->bmp180_eeprom_vals.MD); + b5 = x1 + x2; + + b6 = (b5 - 4000); + x1 = (barom->bmp180_eeprom_vals.B2 * ((b6 * b6) >> 12)) >> 11; + x2 = (barom->bmp180_eeprom_vals.AC2 * b6) >> 11; + x3 = x1 + x2; + b3 = (((((long)barom->bmp180_eeprom_vals.AC1) * 4 + + x3) << barom->oversampling_rate) + 2) >> 2; + x1 = (barom->bmp180_eeprom_vals.AC3 * b6) >> 13; + x2 = (barom->bmp180_eeprom_vals.B1 * (b6 * b6 >> 12)) >> 16; + x3 = ((x1 + x2) + 2) >> 2; + b4 = (barom->bmp180_eeprom_vals.AC4 * + (unsigned long)(x3 + 32768)) >> 15; + b7 = ((unsigned long)raw_pressure - b3) * + (50000 >> barom->oversampling_rate); + if (b7 < 0x80000000) + p = (b7 * 2) / b4; + else + p = (b7 / b4) * 2; + + x1 = (p >> 8) * (p >> 8); + x1 = (x1 * 3038) >> 16; + x2 = (-7357 * p) >> 16; + pressure = p + ((x1 + x2 + 3791) >> 4); + pr_debug("%s: calibrated pressure: %d\n", + __func__, pressure); + + input_report_abs(barom->input_dev, ABS_PRESSURE, pressure); + input_sync(barom->input_dev); + + return; +} + +static int bmp180_input_init(struct bmp180_data *barom) +{ + int err; + + pr_debug("%s: enter\n", __func__); + barom->input_dev = input_allocate_device(); + if (!barom->input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + return -ENOMEM; + } + input_set_drvdata(barom->input_dev, barom); + barom->input_dev->name = "barometer"; + input_set_capability(barom->input_dev, EV_ABS, ABS_PRESSURE); + + /* Need to define the correct min and max */ + input_set_abs_params(barom->input_dev, ABS_PRESSURE, + PRESSURE_MIN, PRESSURE_MAX, + PRESSURE_FUZZ, PRESSURE_FLAT); + + pr_debug("%s: registering barometer input device\n", __func__); + + err = input_register_device(barom->input_dev); + if (err) { + pr_err("%s: unable to register input polled device %s\n", + __func__, barom->input_dev->name); + goto err_register_device; + } + + goto done; + +err_register_device: + input_free_device(barom->input_dev); +done: + return err; +} + +static void bmp180_input_cleanup(struct bmp180_data *barom) +{ + input_unregister_device(barom->input_dev); + input_free_device(barom->input_dev); +} + +static int bmp180_read_store_eeprom_val(struct bmp180_data *barom) +{ + int err; + u16 buf[11]; + + err = bmp180_i2c_read(barom->client, BMP180_EEPROM_AC1_U, + (u8 *)buf, 22); + if (err) { + pr_err("%s: Cannot read EEPROM values\n", __func__); + return err; + } + barom->bmp180_eeprom_vals.AC1 = be16_to_cpu(buf[0]); + barom->bmp180_eeprom_vals.AC2 = be16_to_cpu(buf[1]); + barom->bmp180_eeprom_vals.AC3 = be16_to_cpu(buf[2]); + barom->bmp180_eeprom_vals.AC4 = be16_to_cpu(buf[3]); + barom->bmp180_eeprom_vals.AC5 = be16_to_cpu(buf[4]); + barom->bmp180_eeprom_vals.AC6 = be16_to_cpu(buf[5]); + barom->bmp180_eeprom_vals.B1 = be16_to_cpu(buf[6]); + barom->bmp180_eeprom_vals.B2 = be16_to_cpu(buf[7]); + barom->bmp180_eeprom_vals.MB = be16_to_cpu(buf[8]); + barom->bmp180_eeprom_vals.MC = be16_to_cpu(buf[9]); + barom->bmp180_eeprom_vals.MD = be16_to_cpu(buf[10]); + return 0; +} + +static enum hrtimer_restart bmp180_timer_func(struct hrtimer *timer) +{ + struct bmp180_data *barom = container_of(timer, + struct bmp180_data, timer); + + pr_debug("%s: start\n", __func__); + queue_work(barom->wq, &barom->work_pressure); + hrtimer_forward_now(&barom->timer, barom->poll_delay); + return HRTIMER_RESTART; +} + +static ssize_t bmp180_poll_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bmp180_data *barom = dev_get_drvdata(dev); + + return sprintf(buf, "%lld\n", + ktime_to_ns(barom->poll_delay)); +} + +static ssize_t bmp180_poll_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err; + int64_t new_delay; + struct bmp180_data *barom = dev_get_drvdata(dev); + + err = strict_strtoll(buf, 10, &new_delay); + if (err < 0) + return err; + + pr_debug("%s: new delay = %lldns, old delay = %lldns\n", + __func__, new_delay, ktime_to_ns(barom->poll_delay)); + + if (new_delay < DELAY_LOWBOUND) + new_delay = DELAY_LOWBOUND; + + mutex_lock(&barom->lock); + if (new_delay != ktime_to_ns(barom->poll_delay)) + barom->poll_delay = ns_to_ktime(new_delay); + + mutex_unlock(&barom->lock); + + return size; +} + +static ssize_t bmp180_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bmp180_data *barom = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", barom->enabled); +} + +static ssize_t bmp180_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + bool new_value; + struct bmp180_data *barom = dev_get_drvdata(dev); + + pr_debug("%s: enable %s\n", __func__, buf); + + if (sysfs_streq(buf, "1")) { + new_value = true; + } else if (sysfs_streq(buf, "0")) { + new_value = false; + } else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + pr_debug("%s: new_value = %d, old state = %d\n", + __func__, new_value, barom->enabled); + + mutex_lock(&barom->lock); + if (new_value) + bmp180_enable(barom); + else + bmp180_disable(barom); + + mutex_unlock(&barom->lock); + + return size; +} + +static ssize_t bmp180_oversampling_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bmp180_data *barom = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", barom->oversampling_rate); +} + +static ssize_t bmp180_oversampling_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bmp180_data *barom = dev_get_drvdata(dev); + + unsigned long oversampling; + int success = strict_strtoul(buf, 10, &oversampling); + if (success == 0) { + if (oversampling > 3) + oversampling = 3; + barom->oversampling_rate = oversampling; + return count; + } + return success; +} + +static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP, + bmp180_poll_delay_show, bmp180_poll_delay_store); + +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, + bmp180_enable_show, bmp180_enable_store); + +static DEVICE_ATTR(oversampling, S_IWUSR | S_IRUGO, + bmp180_oversampling_show, bmp180_oversampling_store); + +static struct attribute *bmp180_sysfs_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_poll_delay.attr, + &dev_attr_oversampling.attr, + NULL +}; + +static struct attribute_group bmp180_attribute_group = { + .attrs = bmp180_sysfs_attrs, +}; + +static int __devinit bmp180_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + struct bmp180_data *barom; + + pr_debug("%s: enter\n", __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("%s: client not i2c capable\n", __func__); + return -EIO; + } + + barom = kzalloc(sizeof(*barom), GFP_KERNEL); + if (!barom) { + pr_err("%s: failed to allocate memory for module\n", __func__); + return -ENOMEM; + } + + mutex_init(&barom->lock); + barom->client = client; + + i2c_set_clientdata(client, barom); + + err = bmp180_read_store_eeprom_val(barom); + if (err) { + pr_err("%s: Reading the EEPROM failed\n", __func__); + err = -ENODEV; + goto err_read_eeprom; + } + + hrtimer_init(&barom->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + barom->poll_delay = ns_to_ktime(DELAY_DEFAULT); + barom->timer.function = bmp180_timer_func; + + barom->wq = alloc_workqueue("bmp180_wq", + WQ_UNBOUND | WQ_RESCUER, 1); + if (!barom->wq) { + err = -ENOMEM; + pr_err("%s: could not create workqueue\n", __func__); + goto err_create_workqueue; + } + + INIT_WORK(&barom->work_pressure, bmp180_get_pressure_data); + + err = bmp180_input_init(barom); + if (err) { + pr_err("%s: could not create input device\n", __func__); + goto err_input_init; + } + err = sysfs_create_group(&barom->input_dev->dev.kobj, + &bmp180_attribute_group); + if (err) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_sysfs_create_group; + } + + goto done; + +err_sysfs_create_group: + bmp180_input_cleanup(barom); +err_input_init: + destroy_workqueue(barom->wq); +err_create_workqueue: +err_read_eeprom: + mutex_destroy(&barom->lock); + kfree(barom); +done: + return err; +} + +static int __devexit bmp180_remove(struct i2c_client *client) +{ + /* TO DO: revisit ordering here once _probe order is finalized */ + struct bmp180_data *barom = i2c_get_clientdata(client); + + pr_debug("%s: bmp180_remove +\n", __func__); + sysfs_remove_group(&barom->input_dev->dev.kobj, + &bmp180_attribute_group); + + bmp180_disable(barom); + + bmp180_input_cleanup(barom); + + destroy_workqueue(barom->wq); + + mutex_destroy(&barom->lock); + kfree(barom); + + pr_debug("%s: bmp180_remove -\n", __func__); + return 0; +} + +static int bmp180_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bmp180_data *barom = i2c_get_clientdata(client); + pr_debug("%s: on_before_suspend %d\n", + __func__, barom->on_before_suspend); + + if (barom->on_before_suspend) + bmp180_enable(barom); + return 0; +} + +static int bmp180_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bmp180_data *barom = i2c_get_clientdata(client); + + barom->on_before_suspend = barom->enabled; + pr_debug("%s: on_before_suspend %d\n", + __func__, barom->on_before_suspend); + bmp180_disable(barom); + return 0; +} + +static const struct i2c_device_id bmp180_id[] = { + {BMP180_DRV_NAME, 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, bmp180_id); +static const struct dev_pm_ops bmp180_pm_ops = { + .suspend = bmp180_suspend, + .resume = bmp180_resume, +}; + +static struct i2c_driver bmp180_driver = { + .driver = { + .name = BMP180_DRV_NAME, + .owner = THIS_MODULE, + .pm = &bmp180_pm_ops, + }, + .probe = bmp180_probe, + .remove = __devexit_p(bmp180_remove), + .id_table = bmp180_id, +}; + +static int __init bmp180_init(void) +{ + pr_debug("%s: _init\n", __func__); + return i2c_add_driver(&bmp180_driver); +} + +static void __exit bmp180_exit(void) +{ + pr_debug("%s: _exit +\n", __func__); + i2c_del_driver(&bmp180_driver); + pr_debug("%s: _exit -\n", __func__); + return; +} + +MODULE_AUTHOR("Hyoung Wook Ham <hwham@sta.samsung.com>"); +MODULE_DESCRIPTION("BMP180 Pressure sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRIVER_VERSION); + +module_init(bmp180_init); +module_exit(bmp180_exit); diff --git a/drivers/misc/fsa9480.c b/drivers/misc/fsa9480.c new file mode 100755 index 0000000..92382ea --- /dev/null +++ b/drivers/misc/fsa9480.c @@ -0,0 +1,1067 @@ +/* + * driver/misc/fsa9480.c - FSA9480 micro USB switch device driver + * + * Copyright (C) 2010 Samsung Electronics + * Minkyu Kang <mk7.kang@samsung.com> + * Wonguk Jeong <wonguk.jeong@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 + * + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_data/fsa9480.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/usb/otg_id.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> + +#define DEBUG_DUMP_REGISTERS + +/* FSA9480 I2C registers */ +#define FSA9480_REG_DEVID 0x01 +#define FSA9480_REG_CTRL 0x02 +#define FSA9480_REG_INT1 0x03 +#define FSA9480_REG_INT2 0x04 +#define FSA9480_REG_INT1_MASK 0x05 +#define FSA9480_REG_INT2_MASK 0x06 +#define FSA9480_REG_ADC 0x07 +#define FSA9480_REG_TIMING1 0x08 +#define FSA9480_REG_TIMING2 0x09 +#define FSA9480_REG_DEV_T1 0x0a +#define FSA9480_REG_DEV_T2 0x0b +#define FSA9480_REG_BTN1 0x0c +#define FSA9480_REG_BTN2 0x0d +#define FSA9480_REG_CK 0x0e +#define FSA9480_REG_CK_INT1 0x0f +#define FSA9480_REG_CK_INT2 0x10 +#define FSA9480_REG_CK_INTMASK1 0x11 +#define FSA9480_REG_CK_INTMASK2 0x12 +#define FSA9480_REG_MANSW1 0x13 +#define FSA9480_REG_MANSW2 0x14 +#define FSA9480_REG_ANALOG_TEST 0x15 +#define FSA9480_REG_SCAN_TEST 0x16 +#define FSA9480_REG_DAC_OVERRIDE_1 0x17 +#define FSA9480_REG_DAC_OVERRIDE_2 0x18 +#define FSA9480_REG_VIDEO_DETECT 0x19 +#define FSA9480_REG_CK_PULSE_WIDTH 0x1A +#define FSA9480_REG_MANOVERRIDE1 0x1B +#define FSA9480_REG_STATUS1 0x1C +#define FSA9480_REG_STATUS2 0x1D +#define FSA9480_REG_FUSE1 0x1E + +/* Control */ +#define CON_SWITCH_OPEN (1 << 4) +#define CON_RAW_DATA (1 << 3) +#define CON_MANUAL_SW (1 << 2) +#define CON_WAIT (1 << 1) +#define CON_INT_MASK (1 << 0) +#define CON_MASK (CON_SWITCH_OPEN | CON_RAW_DATA | \ + CON_MANUAL_SW | CON_WAIT) + +/* we always read these as a word */ +/* Device Type 2 */ +#define DEV_AV (1 << 14) +#define DEV_TTY (1 << 13) +#define DEV_PPD (1 << 12) +#define DEV_JIG_UART_OFF (1 << 11) +#define DEV_JIG_UART_ON (1 << 10) +#define DEV_JIG_USB_OFF (1 << 9) +#define DEV_JIG_USB_ON (1 << 8) +/* Device Type 1 */ +#define DEV_USB_OTG (1 << 7) +#define DEV_DEDICATED_CHG (1 << 6) +#define DEV_USB_CHG (1 << 5) +#define DEV_CAR_KIT (1 << 4) +#define DEV_UART (1 << 3) +#define DEV_USB (1 << 2) +#define DEV_AUDIO_2 (1 << 1) +#define DEV_AUDIO_1 (1 << 0) + +#define DEV_USB_MASK (DEV_USB | DEV_JIG_USB_OFF | DEV_JIG_USB_ON) +#define DEV_UART_MASK (DEV_UART | DEV_JIG_UART_OFF) +#define DEV_JIG_MASK (DEV_JIG_USB_OFF | DEV_JIG_USB_ON | \ + DEV_JIG_UART_OFF | DEV_JIG_UART_ON) +#define DEV_CHARGER_MASK (DEV_DEDICATED_CHG | DEV_USB_CHG | DEV_CAR_KIT) + +/* + * Manual Switch + * D- [7:5] / D+ [4:2] + * 000: Open all / 001: USB / 010: AUDIO / 011: UART / 100: V_AUDIO + */ +#define SW_VAUDIO ((4 << 5) | (4 << 2)) +#define SW_UART ((3 << 5) | (3 << 2)) +#define SW_AUDIO ((2 << 5) | (2 << 2)) +#define SW_DHOST ((1 << 5) | (1 << 2)) +#define SW_AUTO ((0 << 5) | (0 << 2)) + +/* Interrupt Mask */ +#define INT_STUCK_KEY_RCV (1 << 12) +#define INT_STUCK_KEY (1 << 11) +#define INT_ADC_CHANGE (1 << 10) +#define INT_RESERVE_ATTACH (1 << 9) +#define INT_AV_CHARGING (1 << 8) +#define INT_OVP_OCP_DIS (1 << 7) +#define INT_OCP_EN (1 << 6) +#define INT_OVP_EN (1 << 5) +#define INT_LKR (1 << 4) +#define INT_LKP (1 << 3) +#define INT_KP (1 << 2) +#define INT_DETACH (1 << 1) +#define INT_ATTACH (1 << 0) + +static const unsigned int adc_timing[] = { + 50, /* ms */ + 100, + 150, + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900, + 1000 +}; + +static const char *device_names[] = { + [FSA9480_DETECT_NONE] = "unknown/none", + [FSA9480_DETECT_USB] = "usb-peripheral", + [FSA9480_DETECT_USB_HOST] = "usb-host", + [FSA9480_DETECT_CHARGER] = "charger", + [FSA9480_DETECT_JIG] = "jig", + [FSA9480_DETECT_UART] = "uart", + [FSA9480_DETECT_AV_365K] = "av-365k", + [FSA9480_DETECT_AV_365K_CHARGER] = "av-365k-charger", +}; + +struct usbsw_nb_info { + struct otg_id_notifier_block otg_id_nb; + struct fsa9480_detect_set *detect_set; + + struct fsa9480_usbsw *usbsw; +}; + +struct fsa9480_usbsw { + struct i2c_client *client; + struct fsa9480_platform_data *pdata; + int mansw; + u32 curr_dev; + struct mutex lock; + u16 intr_mask; + u8 timing; + int external_id_irq; + bool wake_enabled; +#if defined(CONFIG_DEBUG_FS) && defined(DEBUG_DUMP_REGISTERS) + struct dentry *debug_dir; +#endif + + int num_notifiers; + struct usbsw_nb_info notifiers[0]; +}; +#define xceiv_to_fsa(x) container_of((x), struct fsa9480_usbsw, otg) + +#if defined(CONFIG_DEBUG_FS) && defined(DEBUG_DUMP_REGISTERS) + +#define DUMP_FSA9480_REG(client, m, x) ({ \ + int __val; \ + __val = i2c_smbus_read_byte_data((client), FSA9480_REG_##x); \ + seq_printf((m), "%s = 0x%02x\n", #x, __val); \ + __val; \ +}) + +static int fsa9480_show_registers(struct seq_file *m, void *p) +{ + struct fsa9480_usbsw *usbsw = m->private; + + DUMP_FSA9480_REG(usbsw->client, m, DEVID); + DUMP_FSA9480_REG(usbsw->client, m, CTRL); + DUMP_FSA9480_REG(usbsw->client, m, INT1); + DUMP_FSA9480_REG(usbsw->client, m, INT2); + DUMP_FSA9480_REG(usbsw->client, m, INT1_MASK); + DUMP_FSA9480_REG(usbsw->client, m, INT2_MASK); + DUMP_FSA9480_REG(usbsw->client, m, ADC); + DUMP_FSA9480_REG(usbsw->client, m, TIMING1); + DUMP_FSA9480_REG(usbsw->client, m, TIMING2); + DUMP_FSA9480_REG(usbsw->client, m, DEV_T1); + DUMP_FSA9480_REG(usbsw->client, m, DEV_T2); + DUMP_FSA9480_REG(usbsw->client, m, BTN1); + DUMP_FSA9480_REG(usbsw->client, m, BTN2); + DUMP_FSA9480_REG(usbsw->client, m, CK); + DUMP_FSA9480_REG(usbsw->client, m, CK_INT1); + DUMP_FSA9480_REG(usbsw->client, m, CK_INT2); + DUMP_FSA9480_REG(usbsw->client, m, CK_INTMASK1); + DUMP_FSA9480_REG(usbsw->client, m, CK_INTMASK2); + DUMP_FSA9480_REG(usbsw->client, m, MANSW1); + DUMP_FSA9480_REG(usbsw->client, m, MANSW2); + DUMP_FSA9480_REG(usbsw->client, m, BTN1); + DUMP_FSA9480_REG(usbsw->client, m, BTN2); + DUMP_FSA9480_REG(usbsw->client, m, ANALOG_TEST); + DUMP_FSA9480_REG(usbsw->client, m, SCAN_TEST); + DUMP_FSA9480_REG(usbsw->client, m, DAC_OVERRIDE_1); + DUMP_FSA9480_REG(usbsw->client, m, DAC_OVERRIDE_2); + DUMP_FSA9480_REG(usbsw->client, m, VIDEO_DETECT); + DUMP_FSA9480_REG(usbsw->client, m, CK_PULSE_WIDTH); + DUMP_FSA9480_REG(usbsw->client, m, MANOVERRIDE1); + DUMP_FSA9480_REG(usbsw->client, m, STATUS1); + DUMP_FSA9480_REG(usbsw->client, m, STATUS2); + DUMP_FSA9480_REG(usbsw->client, m, FUSE1); + + return 0; +} + +static int fsa9480_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, fsa9480_show_registers, inode->i_private); +} + +static const struct file_operations fsa9480_regs_fops = { + .open = fsa9480_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +static ssize_t fsa9480_show_control(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev); + struct i2c_client *client = usbsw->client; + s32 value; + + value = i2c_smbus_read_byte_data(client, FSA9480_REG_CTRL); + if (value < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, value); + return (ssize_t)value; + } + + return sprintf(buf, "%02x\n", value); +} + +static ssize_t fsa9480_show_device_type(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev); + struct i2c_client *client = usbsw->client; + s32 value; + + value = i2c_smbus_read_word_data(client, FSA9480_REG_DEV_T1); + if (value < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, value); + return (ssize_t)value; + } + + return sprintf(buf, "%04x\n", value); +} + +static ssize_t fsa9480_show_manualsw(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev); + struct i2c_client *client = usbsw->client; + s32 value; + + value = i2c_smbus_read_byte_data(client, FSA9480_REG_MANSW1); + if (value < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, value); + return (ssize_t)value; + } + + if (value == SW_VAUDIO) + return sprintf(buf, "VAUDIO\n"); + else if (value == SW_UART) + return sprintf(buf, "UART\n"); + else if (value == SW_AUDIO) + return sprintf(buf, "AUDIO\n"); + else if (value == SW_DHOST) + return sprintf(buf, "DHOST\n"); + else if (value == SW_AUTO) + return sprintf(buf, "AUTO\n"); + else + return sprintf(buf, "%x", value); +} + +static ssize_t fsa9480_set_manualsw(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev); + struct i2c_client *client = usbsw->client; + s32 value; + unsigned int path = 0; + int ret; + + value = i2c_smbus_read_byte_data(client, FSA9480_REG_CTRL); + if (value < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, value); + return (ssize_t)value; + } + + if ((value & ~CON_MANUAL_SW) != + (CON_SWITCH_OPEN | CON_RAW_DATA | CON_WAIT)) + return -EINVAL; + + if (!strncmp(buf, "VAUDIO", 6)) { + path = SW_VAUDIO; + value &= ~CON_MANUAL_SW; + } else if (!strncmp(buf, "UART", 4)) { + path = SW_UART; + value &= ~CON_MANUAL_SW; + } else if (!strncmp(buf, "AUDIO", 5)) { + path = SW_AUDIO; + value &= ~CON_MANUAL_SW; + } else if (!strncmp(buf, "DHOST", 5)) { + path = SW_DHOST; + value &= ~CON_MANUAL_SW; + } else if (!strncmp(buf, "AUTO", 4)) { + path = SW_AUTO; + value |= CON_MANUAL_SW; + } else { + dev_err(dev, "Wrong command\n"); + return -EINVAL; + } + + usbsw->mansw = path; + + ret = i2c_smbus_write_byte_data(client, FSA9480_REG_MANSW1, path); + if (ret < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + return (ssize_t)value; + } + + ret = i2c_smbus_write_byte_data(client, FSA9480_REG_CTRL, value); + if (ret < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + return (ssize_t)value; + } + + return count; +} + +static DEVICE_ATTR(control, S_IRUGO, fsa9480_show_control, NULL); +static DEVICE_ATTR(device_type, S_IRUGO, fsa9480_show_device_type, NULL); +static DEVICE_ATTR(switch, S_IRUGO | S_IWUSR, + fsa9480_show_manualsw, fsa9480_set_manualsw); + +static struct attribute *fsa9480_attributes[] = { + &dev_attr_control.attr, + &dev_attr_device_type.attr, + &dev_attr_switch.attr, + NULL +}; + +static const struct attribute_group fsa9480_group = { + .attrs = fsa9480_attributes, +}; + +static int fsa9480_reg_init(struct fsa9480_usbsw *usbsw) +{ + struct i2c_client *client = usbsw->client; + unsigned int ctrl = CON_MASK; + s32 ret; + + ret = i2c_smbus_write_word_data(client, FSA9480_REG_INT1_MASK, + usbsw->intr_mask); + if (ret < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + return ret; + } + + /* mask all car kit interrupts */ + ret = i2c_smbus_write_word_data(client, FSA9480_REG_CK_INTMASK1, + 0x07ff); + if (ret < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + return ret; + } + + ret = i2c_smbus_write_byte_data(client, FSA9480_REG_TIMING1, + usbsw->timing); + if (ret < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + return ret; + } + + ret = i2c_smbus_write_byte_data(client, FSA9480_REG_MANSW1, + usbsw->mansw); + if (ret < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + return ret; + } + + if (usbsw->mansw) + ctrl &= ~CON_MANUAL_SW; /* Manual Switching Mode */ + + ret = i2c_smbus_write_byte_data(client, FSA9480_REG_CTRL, ctrl); + if (ret < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int fsa9480_reset(struct fsa9480_usbsw *usbsw) +{ + struct i2c_client *client = usbsw->client; + s32 ret; + + /* soft reset to re-initialize the fsa, and re-do detection */ + ret = i2c_smbus_write_byte_data(client, FSA9480_REG_MANOVERRIDE1, 1); + if (ret < 0) { + dev_err(&client->dev, "cannot soft reset, err %d\n", ret); + return ret; + } + return 0; +} + +static void _detected(struct fsa9480_usbsw *usbsw, int device) +{ + dev_info(&usbsw->client->dev, + "cable detect change, from '%s' to '%s'\n", + device_names[usbsw->curr_dev], device_names[device]); + usbsw->curr_dev = device; + usbsw->pdata->detected(usbsw->curr_dev); +} + +static int fsa9480_detect_callback(struct otg_id_notifier_block *nb) +{ + struct usbsw_nb_info *nb_info = + container_of(nb, struct usbsw_nb_info, otg_id_nb); + struct fsa9480_usbsw *usbsw = nb_info->usbsw; + struct i2c_client *client = usbsw->client; + u16 dev_type; + u8 adc_val; + u32 prev_dev; + int max_events = 100; + + mutex_lock(&usbsw->lock); + + usbsw->pdata->enable(true); + + /* the fsa could have queued up a few events if we haven't processed + * them promptly + */ + while (max_events-- > 0) { + s32 ret = i2c_smbus_read_word_data(client, FSA9480_REG_INT1); + if (!ret) + break; + } + if (!max_events) + dev_warn(&client->dev, "too many events. fsa hosed?\n"); + + /* fsa may take some time to update the dev_type reg after reading + * the int reg. + */ + usleep_range(200, 300); + + dev_type = i2c_smbus_read_word_data(client, FSA9480_REG_DEV_T1); + adc_val = i2c_smbus_read_byte_data(client, FSA9480_REG_ADC); + if (dev_type < 0 || adc_val < 0) { + dev_err(&client->dev, "error reading adc/dev_type regs\n"); + goto err; + } + + dev_dbg(&client->dev, "trying detect (prio=%d): type=%x adc=%x\n", + nb_info->detect_set->prio, dev_type, adc_val); + + prev_dev = usbsw->curr_dev; + + if (dev_type & DEV_USB_MASK) { + /* If there is an external id signal then verify that the ID + * signal is floating. If the ID signal is pulled low then this + * may be a cable misidentification. This can occur if the + * board allows for the ID signal to be redirected away from the + * FSA9480. If the ID signal is not visible to the FSA9480 and + * VBUS is present then the cable will be identified as a USB + * peripheral cable. + * + * In the event of a cable misidentification the FSA9480 chip + * will be reset to force a new detection cycle. + */ + if (usbsw->pdata->external_id >= 0 && + !gpio_get_value(usbsw->pdata->external_id)) { + dev_info(&usbsw->client->dev, "Cable misidentified as " + "a USB-peripheral cable, resetting the " + "FSA9480\n"); + fsa9480_reset(usbsw); + goto handled; + } + + /* usb peripheral mode */ + if (!(nb_info->detect_set->mask & FSA9480_DETECT_USB)) + goto unhandled; + _detected(usbsw, FSA9480_DETECT_USB); + goto handled; + } else if (dev_type & DEV_UART_MASK) { + if (!(nb_info->detect_set->mask & FSA9480_DETECT_UART)) + goto unhandled; + _detected(usbsw, FSA9480_DETECT_UART); + goto handled; + } else if (dev_type & DEV_CHARGER_MASK) { + if (!(nb_info->detect_set->mask & FSA9480_DETECT_CHARGER)) + goto unhandled; + _detected(usbsw, FSA9480_DETECT_CHARGER); + goto handled; + } else if (dev_type & DEV_JIG_MASK) { + if (!(nb_info->detect_set->mask & FSA9480_DETECT_JIG)) + goto unhandled; + _detected(usbsw, FSA9480_DETECT_JIG); + goto handled; + } else if (dev_type & DEV_USB_OTG) { + if (!(nb_info->detect_set->mask & FSA9480_DETECT_USB_HOST)) + goto unhandled; + _detected(usbsw, FSA9480_DETECT_USB_HOST); + + mutex_unlock(&usbsw->lock); + + /* Enable the external ID interrupt to detect the detach of the + * USB host cable since the FSA9480 is unable to detect it. + * The FSA9480 takes a while pulling that line down, so a sleep + * is needed. + */ + usleep_range(10000, 11000); + enable_irq(usbsw->external_id_irq); + return OTG_ID_HANDLED; + } else if (dev_type & DEV_AV) { + /* There are two ID resistances, 1K and 365K that the FSA9480 + * will resolve to the A/V Cable device type. The ADC value can + * be used to tell the difference between the two. + */ + if (adc_val == 0x1a) { + /* Delay to allow VBUS to be seen, if present. There's + * a possibility that we won't charge if it takes + * longer than this for VBUS to be present. */ + msleep(10); + if ((nb_info->detect_set->mask & + FSA9480_DETECT_AV_365K_CHARGER) && + usbsw->pdata->vbus_present()) { + _detected(usbsw, + FSA9480_DETECT_AV_365K_CHARGER); + /* The FSA9480 will not interrupt when a USB or + * charger cable is disconnected from the dock + * so we must detect loss of VBUS via an + * external interrupt. */ + enable_irq(usbsw->pdata->external_vbus_irq); + mutex_unlock(&usbsw->lock); + return OTG_ID_HANDLED; + } else if ((nb_info->detect_set->mask & + FSA9480_DETECT_AV_365K) && + !usbsw->pdata->vbus_present()) { + _detected(usbsw, FSA9480_DETECT_AV_365K); + } else { + goto unhandled; + } + goto handled; + } + } else if (dev_type == 0) { + usbsw->curr_dev = 0; + dev_info(&usbsw->client->dev, + "nothing attached, keeping ownership of port\n"); + goto handled; + } + +unhandled: + usbsw->curr_dev = 0; + if (nb_info->detect_set->fallback) { + /* In this case, we are the last resort and we are supposed to + * keep ownership of ID/D+/D- to monitor them for changes. + * This can happen when no one else + * detected a valid device and it is not one of the above. + */ + + dev_info(&usbsw->client->dev, + "nothing known attached, keeping ownership of port\n"); + goto handled; + } + + +err: + usbsw->pdata->enable(false); + mutex_unlock(&usbsw->lock); + return OTG_ID_UNHANDLED; + +handled: + BUG_ON((usbsw->curr_dev == FSA9480_DETECT_NONE) && + (prev_dev != FSA9480_DETECT_NONE)); + + mutex_unlock(&usbsw->lock); + enable_irq_wake(usbsw->client->irq); + enable_irq(usbsw->client->irq); + + return OTG_ID_HANDLED; +} + +static int fsa9480_proxy_wait_callback(struct otg_id_notifier_block *nb) +{ + struct usbsw_nb_info *nb_info = + container_of(nb, struct usbsw_nb_info, otg_id_nb); + struct fsa9480_usbsw *usbsw = nb_info->usbsw; + + dev_info(&usbsw->client->dev, "taking proxy ownership of port\n"); + + usbsw->pdata->enable(true); + enable_irq_wake(usbsw->client->irq); + enable_irq(usbsw->client->irq); + + return OTG_ID_HANDLED; +} + +static void fsa9480_cancel_callback(struct otg_id_notifier_block *nb) +{ + struct usbsw_nb_info *nb_info = + container_of(nb, struct usbsw_nb_info, otg_id_nb); + struct fsa9480_usbsw *usbsw = nb_info->usbsw; + struct i2c_client *client = usbsw->client; + + dev_info(&client->dev, "cancelling"); +} + +static irqreturn_t fsa9480_irq_thread(int irq, void *data) +{ + struct fsa9480_usbsw *usbsw = data; + struct i2c_client *client = usbsw->client; + s32 intr; + + /* read and clear interrupt status bits */ + intr = i2c_smbus_read_word_data(client, FSA9480_REG_INT1); + if (intr < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, intr); + intr = 0; + } else if (intr == 0) { + /* When the FSA9480 triggers an interrupt with no status bits + * set the FSA9480 may have reset and the registers need to be + * reinitialized. + */ + fsa9480_reg_init(usbsw); + dev_warn(&client->dev, "irq fired, but nothing happened\n"); + } else { + dev_dbg(&client->dev, "got irq 0x%x\n", intr); + } + + if (intr & INT_OCP_EN) + dev_err(&client->dev, "entering over-current protection\n"); + + if (intr & INT_OVP_EN) + dev_err(&client->dev, "entering over-voltage protection\n"); + + if (intr & INT_OVP_OCP_DIS) + dev_err(&client->dev, "exiting protection mode\n"); + + disable_irq_nosync(client->irq); + disable_irq_wake(client->irq); + + mutex_lock(&usbsw->lock); + if (usbsw->curr_dev != FSA9480_DETECT_NONE) { + _detected(usbsw, FSA9480_DETECT_NONE); + + /* undo whatever else we did */ + } + mutex_unlock(&usbsw->lock); + + otg_id_notify(); + + return IRQ_HANDLED; +} + +static irqreturn_t usb_id_irq_thread(int irq, void *data) +{ + struct fsa9480_usbsw *usbsw = data; + struct i2c_client *client = usbsw->client; + + mutex_lock(&usbsw->lock); + + /* The external ID interrupt is only used when a USB host cable is + * attached. + */ + if (usbsw->curr_dev != FSA9480_DETECT_USB_HOST) { + disable_irq_nosync(usbsw->external_id_irq); + mutex_unlock(&usbsw->lock); + return IRQ_HANDLED; + } + + /* The FSA9480 has a bug that prevents it from detecting a change in the + * ID signal when the device type is USB OTG. As a workaround the + * driver uses an external mechanism to determine if the USB OTG cable + * has been detached. + */ + if (gpio_get_value(usbsw->pdata->external_id)) { + disable_irq_nosync(usbsw->external_id_irq); + + usbsw->pdata->enable(true); + + /* If the client has been informed of the USB host attach then + * report the disconnect before reseting the FSA9480. VBUS + * drive needs to be turned off before the reset otherwise the + * FSA9480 will misidentify the unattached state as a USB + * peripheral cable. + */ + _detected(usbsw, FSA9480_DETECT_NONE); + + dev_dbg(&client->dev, "usb host detach workaround, resetting" + " FSA9480 chip\n"); + + /* The FSA9480 will not be able to detect a new cable until it + * has been reset. + */ + fsa9480_reset(usbsw); + + enable_irq_wake(client->irq); + enable_irq(client->irq); + } + + mutex_unlock(&usbsw->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t vbus_irq_thread(int irq, void *data) +{ + struct fsa9480_usbsw *usbsw = data; + + disable_irq_nosync(usbsw->pdata->external_vbus_irq); + + mutex_lock(&usbsw->lock); + if (usbsw->curr_dev != FSA9480_DETECT_AV_365K_CHARGER) { + mutex_unlock(&usbsw->lock); + return IRQ_HANDLED; + } + + /* VBUS has gone away when docked, so reset the state to + * FSA_DETECT_NONE and reset the FSA9480, because it cannot + * detect ID pin changes correctly after dock detach. */ + _detected(usbsw, FSA9480_DETECT_NONE); + fsa9480_reset(usbsw); + enable_irq_wake(usbsw->client->irq); + enable_irq(usbsw->client->irq); + mutex_unlock(&usbsw->lock); + + return IRQ_HANDLED; +} + +static int __devinit fsa9480_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct fsa9480_platform_data *pdata = client->dev.platform_data; + struct fsa9480_usbsw *usbsw; + int ret = 0; + int i; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + if (!pdata || !pdata->detected || !pdata->enable || + !pdata->mask_vbus_irq || !pdata->unmask_vbus_irq || + !pdata->vbus_present || + (pdata->external_vbus_irq < 0)) { + dev_err(&client->dev, "missing/invalid platform data\n"); + return -EINVAL; + } + + usbsw = kzalloc(sizeof(struct fsa9480_usbsw) + + pdata->num_sets * sizeof(struct usbsw_nb_info), + GFP_KERNEL); + if (!usbsw) { + dev_err(&client->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + usbsw->client = client; + usbsw->pdata = pdata; + + i2c_set_clientdata(client, usbsw); + mutex_init(&usbsw->lock); + + if (usbsw->pdata->external_id >= 0) { + gpio_request(usbsw->pdata->external_id, "fsa9840_external_id"); + + usbsw->external_id_irq = gpio_to_irq(usbsw->pdata->external_id); + + ret = request_threaded_irq(usbsw->external_id_irq, NULL, + usb_id_irq_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "fsa9480_external_id", usbsw); + if (ret) { + dev_err(&client->dev, + "failed to request ID IRQ err %d\n", + ret); + goto err_req_id_irq; + } + } + + pdata->mask_vbus_irq(); + ret = request_threaded_irq(pdata->external_vbus_irq, NULL, + vbus_irq_thread, pdata->external_vbus_flags, + "external_vbus", usbsw); + if (ret) { + dev_err(&client->dev, + "failed to request vbus IRQ err %d\n", + ret); + goto err_req_vbus_irq; + } + disable_irq(pdata->external_vbus_irq); + pdata->unmask_vbus_irq(); + + /* mask all irqs to prevent event processing between + * request_irq and disable_irq + */ + usbsw->intr_mask = 0x1fff; + i2c_smbus_write_word_data(client, FSA9480_REG_INT1_MASK, + usbsw->intr_mask); + + ret = request_threaded_irq(client->irq, NULL, fsa9480_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, "fsa9480", + usbsw); + if (ret) { + dev_err(&client->dev, "failed to request IRQ\n"); + goto err_req_irq; + } + disable_irq(client->irq); + + ret = enable_irq_wake(client->irq); + if (ret < 0) { + dev_err(&client->dev, + "failed to enable wakeup src %d\n", ret); + goto err_en_wake; + } + disable_irq_wake(client->irq); + + /* Reconcile the requested ADC detect time with the available settings + * on the FSA9480. + */ + for (i = 0; i < ARRAY_SIZE(adc_timing); i++) { + if (usbsw->pdata->detect_time <= adc_timing[i]) { + usbsw->timing = i; + break; + } + } + + if (i == ARRAY_SIZE(adc_timing)) { + ret = -ERANGE; + goto err_timing; + } + + /* mask interrupts (unmask attach/detach only) */ + usbsw->intr_mask = ~(INT_ATTACH | INT_DETACH | INT_OCP_EN | INT_OVP_EN | + INT_OVP_OCP_DIS | INT_AV_CHARGING); + ret = fsa9480_reset(usbsw); + if (ret < 0) + goto err_reset; + + ret = fsa9480_reg_init(usbsw); + if (ret) + goto err_reg_init; + + ret = sysfs_create_group(&client->dev.kobj, &fsa9480_group); + if (ret) { + dev_err(&client->dev, + "failed to create fsa9480 attribute group\n"); + goto err_sys_create; + } + + usbsw->num_notifiers = pdata->num_sets; + for (i = 0; i < usbsw->num_notifiers; i++) { + struct usbsw_nb_info *info = &usbsw->notifiers[i]; + + info->detect_set = &pdata->detect_sets[i]; + info->usbsw = usbsw; + info->otg_id_nb.detect = fsa9480_detect_callback; + info->otg_id_nb.proxy_wait = fsa9480_proxy_wait_callback; + info->otg_id_nb.cancel = fsa9480_cancel_callback; + info->otg_id_nb.priority = pdata->detect_sets[i].prio; + + ret = otg_id_register_notifier(&info->otg_id_nb); + if (ret < 0) { + dev_err(&client->dev, "Unable to register notifier\n"); + goto err_reg_notifiers; + } + } +#if defined(CONFIG_DEBUG_FS) && defined(DEBUG_DUMP_REGISTERS) + usbsw->debug_dir = debugfs_create_dir("fsa9480", NULL); + + if (usbsw->debug_dir) + debugfs_create_file("regs", S_IRUSR, usbsw->debug_dir, usbsw, + &fsa9480_regs_fops); +#endif + + return 0; + +err_reg_notifiers: + for (i--; i >= 0; i--) + otg_id_unregister_notifier(&usbsw->notifiers[i].otg_id_nb); + sysfs_remove_group(&client->dev.kobj, &fsa9480_group); +err_sys_create: +err_reset: +err_timing: +err_reg_init: +err_en_wake: + if (client->irq) + free_irq(client->irq, usbsw); +err_req_irq: + free_irq(usbsw->pdata->external_vbus_irq, usbsw); +err_req_vbus_irq: + if (usbsw->pdata->external_id >= 0) + free_irq(usbsw->external_id_irq, usbsw); +err_req_id_irq: + if (usbsw->pdata->external_id >= 0) + gpio_free(usbsw->pdata->external_id); + mutex_destroy(&usbsw->lock); + i2c_set_clientdata(client, NULL); + kfree(usbsw); + return ret; +} + +static int __devexit fsa9480_remove(struct i2c_client *client) +{ + struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client); + int i; + +#if defined(CONFIG_DEBUG_FS) && defined(DEBUG_DUMP_REGISTERS) + if (usbsw->debug_dir) + debugfs_remove_recursive(usbsw->debug_dir); +#endif + + for (i = 0; i < usbsw->num_notifiers; i++) + otg_id_unregister_notifier(&usbsw->notifiers[i].otg_id_nb); + + if (usbsw->curr_dev != FSA9480_DETECT_NONE) + _detected(usbsw, FSA9480_DETECT_NONE); + + if (client->irq) { + disable_irq_wake(client->irq); + free_irq(client->irq, usbsw); + } + + if (usbsw->pdata->external_id >= 0) { + if (usbsw->wake_enabled) + disable_irq_wake(usbsw->external_id_irq); + free_irq(usbsw->external_id_irq, usbsw); + gpio_free(usbsw->pdata->external_id); + } + + free_irq(usbsw->pdata->external_vbus_irq, usbsw); + + i2c_set_clientdata(client, NULL); + + sysfs_remove_group(&client->dev.kobj, &fsa9480_group); + mutex_destroy(&usbsw->lock); + + kfree(usbsw); + + return 0; +} + +#if defined(CONFIG_PM) +static int fsa9480_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client); + + if (usbsw->wake_enabled) { + disable_irq_wake(usbsw->external_id_irq); + usbsw->wake_enabled = false; + } + + otg_id_resume(); + enable_irq(usbsw->external_id_irq); + enable_irq(client->irq); + + return 0; +} + +static int fsa9480_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client); + int ret; + + disable_irq(client->irq); + disable_irq(usbsw->external_id_irq); + + mutex_lock(&usbsw->lock); + if (usbsw->curr_dev == FSA9480_DETECT_USB_HOST) { + enable_irq_wake(usbsw->external_id_irq); + usbsw->wake_enabled = true; + } + mutex_unlock(&usbsw->lock); + + ret = otg_id_suspend(); + if (ret) + goto err; + + return 0; + +err: + if (usbsw->wake_enabled) { + disable_irq_wake(usbsw->external_id_irq); + usbsw->wake_enabled = false; + } + enable_irq(usbsw->external_id_irq); + enable_irq(client->irq); + return ret; +} + +static const struct dev_pm_ops fsa9480_pm_ops = { + .suspend = fsa9480_suspend, + .resume = fsa9480_resume, +}; +#endif + +static const struct i2c_device_id fsa9480_id[] = { + {"fsa9480", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, fsa9480_id); + +static struct i2c_driver fsa9480_i2c_driver = { + .driver = { + .name = "fsa9480", +#if defined(CONFIG_PM) + .pm = &fsa9480_pm_ops, +#endif + }, + .probe = fsa9480_probe, + .remove = __devexit_p(fsa9480_remove), + .id_table = fsa9480_id, +}; + +static int __init fsa9480_init(void) +{ + return i2c_add_driver(&fsa9480_i2c_driver); +} +module_init(fsa9480_init); + +static void __exit fsa9480_exit(void) +{ + i2c_del_driver(&fsa9480_i2c_driver); +} +module_exit(fsa9480_exit); + +MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>"); +MODULE_DESCRIPTION("FSA9480 USB Switch driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig new file mode 100644 index 0000000..2eb4d64 --- /dev/null +++ b/drivers/misc/inv_mpu/Kconfig @@ -0,0 +1,29 @@ +config MPU_SENSORS_TIMERIRQ + tristate "MPU Timer IRQ" + help + If you say yes here you get access to the timerirq device handle which + can be used to select on. This can be used instead of IRQ's, sleeping, + or timer threads. Reading from this device returns the same type of + information as reading from the MPU and slave IRQ's. + +menuconfig: INV_SENSORS + bool "Motion Processing Unit" + depends on I2C + default n + +if INV_SENSORS + +config MPU_SENSORS_MPU3050 + tristate "MPU3050" + depends on I2C && INV_SENSORS + select MPU_SENSORS_MPU3050_GYRO + help + If you say yes here you get support for the MPU3050 Gyroscope driver + This driver can also be built as a module. If so, the module + will be called mpu3050. + +source "drivers/misc/inv_mpu/accel/Kconfig" +source "drivers/misc/inv_mpu/compass/Kconfig" +source "drivers/misc/inv_mpu/pressure/Kconfig" + +endif #INV_SENSORS diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile new file mode 100644 index 0000000..97f9be8 --- /dev/null +++ b/drivers/misc/inv_mpu/Makefile @@ -0,0 +1,22 @@ + +# Kernel makefile for motions sensors +# +# + +# MPU +obj-$(CONFIG_MPU_SENSORS_MPU3050) += mpu3050.o + +mpu3050-objs += mpuirq.o +mpu3050-objs += slaveirq.o +mpu3050-objs += mpu-dev.o +mpu3050-objs += mlsl-kernel.o +mpu3050-objs += mldl_cfg.o + +EXTRA_CFLAGS += -Idrivers/misc/inv_mpu + +obj-$(CONFIG_MPU_SENSORS_TIMERIRQ)+= timerirq.o + +obj-y += accel/ +obj-y += compass/ +obj-y += pressure/ + diff --git a/drivers/misc/inv_mpu/README b/drivers/misc/inv_mpu/README new file mode 100644 index 0000000..322af09 --- /dev/null +++ b/drivers/misc/inv_mpu/README @@ -0,0 +1,276 @@ +Kernel driver mpu +===================== + +Supported chips: + * InvenSense IMU3050 + Prefix: 'mpu3050' + Datasheet: + PS-MPU-3000A-00.2.4b.pdf + + * InvenSense IMU6000 + Prefix: 'mpu6000' + Datasheet: + MPU-6000A-00 v1.0.pdf + +Author: InvenSense <http://invensense.com> + +Description +----------- +The mpu is a motion processor unit that controls the mpu3050 gyroscope, a slave +accelerometer, a compass and a pressure sensor, or the mpu6000 and slave +compass. This document describes how to install the driver into a Linux kernel +and a small note about how to set up the file permissions in an android file +system. + +Sysfs entries +------------- +/dev/mpu +/dev/mpuirq +/dev/accelirq +/dev/compassirq +/dev/pressureirq + +General Remarks MPU3050 +----------------------- +* Valid addresses for the MPU3050 is 0x68. +* Accelerometer must be on the secondary I2C bus for MPU3050, the + magnetometer must be on the primary bus and pressure sensor must + be on the primary bus. + +General Remarks MPU6000 +----------------------- +* Valid addresses for the MPU6000 is 0x68. +* Magnetometer must be on the secondary I2C bus for the MPU6000. +* Accelerometer slave address must be set to 0x68 +* Gyro and Accel orientation matrices should be the same + +Programming the chip using /dev/mpu +---------------------------------- +Programming of MPU3050 or MPU6000 is done by first opening the /dev/mpu file and +then performing a series of IOCTLS on the handle returned. The IOCTL codes can +be found in mpu.h. Typically this is done by the mllite library in user +space. + +Adding to a Kernel +================== + +The mpu driver is designed to be inserted in the drivers/misc part of the +kernel. Extracting the tarball from the root kernel dir will place the +contents of the tarball here: + + <kernel root dir>/drivers/misc/inv_mpu + <kernel root dir>/include/linux/mpu.h + <kernel root dir>/include/linux/mpu3050.h + <kernel root dir>/include/linux/mpu6000.h + +After this is done the drivers/misc/Kconfig must be edited to add the line: + + source "drivers/misc/inv_mpu/Kconfig" + +Similarly drivers/misc/Makefile must be edited to add the line: + + obj-y += inv_mpu/ + +Configuration can then be done as normal. + +NOTE: This driver depends on a kernel patch to drivers/char/char.c. This patch +started to be included in most 2.6.35 based kernels. +drivers: misc: pass miscdevice pointer via file private data +https://patchwork.kernel.org/patch/96412/ + +--- + drivers/char/misc.c | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + + +diff --git a/drivers/char/misc.c b/drivers/char/misc.c +index 92ab03d..cd650ca 100644 +--- a/drivers/char/misc.c ++++ b/drivers/char/misc.c +@@ -144,6 +144,7 @@ static int misc_open(struct inode * inode, struct file * file) + old_fops = file->f_op; + file->f_op = new_fops; + if (file->f_op->open) { ++ file->private_data = c; + err=file->f_op->open(inode,file); + if (err) { + fops_put(file->f_op); +--- + +Board and Platform Data +----------------------- + +In order for the driver to work, board and platform data specific to the device +needs to be added to the board file. A mpu_platform_data structure must +be created and populated and set in the i2c_board_info_structure. For details +of each structure member see mpu.h. All values below are simply an example and +should be modified for your platform. + +#include <linux/mpu.h> + +#if defined(CONFIG_MPU_SENSORS_MPU3050) + +static struct mpu_platform_data mpu_data = { + .int_config = 0x10, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + /* accel */ + .accel = { +#ifdef CONFIG_INV_SENSORS_MODULE + .get_slave_descr = NULL, +#else + .get_slave_descr = get_accel_slave_descr, +#endif + .irq = (IH_GPIO_BASE + 138), + .adapt_num = 2, + .bus = EXT_SLAVE_BUS_SECONDARY, +#ifdef CONFIG_MPU_SENSORS_KXTF9 + .address = 0x0F, +#elif defined CONFIG_MPU_SENSORS_BMA150 + .address = 0x38, +#elif defined CONFIG_MPU_SENSORS_LIS331 || defined CONFIG_MPU_SENSORS_LIS303 \ + || defined CONFIG_MPU_SENSORS_LIS3DH || defined CONFIG_MPU_SENSORS_KXSD9 + .address = 0x18, +#elif defined CONFIG_MPU_SENSORS_MMA845X || defined CONFIG_MPU_SENSORS_MMA8450 + .address = 0x1C, +#elif defined CONFIG_MPU_SENSORS_ADI346 + .address = 0x53, +#endif + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + }, + /* compass */ + .compass = { +#ifdef CONFIG_INV_SENSORS_MODULE + .get_slave_descr = NULL, +#else + .get_slave_descr = get_compass_slave_descr, +#endif + .irq = (IH_GPIO_BASE + 137), + .adapt_num = 2, + .bus = EXT_SLAVE_BUS_PRIMARY, +#ifdef CONFIG_MPU_SENSORS_AK8975 + .address = 0x0E, +#elif defined CONFIG_MPU_SENSORS_AMI30x + .address = 0x0E, +#elif defined CONFIG_MPU_SENSORS_AMI306 + .address = 0x0E, +#elif defined CONFIG_MPU_SENSORS_YAS529 + .address = 0x2E, +#elif defined CONFIG_MPU_SENSORS_MMC314X + .address = 0x30, +#elif defined CONFIG_MPU_SENSORS_HSCDTD00XX + .address = 0x0C, +#endif + .orientation = { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 }, + }, +}; +#endif + +#if defined(CONFIG_MPU_SENSORS_MPU6050A2) || \ + defined(CONFIG_MPU_SENSORS_MPU6050B1) + +static struct mpu_platform_data mpu_data = { + .int_config = 0x10, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + /* accel */ + .accel = { +#if defined CONFIG_INV_SENSORS_MODULE + .get_slave_descr = NULL, +#else + .get_slave_descr = get_accel_slave_descr, +#endif + .irq = (IH_GPIO_BASE + 137), + .adapt_num = 2, + .bus = EXT_SLAVE_BUS_PRIMARY, + .address = 0x68, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + }, + /* compass */ + .compass = { +#if defined CONFIG_INV_SENSORS_MODULE + .get_slave_descr = NULL, +#else + .get_slave_descr = get_compass_slave_descr, +#endif + .irq = (IH_GPIO_BASE + 138), + .adapt_num = 2, + .bus = EXT_SLAVE_BUS_SECONDARY, +#ifdef CONFIG_MPU_SENSORS_AK8975 + .address = 0x0E, +#elif defined CONFIG_MPU_SENSORS_AMI30x + .address = 0x0E, +#elif defined CONFIG_MPU_SENSORS_AMI306 + .address = 0x0E, +#elif defined CONFIG_MPU_SENSORS_YAS529 + .address = 0x2E, +#elif defined CONFIG_MPU_SENSORS_MMC314X + .address = 0x30, +#elif defined CONFIG_MPU_SENSORS_HSCDTD00XX + .address = 0x0C, +#endif + .orientation = { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 }, + }, +}; + +#endif + +static struct i2c_board_info __initdata beagle_i2c_2_boardinfo[] = { +#if defined(CONFIG_MPU_SENSORS_MPU3050) || \ + defined(CONFIG_MPU_SENSORS_MPU6050A2) || \ + defined(CONFIG_MPU_SENSORS_MPU6050B1) + { + I2C_BOARD_INFO(MPU_NAME, 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &mpu_data, + }, +#endif +}; + +Typically the IRQ is a GPIO input pin and needs to be configured properly. If +in the above example GPIO 168 corresponds to IRQ 299, the following should be +done as well: + +#define MPU_GPIO_IRQ 168 + + gpio_request(MPU_GPIO_IRQ,"MPUIRQ"); + gpio_direction_input(MPU_GPIO_IRQ) + +Dynamic Debug +============= + +The mpu3050 makes use of dynamic debug. For details on how to use this +refer to Documentation/dynamic-debug-howto.txt + +Android File Permissions +======================== + +To set up the file permissions on an android system, the /dev/mpu and +/dev/mpuirq files needs to be added to the system/core/init/devices.c file +inside the perms_ structure. + +static struct perms_ devperms[] = { + { "/dev/mpu" ,0660, AID_SYSTEM, AID_SYSTEM, 1 }, +}; + +Sufficient file permissions need to be give to read and write it by the system. + +For gingerbread and later the system/core/rootdir/ueventd.rc file needs to be +modified with the appripriate lines added. + +# MPU sensors and IRQ +/dev/mpu 0660 system system +/dev/mpuirq 0660 system system +/dev/accelirq 0660 system system +/dev/compassirq 0660 system system +/dev/pressureirq 0660 system system diff --git a/drivers/misc/inv_mpu/accel/Kconfig b/drivers/misc/inv_mpu/accel/Kconfig new file mode 100644 index 0000000..bef6d61 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/Kconfig @@ -0,0 +1,133 @@ +menuconfig INV_SENSORS_ACCELEROMETERS + bool "Accelerometer Slave Sensors" + default y + ---help--- + Say Y here to get to see options for device drivers for various + accelerometrs for integration with the MPU3050 or MPU6050 driver. + This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if INV_SENSORS_ACCELEROMETERS + +config MPU_SENSORS_ADXL34X + tristate "ADI adxl34x" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the ADI adxl345 or adxl346 accelerometers. + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_BMA222 + tristate "Bosch BMA222" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the Bosch BMA222 accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_BMA150 + tristate "Bosch BMA150" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the Bosch BMA150 accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_BMA250 + tristate "Bosch BMA250" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the Bosch BMA250 accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_KXSD9 + tristate "Kionix KXSD9" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the Kionix KXSD9 accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_KXTF9 + tristate "Kionix KXTF9" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the Kionix KXFT9 accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_LIS331DLH + tristate "ST lis331dlh" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the ST lis331dlh accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_LIS3DH + tristate "ST lis3dh" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the ST lis3dh accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_LSM303DLHA + tristate "ST lsm303dlh" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the ST lsm303dlh accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_MMA8450 + tristate "Freescale mma8450" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the Freescale mma8450 accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_MMA845X + tristate "Freescale mma8451/8452/8453" + depends on MPU_SENSORS_MPU3050 + help + This enables support for the Freescale mma8451/8452/8453 accelerometer + This support is for integration with the MPU3050 gyroscope device + driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +config MPU_SENSORS_MPU6050_ACCEL + tristate "MPU6050 built in accelerometer" + depends on MPU_SENSORS_MPU6050B1 || MPU_SENSORS_MPU6050A2 + help + This enables support for the MPU6050 built in accelerometer. + This the built in support for integration with the MPU6050 gyroscope + device driver. This is the only accelerometer supported with the + MPU6050. Specifying another accelerometer in the board file will + result in runtime errors. + +endif diff --git a/drivers/misc/inv_mpu/accel/Makefile b/drivers/misc/inv_mpu/accel/Makefile new file mode 100644 index 0000000..10699fb --- /dev/null +++ b/drivers/misc/inv_mpu/accel/Makefile @@ -0,0 +1,37 @@ +# +# Accel Slaves to MPUxxxx +# +obj-$(CONFIG_MPU_SENSORS_ADXL34X) += inv_mpu_adxl34x.o +inv_mpu_adxl34x-objs += adxl34x.o + +obj-$(CONFIG_MPU_SENSORS_BMA150) += inv_mpu_bma150.o +inv_mpu_bma150-objs += bma150.o + +obj-$(CONFIG_MPU_SENSORS_KXTF9) += inv_mpu_kxtf9.o +inv_mpu_kxtf9-objs += kxtf9.o + +obj-$(CONFIG_MPU_SENSORS_BMA222) += inv_mpu_bma222.o +inv_mpu_bma222-objs += bma222.o + +obj-$(CONFIG_MPU_SENSORS_BMA250) += inv_mpu_bma250.o +inv_mpu_bma250-objs += bma250.o + +obj-$(CONFIG_MPU_SENSORS_KXSD9) += inv_mpu_kxsd9.o +inv_mpu_kxsd9-objs += kxsd9.o + +obj-$(CONFIG_MPU_SENSORS_LIS331DLH) += inv_mpu_lis331.o +inv_mpu_lis331-objs += lis331.o + +obj-$(CONFIG_MPU_SENSORS_LIS3DH) += inv_mpu_lis3dh.o +inv_mpu_lis3dh-objs += lis3dh.o + +obj-$(CONFIG_MPU_SENSORS_LSM303DLHA) += inv_mpu_lsm303a.o +inv_mpu_lsm303a-objs += lsm303a.o + +obj-$(CONFIG_MPU_SENSORS_MMA8450) += inv_mpu_mma8450.o +inv_mpu_mma8450-objs += mma8450.o + +obj-$(CONFIG_MPU_SENSORS_MMA845X) += inv_mpu_mma845x.o +inv_mpu_mma845x-objs += mma845x.o + +EXTRA_CFLAGS += -Idrivers/misc/inv_mpu diff --git a/drivers/misc/inv_mpu/accel/adxl34x.c b/drivers/misc/inv_mpu/accel/adxl34x.c new file mode 100644 index 0000000..a389d5b --- /dev/null +++ b/drivers/misc/inv_mpu/accel/adxl34x.c @@ -0,0 +1,728 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file adxl34x.c + * @brief Accelerometer setup and handling methods for AD adxl345 and + * adxl346. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> + +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" + +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* -------------------------------------------------------------------------- */ + +/* registers */ +#define ADXL34X_ODR_REG (0x2C) +#define ADXL34X_PWR_REG (0x2D) +#define ADXL34X_DATAFORMAT_REG (0x31) + +/* masks */ +#define ADXL34X_ODR_MASK (0x0F) +#define ADXL34X_PWR_SLEEP_MASK (0x04) +#define ADXL34X_PWR_MEAS_MASK (0x08) +#define ADXL34X_DATAFORMAT_JUSTIFY_MASK (0x04) +#define ADXL34X_DATAFORMAT_FSR_MASK (0x03) + +/* -------------------------------------------------------------------------- */ + +struct adxl34x_config { + unsigned int odr; /** < output data rate in mHz */ + unsigned int fsr; /** < full scale range mg */ + unsigned int fsr_reg_mask; /** < register setting for fsr */ +}; + +struct adxl34x_private_data { + struct adxl34x_config suspend; /** < suspend configuration */ + struct adxl34x_config resume; /** < resume configuration */ +}; + +/** + * @brief Set the output data rate for the particular configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * Config to modify with new ODR. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param odr + * Output data rate in units of 1/1000Hz (mHz). + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int adxl34x_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct adxl34x_config *config, + int apply, + long odr) +{ + int result = INV_SUCCESS; + unsigned char new_odr_mask; + + /* ADXL346 (Rev. A) pages 13, 24 */ + if (odr >= 3200000) { + new_odr_mask = 0x0F; + config->odr = 3200000; + } else if (odr >= 1600000) { + new_odr_mask = 0x0E; + config->odr = 1600000; + } else if (odr >= 800000) { + new_odr_mask = 0x0D; + config->odr = 800000; + } else if (odr >= 400000) { + new_odr_mask = 0x0C; + config->odr = 400000; + } else if (odr >= 200000) { + new_odr_mask = 0x0B; + config->odr = 200000; + } else if (odr >= 100000) { + new_odr_mask = 0x0A; + config->odr = 100000; + } else if (odr >= 50000) { + new_odr_mask = 0x09; + config->odr = 50000; + } else if (odr >= 25000) { + new_odr_mask = 0x08; + config->odr = 25000; + } else if (odr >= 12500) { + new_odr_mask = 0x07; + config->odr = 12500; + } else if (odr >= 6250) { + new_odr_mask = 0x06; + config->odr = 6250; + } else if (odr >= 3130) { + new_odr_mask = 0x05; + config->odr = 3130; + } else if (odr >= 1560) { + new_odr_mask = 0x04; + config->odr = 1560; + } else if (odr >= 780) { + new_odr_mask = 0x03; + config->odr = 780; + } else if (odr >= 390) { + new_odr_mask = 0x02; + config->odr = 390; + } else if (odr >= 200) { + new_odr_mask = 0x01; + config->odr = 200; + } else { + new_odr_mask = 0x00; + config->odr = 100; + } + + if (apply) { + unsigned char reg_odr; + result = inv_serial_read(mlsl_handle, pdata->address, + ADXL34X_ODR_REG, 1, ®_odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg_odr &= ~ADXL34X_ODR_MASK; + reg_odr |= new_odr_mask; + result = inv_serial_single_write(mlsl_handle, pdata->address, + ADXL34X_ODR_REG, reg_odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("ODR: %d mHz\n", config->odr); + } + return result; +} + +/** + * @brief Set the full scale range of the accels + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * pointer to configuration. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param fsr + * requested full scale range in milli gees (mg). + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int adxl34x_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct adxl34x_config *config, + int apply, + long fsr) +{ + int result = INV_SUCCESS; + + if (fsr <= 2000) { + config->fsr_reg_mask = 0x00; + config->fsr = 2000; + } else if (fsr <= 4000) { + config->fsr_reg_mask = 0x01; + config->fsr = 4000; + } else if (fsr <= 8000) { + config->fsr_reg_mask = 0x02; + config->fsr = 8000; + } else { /* 8001 -> oo */ + config->fsr_reg_mask = 0x03; + config->fsr = 16000; + } + + if (apply) { + unsigned char reg_df; + result = inv_serial_read(mlsl_handle, pdata->address, + ADXL34X_DATAFORMAT_REG, 1, ®_df); + reg_df &= ~ADXL34X_DATAFORMAT_FSR_MASK; + result = inv_serial_single_write(mlsl_handle, pdata->address, + ADXL34X_DATAFORMAT_REG, + reg_df | config->fsr_reg_mask); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("FSR: %d mg\n", config->fsr); + } + return result; +} + +/** + * @brief facility to retrieve the device configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to store the returned configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int adxl34x_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct adxl34x_private_data *private_data = + (struct adxl34x_private_data *)(pdata->private_data); + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +/** + * @brief device configuration facility. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to the configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int adxl34x_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct adxl34x_private_data *private_data = + (struct adxl34x_private_data *)(pdata->private_data); + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return adxl34x_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return adxl34x_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return adxl34x_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return adxl34x_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + return INV_SUCCESS; +} + +/** + * @brief suspends the device to put it in its lowest power mode. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int adxl34x_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + + /* + struct adxl34x_config *suspend_config = + &((struct adxl34x_private_data *)pdata->private_data)->suspend; + + result = adxl34x_set_odr(mlsl_handle, pdata, suspend_config, + TRUE, suspend_config->odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; +} + result = adxl34x_set_fsr(mlsl_handle, pdata, suspend_config, + TRUE, suspend_config->fsr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; +} + */ + + /* + Page 25 + When clearing the sleep bit, it is recommended that the part + be placed into standby mode and then set back to measurement mode + with a subsequent write. + This is done to ensure that the device is properly biased if sleep + mode is manually disabled; otherwise, the first few samples of data + after the sleep bit is cleared may have additional noise, + especially if the device was asleep when the bit was cleared. */ + + /* go in standy-by mode (suspends measurements) */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + ADXL34X_PWR_REG, ADXL34X_PWR_MEAS_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* and then in sleep */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + ADXL34X_PWR_REG, + ADXL34X_PWR_MEAS_MASK | ADXL34X_PWR_SLEEP_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +/** + * @brief resume the device in the proper power state given the configuration + * chosen. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int adxl34x_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + struct adxl34x_config *resume_config = + &((struct adxl34x_private_data *)pdata->private_data)->resume; + unsigned char reg; + + /* + Page 25 + When clearing the sleep bit, it is recommended that the part + be placed into standby mode and then set back to measurement mode + with a subsequent write. + This is done to ensure that the device is properly biased if sleep + mode is manually disabled; otherwise, the first few samples of data + after the sleep bit is cleared may have additional noise, + especially if the device was asleep when the bit was cleared. */ + + /* remove sleep, but leave in stand-by */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + ADXL34X_PWR_REG, ADXL34X_PWR_MEAS_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = adxl34x_set_odr(mlsl_handle, pdata, resume_config, + TRUE, resume_config->odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* + -> FSR + -> Justiy bit for Big endianess + -> resulution to 10 bits + */ + reg = ADXL34X_DATAFORMAT_JUSTIFY_MASK; + reg |= resume_config->fsr_reg_mask; + result = inv_serial_single_write(mlsl_handle, pdata->address, + ADXL34X_DATAFORMAT_REG, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* go in measurement mode */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + ADXL34X_PWR_REG, 0x00); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* DATA_FORMAT: full resolution of +/-2g; data is left justified */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + 0x31, reg); + + return result; +} + +/** + * @brief one-time device driver initialization function. + * If the driver is built as a kernel module, this function will be + * called when the module is loaded in the kernel. + * If the driver is built-in in the kernel, this function will be + * called at boot time. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int adxl34x_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + long range; + + struct adxl34x_private_data *private_data; + private_data = (struct adxl34x_private_data *) + kzalloc(sizeof(struct adxl34x_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + result = adxl34x_set_odr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = adxl34x_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + range = range_fixedpoint_to_long_mg(slave->range); + result = adxl34x_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, range); + result = adxl34x_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, range); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = adxl34x_suspend(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief one-time device driver exit function. + * If the driver is built as a kernel module, this function will be + * called when the module is removed from the kernel. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int adxl34x_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +/** + * @brief read the sensor data from the device. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a buffer to store the data read. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int adxl34x_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result; + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, data); + return result; +} + +static struct ext_slave_descr adxl34x_descr = { + .init = adxl34x_init, + .exit = adxl34x_exit, + .suspend = adxl34x_suspend, + .resume = adxl34x_resume, + .read = adxl34x_read, + .config = adxl34x_config, + .get_config = adxl34x_get_config, + .name = "adxl34x", /* 5 or 6 */ + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_ADXL34X, + .read_reg = 0x32, + .read_len = 6, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {2, 0}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *adxl34x_get_slave_descr(void) +{ + return &adxl34x_descr; +} + +/* -------------------------------------------------------------------------- */ +struct adxl34x_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int adxl34x_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct adxl34x_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + adxl34x_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int adxl34x_mod_remove(struct i2c_client *client) +{ + struct adxl34x_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + adxl34x_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id adxl34x_mod_id[] = { + { "adxl34x", ACCEL_ID_ADXL34X }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, adxl34x_mod_id); + +static struct i2c_driver adxl34x_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = adxl34x_mod_probe, + .remove = adxl34x_mod_remove, + .id_table = adxl34x_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "adxl34x_mod", + }, + .address_list = normal_i2c, +}; + +static int __init adxl34x_mod_init(void) +{ + int res = i2c_add_driver(&adxl34x_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "adxl34x_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit adxl34x_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&adxl34x_mod_driver); +} + +module_init(adxl34x_mod_init); +module_exit(adxl34x_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate ADXL34X sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("adxl34x_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/bma150.c b/drivers/misc/inv_mpu/accel/bma150.c new file mode 100644 index 0000000..2d0a511 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/bma150.c @@ -0,0 +1,777 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file bma150.c + * @brief Accelerometer setup and handling methods for Bosch BMA150. + */ + +/* -------------------------------------------------------------------------- */ +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" + +/* -------------------------------------------------------------------------- */ +/* registers */ +#define BMA150_CTRL_REG (0x14) +#define BMA150_INT_REG (0x15) +#define BMA150_PWR_REG (0x0A) + +/* masks */ +#define BMA150_CTRL_MASK (0x18) +#define BMA150_CTRL_MASK_ODR (0xF8) +#define BMA150_CTRL_MASK_FSR (0xE7) +#define BMA150_INT_MASK_WUP (0xF8) +#define BMA150_INT_MASK_IRQ (0xDF) +#define BMA150_PWR_MASK_SLEEP (0x01) +#define BMA150_PWR_MASK_SOFT_RESET (0x02) + +/* -------------------------------------------------------------------------- */ +struct bma150_config { + unsigned int odr; /** < output data rate mHz */ + unsigned int fsr; /** < full scale range mgees */ + unsigned int irq_type; /** < type of IRQ, see bma150_set_irq */ + unsigned char ctrl_reg; /** < control register value */ + unsigned char int_reg; /** < interrupt control register value */ +}; + +struct bma150_private_data { + struct bma150_config suspend; /** < suspend configuration */ + struct bma150_config resume; /** < resume configuration */ +}; + +/** + * @brief Simply disables the IRQ since it is not usable on BMA150 devices. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * configuration to apply to, suspend or resume + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param irq_type + * the type of IRQ. Valid values are + * - MPU_SLAVE_IRQ_TYPE_NONE + * - MPU_SLAVE_IRQ_TYPE_MOTION + * - MPU_SLAVE_IRQ_TYPE_DATA_READY + * The only supported IRQ type is MPU_SLAVE_IRQ_TYPE_NONE which + * corresponds to disabling the IRQ completely. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_set_irq(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct bma150_config *config, + int apply, + long irq_type) +{ + int result = INV_SUCCESS; + + if (irq_type != MPU_SLAVE_IRQ_TYPE_NONE) + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + + config->irq_type = MPU_SLAVE_IRQ_TYPE_NONE; + config->int_reg = 0x00; + + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_CTRL_REG, config->ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_INT_REG, config->int_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +/** + * @brief Set the output data rate for the particular configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * Config to modify with new ODR. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param odr + * Output data rate in units of 1/1000Hz (mHz). + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct bma150_config *config, + int apply, + long odr) +{ + unsigned char odr_bits = 0; + unsigned char wup_bits = 0; + int result = INV_SUCCESS; + + if (odr > 100000) { + config->odr = 190000; + odr_bits = 0x03; + } else if (odr > 50000) { + config->odr = 100000; + odr_bits = 0x02; + } else if (odr > 25000) { + config->odr = 50000; + odr_bits = 0x01; + } else if (odr > 0) { + config->odr = 25000; + odr_bits = 0x00; + } else { + config->odr = 0; + wup_bits = 0x00; + } + + config->int_reg &= BMA150_INT_MASK_WUP; + config->ctrl_reg &= BMA150_CTRL_MASK_ODR; + config->ctrl_reg |= odr_bits; + + MPL_LOGV("ODR: %d\n", config->odr); + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_CTRL_REG, config->ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_INT_REG, config->int_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + return result; +} + +/** + * @brief Set the full scale range of the accels + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * pointer to configuration. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param fsr + * requested full scale range. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct bma150_config *config, + int apply, + long fsr) +{ + unsigned char fsr_bits; + int result = INV_SUCCESS; + + if (fsr <= 2048) { + fsr_bits = 0x00; + config->fsr = 2048; + } else if (fsr <= 4096) { + fsr_bits = 0x08; + config->fsr = 4096; + } else { + fsr_bits = 0x10; + config->fsr = 8192; + } + + config->ctrl_reg &= BMA150_CTRL_MASK_FSR; + config->ctrl_reg |= fsr_bits; + + MPL_LOGV("FSR: %d\n", config->fsr); + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_CTRL_REG, config->ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_CTRL_REG, config->ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +/** + * @brief one-time device driver initialization function. + * If the driver is built as a kernel module, this function will be + * called when the module is loaded in the kernel. + * If the driver is built-in in the kernel, this function will be + * called at boot time. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char reg; + long range; + + struct bma150_private_data *private_data; + private_data = (struct bma150_private_data *) + kzalloc(sizeof(struct bma150_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_PWR_REG, BMA150_PWR_MASK_SOFT_RESET); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(1); + + result = inv_serial_read(mlsl_handle, pdata->address, + BMA150_CTRL_REG, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + private_data->resume.ctrl_reg = reg; + private_data->suspend.ctrl_reg = reg; + + result = inv_serial_read(mlsl_handle, pdata->address, + BMA150_INT_REG, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + private_data->resume.int_reg = reg; + private_data->suspend.int_reg = reg; + + result = bma150_set_odr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma150_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + range = range_fixedpoint_to_long_mg(slave->range); + result = bma150_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, range); + result = bma150_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, range); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = bma150_set_irq(mlsl_handle, pdata, &private_data->suspend, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma150_set_irq(mlsl_handle, pdata, &private_data->resume, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_PWR_REG, BMA150_PWR_MASK_SLEEP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief one-time device driver exit function. + * If the driver is built as a kernel module, this function will be + * called when the module is removed from the kernel. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +/** + * @brief device configuration facility. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to the configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct bma150_private_data *private_data = + (struct bma150_private_data *)(pdata->private_data); + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return bma150_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return bma150_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return bma150_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return bma150_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + return bma150_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_RESUME: + return bma150_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + return INV_SUCCESS; +} + +/** + * @brief facility to retrieve the device configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to store the returned configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct bma150_private_data *private_data = + (struct bma150_private_data *)(pdata->private_data); + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.irq_type; + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.irq_type; + break; + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +/** + * @brief suspends the device to put it in its lowest power mode. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char ctrl_reg; + unsigned char int_reg; + + struct bma150_private_data *private_data = + (struct bma150_private_data *)(pdata->private_data); + + ctrl_reg = private_data->suspend.ctrl_reg; + int_reg = private_data->suspend.int_reg; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_PWR_REG, BMA150_PWR_MASK_SOFT_RESET); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(1); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_CTRL_REG, ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_INT_REG, int_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_PWR_REG, BMA150_PWR_MASK_SLEEP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief resume the device in the proper power state given the configuration + * chosen. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char ctrl_reg; + unsigned char int_reg; + + struct bma150_private_data *private_data = + (struct bma150_private_data *)(pdata->private_data); + + ctrl_reg = private_data->resume.ctrl_reg; + int_reg = private_data->resume.int_reg; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_PWR_REG, BMA150_PWR_MASK_SOFT_RESET); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(1); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_CTRL_REG, ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_INT_REG, int_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA150_PWR_REG, 0x00); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief read the sensor data from the device. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a buffer to store the data read. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma150_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + return inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, data); +} + +static struct ext_slave_descr bma150_descr = { + .init = bma150_init, + .exit = bma150_exit, + .suspend = bma150_suspend, + .resume = bma150_resume, + .read = bma150_read, + .config = bma150_config, + .get_config = bma150_get_config, + .name = "bma150", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_BMA150, + .read_reg = 0x02, + .read_len = 6, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {2, 0}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *bma150_get_slave_descr(void) +{ + return &bma150_descr; +} + +/* -------------------------------------------------------------------------- */ + +/* Platform data for the MPU */ +struct bma150_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int bma150_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct bma150_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + bma150_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int bma150_mod_remove(struct i2c_client *client) +{ + struct bma150_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + bma150_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id bma150_mod_id[] = { + { "bma150", ACCEL_ID_BMA150 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, bma150_mod_id); + +static struct i2c_driver bma150_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = bma150_mod_probe, + .remove = bma150_mod_remove, + .id_table = bma150_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "bma150_mod", + }, + .address_list = normal_i2c, +}; + +static int __init bma150_mod_init(void) +{ + int res = i2c_add_driver(&bma150_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "bma150_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit bma150_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&bma150_mod_driver); +} + +module_init(bma150_mod_init); +module_exit(bma150_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate BMA150 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("bma150_mod"); + +/** + * @} + */ + diff --git a/drivers/misc/inv_mpu/accel/bma222.c b/drivers/misc/inv_mpu/accel/bma222.c new file mode 100644 index 0000000..94c6a6d --- /dev/null +++ b/drivers/misc/inv_mpu/accel/bma222.c @@ -0,0 +1,654 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/* + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file bma222.c + * @brief Accelerometer setup and handling methods for Bosch BMA222. + */ + +/* ------------------ */ +/* - Include Files. - */ +/* ------------------ */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" + +/* -------------------------------------------------------------------------- */ + +#define BMA222_STATUS_REG (0x0A) +#define BMA222_FSR_REG (0x0F) +#define ADXL34X_ODR_REG (0x10) +#define BMA222_PWR_REG (0x11) +#define BMA222_SOFTRESET_REG (0x14) + +#define BMA222_STATUS_RDY_MASK (0x80) +#define BMA222_FSR_MASK (0x0F) +#define BMA222_ODR_MASK (0x1F) +#define BMA222_PWR_SLEEP_MASK (0x80) +#define BMA222_PWR_AWAKE_MASK (0x00) +#define BMA222_SOFTRESET_MASK (0xB6) +#define BMA222_SOFTRESET_MASK (0xB6) + +/* -------------------------------------------------------------------------- */ + +struct bma222_config { + unsigned int odr; /** < output data rate in mHz */ + unsigned int fsr; /** < full scale range mg */ +}; + +struct bma222_private_data { + struct bma222_config suspend; /** < suspend configuration */ + struct bma222_config resume; /** < resume configuration */ +}; + + +/* -------------------------------------------------------------------------- */ + +/** + * @brief Set the output data rate for the particular configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * Config to modify with new ODR. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param odr + * Output data rate in units of 1/1000Hz (mHz). + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma222_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct bma222_config *config, + int apply, + long odr) +{ + int result = INV_SUCCESS; + unsigned char reg_odr; + + if (odr >= 1000000) { + reg_odr = 0x0F; + config->odr = 1000000; + } else if (odr >= 500000) { + reg_odr = 0x0E; + config->odr = 500000; + } else if (odr >= 250000) { + reg_odr = 0x0D; + config->odr = 250000; + } else if (odr >= 125000) { + reg_odr = 0x0C; + config->odr = 125000; + } else if (odr >= 62500) { + reg_odr = 0x0B; + config->odr = 62500; + } else if (odr >= 32000) { + reg_odr = 0x0A; + config->odr = 32000; + } else if (odr >= 16000) { + reg_odr = 0x09; + config->odr = 16000; + } else { + reg_odr = 0x08; + config->odr = 8000; + } + + if (apply) { + MPL_LOGV("ODR: %d\n", config->odr); + result = inv_serial_single_write(mlsl_handle, pdata->address, + ADXL34X_ODR_REG, reg_odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +/** + * @brief Set the full scale range of the accels + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * pointer to configuration. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param fsr + * requested full scale range. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma222_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct bma222_config *config, + int apply, + long fsr) +{ + int result = INV_SUCCESS; + unsigned char reg_fsr_mask; + + if (fsr <= 2000) { + reg_fsr_mask = 0x03; + config->fsr = 2000; + } else if (fsr <= 4000) { + reg_fsr_mask = 0x05; + config->fsr = 4000; + } else if (fsr <= 8000) { + reg_fsr_mask = 0x08; + config->fsr = 8000; + } else { /* 8001 -> oo */ + reg_fsr_mask = 0x0C; + config->fsr = 16000; + } + + if (apply) { + MPL_LOGV("FSR: %d\n", config->fsr); + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA222_FSR_REG, reg_fsr_mask); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +/** + * @brief one-time device driver initialization function. + * If the driver is built as a kernel module, this function will be + * called when the module is loaded in the kernel. + * If the driver is built-in in the kernel, this function will be + * called at boot time. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma222_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + + struct bma222_private_data *private_data; + private_data = (struct bma222_private_data *) + kzalloc(sizeof(struct bma222_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA222_SOFTRESET_REG, BMA222_SOFTRESET_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(1); + + result = bma222_set_odr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma222_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = bma222_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 2000); + result = bma222_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, 2000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA222_PWR_REG, BMA222_PWR_SLEEP_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief one-time device driver exit function. + * If the driver is built as a kernel module, this function will be + * called when the module is removed from the kernel. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma222_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + + +/** + * @brief facility to retrieve the device configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to store the returned configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma222_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct bma222_private_data *private_data = + (struct bma222_private_data *)(pdata->private_data); + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +/** + * @brief device configuration facility. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to the configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma222_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct bma222_private_data *private_data = + (struct bma222_private_data *)(pdata->private_data); + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return bma222_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return bma222_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return bma222_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return bma222_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + return INV_SUCCESS; +} + +/** + * @brief suspends the device to put it in its lowest power mode. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma222_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + struct bma222_config *suspend_config = + &((struct bma222_private_data *)pdata->private_data)->suspend; + + result = bma222_set_odr(mlsl_handle, pdata, suspend_config, + TRUE, suspend_config->odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma222_set_fsr(mlsl_handle, pdata, suspend_config, + TRUE, suspend_config->fsr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA222_PWR_REG, BMA222_PWR_SLEEP_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + msleep(3); /* 3 ms powerup time maximum */ + return result; +} + +/** + * @brief resume the device in the proper power state given the configuration + * chosen. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma222_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + struct bma222_config *resume_config = + &((struct bma222_private_data *)pdata->private_data)->resume; + + /* Soft reset */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA222_SOFTRESET_REG, BMA222_SOFTRESET_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(10); + + result = bma222_set_odr(mlsl_handle, pdata, resume_config, + TRUE, resume_config->odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma222_set_fsr(mlsl_handle, pdata, resume_config, + TRUE, resume_config->fsr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief read the sensor data from the device. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a buffer to store the data read. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma222_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result = INV_SUCCESS; + result = inv_serial_read(mlsl_handle, pdata->address, + BMA222_STATUS_REG, 1, data); + if (data[0] & BMA222_STATUS_RDY_MASK) { + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, data); + return result; + } else + return INV_ERROR_ACCEL_DATA_NOT_READY; +} + +static struct ext_slave_descr bma222_descr = { + .init = bma222_init, + .exit = bma222_exit, + .suspend = bma222_suspend, + .resume = bma222_resume, + .read = bma222_read, + .config = bma222_config, + .get_config = bma222_get_config, + .name = "bma222", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_BMA222, + .read_reg = 0x02, + .read_len = 6, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {2, 0}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *bma222_get_slave_descr(void) +{ + return &bma222_descr; +} + +/* -------------------------------------------------------------------------- */ + +struct bma222_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int bma222_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct bma222_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + bma222_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int bma222_mod_remove(struct i2c_client *client) +{ + struct bma222_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + bma222_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id bma222_mod_id[] = { + { "bma222", ACCEL_ID_BMA222 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, bma222_mod_id); + +static struct i2c_driver bma222_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = bma222_mod_probe, + .remove = bma222_mod_remove, + .id_table = bma222_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "bma222_mod", + }, + .address_list = normal_i2c, +}; + +static int __init bma222_mod_init(void) +{ + int res = i2c_add_driver(&bma222_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "bma222_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit bma222_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&bma222_mod_driver); +} + +module_init(bma222_mod_init); +module_exit(bma222_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate BMA222 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("bma222_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/bma250.c b/drivers/misc/inv_mpu/accel/bma250.c new file mode 100644 index 0000000..0f42ae8 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/bma250.c @@ -0,0 +1,783 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file bma250.c + * @brief Accelerometer setup and handling methods for Bosch BMA250. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" + +/* -------------------------------------------------------------------------- */ + +/* registers */ +#define BMA250_STATUS_REG (0x0A) +#define BMA250_FSR_REG (0x0F) +#define BMA250_ODR_REG (0x10) +#define BMA250_PWR_REG (0x11) +#define BMA250_SOFTRESET_REG (0x14) +#define BMA250_INT_TYPE_REG (0x17) +#define BMA250_INT_DST_REG (0x1A) +#define BMA250_INT_SRC_REG (0x1E) + +/* masks */ +#define BMA250_STATUS_RDY_MASK (0x80) +#define BMA250_FSR_MASK (0x0F) +#define BMA250_ODR_MASK (0x1F) +#define BMA250_PWR_SLEEP_MASK (0x80) +#define BMA250_PWR_AWAKE_MASK (0x00) +#define BMA250_SOFTRESET_MASK (0xB6) +#define BMA250_INT_TYPE_MASK (0x10) +#define BMA250_INT_DST_1_MASK (0x01) +#define BMA250_INT_DST_2_MASK (0x80) +#define BMA250_INT_SRC_MASK (0x00) + +/* -------------------------------------------------------------------------- */ + +struct bma250_config { + unsigned int odr; /** < output data rate in mHz */ + unsigned int fsr; /** < full scale range mg */ + unsigned char irq_type; +}; + +struct bma250_private_data { + struct bma250_config suspend; /** < suspend configuration */ + struct bma250_config resume; /** < resume configuration */ +}; + +/* -------------------------------------------------------------------------- */ +/** + * @brief Sets the IRQ to fire when one of the IRQ events occur. + * Threshold and duration will not be used unless the type is MOT or + * NMOT. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * configuration to apply to, suspend or resume + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param irq_type + * the type of IRQ. Valid values are + * - MPU_SLAVE_IRQ_TYPE_NONE + * - MPU_SLAVE_IRQ_TYPE_MOTION + * - MPU_SLAVE_IRQ_TYPE_DATA_READY + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_set_irq(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct bma250_config *config, + int apply, long irq_type) +{ + int result = INV_SUCCESS; + unsigned char irqtype_reg; + unsigned char irqdst_reg; + unsigned char irqsrc_reg; + + switch (irq_type) { + case MPU_SLAVE_IRQ_TYPE_DATA_READY: + /* data ready int. */ + irqtype_reg = BMA250_INT_TYPE_MASK; + /* routed to interrupt pin 1 */ + irqdst_reg = BMA250_INT_DST_1_MASK; + /* from filtered data */ + irqsrc_reg = BMA250_INT_SRC_MASK; + break; + /* unfinished + case MPU_SLAVE_IRQ_TYPE_MOTION: + reg1 = 0x00; + reg2 = config->mot_int1_cfg; + reg3 = ; + break; + */ + case MPU_SLAVE_IRQ_TYPE_NONE: + irqtype_reg = 0x00; + irqdst_reg = 0x00; + irqsrc_reg = 0x00; + break; + default: + return INV_ERROR_INVALID_PARAMETER; + break; + } + + config->irq_type = (unsigned char)irq_type; + + if (apply) { + /* select the type of interrupt to use */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA250_INT_TYPE_REG, irqtype_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* select to which interrupt pin to route it to */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA250_INT_DST_REG, irqdst_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* select whether the interrupt works off filtered or + unfiltered data */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA250_INT_SRC_REG, irqsrc_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +/** + * @brief Set the output data rate for the particular configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * Config to modify with new ODR. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param odr + * Output data rate in units of 1/1000Hz (mHz). + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct bma250_config *config, + int apply, + long odr) +{ + int result = INV_SUCCESS; + unsigned char reg_odr; + + if (odr >= 1000000) { + reg_odr = 0x0F; + config->odr = 1000000; + } else if (odr >= 500000) { + reg_odr = 0x0E; + config->odr = 500000; + } else if (odr >= 250000) { + reg_odr = 0x0D; + config->odr = 250000; + } else if (odr >= 125000) { + reg_odr = 0x0C; + config->odr = 125000; + } else if (odr >= 62500) { + reg_odr = 0x0B; + config->odr = 62500; + } else if (odr >= 31250) { + reg_odr = 0x0A; + config->odr = 31250; + } else if (odr >= 15630) { + reg_odr = 0x09; + config->odr = 15630; + } else { + reg_odr = 0x08; + config->odr = 7810; + } + + if (apply) { + MPL_LOGV("ODR: %d\n", config->odr); + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA250_ODR_REG, reg_odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + return result; +} + +/** + * @brief Set the full scale range of the accels + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * pointer to configuration. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param fsr + * requested full scale range. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct bma250_config *config, + int apply, + long fsr) +{ + int result = INV_SUCCESS; + unsigned char reg_fsr_mask; + + if (fsr <= 2000) { + reg_fsr_mask = 0x03; + config->fsr = 2000; + } else if (fsr <= 4000) { + reg_fsr_mask = 0x05; + config->fsr = 4000; + } else if (fsr <= 8000) { + reg_fsr_mask = 0x08; + config->fsr = 8000; + } else { /* 8001 -> oo */ + reg_fsr_mask = 0x0C; + config->fsr = 16000; + } + + if (apply) { + MPL_LOGV("FSR: %d\n", config->fsr); + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA250_FSR_REG, reg_fsr_mask); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +/** + * @brief one-time device driver initialization function. + * If the driver is built as a kernel module, this function will be + * called when the module is loaded in the kernel. + * If the driver is built-in in the kernel, this function will be + * called at boot time. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + long range; + + struct bma250_private_data *private_data; + private_data = kzalloc(sizeof(struct bma250_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA250_SOFTRESET_REG, BMA250_SOFTRESET_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(1); + + result = bma250_set_odr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma250_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + range = range_fixedpoint_to_long_mg(slave->range); + result = bma250_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, range); + result = bma250_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, range); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = bma250_set_irq(mlsl_handle, pdata, &private_data->suspend, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma250_set_irq(mlsl_handle, pdata, &private_data->resume, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA250_PWR_REG, BMA250_PWR_SLEEP_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief one-time device driver exit function. + * If the driver is built as a kernel module, this function will be + * called when the module is removed from the kernel. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +/** + * @brief device configuration facility. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to the configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct bma250_private_data *private_data = + (struct bma250_private_data *)(pdata->private_data); + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return bma250_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return bma250_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return bma250_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return bma250_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + return bma250_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_RESUME: + return bma250_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + return INV_SUCCESS; +} + +/** + * @brief facility to retrieve the device configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to store the returned configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct bma250_private_data *private_data = + (struct bma250_private_data *)(pdata->private_data); + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.irq_type; + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.irq_type; + break; + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +/** + * @brief suspends the device to put it in its lowest power mode. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + struct bma250_config *suspend_config = + &((struct bma250_private_data *)pdata->private_data)->suspend; + + result = bma250_set_odr(mlsl_handle, pdata, suspend_config, + TRUE, suspend_config->odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma250_set_fsr(mlsl_handle, pdata, suspend_config, + TRUE, suspend_config->fsr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma250_set_irq(mlsl_handle, pdata, suspend_config, + TRUE, suspend_config->irq_type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA250_PWR_REG, BMA250_PWR_SLEEP_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + msleep(3); /* 3 ms powerup time maximum */ + return result; +} + +/** + * @brief resume the device in the proper power state given the configuration + * chosen. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + struct bma250_config *resume_config = + &((struct bma250_private_data *)pdata->private_data)->resume; + + result = bma250_set_odr(mlsl_handle, pdata, resume_config, + TRUE, resume_config->odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma250_set_fsr(mlsl_handle, pdata, resume_config, + TRUE, resume_config->fsr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = bma250_set_irq(mlsl_handle, pdata, resume_config, + TRUE, resume_config->irq_type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->address, + BMA250_PWR_REG, BMA250_PWR_AWAKE_MASK); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief read the sensor data from the device. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a buffer to store the data read. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int bma250_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result = INV_SUCCESS; + if (1) { /* KLP - workaroud for small data ready window */ + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, data); + return result; + } else + return INV_ERROR_ACCEL_DATA_NOT_READY; +} + +static struct ext_slave_descr bma250_descr = { + .init = bma250_init, + .exit = bma250_exit, + .suspend = bma250_suspend, + .resume = bma250_resume, + .read = bma250_read, + .config = bma250_config, + .get_config = bma250_get_config, + .name = "bma250", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_BMA250, + .read_reg = 0x02, + .read_len = 6, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {2, 0}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *bma250_get_slave_descr(void) +{ + return &bma250_descr; +} + +/* -------------------------------------------------------------------------- */ + +/* Platform data for the MPU */ +struct bma250_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int bma250_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct bma250_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + bma250_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int bma250_mod_remove(struct i2c_client *client) +{ + struct bma250_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + bma250_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id bma250_mod_id[] = { + { "bma250", ACCEL_ID_BMA250 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, bma250_mod_id); + +static struct i2c_driver bma250_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = bma250_mod_probe, + .remove = bma250_mod_remove, + .id_table = bma250_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "bma250_mod", + }, + .address_list = normal_i2c, +}; + +static int __init bma250_mod_init(void) +{ + int res = i2c_add_driver(&bma250_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "bma250_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit bma250_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&bma250_mod_driver); +} + +module_init(bma250_mod_init); +module_exit(bma250_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate BMA250 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("bma250_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/cma3000.c b/drivers/misc/inv_mpu/accel/cma3000.c new file mode 100644 index 0000000..17bdee4 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/cma3000.c @@ -0,0 +1,222 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/* + * @addtogroup ACCELDL + * @brief Accelerometer setup and handling methods for VTI CMA3000. + * + * @{ + * @file cma3000.c + * @brief Accelerometer setup and handling methods for VTI CMA3000 + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* -------------------------------------------------------------------------- */ + +static int cma3000_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + /* RAM reset */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, 0x1d, 0xcd); + return result; +} + +static int cma3000_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + + return INV_SUCCESS; +} + +static int cma3000_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result; + result = inv_serial_read(mlsl_handle, pdata->address, + slave->reg, slave->len, data); + return result; +} + +static struct ext_slave_descr cma3000_descr = { + .init = NULL, + .exit = NULL, + .suspend = cma3000_suspend, + .resume = cma3000_resume, + .read = cma3000_read, + .config = NULL, + .get_config = NULL, + .name = "cma3000", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ID_INVALID, + .read_reg = 0x06, + .read_len = 6, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {2, 0}, + .trigger = NULL, + +}; + +static +struct ext_slave_descr *cma3000_get_slave_descr(void) +{ + return &cma3000_descr; +} + +/* -------------------------------------------------------------------------- */ + +struct cma3000_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int cma3000_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct cma3000_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + cma3000_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int cma3000_mod_remove(struct i2c_client *client) +{ + struct cma3000_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + cma3000_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id cma3000_mod_id[] = { + { "cma3000", ACCEL_ID_CMA3000 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cma3000_mod_id); + +static struct i2c_driver cma3000_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = cma3000_mod_probe, + .remove = cma3000_mod_remove, + .id_table = cma3000_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "cma3000_mod", + }, + .address_list = normal_i2c, +}; + +static int __init cma3000_mod_init(void) +{ + int res = i2c_add_driver(&cma3000_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "cma3000_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit cma3000_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&cma3000_mod_driver); +} + +module_init(cma3000_mod_init); +module_exit(cma3000_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate CMA3000 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cma3000_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/kxsd9.c b/drivers/misc/inv_mpu/accel/kxsd9.c new file mode 100644 index 0000000..7d19df5 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/kxsd9.c @@ -0,0 +1,264 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Accelerometer setup and handling methods for Kionix KXSD9. + * + * @{ + * @file kxsd9.c + * @brief Accelerometer setup and handling methods for Kionix KXSD9. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* -------------------------------------------------------------------------- */ + +static int kxsd9_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + /* CTRL_REGB: low-power standby mode */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, 0x0d, 0x0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +/* full scale setting - register and mask */ +#define ACCEL_KIONIX_CTRL_REG (0x0C) +#define ACCEL_KIONIX_CTRL_MASK (0x3) + +static int kxsd9_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + unsigned char reg; + + /* Full Scale */ + reg = 0x0; + reg &= ~ACCEL_KIONIX_CTRL_MASK; + reg |= 0x00; + if (slave->range.mantissa == 4) { /* 4g scale = 4.9951 */ + reg |= 0x2; + slave->range.fraction = 9951; + } else if (slave->range.mantissa == 7) { /* 6g scale = 7.5018 */ + reg |= 0x1; + slave->range.fraction = 5018; + } else if (slave->range.mantissa == 9) { /* 8g scale = 9.9902 */ + reg |= 0x0; + slave->range.fraction = 9902; + } else { + slave->range.mantissa = 2; /* 2g scale = 2.5006 */ + slave->range.fraction = 5006; + reg |= 0x3; + } + reg |= 0xC0; /* 100Hz LPF */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_KIONIX_CTRL_REG, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* normal operation */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, 0x0d, 0x40); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return INV_SUCCESS; +} + +static int kxsd9_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result; + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, data); + return result; +} + +static struct ext_slave_descr kxsd9_descr = { + .init = NULL, + .exit = NULL, + .suspend = kxsd9_suspend, + .resume = kxsd9_resume, + .read = kxsd9_read, + .config = NULL, + .get_config = NULL, + .name = "kxsd9", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_KXSD9, + .read_reg = 0x00, + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {2, 5006}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *kxsd9_get_slave_descr(void) +{ + return &kxsd9_descr; +} + +/* -------------------------------------------------------------------------- */ +struct kxsd9_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int kxsd9_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct kxsd9_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + kxsd9_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int kxsd9_mod_remove(struct i2c_client *client) +{ + struct kxsd9_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + kxsd9_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id kxsd9_mod_id[] = { + { "kxsd9", ACCEL_ID_KXSD9 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, kxsd9_mod_id); + +static struct i2c_driver kxsd9_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = kxsd9_mod_probe, + .remove = kxsd9_mod_remove, + .id_table = kxsd9_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "kxsd9_mod", + }, + .address_list = normal_i2c, +}; + +static int __init kxsd9_mod_init(void) +{ + int res = i2c_add_driver(&kxsd9_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "kxsd9_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit kxsd9_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&kxsd9_mod_driver); +} + +module_init(kxsd9_mod_init); +module_exit(kxsd9_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate KXSD9 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("kxsd9_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/kxtf9.c b/drivers/misc/inv_mpu/accel/kxtf9.c new file mode 100644 index 0000000..4a6b4b0 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/kxtf9.c @@ -0,0 +1,841 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Accelerometer setup and handling methods for Kionix KXTF9. + * + * @{ + * @file kxtf9.c + * @brief Accelerometer setup and handling methods for Kionix KXTF9. +*/ + +/* -------------------------------------------------------------------------- */ + +#undef MPL_LOG_NDEBUG +#define MPL_LOG_NDEBUG 1 + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +#define KXTF9_XOUT_HPF_L (0x00) /* 0000 0000 */ +#define KXTF9_XOUT_HPF_H (0x01) /* 0000 0001 */ +#define KXTF9_YOUT_HPF_L (0x02) /* 0000 0010 */ +#define KXTF9_YOUT_HPF_H (0x03) /* 0000 0011 */ +#define KXTF9_ZOUT_HPF_L (0x04) /* 0001 0100 */ +#define KXTF9_ZOUT_HPF_H (0x05) /* 0001 0101 */ +#define KXTF9_XOUT_L (0x06) /* 0000 0110 */ +#define KXTF9_XOUT_H (0x07) /* 0000 0111 */ +#define KXTF9_YOUT_L (0x08) /* 0000 1000 */ +#define KXTF9_YOUT_H (0x09) /* 0000 1001 */ +#define KXTF9_ZOUT_L (0x0A) /* 0001 1010 */ +#define KXTF9_ZOUT_H (0x0B) /* 0001 1011 */ +#define KXTF9_ST_RESP (0x0C) /* 0000 1100 */ +#define KXTF9_WHO_AM_I (0x0F) /* 0000 1111 */ +#define KXTF9_TILT_POS_CUR (0x10) /* 0001 0000 */ +#define KXTF9_TILT_POS_PRE (0x11) /* 0001 0001 */ +#define KXTF9_INT_SRC_REG1 (0x15) /* 0001 0101 */ +#define KXTF9_INT_SRC_REG2 (0x16) /* 0001 0110 */ +#define KXTF9_STATUS_REG (0x18) /* 0001 1000 */ +#define KXTF9_INT_REL (0x1A) /* 0001 1010 */ +#define KXTF9_CTRL_REG1 (0x1B) /* 0001 1011 */ +#define KXTF9_CTRL_REG2 (0x1C) /* 0001 1100 */ +#define KXTF9_CTRL_REG3 (0x1D) /* 0001 1101 */ +#define KXTF9_INT_CTRL_REG1 (0x1E) /* 0001 1110 */ +#define KXTF9_INT_CTRL_REG2 (0x1F) /* 0001 1111 */ +#define KXTF9_INT_CTRL_REG3 (0x20) /* 0010 0000 */ +#define KXTF9_DATA_CTRL_REG (0x21) /* 0010 0001 */ +#define KXTF9_TILT_TIMER (0x28) /* 0010 1000 */ +#define KXTF9_WUF_TIMER (0x29) /* 0010 1001 */ +#define KXTF9_TDT_TIMER (0x2B) /* 0010 1011 */ +#define KXTF9_TDT_H_THRESH (0x2C) /* 0010 1100 */ +#define KXTF9_TDT_L_THRESH (0x2D) /* 0010 1101 */ +#define KXTF9_TDT_TAP_TIMER (0x2E) /* 0010 1110 */ +#define KXTF9_TDT_TOTAL_TIMER (0x2F) /* 0010 1111 */ +#define KXTF9_TDT_LATENCY_TIMER (0x30) /* 0011 0000 */ +#define KXTF9_TDT_WINDOW_TIMER (0x31) /* 0011 0001 */ +#define KXTF9_WUF_THRESH (0x5A) /* 0101 1010 */ +#define KXTF9_TILT_ANGLE (0x5C) /* 0101 1100 */ +#define KXTF9_HYST_SET (0x5F) /* 0101 1111 */ + +#define KXTF9_MAX_DUR (0xFF) +#define KXTF9_MAX_THS (0xFF) +#define KXTF9_THS_COUNTS_P_G (32) + +/* -------------------------------------------------------------------------- */ + +struct kxtf9_config { + unsigned int odr; /* Output data rate mHz */ + unsigned int fsr; /* full scale range mg */ + unsigned int ths; /* Motion no-motion thseshold mg */ + unsigned int dur; /* Motion no-motion duration ms */ + unsigned int irq_type; + unsigned char reg_ths; + unsigned char reg_dur; + unsigned char reg_odr; + unsigned char reg_int_cfg1; + unsigned char reg_int_cfg2; + unsigned char ctrl_reg1; +}; + +struct kxtf9_private_data { + struct kxtf9_config suspend; + struct kxtf9_config resume; +}; + +static int kxtf9_set_ths(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct kxtf9_config *config, int apply, long ths) +{ + int result = INV_SUCCESS; + if ((ths * KXTF9_THS_COUNTS_P_G / 1000) > KXTF9_MAX_THS) + ths = (KXTF9_MAX_THS * 1000) / KXTF9_THS_COUNTS_P_G; + + if (ths < 0) + ths = 0; + + config->ths = ths; + config->reg_ths = (unsigned char) + ((long)(ths * KXTF9_THS_COUNTS_P_G) / 1000); + MPL_LOGV("THS: %d, 0x%02x\n", config->ths, (int)config->reg_ths); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_WUF_THRESH, + config->reg_ths); + return result; +} + +static int kxtf9_set_dur(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct kxtf9_config *config, int apply, long dur) +{ + int result = INV_SUCCESS; + long reg_dur = (dur * config->odr) / 1000000; + config->dur = dur; + + if (reg_dur > KXTF9_MAX_DUR) + reg_dur = KXTF9_MAX_DUR; + + config->reg_dur = (unsigned char)reg_dur; + MPL_LOGV("DUR: %d, 0x%02x\n", config->dur, (int)config->reg_dur); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_WUF_TIMER, + (unsigned char)reg_dur); + return result; +} + +/** + * Sets the IRQ to fire when one of the IRQ events occur. Threshold and + * duration will not be used uless the type is MOT or NMOT. + * + * @param config configuration to apply to, suspend or resume + * @param irq_type The type of IRQ. Valid values are + * - MPU_SLAVE_IRQ_TYPE_NONE + * - MPU_SLAVE_IRQ_TYPE_MOTION + * - MPU_SLAVE_IRQ_TYPE_DATA_READY + */ +static int kxtf9_set_irq(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct kxtf9_config *config, int apply, long irq_type) +{ + int result = INV_SUCCESS; + struct kxtf9_private_data *private_data = pdata->private_data; + + config->irq_type = (unsigned char)irq_type; + config->ctrl_reg1 &= ~0x22; + if (irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + config->ctrl_reg1 |= 0x20; + config->reg_int_cfg1 = 0x38; + config->reg_int_cfg2 = 0x00; + } else if (irq_type == MPU_SLAVE_IRQ_TYPE_MOTION) { + config->ctrl_reg1 |= 0x02; + if ((unsigned long)config == + (unsigned long)&private_data->suspend) + config->reg_int_cfg1 = 0x34; + else + config->reg_int_cfg1 = 0x24; + config->reg_int_cfg2 = 0xE0; + } else { + config->reg_int_cfg1 = 0x00; + config->reg_int_cfg2 = 0x00; + } + + if (apply) { + /* Must clear bit 7 before writing new configuration */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, 0x40); + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_INT_CTRL_REG1, + config->reg_int_cfg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_INT_CTRL_REG2, + config->reg_int_cfg2); + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, + config->ctrl_reg1); + } + MPL_LOGV("CTRL_REG1: %lx, INT_CFG1: %lx, INT_CFG2: %lx\n", + (unsigned long)config->ctrl_reg1, + (unsigned long)config->reg_int_cfg1, + (unsigned long)config->reg_int_cfg2); + + return result; +} + +/** + * Set the Output data rate for the particular configuration + * + * @param config Config to modify with new ODR + * @param odr Output data rate in units of 1/1000Hz + */ +static int kxtf9_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct kxtf9_config *config, int apply, long odr) +{ + unsigned char bits; + int result = INV_SUCCESS; + + /* Data sheet says there is 12.5 hz, but that seems to produce a single + * correct data value, thus we remove it from the table */ + if (odr > 400000) { + config->odr = 800000; + bits = 0x06; + } else if (odr > 200000) { + config->odr = 400000; + bits = 0x05; + } else if (odr > 100000) { + config->odr = 200000; + bits = 0x04; + } else if (odr > 50000) { + config->odr = 100000; + bits = 0x03; + } else if (odr > 25000) { + config->odr = 50000; + bits = 0x02; + } else if (odr != 0) { + config->odr = 25000; + bits = 0x01; + } else { + config->odr = 0; + bits = 0; + } + + if (odr != 0) + config->ctrl_reg1 |= 0x80; + else + config->ctrl_reg1 &= ~0x80; + + config->reg_odr = bits; + kxtf9_set_dur(mlsl_handle, pdata, config, apply, config->dur); + MPL_LOGV("ODR: %d, 0x%02x\n", config->odr, (int)config->ctrl_reg1); + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_DATA_CTRL_REG, + config->reg_odr); + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, 0x40); + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, + config->ctrl_reg1); + } + return result; +} + +/** + * Set the full scale range of the accels + * + * @param config pointer to configuration + * @param fsr requested full scale range + */ +static int kxtf9_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct kxtf9_config *config, int apply, long fsr) +{ + int result = INV_SUCCESS; + + config->ctrl_reg1 = (config->ctrl_reg1 & 0xE7); + if (fsr <= 2000) { + config->fsr = 2000; + config->ctrl_reg1 |= 0x00; + } else if (fsr <= 4000) { + config->fsr = 4000; + config->ctrl_reg1 |= 0x08; + } else { + config->fsr = 8000; + config->ctrl_reg1 |= 0x10; + } + + MPL_LOGV("FSR: %d\n", config->fsr); + if (apply) { + /* Must clear bit 7 before writing new configuration */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, 0x40); + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, + config->ctrl_reg1); + } + return result; +} + +static int kxtf9_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char data; + struct kxtf9_private_data *private_data = pdata->private_data; + + /* Wake up */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, 0x40); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* INT_CTRL_REG1: */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_INT_CTRL_REG1, + private_data->suspend.reg_int_cfg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* WUF_THRESH: */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_WUF_THRESH, + private_data->suspend.reg_ths); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* DATA_CTRL_REG */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_DATA_CTRL_REG, + private_data->suspend.reg_odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* WUF_TIMER */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_WUF_TIMER, + private_data->suspend.reg_dur); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Normal operation */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, + private_data->suspend.ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, pdata->address, + KXTF9_INT_REL, 1, &data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/* full scale setting - register and mask */ +#define ACCEL_KIONIX_CTRL_REG (0x1b) +#define ACCEL_KIONIX_CTRL_MASK (0x18) + +static int kxtf9_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + unsigned char data; + struct kxtf9_private_data *private_data = pdata->private_data; + + /* Wake up */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, 0x40); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* INT_CTRL_REG1: */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_INT_CTRL_REG1, + private_data->resume.reg_int_cfg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* WUF_THRESH: */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_WUF_THRESH, + private_data->resume.reg_ths); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* DATA_CTRL_REG */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_DATA_CTRL_REG, + private_data->resume.reg_odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* WUF_TIMER */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_WUF_TIMER, + private_data->resume.reg_dur); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Normal operation */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, + private_data->resume.ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, pdata->address, + KXTF9_INT_REL, 1, &data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return INV_SUCCESS; +} + +static int kxtf9_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + + struct kxtf9_private_data *private_data; + int result = INV_SUCCESS; + + private_data = (struct kxtf9_private_data *) + kzalloc(sizeof(struct kxtf9_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + /* RAM reset */ + /* Fastest Reset */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG1, 0x40); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Fastest Reset */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_DATA_CTRL_REG, 0x36); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Reset */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + KXTF9_CTRL_REG3, 0xcd); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(2); + + pdata->private_data = private_data; + + private_data->resume.ctrl_reg1 = 0xC0; + private_data->suspend.ctrl_reg1 = 0x40; + + result = kxtf9_set_dur(mlsl_handle, pdata, &private_data->suspend, + FALSE, 1000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = kxtf9_set_dur(mlsl_handle, pdata, &private_data->resume, + FALSE, 2540); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = kxtf9_set_odr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 50000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = kxtf9_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + + result = kxtf9_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 2000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = kxtf9_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, 2000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = kxtf9_set_ths(mlsl_handle, pdata, &private_data->suspend, + FALSE, 80); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = kxtf9_set_ths(mlsl_handle, pdata, &private_data->resume, + FALSE, 40); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = kxtf9_set_irq(mlsl_handle, pdata, &private_data->suspend, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = kxtf9_set_irq(mlsl_handle, pdata, &private_data->resume, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int kxtf9_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static int kxtf9_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct kxtf9_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return kxtf9_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return kxtf9_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return kxtf9_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return kxtf9_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_THS: + return kxtf9_set_ths(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_THS: + return kxtf9_set_ths(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_DUR: + return kxtf9_set_dur(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_DUR: + return kxtf9_set_dur(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + return kxtf9_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_RESUME: + return kxtf9_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static int kxtf9_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct kxtf9_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_MOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.ths; + break; + case MPU_SLAVE_CONFIG_NMOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.ths; + break; + case MPU_SLAVE_CONFIG_MOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.dur; + break; + case MPU_SLAVE_CONFIG_NMOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.dur; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.irq_type; + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.irq_type; + break; + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static int kxtf9_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result; + unsigned char reg; + result = inv_serial_read(mlsl_handle, pdata->address, + KXTF9_INT_SRC_REG2, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (!(reg & 0x10)) + return INV_ERROR_ACCEL_DATA_NOT_READY; + + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static struct ext_slave_descr kxtf9_descr = { + .init = kxtf9_init, + .exit = kxtf9_exit, + .suspend = kxtf9_suspend, + .resume = kxtf9_resume, + .read = kxtf9_read, + .config = kxtf9_config, + .get_config = kxtf9_get_config, + .name = "kxtf9", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_KXTF9, + .read_reg = 0x06, + .read_len = 6, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {2, 0}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *kxtf9_get_slave_descr(void) +{ + return &kxtf9_descr; +} + +/* -------------------------------------------------------------------------- */ +struct kxtf9_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int kxtf9_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct kxtf9_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + kxtf9_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int kxtf9_mod_remove(struct i2c_client *client) +{ + struct kxtf9_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + kxtf9_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id kxtf9_mod_id[] = { + { "kxtf9", ACCEL_ID_KXTF9 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, kxtf9_mod_id); + +static struct i2c_driver kxtf9_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = kxtf9_mod_probe, + .remove = kxtf9_mod_remove, + .id_table = kxtf9_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "kxtf9_mod", + }, + .address_list = normal_i2c, +}; + +static int __init kxtf9_mod_init(void) +{ + int res = i2c_add_driver(&kxtf9_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "kxtf9_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit kxtf9_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&kxtf9_mod_driver); +} + +module_init(kxtf9_mod_init); +module_exit(kxtf9_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate KXTF9 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("kxtf9_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/lis331.c b/drivers/misc/inv_mpu/accel/lis331.c new file mode 100644 index 0000000..adf33e5 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/lis331.c @@ -0,0 +1,740 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file lis331.c + * @brief Accelerometer setup and handling methods for ST LIS331DLH. + */ + +/* -------------------------------------------------------------------------- */ + +#undef MPL_LOG_NDEBUG +#define MPL_LOG_NDEBUG 1 + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* full scale setting - register & mask */ +#define LIS331_CTRL_REG1 (0x20) +#define LIS331_CTRL_REG2 (0x21) +#define LIS331_CTRL_REG3 (0x22) +#define LIS331_CTRL_REG4 (0x23) +#define LIS331_CTRL_REG5 (0x24) +#define LIS331_HP_FILTER_RESET (0x25) +#define LIS331_REFERENCE (0x26) +#define LIS331_STATUS_REG (0x27) +#define LIS331_OUT_X_L (0x28) +#define LIS331_OUT_X_H (0x29) +#define LIS331_OUT_Y_L (0x2a) +#define LIS331_OUT_Y_H (0x2b) +#define LIS331_OUT_Z_L (0x2b) +#define LIS331_OUT_Z_H (0x2d) + +#define LIS331_INT1_CFG (0x30) +#define LIS331_INT1_SRC (0x31) +#define LIS331_INT1_THS (0x32) +#define LIS331_INT1_DURATION (0x33) + +#define LIS331_INT2_CFG (0x34) +#define LIS331_INT2_SRC (0x35) +#define LIS331_INT2_THS (0x36) +#define LIS331_INT2_DURATION (0x37) + +#define LIS331_CTRL_MASK (0x30) +#define LIS331_SLEEP_MASK (0x20) + +#define LIS331_MAX_DUR (0x7F) + +/* -------------------------------------------------------------------------- */ + +struct lis331dlh_config { + unsigned int odr; + unsigned int fsr; /* full scale range mg */ + unsigned int ths; /* Motion no-motion thseshold mg */ + unsigned int dur; /* Motion no-motion duration ms */ + unsigned char reg_ths; + unsigned char reg_dur; + unsigned char ctrl_reg1; + unsigned char irq_type; + unsigned char mot_int1_cfg; +}; + +struct lis331dlh_private_data { + struct lis331dlh_config suspend; + struct lis331dlh_config resume; +}; + +/* -------------------------------------------------------------------------- */ +static int lis331dlh_set_ths(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis331dlh_config *config, + int apply, long ths) +{ + int result = INV_SUCCESS; + if ((unsigned int)ths >= config->fsr) + ths = (long)config->fsr - 1; + + if (ths < 0) + ths = 0; + + config->ths = ths; + config->reg_ths = (unsigned char)(long)((ths * 128L) / (config->fsr)); + MPL_LOGV("THS: %d, 0x%02x\n", config->ths, (int)config->reg_ths); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_THS, + config->reg_ths); + return result; +} + +static int lis331dlh_set_dur(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis331dlh_config *config, + int apply, long dur) +{ + int result = INV_SUCCESS; + long reg_dur = (dur * config->odr) / 1000000L; + config->dur = dur; + + if (reg_dur > LIS331_MAX_DUR) + reg_dur = LIS331_MAX_DUR; + + config->reg_dur = (unsigned char)reg_dur; + MPL_LOGV("DUR: %d, 0x%02x\n", config->dur, (int)config->reg_dur); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_DURATION, + (unsigned char)reg_dur); + return result; +} + +/** + * Sets the IRQ to fire when one of the IRQ events occur. Threshold and + * duration will not be used uless the type is MOT or NMOT. + * + * @param config configuration to apply to, suspend or resume + * @param irq_type The type of IRQ. Valid values are + * - MPU_SLAVE_IRQ_TYPE_NONE + * - MPU_SLAVE_IRQ_TYPE_MOTION + * - MPU_SLAVE_IRQ_TYPE_DATA_READY + */ +static int lis331dlh_set_irq(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis331dlh_config *config, + int apply, long irq_type) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + + config->irq_type = (unsigned char)irq_type; + if (irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x02; + reg2 = 0x00; + } else if (irq_type == MPU_SLAVE_IRQ_TYPE_MOTION) { + reg1 = 0x00; + reg2 = config->mot_int1_cfg; + } else { + reg1 = 0x00; + reg2 = 0x00; + } + + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG3, reg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_CFG, reg2); + } + + return result; +} + +/** + * Set the Output data rate for the particular configuration + * + * @param config Config to modify with new ODR + * @param odr Output data rate in units of 1/1000Hz + */ +static int lis331dlh_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis331dlh_config *config, + int apply, long odr) +{ + unsigned char bits; + int result = INV_SUCCESS; + + if (odr > 400000) { + config->odr = 1000000; + bits = 0x38; + } else if (odr > 100000) { + config->odr = 400000; + bits = 0x30; + } else if (odr > 50000) { + config->odr = 100000; + bits = 0x28; + } else if (odr > 10000) { + config->odr = 50000; + bits = 0x20; + } else if (odr > 5000) { + config->odr = 10000; + bits = 0xC0; + } else if (odr > 2000) { + config->odr = 5000; + bits = 0xB0; + } else if (odr > 1000) { + config->odr = 2000; + bits = 0x80; + } else if (odr > 500) { + config->odr = 1000; + bits = 0x60; + } else if (odr > 0) { + config->odr = 500; + bits = 0x40; + } else { + config->odr = 0; + bits = 0; + } + + config->ctrl_reg1 = bits | (config->ctrl_reg1 & 0x7); + lis331dlh_set_dur(mlsl_handle, pdata, config, apply, config->dur); + MPL_LOGV("ODR: %d, 0x%02x\n", config->odr, (int)config->ctrl_reg1); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG1, + config->ctrl_reg1); + return result; +} + +/** + * Set the full scale range of the accels + * + * @param config pointer to configuration + * @param fsr requested full scale range + */ +static int lis331dlh_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis331dlh_config *config, + int apply, long fsr) +{ + unsigned char reg1 = 0x40; + int result = INV_SUCCESS; + + if (fsr <= 2048) { + config->fsr = 2048; + } else if (fsr <= 4096) { + reg1 |= 0x30; + config->fsr = 4096; + } else { + reg1 |= 0x10; + config->fsr = 8192; + } + + lis331dlh_set_ths(mlsl_handle, pdata, config, apply, config->ths); + MPL_LOGV("FSR: %d\n", config->fsr); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG4, reg1); + + return result; +} + +static int lis331dlh_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + struct lis331dlh_private_data *private_data = + (struct lis331dlh_private_data *)(pdata->private_data); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG1, + private_data->suspend.ctrl_reg1); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG2, 0x0f); + reg1 = 0x40; + if (private_data->suspend.fsr == 8192) + reg1 |= 0x30; + else if (private_data->suspend.fsr == 4096) + reg1 |= 0x10; + /* else bits [4..5] are already zero */ + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG4, reg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_THS, + private_data->suspend.reg_ths); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_DURATION, + private_data->suspend.reg_dur); + + if (private_data->suspend.irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x02; + reg2 = 0x00; + } else if (private_data->suspend.irq_type == + MPU_SLAVE_IRQ_TYPE_MOTION) { + reg1 = 0x00; + reg2 = private_data->suspend.mot_int1_cfg; + } else { + reg1 = 0x00; + reg2 = 0x00; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG3, reg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_CFG, reg2); + result = inv_serial_read(mlsl_handle, pdata->address, + LIS331_HP_FILTER_RESET, 1, ®1); + return result; +} + +static int lis331dlh_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + struct lis331dlh_private_data *private_data = + (struct lis331dlh_private_data *)(pdata->private_data); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG1, + private_data->resume.ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(6); + + /* Full Scale */ + reg1 = 0x40; + if (private_data->resume.fsr == 8192) + reg1 |= 0x30; + else if (private_data->resume.fsr == 4096) + reg1 |= 0x10; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG4, reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Configure high pass filter */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG2, 0x0F); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (private_data->resume.irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x02; + reg2 = 0x00; + } else if (private_data->resume.irq_type == MPU_SLAVE_IRQ_TYPE_MOTION) { + reg1 = 0x00; + reg2 = private_data->resume.mot_int1_cfg; + } else { + reg1 = 0x00; + reg2 = 0x00; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG3, reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_THS, + private_data->resume.reg_ths); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_DURATION, + private_data->resume.reg_dur); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_CFG, reg2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, pdata->address, + LIS331_HP_FILTER_RESET, 1, ®1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int lis331dlh_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result = INV_SUCCESS; + result = inv_serial_read(mlsl_handle, pdata->address, + LIS331_STATUS_REG, 1, data); + if (data[0] & 0x0F) { + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, + data); + return result; + } else + return INV_ERROR_ACCEL_DATA_NOT_READY; +} + +static int lis331dlh_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + struct lis331dlh_private_data *private_data; + long range; + private_data = (struct lis331dlh_private_data *) + kzalloc(sizeof(struct lis331dlh_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + private_data->resume.ctrl_reg1 = 0x37; + private_data->suspend.ctrl_reg1 = 0x47; + private_data->resume.mot_int1_cfg = 0x95; + private_data->suspend.mot_int1_cfg = 0x2a; + + lis331dlh_set_odr(mlsl_handle, pdata, &private_data->suspend, FALSE, 0); + lis331dlh_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + + range = range_fixedpoint_to_long_mg(slave->range); + lis331dlh_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, range); + lis331dlh_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, range); + + lis331dlh_set_ths(mlsl_handle, pdata, &private_data->suspend, + FALSE, 80); + lis331dlh_set_ths(mlsl_handle, pdata, &private_data->resume, FALSE, 40); + + + lis331dlh_set_dur(mlsl_handle, pdata, &private_data->suspend, + FALSE, 1000); + lis331dlh_set_dur(mlsl_handle, pdata, &private_data->resume, + FALSE, 2540); + + lis331dlh_set_irq(mlsl_handle, pdata, &private_data->suspend, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + lis331dlh_set_irq(mlsl_handle, pdata, &private_data->resume, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + return INV_SUCCESS; +} + +static int lis331dlh_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static int lis331dlh_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct lis331dlh_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return lis331dlh_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return lis331dlh_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return lis331dlh_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return lis331dlh_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_THS: + return lis331dlh_set_ths(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_THS: + return lis331dlh_set_ths(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_DUR: + return lis331dlh_set_dur(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_DUR: + return lis331dlh_set_dur(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + return lis331dlh_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_RESUME: + return lis331dlh_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static int lis331dlh_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct lis331dlh_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_MOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.ths; + break; + case MPU_SLAVE_CONFIG_NMOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.ths; + break; + case MPU_SLAVE_CONFIG_MOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.dur; + break; + case MPU_SLAVE_CONFIG_NMOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.dur; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.irq_type; + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.irq_type; + break; + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static struct ext_slave_descr lis331dlh_descr = { + .init = lis331dlh_init, + .exit = lis331dlh_exit, + .suspend = lis331dlh_suspend, + .resume = lis331dlh_resume, + .read = lis331dlh_read, + .config = lis331dlh_config, + .get_config = lis331dlh_get_config, + .name = "lis331dlh", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_LIS331, + .read_reg = (0x28 | 0x80), /* 0x80 for burst reads */ + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {2, 480}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *lis331_get_slave_descr(void) +{ + return &lis331dlh_descr; +} + +/* -------------------------------------------------------------------------- */ +struct lis331_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int lis331_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct lis331_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + lis331_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int lis331_mod_remove(struct i2c_client *client) +{ + struct lis331_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + lis331_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id lis331_mod_id[] = { + { "lis331", ACCEL_ID_LIS331 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lis331_mod_id); + +static struct i2c_driver lis331_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = lis331_mod_probe, + .remove = lis331_mod_remove, + .id_table = lis331_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "lis331_mod", + }, + .address_list = normal_i2c, +}; + +static int __init lis331_mod_init(void) +{ + int res = i2c_add_driver(&lis331_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "lis331_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit lis331_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&lis331_mod_driver); +} + +module_init(lis331_mod_init); +module_exit(lis331_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate LIS331 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("lis331_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/lis3dh.c b/drivers/misc/inv_mpu/accel/lis3dh.c new file mode 100644 index 0000000..9a07fcc --- /dev/null +++ b/drivers/misc/inv_mpu/accel/lis3dh.c @@ -0,0 +1,738 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file lis3dh.c + * @brief Accelerometer setup and handling methods for ST LIS3DH. + */ + +/* -------------------------------------------------------------------------- */ + +#undef MPL_LOG_NDEBUG +#define MPL_LOG_NDEBUG 0 + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* full scale setting - register & mask */ +#define LIS3DH_CTRL_REG1 (0x20) +#define LIS3DH_CTRL_REG2 (0x21) +#define LIS3DH_CTRL_REG3 (0x22) +#define LIS3DH_CTRL_REG4 (0x23) +#define LIS3DH_CTRL_REG5 (0x24) +#define LIS3DH_CTRL_REG6 (0x25) +#define LIS3DH_REFERENCE (0x26) +#define LIS3DH_STATUS_REG (0x27) +#define LIS3DH_OUT_X_L (0x28) +#define LIS3DH_OUT_X_H (0x29) +#define LIS3DH_OUT_Y_L (0x2a) +#define LIS3DH_OUT_Y_H (0x2b) +#define LIS3DH_OUT_Z_L (0x2b) +#define LIS3DH_OUT_Z_H (0x2d) + +#define LIS3DH_INT1_CFG (0x30) +#define LIS3DH_INT1_SRC (0x31) +#define LIS3DH_INT1_THS (0x32) +#define LIS3DH_INT1_DURATION (0x33) + +#define LIS3DH_MAX_DUR (0x7F) + +/* -------------------------------------------------------------------------- */ + +struct lis3dh_config { + unsigned int odr; + unsigned int fsr; /* full scale range mg */ + unsigned int ths; /* Motion no-motion thseshold mg */ + unsigned int dur; /* Motion no-motion duration ms */ + unsigned char reg_ths; + unsigned char reg_dur; + unsigned char ctrl_reg1; + unsigned char irq_type; + unsigned char mot_int1_cfg; +}; + +struct lis3dh_private_data { + struct lis3dh_config suspend; + struct lis3dh_config resume; +}; + +/* -------------------------------------------------------------------------- */ + +static int lis3dh_set_ths(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis3dh_config *config, int apply, long ths) +{ + int result = INV_SUCCESS; + if ((unsigned int)ths > 1000 * config->fsr) + ths = (long)1000 * config->fsr; + + if (ths < 0) + ths = 0; + + config->ths = ths; + config->reg_ths = (unsigned char)(long)((ths * 128L) / (config->fsr)); + MPL_LOGV("THS: %d, 0x%02x\n", config->ths, (int)config->reg_ths); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_INT1_THS, + config->reg_ths); + return result; +} + +static int lis3dh_set_dur(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis3dh_config *config, int apply, long dur) +{ + int result = INV_SUCCESS; + long reg_dur = (dur * config->odr) / 1000000L; + config->dur = dur; + + if (reg_dur > LIS3DH_MAX_DUR) + reg_dur = LIS3DH_MAX_DUR; + + config->reg_dur = (unsigned char)reg_dur; + MPL_LOGV("DUR: %d, 0x%02x\n", config->dur, (int)config->reg_dur); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_INT1_DURATION, + (unsigned char)reg_dur); + return result; +} + +/** + * Sets the IRQ to fire when one of the IRQ events occur. Threshold and + * duration will not be used uless the type is MOT or NMOT. + * + * @param config configuration to apply to, suspend or resume + * @param irq_type The type of IRQ. Valid values are + * - MPU_SLAVE_IRQ_TYPE_NONE + * - MPU_SLAVE_IRQ_TYPE_MOTION + * - MPU_SLAVE_IRQ_TYPE_DATA_READY + */ +static int lis3dh_set_irq(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis3dh_config *config, + int apply, long irq_type) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + + config->irq_type = (unsigned char)irq_type; + if (irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x10; + reg2 = 0x00; + } else if (irq_type == MPU_SLAVE_IRQ_TYPE_MOTION) { + reg1 = 0x40; + reg2 = config->mot_int1_cfg; + } else { + reg1 = 0x00; + reg2 = 0x00; + } + + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG3, reg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_INT1_CFG, reg2); + } + + return result; +} + +/** + * Set the Output data rate for the particular configuration + * + * @param config Config to modify with new ODR + * @param odr Output data rate in units of 1/1000Hz + */ +static int lis3dh_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis3dh_config *config, int apply, long odr) +{ + unsigned char bits; + int result = INV_SUCCESS; + + if (odr > 400000) { + config->odr = 1250000; + bits = 0x90; + } else if (odr > 200000) { + config->odr = 400000; + bits = 0x70; + } else if (odr > 100000) { + config->odr = 200000; + bits = 0x60; + } else if (odr > 50000) { + config->odr = 100000; + bits = 0x50; + } else if (odr > 25000) { + config->odr = 50000; + bits = 0x40; + } else if (odr > 10000) { + config->odr = 25000; + bits = 0x30; + } else if (odr > 1000) { + config->odr = 10000; + bits = 0x20; + } else if (odr > 500) { + config->odr = 1000; + bits = 0x10; + } else { + config->odr = 0; + bits = 0; + } + + config->ctrl_reg1 = bits | (config->ctrl_reg1 & 0xf); + lis3dh_set_dur(mlsl_handle, pdata, config, apply, config->dur); + MPL_LOGV("ODR: %d, 0x%02x\n", config->odr, (int)config->ctrl_reg1); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG1, + config->ctrl_reg1); + return result; +} + +/** + * Set the full scale range of the accels + * + * @param config pointer to configuration + * @param fsr requested full scale range + */ +static int lis3dh_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lis3dh_config *config, int apply, long fsr) +{ + int result = INV_SUCCESS; + unsigned char reg1 = 0x48; + + if (fsr <= 2048) { + config->fsr = 2048; + } else if (fsr <= 4096) { + reg1 |= 0x10; + config->fsr = 4096; + } else if (fsr <= 8192) { + reg1 |= 0x20; + config->fsr = 8192; + } else { + reg1 |= 0x30; + config->fsr = 16348; + } + + lis3dh_set_ths(mlsl_handle, pdata, config, apply, config->ths); + MPL_LOGV("FSR: %d\n", config->fsr); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG4, reg1); + + return result; +} + +static int lis3dh_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + struct lis3dh_private_data *private_data = pdata->private_data; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG1, + private_data->suspend.ctrl_reg1); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG2, 0x31); + reg1 = 0x48; + if (private_data->suspend.fsr == 16384) + reg1 |= 0x30; + else if (private_data->suspend.fsr == 8192) + reg1 |= 0x20; + else if (private_data->suspend.fsr == 4096) + reg1 |= 0x10; + else if (private_data->suspend.fsr == 2048) + reg1 |= 0x00; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG4, reg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_INT1_THS, + private_data->suspend.reg_ths); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_INT1_DURATION, + private_data->suspend.reg_dur); + + if (private_data->suspend.irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x10; + reg2 = 0x00; + } else if (private_data->suspend.irq_type == + MPU_SLAVE_IRQ_TYPE_MOTION) { + reg1 = 0x40; + reg2 = private_data->suspend.mot_int1_cfg; + } else { + reg1 = 0x00; + reg2 = 0x00; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG3, reg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_INT1_CFG, reg2); + result = inv_serial_read(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG6, 1, ®1); + + return result; +} + +static int lis3dh_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char reg1; + unsigned char reg2; + struct lis3dh_private_data *private_data = pdata->private_data; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG1, + private_data->resume.ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(6); + + /* Full Scale */ + reg1 = 0x48; + if (private_data->suspend.fsr == 16384) + reg1 |= 0x30; + else if (private_data->suspend.fsr == 8192) + reg1 |= 0x20; + else if (private_data->suspend.fsr == 4096) + reg1 |= 0x10; + else if (private_data->suspend.fsr == 2048) + reg1 |= 0x00; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG4, reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Configure high pass filter */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG2, 0x31); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (private_data->resume.irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x10; + reg2 = 0x00; + } else if (private_data->resume.irq_type == MPU_SLAVE_IRQ_TYPE_MOTION) { + reg1 = 0x40; + reg2 = private_data->resume.mot_int1_cfg; + } else { + reg1 = 0x00; + reg2 = 0x00; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG3, reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_INT1_THS, + private_data->resume.reg_ths); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_INT1_DURATION, + private_data->resume.reg_dur); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_INT1_CFG, reg2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG6, 1, ®1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int lis3dh_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result = INV_SUCCESS; + result = inv_serial_read(mlsl_handle, pdata->address, + LIS3DH_STATUS_REG, 1, data); + if (data[0] & 0x0F) { + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, + data); + return result; + } else + return INV_ERROR_ACCEL_DATA_NOT_READY; +} + +static int lis3dh_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + long range; + struct lis3dh_private_data *private_data; + private_data = (struct lis3dh_private_data *) + kzalloc(sizeof(struct lis3dh_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + private_data->resume.ctrl_reg1 = 0x67; + private_data->suspend.ctrl_reg1 = 0x18; + private_data->resume.mot_int1_cfg = 0x95; + private_data->suspend.mot_int1_cfg = 0x2a; + + lis3dh_set_odr(mlsl_handle, pdata, &private_data->suspend, FALSE, 0); + lis3dh_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + + range = range_fixedpoint_to_long_mg(slave->range); + lis3dh_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, range); + lis3dh_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, range); + + lis3dh_set_ths(mlsl_handle, pdata, &private_data->suspend, + FALSE, 80); + lis3dh_set_ths(mlsl_handle, pdata, &private_data->resume, + FALSE, 40); + + lis3dh_set_dur(mlsl_handle, pdata, &private_data->suspend, + FALSE, 1000); + lis3dh_set_dur(mlsl_handle, pdata, &private_data->resume, + FALSE, 2540); + + lis3dh_set_irq(mlsl_handle, pdata, &private_data->suspend, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + lis3dh_set_irq(mlsl_handle, pdata, &private_data->resume, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS3DH_CTRL_REG1, 0x07); + msleep(6); + + return INV_SUCCESS; +} + +static int lis3dh_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static int lis3dh_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct lis3dh_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return lis3dh_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return lis3dh_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return lis3dh_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return lis3dh_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_THS: + return lis3dh_set_ths(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_THS: + return lis3dh_set_ths(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_DUR: + return lis3dh_set_dur(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_DUR: + return lis3dh_set_dur(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + return lis3dh_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_RESUME: + return lis3dh_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + return INV_SUCCESS; +} + +static int lis3dh_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct lis3dh_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_MOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.ths; + break; + case MPU_SLAVE_CONFIG_NMOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.ths; + break; + case MPU_SLAVE_CONFIG_MOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.dur; + break; + case MPU_SLAVE_CONFIG_NMOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.dur; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.irq_type; + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.irq_type; + break; + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static struct ext_slave_descr lis3dh_descr = { + .init = lis3dh_init, + .exit = lis3dh_exit, + .suspend = lis3dh_suspend, + .resume = lis3dh_resume, + .read = lis3dh_read, + .config = lis3dh_config, + .get_config = lis3dh_get_config, + .name = "lis3dh", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_LIS3DH, + .read_reg = 0x28 | 0x80, /* 0x80 for burst reads */ + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {2, 480}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *lis3dh_get_slave_descr(void) +{ + return &lis3dh_descr; +} + +/* -------------------------------------------------------------------------- */ +struct lis3dh_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int lis3dh_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct lis3dh_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + lis3dh_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int lis3dh_mod_remove(struct i2c_client *client) +{ + struct lis3dh_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + lis3dh_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id lis3dh_mod_id[] = { + { "lis3dh", ACCEL_ID_LIS3DH }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lis3dh_mod_id); + +static struct i2c_driver lis3dh_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = lis3dh_mod_probe, + .remove = lis3dh_mod_remove, + .id_table = lis3dh_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "lis3dh_mod", + }, + .address_list = normal_i2c, +}; + +static int __init lis3dh_mod_init(void) +{ + int res = i2c_add_driver(&lis3dh_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "lis3dh_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit lis3dh_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&lis3dh_mod_driver); +} + +module_init(lis3dh_mod_init); +module_exit(lis3dh_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate LIS3DH sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("lis3dh_mod"); + +/* + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/lsm303a.c b/drivers/misc/inv_mpu/accel/lsm303a.c new file mode 100644 index 0000000..5df6916 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/lsm303a.c @@ -0,0 +1,878 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file lsm303a.c + * @brief Accelerometer setup and handling methods for ST LSM303. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* -------------------------------------------------------------------------- */ + +/* full scale setting - register & mask */ +#define LIS331_CTRL_REG1 (0x20) +#define LIS331_CTRL_REG2 (0x21) +#define LIS331_CTRL_REG3 (0x22) +#define LIS331_CTRL_REG4 (0x23) +#define LIS331_CTRL_REG5 (0x24) +#define LIS331_HP_FILTER_RESET (0x25) +#define LIS331_REFERENCE (0x26) +#define LIS331_STATUS_REG (0x27) +#define LIS331_OUT_X_L (0x28) +#define LIS331_OUT_X_H (0x29) +#define LIS331_OUT_Y_L (0x2a) +#define LIS331_OUT_Y_H (0x2b) +#define LIS331_OUT_Z_L (0x2b) +#define LIS331_OUT_Z_H (0x2d) + +#define LIS331_INT1_CFG (0x30) +#define LIS331_INT1_SRC (0x31) +#define LIS331_INT1_THS (0x32) +#define LIS331_INT1_DURATION (0x33) + +#define LIS331_INT2_CFG (0x34) +#define LIS331_INT2_SRC (0x35) +#define LIS331_INT2_THS (0x36) +#define LIS331_INT2_DURATION (0x37) + +#define LIS331_CTRL_MASK (0x30) +#define LIS331_SLEEP_MASK (0x20) + +#define LIS331_MAX_DUR (0x7F) + +/* -------------------------------------------------------------------------- */ + +struct lsm303dlha_config { + unsigned int odr; + unsigned int fsr; /** < full scale range mg */ + unsigned int ths; /** < Motion no-motion thseshold mg */ + unsigned int dur; /** < Motion no-motion duration ms */ + unsigned char reg_ths; + unsigned char reg_dur; + unsigned char ctrl_reg1; + unsigned char irq_type; + unsigned char mot_int1_cfg; +}; + +struct lsm303dlha_private_data { + struct lsm303dlha_config suspend; + struct lsm303dlha_config resume; +}; + +/* -------------------------------------------------------------------------- */ + +static int lsm303dlha_set_ths(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lsm303dlha_config *config, + int apply, + long ths) +{ + int result = INV_SUCCESS; + if ((unsigned int) ths >= config->fsr) + ths = (long) config->fsr - 1; + + if (ths < 0) + ths = 0; + + config->ths = ths; + config->reg_ths = (unsigned char)(long)((ths * 128L) / (config->fsr)); + MPL_LOGV("THS: %d, 0x%02x\n", config->ths, (int)config->reg_ths); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_THS, + config->reg_ths); + return result; +} + +static int lsm303dlha_set_dur(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lsm303dlha_config *config, + int apply, + long dur) +{ + int result = INV_SUCCESS; + long reg_dur = (dur * config->odr) / 1000000L; + config->dur = dur; + + if (reg_dur > LIS331_MAX_DUR) + reg_dur = LIS331_MAX_DUR; + + config->reg_dur = (unsigned char) reg_dur; + MPL_LOGV("DUR: %d, 0x%02x\n", config->dur, (int)config->reg_dur); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_DURATION, + (unsigned char)reg_dur); + return result; +} + +/** + * Sets the IRQ to fire when one of the IRQ events occur. Threshold and + * duration will not be used uless the type is MOT or NMOT. + * + * @param config configuration to apply to, suspend or resume + * @param irq_type The type of IRQ. Valid values are + * - MPU_SLAVE_IRQ_TYPE_NONE + * - MPU_SLAVE_IRQ_TYPE_MOTION + * - MPU_SLAVE_IRQ_TYPE_DATA_READY + */ +static int lsm303dlha_set_irq(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lsm303dlha_config *config, + int apply, + long irq_type) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + + config->irq_type = (unsigned char)irq_type; + if (irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x02; + reg2 = 0x00; + } else if (irq_type == MPU_SLAVE_IRQ_TYPE_MOTION) { + reg1 = 0x00; + reg2 = config->mot_int1_cfg; + } else { + reg1 = 0x00; + reg2 = 0x00; + } + + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG3, reg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_CFG, reg2); + } + + return result; +} + +/** + * @brief Set the output data rate for the particular configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * Config to modify with new ODR. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param odr + * Output data rate in units of 1/1000Hz (mHz). + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int lsm303dlha_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lsm303dlha_config *config, + int apply, + long odr) +{ + unsigned char bits; + int result = INV_SUCCESS; + + if (odr > 400000) { + config->odr = 1000000; + bits = 0x38; + } else if (odr > 100000) { + config->odr = 400000; + bits = 0x30; + } else if (odr > 50000) { + config->odr = 100000; + bits = 0x28; + } else if (odr > 10000) { + config->odr = 50000; + bits = 0x20; + } else if (odr > 5000) { + config->odr = 10000; + bits = 0xC0; + } else if (odr > 2000) { + config->odr = 5000; + bits = 0xB0; + } else if (odr > 1000) { + config->odr = 2000; + bits = 0x80; + } else if (odr > 500) { + config->odr = 1000; + bits = 0x60; + } else if (odr > 0) { + config->odr = 500; + bits = 0x40; + } else { + config->odr = 0; + bits = 0; + } + + config->ctrl_reg1 = bits | (config->ctrl_reg1 & 0x7); + lsm303dlha_set_dur(mlsl_handle, pdata, + config, apply, config->dur); + MPL_LOGV("ODR: %d, 0x%02x\n", config->odr, (int)config->ctrl_reg1); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG1, + config->ctrl_reg1); + return result; +} + +/** + * @brief Set the full scale range of the accels + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * pointer to configuration. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param fsr + * requested full scale range. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int lsm303dlha_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct lsm303dlha_config *config, + int apply, + long fsr) +{ + unsigned char reg1 = 0x40; + int result = INV_SUCCESS; + + if (fsr <= 2048) { + config->fsr = 2048; + } else if (fsr <= 4096) { + reg1 |= 0x30; + config->fsr = 4096; + } else { + reg1 |= 0x10; + config->fsr = 8192; + } + + lsm303dlha_set_ths(mlsl_handle, pdata, + config, apply, config->ths); + MPL_LOGV("FSR: %d\n", config->fsr); + if (apply) + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG4, reg1); + + return result; +} + +/** + * @brief suspends the device to put it in its lowest power mode. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int lsm303dlha_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + struct lsm303dlha_private_data *private_data = + (struct lsm303dlha_private_data *)(pdata->private_data); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG1, + private_data->suspend.ctrl_reg1); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG2, 0x0f); + reg1 = 0x40; + if (private_data->suspend.fsr == 8192) + reg1 |= 0x30; + else if (private_data->suspend.fsr == 4096) + reg1 |= 0x10; + /* else bits [4..5] are already zero */ + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG4, reg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_THS, + private_data->suspend.reg_ths); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_DURATION, + private_data->suspend.reg_dur); + + if (private_data->suspend.irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x02; + reg2 = 0x00; + } else if (private_data->suspend.irq_type == + MPU_SLAVE_IRQ_TYPE_MOTION) { + reg1 = 0x00; + reg2 = private_data->suspend.mot_int1_cfg; + } else { + reg1 = 0x00; + reg2 = 0x00; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG3, reg1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_CFG, reg2); + result = inv_serial_read(mlsl_handle, pdata->address, + LIS331_HP_FILTER_RESET, 1, ®1); + return result; +} + +/** + * @brief resume the device in the proper power state given the configuration + * chosen. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int lsm303dlha_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + struct lsm303dlha_private_data *private_data = + (struct lsm303dlha_private_data *)(pdata->private_data); + + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG1, + private_data->resume.ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(6); + + /* Full Scale */ + reg1 = 0x40; + if (private_data->resume.fsr == 8192) + reg1 |= 0x30; + else if (private_data->resume.fsr == 4096) + reg1 |= 0x10; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG4, reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Configure high pass filter */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG2, 0x0F); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (private_data->resume.irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x02; + reg2 = 0x00; + } else if (private_data->resume.irq_type == + MPU_SLAVE_IRQ_TYPE_MOTION) { + reg1 = 0x00; + reg2 = private_data->resume.mot_int1_cfg; + } else { + reg1 = 0x00; + reg2 = 0x00; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_CTRL_REG3, reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_THS, + private_data->resume.reg_ths); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_DURATION, + private_data->resume.reg_dur); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + LIS331_INT1_CFG, reg2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, pdata->address, + LIS331_HP_FILTER_RESET, 1, ®1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +/** + * @brief read the sensor data from the device. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a buffer to store the data read. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int lsm303dlha_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result = INV_SUCCESS; + result = inv_serial_read(mlsl_handle, pdata->address, + LIS331_STATUS_REG, 1, data); + if (data[0] & 0x0F) { + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, data); + return result; + } else + return INV_ERROR_ACCEL_DATA_NOT_READY; +} + +/** + * @brief one-time device driver initialization function. + * If the driver is built as a kernel module, this function will be + * called when the module is loaded in the kernel. + * If the driver is built-in in the kernel, this function will be + * called at boot time. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int lsm303dlha_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + long range; + struct lsm303dlha_private_data *private_data; + private_data = (struct lsm303dlha_private_data *) + kzalloc(sizeof(struct lsm303dlha_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + private_data->resume.ctrl_reg1 = 0x37; + private_data->suspend.ctrl_reg1 = 0x47; + private_data->resume.mot_int1_cfg = 0x95; + private_data->suspend.mot_int1_cfg = 0x2a; + + lsm303dlha_set_odr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 0); + lsm303dlha_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + + range = range_fixedpoint_to_long_mg(slave->range); + lsm303dlha_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, range); + lsm303dlha_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, range); + + lsm303dlha_set_ths(mlsl_handle, pdata, &private_data->suspend, + FALSE, 80); + lsm303dlha_set_ths(mlsl_handle, pdata, &private_data->resume, + FALSE, 40); + + lsm303dlha_set_dur(mlsl_handle, pdata, &private_data->suspend, + FALSE, 1000); + lsm303dlha_set_dur(mlsl_handle, pdata, &private_data->resume, + FALSE, 2540); + + lsm303dlha_set_irq(mlsl_handle, pdata, &private_data->suspend, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + lsm303dlha_set_irq(mlsl_handle, pdata, &private_data->resume, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + return INV_SUCCESS; +} + +/** + * @brief one-time device driver exit function. + * If the driver is built as a kernel module, this function will be + * called when the module is removed from the kernel. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int lsm303dlha_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +/** + * @brief device configuration facility. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to the configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int lsm303dlha_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct lsm303dlha_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return lsm303dlha_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return lsm303dlha_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return lsm303dlha_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return lsm303dlha_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_THS: + return lsm303dlha_set_ths(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_THS: + return lsm303dlha_set_ths(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_DUR: + return lsm303dlha_set_dur(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_DUR: + return lsm303dlha_set_dur(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + return lsm303dlha_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_RESUME: + return lsm303dlha_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +/** + * @brief facility to retrieve the device configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to store the returned configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int lsm303dlha_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct lsm303dlha_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_MOT_THS: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.ths; + break; + case MPU_SLAVE_CONFIG_NMOT_THS: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.ths; + break; + case MPU_SLAVE_CONFIG_MOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.dur; + break; + case MPU_SLAVE_CONFIG_NMOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.dur; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.irq_type; + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.irq_type; + break; + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static struct ext_slave_descr lsm303dlha_descr = { + .init = lsm303dlha_init, + .exit = lsm303dlha_exit, + .suspend = lsm303dlha_suspend, + .resume = lsm303dlha_resume, + .read = lsm303dlha_read, + .config = lsm303dlha_config, + .get_config = lsm303dlha_get_config, + .name = "lsm303dlha", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_LSM303A, + .read_reg = (0x28 | 0x80), /* 0x80 for burst reads */ + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {2, 480}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *lsm303a_get_slave_descr(void) +{ + return &lsm303dlha_descr; +} + +/* -------------------------------------------------------------------------- */ +struct lsm303a_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int lsm303a_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct lsm303a_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + lsm303a_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int lsm303a_mod_remove(struct i2c_client *client) +{ + struct lsm303a_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + lsm303a_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id lsm303a_mod_id[] = { + { "lsm303a", ACCEL_ID_LSM303A }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lsm303a_mod_id); + +static struct i2c_driver lsm303a_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = lsm303a_mod_probe, + .remove = lsm303a_mod_remove, + .id_table = lsm303a_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "lsm303a_mod", + }, + .address_list = normal_i2c, +}; + +static int __init lsm303a_mod_init(void) +{ + int res = i2c_add_driver(&lsm303a_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "lsm303a_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit lsm303a_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&lsm303a_mod_driver); +} + +module_init(lsm303a_mod_init); +module_exit(lsm303a_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate LSM303A sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("lsm303a_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/mma8450.c b/drivers/misc/inv_mpu/accel/mma8450.c new file mode 100644 index 0000000..772fc46 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/mma8450.c @@ -0,0 +1,807 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file mma8450.c + * @brief Accelerometer setup and handling methods for Freescale MMA8450. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* full scale setting - register & mask */ +#define ACCEL_MMA8450_XYZ_DATA_CFG (0x16) + +#define ACCEL_MMA8450_CTRL_REG1 (0x38) +#define ACCEL_MMA8450_CTRL_REG4 (0x3B) +#define ACCEL_MMA8450_CTRL_REG5 (0x3C) + +#define ACCEL_MMA8450_CTRL_REG (0x38) +#define ACCEL_MMA8450_CTRL_MASK (0x03) + +#define ACCEL_MMA8450_SLEEP_MASK (0x03) + +/* -------------------------------------------------------------------------- */ + +struct mma8450_config { + unsigned int odr; + unsigned int fsr; /** < full scale range mg */ + unsigned int ths; /** < Motion no-motion thseshold mg */ + unsigned int dur; /** < Motion no-motion duration ms */ + unsigned char reg_ths; + unsigned char reg_dur; + unsigned char ctrl_reg1; + unsigned char irq_type; + unsigned char mot_int1_cfg; +}; + +struct mma8450_private_data { + struct mma8450_config suspend; + struct mma8450_config resume; +}; + + +/* -------------------------------------------------------------------------- */ + +static int mma8450_set_ths(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma8450_config *config, + int apply, + long ths) +{ + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; +} + +static int mma8450_set_dur(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma8450_config *config, + int apply, + long dur) +{ + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; +} + +/** + * @brief Sets the IRQ to fire when one of the IRQ events occur. + * Threshold and duration will not be used unless the type is MOT or + * NMOT. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * configuration to apply to, suspend or resume + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param irq_type + * the type of IRQ. Valid values are + * - MPU_SLAVE_IRQ_TYPE_NONE + * - MPU_SLAVE_IRQ_TYPE_MOTION + * - MPU_SLAVE_IRQ_TYPE_DATA_READY + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_set_irq(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma8450_config *config, + int apply, + long irq_type) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + unsigned char reg3; + + config->irq_type = (unsigned char)irq_type; + if (irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x01; + reg2 = 0x01; + reg3 = 0x07; + } else if (irq_type == MPU_SLAVE_IRQ_TYPE_NONE) { + reg1 = 0x00; + reg2 = 0x00; + reg3 = 0x00; + } else { + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + } + + if (apply) { + /* XYZ_DATA_CFG: event flag enabled on Z axis */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_XYZ_DATA_CFG, reg3); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_CTRL_REG4, reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_CTRL_REG5, reg2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + return result; +} + +/** + * @brief Set the output data rate for the particular configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * Config to modify with new ODR. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param odr + * Output data rate in units of 1/1000Hz (mHz). + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma8450_config *config, + int apply, + long odr) +{ + unsigned char bits; + int result = INV_SUCCESS; + + if (odr > 200000) { + config->odr = 400000; + bits = 0x00; + } else if (odr > 100000) { + config->odr = 200000; + bits = 0x04; + } else if (odr > 50000) { + config->odr = 100000; + bits = 0x08; + } else if (odr > 25000) { + config->odr = 50000; + bits = 0x0B; + } else if (odr > 12500) { + config->odr = 25000; + bits = 0x40; /* Sleep -> Auto wake mode */ + } else if (odr > 1563) { + config->odr = 12500; + bits = 0x10; + } else if (odr > 0) { + config->odr = 1563; + bits = 0x14; + } else { + config->ctrl_reg1 = 0; /* Set FS1.FS2 to Standby */ + config->odr = 0; + bits = 0; + } + + config->ctrl_reg1 = bits | (config->ctrl_reg1 & 0x3); + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_CTRL_REG1, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_CTRL_REG1, config->ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("ODR: %d mHz, 0x%02x\n", + config->odr, (int)config->ctrl_reg1); + } + return result; +} + +/** + * @brief Set the full scale range of the accels + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * pointer to configuration. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param fsr + * requested full scale range. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma8450_config *config, + int apply, + long fsr) +{ + unsigned char bits; + int result = INV_SUCCESS; + + if (fsr <= 2000) { + bits = 0x01; + config->fsr = 2000; + } else if (fsr <= 4000) { + bits = 0x02; + config->fsr = 4000; + } else { + bits = 0x03; + config->fsr = 8000; + } + + config->ctrl_reg1 = bits | (config->ctrl_reg1 & 0xFC); + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_CTRL_REG1, config->ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("FSR: %d mg\n", config->fsr); + } + return result; +} + +/** + * @brief suspends the device to put it in its lowest power mode. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + struct mma8450_private_data *private_data = pdata->private_data; + + if (private_data->suspend.fsr == 4000) + slave->range.mantissa = 4; + else if (private_data->suspend.fsr == 8000) + slave->range.mantissa = 8; + else + slave->range.mantissa = 2; + slave->range.fraction = 0; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_CTRL_REG1, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (private_data->resume.ctrl_reg1) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_CTRL_REG1, + private_data->suspend.ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + result = mma8450_set_irq(mlsl_handle, pdata, + &private_data->suspend, + TRUE, private_data->suspend.irq_type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +/** + * @brief resume the device in the proper power state given the configuration + * chosen. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + struct mma8450_private_data *private_data = pdata->private_data; + + /* Full Scale */ + if (private_data->resume.fsr == 4000) + slave->range.mantissa = 4; + else if (private_data->resume.fsr == 8000) + slave->range.mantissa = 8; + else + slave->range.mantissa = 2; + slave->range.fraction = 0; + + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_CTRL_REG1, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (private_data->resume.ctrl_reg1) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA8450_CTRL_REG1, + private_data->resume.ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = mma8450_set_irq(mlsl_handle, pdata, + &private_data->resume, + TRUE, private_data->resume.irq_type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief read the sensor data from the device. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a buffer to store the data read. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, unsigned char *data) +{ + int result; + unsigned char local_data[4]; /* Status register + 3 bytes data */ + result = inv_serial_read(mlsl_handle, pdata->address, + 0x00, + sizeof(local_data), local_data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + memcpy(data, &local_data[1], (slave->read_len) - 1); + MPL_LOGV("Data Not Ready: %02x %02x %02x %02x\n", + local_data[0], + local_data[1], + local_data[2], + local_data[3]); + if (!((local_data[0]) & 0x04)) + result = INV_ERROR_ACCEL_DATA_NOT_READY; + + return result; +} + +/** + * @brief one-time device driver initialization function. + * If the driver is built as a kernel module, this function will be + * called when the module is loaded in the kernel. + * If the driver is built-in in the kernel, this function will be + * called at boot time. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + struct mma8450_private_data *private_data; + private_data = (struct mma8450_private_data *) + kzalloc(sizeof(struct mma8450_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + mma8450_set_odr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 0); + mma8450_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + mma8450_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 2000); + mma8450_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, 2000); + mma8450_set_irq(mlsl_handle, pdata, &private_data->suspend, + FALSE, + MPU_SLAVE_IRQ_TYPE_NONE); + mma8450_set_irq(mlsl_handle, pdata, &private_data->resume, + FALSE, + MPU_SLAVE_IRQ_TYPE_NONE); + return INV_SUCCESS; +} + +/** + * @brief one-time device driver exit function. + * If the driver is built as a kernel module, this function will be + * called when the module is removed from the kernel. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +/** + * @brief device configuration facility. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to the configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct mma8450_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return mma8450_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return mma8450_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return mma8450_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return mma8450_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_THS: + return mma8450_set_ths(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_THS: + return mma8450_set_ths(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_DUR: + return mma8450_set_dur(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_DUR: + return mma8450_set_dur(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + return mma8450_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_RESUME: + return mma8450_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +/** + * @brief facility to retrieve the device configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a pointer to store the returned configuration data structure. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma8450_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct mma8450_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_MOT_THS: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.ths; + break; + case MPU_SLAVE_CONFIG_NMOT_THS: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.ths; + break; + case MPU_SLAVE_CONFIG_MOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.dur; + break; + case MPU_SLAVE_CONFIG_NMOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.dur; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.irq_type; + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.irq_type; + break; + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static struct ext_slave_descr mma8450_descr = { + .init = mma8450_init, + .exit = mma8450_exit, + .suspend = mma8450_suspend, + .resume = mma8450_resume, + .read = mma8450_read, + .config = mma8450_config, + .get_config = mma8450_get_config, + .name = "mma8450", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_MMA8450, + .read_reg = 0x00, + .read_len = 4, + .endian = EXT_SLAVE_FS8_BIG_ENDIAN, + .range = {2, 0}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *mma8450_get_slave_descr(void) +{ + return &mma8450_descr; +} + +/* -------------------------------------------------------------------------- */ +struct mma8450_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int mma8450_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct mma8450_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + mma8450_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int mma8450_mod_remove(struct i2c_client *client) +{ + struct mma8450_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + mma8450_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id mma8450_mod_id[] = { + { "mma8450", ACCEL_ID_MMA8450 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mma8450_mod_id); + +static struct i2c_driver mma8450_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = mma8450_mod_probe, + .remove = mma8450_mod_remove, + .id_table = mma8450_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "mma8450_mod", + }, + .address_list = normal_i2c, +}; + +static int __init mma8450_mod_init(void) +{ + int res = i2c_add_driver(&mma8450_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "mma8450_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit mma8450_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&mma8450_mod_driver); +} + +module_init(mma8450_mod_init); +module_exit(mma8450_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate MMA8450 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("mma8450_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/mma845x.c b/drivers/misc/inv_mpu/accel/mma845x.c new file mode 100644 index 0000000..46d1d1f --- /dev/null +++ b/drivers/misc/inv_mpu/accel/mma845x.c @@ -0,0 +1,713 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file mma845x.c + * @brief Accelerometer setup and handling methods for Freescale MMA845X + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +#define ACCEL_MMA845X_XYZ_DATA_CFG (0x0E) +#define ACCEL_MMA845X_CTRL_REG1 (0x2A) +#define ACCEL_MMA845X_CTRL_REG4 (0x2D) +#define ACCEL_MMA845X_CTRL_REG5 (0x2E) + +#define ACCEL_MMA845X_SLEEP_MASK (0x01) + +/* full scale setting - register & mask */ +#define ACCEL_MMA845X_CFG_REG (0x0E) +#define ACCEL_MMA845X_CTRL_MASK (0x03) + +/* -------------------------------------------------------------------------- */ + +struct mma845x_config { + unsigned int odr; + unsigned int fsr; /** < full scale range mg */ + unsigned int ths; /** < Motion no-motion thseshold mg */ + unsigned int dur; /** < Motion no-motion duration ms */ + unsigned char reg_ths; + unsigned char reg_dur; + unsigned char ctrl_reg1; + unsigned char irq_type; + unsigned char mot_int1_cfg; +}; + +struct mma845x_private_data { + struct mma845x_config suspend; + struct mma845x_config resume; +}; + +/* -------------------------------------------------------------------------- */ + +static int mma845x_set_ths(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma845x_config *config, + int apply, + long ths) +{ + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; +} + +static int mma845x_set_dur(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma845x_config *config, + int apply, + long dur) +{ + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; +} + +/** + * @brief Sets the IRQ to fire when one of the IRQ events occur. + * Threshold and duration will not be used unless the type is MOT or + * NMOT. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * configuration to apply to, suspend or resume + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param irq_type + * the type of IRQ. Valid values are + * - MPU_SLAVE_IRQ_TYPE_NONE + * - MPU_SLAVE_IRQ_TYPE_MOTION + * - MPU_SLAVE_IRQ_TYPE_DATA_READY + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma845x_set_irq(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma845x_config *config, + int apply, + long irq_type) +{ + int result = INV_SUCCESS; + unsigned char reg1; + unsigned char reg2; + + config->irq_type = (unsigned char)irq_type; + if (irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) { + reg1 = 0x01; + reg2 = 0x01; + } else if (irq_type == MPU_SLAVE_IRQ_TYPE_NONE) { + reg1 = 0x00; + reg2 = 0x00; + } else { + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + } + + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA845X_CTRL_REG4, reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA845X_CTRL_REG5, reg2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + return result; +} + +/** + * @brief Set the output data rate for the particular configuration. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * Config to modify with new ODR. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param odr + * Output data rate in units of 1/1000Hz (mHz). + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma845x_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma845x_config *config, + int apply, + long odr) +{ + unsigned char bits; + int result = INV_SUCCESS; + + if (odr > 400000) { + config->odr = 800000; + bits = 0x01; + } else if (odr > 200000) { + config->odr = 400000; + bits = 0x09; + } else if (odr > 100000) { + config->odr = 200000; + bits = 0x11; + } else if (odr > 50000) { + config->odr = 100000; + bits = 0x19; + } else if (odr > 12500) { + config->odr = 50000; + bits = 0x21; + } else if (odr > 6250) { + config->odr = 12500; + bits = 0x29; + } else if (odr > 1560) { + config->odr = 6250; + bits = 0x31; + } else if (odr > 0) { + config->odr = 1560; + bits = 0x39; + } else { + config->ctrl_reg1 = 0; /* Set FS1.FS2 to Standby */ + config->odr = 0; + bits = 0; + } + + config->ctrl_reg1 = bits; + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA845X_CTRL_REG1, + config->ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("ODR: %d mHz, 0x%02x\n", config->odr, + (int)config->ctrl_reg1); + } + return result; +} + +/** + * @brief Set the full scale range of the accels + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param pdata + * a pointer to the slave platform data. + * @param config + * pointer to configuration. + * @param apply + * whether to apply immediately or save the settings to be applied + * at the next resume. + * @param fsr + * requested full scale range. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma845x_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mma845x_config *config, + int apply, + long fsr) +{ + unsigned char bits; + int result = INV_SUCCESS; + + if (fsr <= 2000) { + bits = 0x00; + config->fsr = 2000; + } else if (fsr <= 4000) { + bits = 0x01; + config->fsr = 4000; + } else { + bits = 0x02; + config->fsr = 8000; + } + + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA845X_XYZ_DATA_CFG, + bits); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("FSR: %d mg\n", config->fsr); + } + return result; +} + +/** + * @brief suspends the device to put it in its lowest power mode. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma845x_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + struct mma845x_private_data *private_data = pdata->private_data; + + /* Full Scale */ + if (private_data->suspend.fsr == 4000) + slave->range.mantissa = 4; + else if (private_data->suspend.fsr == 8000) + slave->range.mantissa = 8; + else + slave->range.mantissa = 2; + + slave->range.fraction = 0; + + result = mma845x_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + TRUE, private_data->suspend.fsr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA845X_CTRL_REG1, + private_data->suspend.ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief resume the device in the proper power state given the configuration + * chosen. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma845x_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + struct mma845x_private_data *private_data = pdata->private_data; + + /* Full Scale */ + if (private_data->resume.fsr == 4000) + slave->range.mantissa = 4; + else if (private_data->resume.fsr == 8000) + slave->range.mantissa = 8; + else + slave->range.mantissa = 2; + + slave->range.fraction = 0; + + result = mma845x_set_fsr(mlsl_handle, pdata, + &private_data->resume, + TRUE, private_data->resume.fsr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + ACCEL_MMA845X_CTRL_REG1, + private_data->resume.ctrl_reg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +/** + * @brief read the sensor data from the device. + * + * @param mlsl_handle + * the handle to the serial channel the device is connected to. + * @param slave + * a pointer to the slave descriptor data structure. + * @param pdata + * a pointer to the slave platform data. + * @param data + * a buffer to store the data read. + * + * @return INV_SUCCESS if successful or a non-zero error code. + */ +static int mma845x_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, unsigned char *data) +{ + int result; + unsigned char local_data[7]; /* Status register + 6 bytes data */ + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, sizeof(local_data), + local_data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + memcpy(data, &local_data[1], slave->read_len); + return result; +} + +static int mma845x_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + long range; + struct mma845x_private_data *private_data; + private_data = (struct mma845x_private_data *) + kzalloc(sizeof(struct mma845x_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + mma845x_set_odr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 0); + mma845x_set_odr(mlsl_handle, pdata, &private_data->resume, + FALSE, 200000); + + range = range_fixedpoint_to_long_mg(slave->range); + mma845x_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, range); + mma845x_set_fsr(mlsl_handle, pdata, &private_data->resume, + FALSE, range); + + mma845x_set_irq(mlsl_handle, pdata, &private_data->suspend, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + mma845x_set_irq(mlsl_handle, pdata, &private_data->resume, + FALSE, MPU_SLAVE_IRQ_TYPE_NONE); + return INV_SUCCESS; +} + +static int mma845x_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static int mma845x_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct mma845x_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return mma845x_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return mma845x_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return mma845x_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return mma845x_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_THS: + return mma845x_set_ths(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_THS: + return mma845x_set_ths(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_DUR: + return mma845x_set_dur(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_DUR: + return mma845x_set_dur(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + return mma845x_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, + *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_RESUME: + return mma845x_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, + *((long *)data->data)); + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static int mma845x_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct mma845x_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_MOT_THS: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.ths; + break; + case MPU_SLAVE_CONFIG_NMOT_THS: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.ths; + break; + case MPU_SLAVE_CONFIG_MOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.dur; + break; + case MPU_SLAVE_CONFIG_NMOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.dur; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long) private_data->suspend.irq_type; + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + (*(unsigned long *)data->data) = + (unsigned long) private_data->resume.irq_type; + break; + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static struct ext_slave_descr mma845x_descr = { + .init = mma845x_init, + .exit = mma845x_exit, + .suspend = mma845x_suspend, + .resume = mma845x_resume, + .read = mma845x_read, + .config = mma845x_config, + .get_config = mma845x_get_config, + .name = "mma845x", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_MMA845X, + .read_reg = 0x00, + .read_len = 6, + .endian = EXT_SLAVE_FS16_BIG_ENDIAN, + .range = {2, 0}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *mma845x_get_slave_descr(void) +{ + return &mma845x_descr; +} + +/* -------------------------------------------------------------------------- */ +struct mma845x_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int mma845x_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct mma845x_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + mma845x_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int mma845x_mod_remove(struct i2c_client *client) +{ + struct mma845x_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + mma845x_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id mma845x_mod_id[] = { + { "mma845x", ACCEL_ID_MMA845X }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mma845x_mod_id); + +static struct i2c_driver mma845x_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = mma845x_mod_probe, + .remove = mma845x_mod_remove, + .id_table = mma845x_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "mma845x_mod", + }, + .address_list = normal_i2c, +}; + +static int __init mma845x_mod_init(void) +{ + int res = i2c_add_driver(&mma845x_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "mma845x_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit mma845x_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&mma845x_mod_driver); +} + +module_init(mma845x_mod_init); +module_exit(mma845x_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate MMA845X sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("mma845x_mod"); + + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/mpu6050.c b/drivers/misc/inv_mpu/accel/mpu6050.c new file mode 100644 index 0000000..57c01d2 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/mpu6050.c @@ -0,0 +1,433 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file mpu6050.c + * @brief Accelerometer setup and handling methods for Invensense MPU6050 + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* -------------------------------------------------------------------------- */ + +struct mpu6050_config { + unsigned int odr; /**< output data rate 1/1000 Hz */ + unsigned int fsr; /**< full scale range mg */ + unsigned int ths; /**< mot/no-mot thseshold mg */ + unsigned int dur; /**< mot/no-mot duration ms */ +}; + +struct mpu6050_private_data { + struct mpu6050_config suspend; + struct mpu6050_config resume; +}; + +/* -------------------------------------------------------------------------- */ + +/** + * Record the odr for use in computing duration values. + * + * @param config Config to set, suspend or resume structure + * @param odr output data rate in 1/1000 hz + */ +static int mpu6050_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *slave, + struct mpu6050_config *config, long apply, long odr) +{ + config->odr = odr; + return INV_SUCCESS; +} + +static int mpu6050_set_ths(void *mlsl_handle, + struct ext_slave_platform_data *slave, + struct mpu6050_config *config, long apply, long ths) +{ + if (ths < 0) + ths = 0; + + config->ths = ths; + MPL_LOGV("THS: %d\n", config->ths); + return INV_SUCCESS; +} + +static int mpu6050_set_dur(void *mlsl_handle, + struct ext_slave_platform_data *slave, + struct mpu6050_config *config, long apply, long dur) +{ + if (dur < 0) + dur = 0; + + config->dur = dur; + MPL_LOGV("DUR: %d\n", config->dur); + return INV_SUCCESS; +} + +static int mpu6050_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *slave, + struct mpu6050_config *config, long apply, long fsr) +{ + if (fsr <= 2000) + config->fsr = 2000; + else if (fsr <= 4000) + config->fsr = 4000; + else + config->fsr = 8000; + + MPL_LOGV("FSR: %d\n", config->fsr); + return INV_SUCCESS; +} + +static int mpu6050_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + struct mpu6050_private_data *private_data; + + (void *)private_data; + return INV_ERROR_INVALID_MODULE; + + private_data = (struct mpu6050_private_data *) + kzalloc(sizeof(struct mpu6050_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + mpu6050_set_odr(mlsl_handle, pdata, &private_data->suspend, FALSE, 0); + mpu6050_set_odr(mlsl_handle, pdata, + &private_data->resume, FALSE, 200000); + mpu6050_set_fsr(mlsl_handle, pdata, &private_data->suspend, + FALSE, 2000); + mpu6050_set_fsr(mlsl_handle, pdata, &private_data->resume, FALSE, 2000); + mpu6050_set_ths(mlsl_handle, pdata, &private_data->suspend, FALSE, 80); + mpu6050_set_ths(mlsl_handle, pdata, &private_data->resume, FALSE, 40); + mpu6050_set_dur(mlsl_handle, pdata, &private_data->suspend, + FALSE, 1000); + mpu6050_set_dur(mlsl_handle, pdata, &private_data->resume, FALSE, 2540); + return INV_SUCCESS; +} + +static int mpu6050_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static int mpu6050_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + unsigned char reg; + int result; + + result = inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg |= (BIT_STBY_XA | BIT_STBY_YA | BIT_STBY_ZA); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return INV_SUCCESS; +} + +static int mpu6050_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + unsigned char reg; + struct mpu6050_private_data *private_data; + + private_data = (struct mpu6050_private_data *)pdata->private_data; + + result = inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_1, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if ((reg & BITS_PWRSEL) != BITS_PWRSEL) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_1, reg | BITS_PWRSEL); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + msleep(2); + + result = inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg &= ~(BIT_STBY_XA | BIT_STBY_YA | BIT_STBY_ZA); + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (slave->range.mantissa == 2) + reg = 0; + else if (slave->range.mantissa == 4) + reg = 1 << 3; + else if (slave->range.mantissa == 8) + reg = 2 << 3; + else if (slave->range.mantissa == 16) + reg = 3 << 3; + else + return INV_ERROR; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_CONFIG, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + reg = (unsigned char)private_data->suspend.ths / ACCEL_MOT_THR_LSB; + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_MOT_THR, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + reg = (unsigned char) + ACCEL_ZRMOT_THR_LSB_CONVERSION(private_data->resume.ths); + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_ZRMOT_THR, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + reg = (unsigned char)private_data->suspend.ths / ACCEL_MOT_DUR_LSB; + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_MOT_DUR, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + reg = (unsigned char)private_data->resume.ths / ACCEL_ZRMOT_DUR_LSB; + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_ZRMOT_DUR, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int mpu6050_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result; + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, data); + return result; +} + +static int mpu6050_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct mpu6050_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return mpu6050_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return mpu6050_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return mpu6050_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return mpu6050_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_THS: + return mpu6050_set_ths(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_THS: + return mpu6050_set_ths(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_DUR: + return mpu6050_set_dur(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_DUR: + return mpu6050_set_dur(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: +#if 0 + return mpu6050_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); +#endif + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: +#if 0 + return mpu6050_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); +#endif + break; + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static int mpu6050_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct mpu6050_private_data *private_data = pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_MOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.ths; + break; + case MPU_SLAVE_CONFIG_NMOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.ths; + break; + case MPU_SLAVE_CONFIG_MOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.dur; + break; + case MPU_SLAVE_CONFIG_NMOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.dur; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: +#if 0 + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.irq_type; +#endif + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: +#if 0 + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.irq_type; +#endif + break; + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static struct ext_slave_descr mpu6050_descr = { + .init = mpu6050_init, + .exit = mpu6050_exit, + .suspend = mpu6050_suspend, + .resume = mpu6050_resume, + .read = mpu6050_read, + .config = mpu6050_config, + .get_config = mpu6050_get_config, + .name = "mpu6050", + .type = EXT_SLAVE_TYPE_ACCELEROMETER, + .id = ACCEL_ID_MPU6050, + .read_reg = 0x3B, + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {2, 0}, + .trigger = NULL, +}; + +struct ext_slave_descr *mpu6050_get_slave_descr(void) +{ + return &mpu6050_descr; +} + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/accel/mpu6050.h b/drivers/misc/inv_mpu/accel/mpu6050.h new file mode 100644 index 0000000..4d4d74e --- /dev/null +++ b/drivers/misc/inv_mpu/accel/mpu6050.h @@ -0,0 +1,28 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + + +#ifndef __MPU6050_H__ +#define __MPU6050_H__ + +#include <linux/mpu.h> + +struct ext_slave_descr *mpu6050_get_slave_descr(void); + +#endif diff --git a/drivers/misc/inv_mpu/compass/Kconfig b/drivers/misc/inv_mpu/compass/Kconfig new file mode 100644 index 0000000..db6d86b --- /dev/null +++ b/drivers/misc/inv_mpu/compass/Kconfig @@ -0,0 +1,112 @@ +menuconfig INV_SENSORS_COMPASS + bool "Compass Slave Sensors" + default y + ---help--- + Say Y here to get to see options for device drivers for various + compasses. This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if INV_SENSORS_COMPASS + +config MPU_SENSORS_AK8975 + tristate "AKM ak8975" + help + This enables support for the AKM ak8975 compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_MMC314X + tristate "MEMSIC mmc314x" + help + This enables support for the MEMSIC mmc314x compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_AMI30X + tristate "Aichi Steel ami30X" + help + This enables support for the Aichi Steel ami304/ami305 compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_AMI306 + tristate "Aichi Steel ami306" + help + This enables support for the Aichi Steel ami306 compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_HMC5883 + tristate "Honeywell hmc5883" + help + This enables support for the Honeywell hmc5883 compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_LSM303DLHM + tristate "ST lsm303dlh" + help + This enables support for the ST lsm303dlh compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_MMC314XMS + tristate "MEMSIC mmc314xMS" + help + This enables support for the MEMSIC mmc314xMS compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_YAS529 + tristate "Yamaha yas529" + depends on INPUT_YAS_MAGNETOMETER + help + This enables support for the Yamaha yas529 compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_YAS530 + tristate "Yamaha yas530" + help + This enables support for the Yamaha yas530 compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_HSCDTD002B + tristate "Alps hscdtd002b" + help + This enables support for the Alps hscdtd002b compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +config MPU_SENSORS_HSCDTD004A + tristate "Alps hscdtd004a" + help + This enables support for the Alps hscdtd004a compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +endif diff --git a/drivers/misc/inv_mpu/compass/Makefile b/drivers/misc/inv_mpu/compass/Makefile new file mode 100644 index 0000000..602e9f8 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/Makefile @@ -0,0 +1,34 @@ +# +# Compass Slaves MPUxxxx +# +obj-$(CONFIG_MPU_SENSORS_AMI30X) += inv_mpu_ami30x.o +inv_mpu_ami30x-objs += ami30x.o + +obj-$(CONFIG_MPU_SENSORS_AMI306) += inv_mpu_ami306.o +inv_mpu_ami306-objs += ami306.o + +obj-$(CONFIG_MPU_SENSORS_HMC5883) += inv_mpu_hmc5883.o +inv_mpu_hmc5883-objs += hmc5883.o + +obj-$(CONFIG_MPU_SENSORS_LSM303DLHM) += inv_mpu_lsm303m.o +inv_mpu_lsm303m-objs += lsm303m.o + +obj-$(CONFIG_MPU_SENSORS_MMC314X) += inv_mpu_mmc314x.o +inv_mpu_mmc314x-objs += mmc314x.o + +obj-$(CONFIG_MPU_SENSORS_YAS529) += inv_mpu_yas529.o +inv_mpu_yas529-objs += yas529-kernel.o + +obj-$(CONFIG_MPU_SENSORS_YAS530) += inv_mpu_yas530.o +inv_mpu_yas530-objs += yas530.o + +obj-$(CONFIG_MPU_SENSORS_HSCDTD002B) += inv_mpu_hscdtd002b.o +inv_mpu_hscdtd002b-objs += hscdtd002b.o + +obj-$(CONFIG_MPU_SENSORS_HSCDTD004A) += inv_mpu_hscdtd004a.o +inv_mpu_hscdtd004a-objs += hscdtd004a.o + +obj-$(CONFIG_MPU_SENSORS_AK8975) += inv_mpu_ak8975.o +inv_mpu_ak8975-objs += ak8975.o + +EXTRA_CFLAGS += -Idrivers/misc/inv_mpu diff --git a/drivers/misc/inv_mpu/compass/ak8975.c b/drivers/misc/inv_mpu/compass/ak8975.c new file mode 100644 index 0000000..5c60d49 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/ak8975.c @@ -0,0 +1,500 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file ak8975.c + * @brief Magnetometer setup and handling methods for the AKM AK8975, + * AKM AK8975B, and AKM AK8975C compass devices. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +/* -------------------------------------------------------------------------- */ +#define AK8975_REG_ST1 (0x02) +#define AK8975_REG_HXL (0x03) +#define AK8975_REG_ST2 (0x09) + +#define AK8975_REG_CNTL (0x0A) +#define AK8975_REG_ASAX (0x10) +#define AK8975_REG_ASAY (0x11) +#define AK8975_REG_ASAZ (0x12) + +#define AK8975_CNTL_MODE_POWER_DOWN (0x00) +#define AK8975_CNTL_MODE_SINGLE_MEASUREMENT (0x01) +#define AK8975_CNTL_MODE_FUSE_ROM_ACCESS (0x0f) + +/* -------------------------------------------------------------------------- */ +struct ak8975_config { + char asa[COMPASS_NUM_AXES]; /* axis sensitivity adjustment */ +}; + +struct ak8975_private_data { + struct ak8975_config init; +}; + +/* -------------------------------------------------------------------------- */ +static int ak8975_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char serial_data[COMPASS_NUM_AXES]; + + struct ak8975_private_data *private_data; + private_data = (struct ak8975_private_data *) + kzalloc(sizeof(struct ak8975_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_POWER_DOWN); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Wait at least 100us */ + udelay(100); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_FUSE_ROM_ACCESS); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Wait at least 200us */ + udelay(200); + + result = inv_serial_read(mlsl_handle, pdata->address, + AK8975_REG_ASAX, + COMPASS_NUM_AXES, serial_data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + pdata->private_data = private_data; + + private_data->init.asa[0] = serial_data[0]; + private_data->init.asa[1] = serial_data[1]; + private_data->init.asa[2] = serial_data[2]; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_POWER_DOWN); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + udelay(100); + return INV_SUCCESS; +} + +static int ak8975_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static int ak8975_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + result = + inv_serial_single_write(mlsl_handle, pdata->address, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_POWER_DOWN); + msleep(1); /* wait at least 100us */ + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int ak8975_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + result = + inv_serial_single_write(mlsl_handle, pdata->address, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_SINGLE_MEASUREMENT); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int ak8975_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, unsigned char *data) +{ + unsigned char regs[8]; + unsigned char *stat = ®s[0]; + unsigned char *stat2 = ®s[7]; + int result = INV_SUCCESS; + int status = INV_SUCCESS; + + result = + inv_serial_read(mlsl_handle, pdata->address, AK8975_REG_ST1, + 8, regs); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Always return the data and the status registers */ + memcpy(data, ®s[1], 6); + data[6] = regs[0]; + data[7] = regs[7]; + + /* + * ST : data ready - + * Measurement has been completed and data is ready to be read. + */ + if (*stat & 0x01) + status = INV_SUCCESS; + + /* + * ST2 : data error - + * occurs when data read is started outside of a readable period; + * data read would not be correct. + * Valid in continuous measurement mode only. + * In single measurement mode this error should not occour but we + * stil account for it and return an error, since the data would be + * corrupted. + * DERR bit is self-clearing when ST2 register is read. + */ + if (*stat2 & 0x04) + status = INV_ERROR_COMPASS_DATA_ERROR; + /* + * ST2 : overflow - + * the sum of the absolute values of all axis |X|+|Y|+|Z| < 2400uT. + * This is likely to happen in presence of an external magnetic + * disturbance; it indicates, the sensor data is incorrect and should + * be ignored. + * An error is returned. + * HOFL bit clears when a new measurement starts. + */ + if (*stat2 & 0x08) + status = INV_ERROR_COMPASS_DATA_OVERFLOW; + /* + * ST : overrun - + * the previous sample was not fetched and lost. + * Valid in continuous measurement mode only. + * In single measurement mode this error should not occour and we + * don't consider this condition an error. + * DOR bit is self-clearing when ST2 or any meas. data register is + * read. + */ + if (*stat & 0x02) { + /* status = INV_ERROR_COMPASS_DATA_UNDERFLOW; */ + status = INV_SUCCESS; + } + + /* + * trigger next measurement if: + * - stat is non zero; + * - if stat is zero and stat2 is non zero. + * Won't trigger if data is not ready and there was no error. + */ + if (*stat != 0x00 || *stat2 != 0x00) { + result = inv_serial_single_write( + mlsl_handle, pdata->address, + AK8975_REG_CNTL, AK8975_CNTL_MODE_SINGLE_MEASUREMENT); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + return status; +} + +static int ak8975_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + int result; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_WRITE_REGISTERS: + result = inv_serial_write(mlsl_handle, pdata->address, + data->len, + (unsigned char *)data->data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + break; + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + case MPU_SLAVE_CONFIG_ODR_RESUME: + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + case MPU_SLAVE_CONFIG_FSR_RESUME: + case MPU_SLAVE_CONFIG_MOT_THS: + case MPU_SLAVE_CONFIG_NMOT_THS: + case MPU_SLAVE_CONFIG_MOT_DUR: + case MPU_SLAVE_CONFIG_NMOT_DUR: + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static int ak8975_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct ak8975_private_data *private_data = pdata->private_data; + int result; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_READ_REGISTERS: + { + unsigned char *serial_data = + (unsigned char *)data->data; + result = + inv_serial_read(mlsl_handle, pdata->address, + serial_data[0], data->len - 1, + &serial_data[1]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + break; + } + case MPU_SLAVE_READ_SCALE: + { + unsigned char *serial_data = + (unsigned char *)data->data; + serial_data[0] = private_data->init.asa[0]; + serial_data[1] = private_data->init.asa[1]; + serial_data[2] = private_data->init.asa[2]; + result = INV_SUCCESS; + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + break; + } + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = 0; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = 8000; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + case MPU_SLAVE_CONFIG_FSR_RESUME: + case MPU_SLAVE_CONFIG_MOT_THS: + case MPU_SLAVE_CONFIG_NMOT_THS: + case MPU_SLAVE_CONFIG_MOT_DUR: + case MPU_SLAVE_CONFIG_NMOT_DUR: + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static struct ext_slave_read_trigger ak8975_read_trigger = { + /*.reg = */ 0x0A, + /*.value = */ 0x01 +}; + +static struct ext_slave_descr ak8975_descr = { + .init = ak8975_init, + .exit = ak8975_exit, + .suspend = ak8975_suspend, + .resume = ak8975_resume, + .read = ak8975_read, + .config = ak8975_config, + .get_config = ak8975_get_config, + .name = "ak8975", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_AK8975, + .read_reg = 0x01, + .read_len = 9, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {9830, 4000}, + .trigger = &ak8975_read_trigger, +}; + +static +struct ext_slave_descr *ak8975_get_slave_descr(void) +{ + return &ak8975_descr; +} + +/* -------------------------------------------------------------------------- */ +struct ak8975_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int ak8975_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct ak8975_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + ak8975_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int ak8975_mod_remove(struct i2c_client *client) +{ + struct ak8975_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + inv_mpu_unregister_slave(client, private_data->pdata, + ak8975_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id ak8975_mod_id[] = { + { "ak8975", COMPASS_ID_AK8975 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ak8975_mod_id); + +static struct i2c_driver ak8975_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = ak8975_mod_probe, + .remove = ak8975_mod_remove, + .id_table = ak8975_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "ak8975_mod", + }, + .address_list = normal_i2c, +}; + +static int __init ak8975_mod_init(void) +{ + int res = i2c_add_driver(&ak8975_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "ak8975_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit ak8975_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&ak8975_mod_driver); +} + +module_init(ak8975_mod_init); +module_exit(ak8975_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate AK8975 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ak8975_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/ami306.c b/drivers/misc/inv_mpu/compass/ami306.c new file mode 100644 index 0000000..ecf4541 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/ami306.c @@ -0,0 +1,1020 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file ami306.c + * @brief Magnetometer setup and handling methods for Aichi AMI306 + * compass. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include "ami_hw.h" +#include "ami_sensor_def.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +/* -------------------------------------------------------------------------- */ +#define AMI306_REG_DATAX (0x10) +#define AMI306_REG_STAT1 (0x18) +#define AMI306_REG_CNTL1 (0x1B) +#define AMI306_REG_CNTL2 (0x1C) +#define AMI306_REG_CNTL3 (0x1D) +#define AMI306_REG_CNTL4_1 (0x5C) +#define AMI306_REG_CNTL4_2 (0x5D) + +#define AMI306_BIT_CNTL1_PC1 (0x80) +#define AMI306_BIT_CNTL1_ODR1 (0x10) +#define AMI306_BIT_CNTL1_FS1 (0x02) + +#define AMI306_BIT_CNTL2_IEN (0x10) +#define AMI306_BIT_CNTL2_DREN (0x08) +#define AMI306_BIT_CNTL2_DRP (0x04) +#define AMI306_BIT_CNTL3_F0RCE (0x40) + +#define AMI_FINE_MAX (96) +#define AMI_STANDARD_OFFSET (0x800) +#define AMI_GAIN_COR_DEFAULT (1000) + +/* -------------------------------------------------------------------------- */ +struct ami306_private_data { + int isstandby; + unsigned char fine[3]; + AMI_SENSOR_PARAMETOR param; + AMI_WIN_PARAMETER win; +}; + +/* -------------------------------------------------------------------------- */ +static inline unsigned short little_u8_to_u16(unsigned char *p_u8) +{ + return p_u8[0] | (p_u8[1] << 8); +} + +static int ami306_set_bits8(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + unsigned char reg, unsigned char bits) +{ + int result; + unsigned char buf; + + result = inv_serial_read(mlsl_handle, pdata->address, reg, 1, &buf); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + buf |= bits; + result = inv_serial_single_write(mlsl_handle, pdata->address, reg, buf); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int ami306_wait_data_ready(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + unsigned long usecs, unsigned long times) +{ + int result = 0; + unsigned char buf; + + for (; 0 < times; --times) { + udelay(usecs); + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_STA1, 1, &buf); + if (buf & AMI_STA1_DRDY_BIT) + return 0; + else if (buf & AMI_STA1_DOR_BIT) + return INV_ERROR_COMPASS_DATA_OVERFLOW; + } + return INV_ERROR_COMPASS_DATA_NOT_READY; +} + +static int ami306_read_raw_data(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + short dat[3]) +{ + int result; + unsigned char buf[6]; + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_DATAX, sizeof(buf), buf); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dat[0] = little_u8_to_u16(&buf[0]); + dat[1] = little_u8_to_u16(&buf[2]); + dat[2] = little_u8_to_u16(&buf[4]); + return result; +} + +#define AMI_WAIT_DATAREADY_RETRY 3 /* retry times */ +#define AMI_DRDYWAIT 800 /* u(micro) sec */ +static int ami306_force_mesurement(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + short ver[3]) +{ + int result; + int status; + result = ami306_set_bits8(mlsl_handle, pdata, + AMI_REG_CTRL3, AMI_CTRL3_FORCE_BIT); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = ami306_wait_data_ready(mlsl_handle, pdata, + AMI_DRDYWAIT, AMI_WAIT_DATAREADY_RETRY); + if (result && result != INV_ERROR_COMPASS_DATA_OVERFLOW) { + LOG_RESULT_LOCATION(result); + return result; + } + /* READ DATA X,Y,Z */ + status = ami306_read_raw_data(mlsl_handle, pdata, ver); + if (status) { + LOG_RESULT_LOCATION(status); + return status; + } + + return result; +} + +static int ami306_mea(void *mlsl_handle, + struct ext_slave_platform_data *pdata, short val[3]) +{ + int result = ami306_force_mesurement(mlsl_handle, pdata, val); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + val[0] += AMI_STANDARD_OFFSET; + val[1] += AMI_STANDARD_OFFSET; + val[2] += AMI_STANDARD_OFFSET; + return result; +} + +static int ami306_write_offset(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + unsigned char *fine) +{ + int result = 0; + unsigned char dat[3]; + dat[0] = AMI_REG_OFFX; + dat[1] = 0x7f & fine[0]; + dat[2] = 0; + result = inv_serial_write(mlsl_handle, pdata->address, + sizeof(dat), dat); + dat[0] = AMI_REG_OFFY; + dat[1] = 0x7f & fine[1]; + dat[2] = 0; + result = inv_serial_write(mlsl_handle, pdata->address, + sizeof(dat), dat); + dat[0] = AMI_REG_OFFZ; + dat[1] = 0x7f & fine[2]; + dat[2] = 0; + result = inv_serial_write(mlsl_handle, pdata->address, + sizeof(dat), dat); + return result; +} + +static int ami306_start_sensor(void *mlsl_handle, + struct ext_slave_platform_data *pdata) +{ + int result = 0; + unsigned char buf[3]; + struct ami306_private_data *private_data = pdata->private_data; + + /* Step 1 */ + result = ami306_set_bits8(mlsl_handle, pdata, + AMI_REG_CTRL1, + AMI_CTRL1_PC1 | AMI_CTRL1_FS1_FORCE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Step 2 */ + result = ami306_set_bits8(mlsl_handle, pdata, + AMI_REG_CTRL2, AMI_CTRL2_DREN); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Step 3 */ + buf[0] = AMI_REG_CTRL4; + buf[1] = AMI_CTRL4_HS & 0xFF; + buf[2] = (AMI_CTRL4_HS >> 8) & 0xFF; + result = inv_serial_write(mlsl_handle, pdata->address, + sizeof(buf), buf); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Step 4 */ + result = ami306_write_offset(mlsl_handle, pdata, private_data->fine); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +/** + * This function does this. + * + * @param mlsl_handle this param is this. + * @param slave + * @param pdata + * + * @return INV_SUCCESS or non-zero error code. + */ +static int ami306_read_param(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = 0; + unsigned char regs[12]; + struct ami306_private_data *private_data = pdata->private_data; + AMI_SENSOR_PARAMETOR *param = &private_data->param; + + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_SENX, sizeof(regs), regs); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Little endian 16 bit registers */ + param->m_gain.x = little_u8_to_u16(®s[0]); + param->m_gain.y = little_u8_to_u16(®s[2]); + param->m_gain.z = little_u8_to_u16(®s[4]); + + param->m_interference.xy = regs[7]; + param->m_interference.xz = regs[6]; + param->m_interference.yx = regs[9]; + param->m_interference.yz = regs[8]; + param->m_interference.zx = regs[11]; + param->m_interference.zy = regs[10]; + + param->m_offset.x = AMI_STANDARD_OFFSET; + param->m_offset.y = AMI_STANDARD_OFFSET; + param->m_offset.z = AMI_STANDARD_OFFSET; + + param->m_gain_cor.x = AMI_GAIN_COR_DEFAULT; + param->m_gain_cor.y = AMI_GAIN_COR_DEFAULT; + param->m_gain_cor.z = AMI_GAIN_COR_DEFAULT; + + return result; +} + +static int ami306_initial_b0_adjust(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char fine[3] = { 0 }; + short data[3]; + int diff[3] = { 0x7fff, 0x7fff, 0x7fff }; + int fn = 0; + int ax = 0; + unsigned char buf[3]; + struct ami306_private_data *private_data = pdata->private_data; + + result = ami306_set_bits8(mlsl_handle, pdata, + AMI_REG_CTRL2, AMI_CTRL2_DREN); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + buf[0] = AMI_REG_CTRL4; + buf[1] = AMI_CTRL4_HS & 0xFF; + buf[2] = (AMI_CTRL4_HS >> 8) & 0xFF; + result = inv_serial_write(mlsl_handle, pdata->address, + sizeof(buf), buf); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + for (fn = 0; fn < AMI_FINE_MAX; ++fn) { /* fine 0 -> 95 */ + fine[0] = fine[1] = fine[2] = fn; + result = ami306_write_offset(mlsl_handle, pdata, fine); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = ami306_force_mesurement(mlsl_handle, pdata, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("[%d] x:%-5d y:%-5d z:%-5d\n", + fn, data[0], data[1], data[2]); + + for (ax = 0; ax < 3; ax++) { + /* search point most close to zero. */ + if (diff[ax] > abs(data[ax])) { + private_data->fine[ax] = fn; + diff[ax] = abs(data[ax]); + } + } + } + MPL_LOGV("fine x:%-5d y:%-5d z:%-5d\n", + private_data->fine[0], private_data->fine[1], + private_data->fine[2]); + + result = ami306_write_offset(mlsl_handle, pdata, private_data->fine); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Software Reset */ + result = ami306_set_bits8(mlsl_handle, pdata, + AMI_REG_CTRL3, AMI_CTRL3_SRST_BIT); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +#define SEH_RANGE_MIN 100 +#define SEH_RANGE_MAX 3950 +static int ami306_search_offset(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + int axis; + unsigned char regs[6]; + unsigned char run_flg[3] = { 1, 1, 1 }; + unsigned char fine[3]; + unsigned char win_range_fine[3]; + unsigned short fine_output[3]; + short val[3]; + unsigned short cnt[3] = { 0 }; + struct ami306_private_data *private_data = pdata->private_data; + + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_FINEOUTPUT_X, sizeof(regs), regs); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + fine_output[0] = little_u8_to_u16(®s[0]); + fine_output[1] = little_u8_to_u16(®s[2]); + fine_output[2] = little_u8_to_u16(®s[4]); + + for (axis = 0; axis < 3; ++axis) { + if (fine_output[axis] == 0) { + MPL_LOGV("error fine_output %d axis:%d\n", + __LINE__, axis); + return -1; + } + /* fines per a window */ + win_range_fine[axis] = (SEH_RANGE_MAX - SEH_RANGE_MIN) + / fine_output[axis]; + } + + /* get current fine */ + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_OFFX, 2, ®s[0]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_OFFY, 2, ®s[2]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_OFFZ, 2, ®s[4]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + fine[0] = (unsigned char)(regs[0] & 0x7f); + fine[1] = (unsigned char)(regs[2] & 0x7f); + fine[2] = (unsigned char)(regs[4] & 0x7f); + + while (run_flg[0] == 1 || run_flg[1] == 1 || run_flg[2] == 1) { + + result = ami306_mea(mlsl_handle, pdata, val); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("val x:%-5d y:%-5d z:%-5d\n", val[0], val[1], val[2]); + MPL_LOGV("now fine x:%-5d y:%-5d z:%-5d\n", + fine[0], fine[1], fine[2]); + + for (axis = 0; axis < 3; ++axis) { + if (axis == 0) { /* X-axis is reversed */ + val[axis] = 0x0FFF & ~val[axis]; + } + if (val[axis] < SEH_RANGE_MIN) { + /* At the case of less low limmit. */ + fine[axis] -= win_range_fine[axis]; + MPL_LOGV("min : fine=%d diff=%d\n", + fine[axis], win_range_fine[axis]); + } + if (val[axis] > SEH_RANGE_MAX) { + /* At the case of over high limmit. */ + fine[axis] += win_range_fine[axis]; + MPL_LOGV("max : fine=%d diff=%d\n", + fine[axis], win_range_fine[axis]); + } + if (SEH_RANGE_MIN <= val[axis] && + val[axis] <= SEH_RANGE_MAX) { + /* In the current window. */ + int diff_fine = + (val[axis] - AMI_STANDARD_OFFSET) / + fine_output[axis]; + fine[axis] += diff_fine; + run_flg[axis] = 0; + MPL_LOGV("mid : fine=%d diff=%d\n", + fine[axis], diff_fine); + } + + if (!(0 <= fine[axis] && fine[axis] < AMI_FINE_MAX)) { + MPL_LOGE("fine err :%d\n", cnt[axis]); + goto out; + } + if (cnt[axis] > 3) { + MPL_LOGE("cnt err :%d\n", cnt[axis]); + goto out; + } + cnt[axis]++; + } + MPL_LOGV("new fine x:%-5d y:%-5d z:%-5d\n", + fine[0], fine[1], fine[2]); + + /* set current fine */ + result = ami306_write_offset(mlsl_handle, pdata, fine); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + memcpy(private_data->fine, fine, sizeof(fine)); + out: + result = ami306_set_bits8(mlsl_handle, pdata, + AMI_REG_CTRL3, AMI_CTRL3_SRST_BIT); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + udelay(250 + 50); + return 0; +} + +static int ami306_read_win(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = 0; + unsigned char regs[6]; + struct ami306_private_data *private_data = pdata->private_data; + AMI_WIN_PARAMETER *win = &private_data->win; + + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_OFFOTPX, sizeof(regs), regs); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + win->m_0Gauss_fine.x = (unsigned char)(regs[0] & 0x7f); + win->m_0Gauss_fine.y = (unsigned char)(regs[2] & 0x7f); + win->m_0Gauss_fine.z = (unsigned char)(regs[4] & 0x7f); + + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_OFFX, 2, ®s[0]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_OFFY, 2, ®s[2]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_REG_OFFZ, 2, ®s[4]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + win->m_fine.x = (unsigned char)(regs[0] & 0x7f); + win->m_fine.y = (unsigned char)(regs[2] & 0x7f); + win->m_fine.z = (unsigned char)(regs[4] & 0x7f); + + result = inv_serial_read(mlsl_handle, pdata->address, + AMI_FINEOUTPUT_X, sizeof(regs), regs); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + win->m_fine_output.x = little_u8_to_u16(®s[0]); + win->m_fine_output.y = little_u8_to_u16(®s[2]); + win->m_fine_output.z = little_u8_to_u16(®s[4]); + + return result; +} + +static int ami306_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char reg; + result = inv_serial_read(mlsl_handle, pdata->address, + AMI306_REG_CNTL1, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + reg &= ~(AMI306_BIT_CNTL1_PC1 | AMI306_BIT_CNTL1_FS1); + result = inv_serial_single_write(mlsl_handle, pdata->address, + AMI306_REG_CNTL1, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +static int ami306_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + unsigned char regs[] = { + AMI306_REG_CNTL4_1, + 0x7E, + 0xA0 + }; + /* Step1. Set CNTL1 reg to power model active (Write CNTL1:PC1=1) */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + AMI306_REG_CNTL1, + AMI306_BIT_CNTL1_PC1 | + AMI306_BIT_CNTL1_FS1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Step2. Set CNTL2 reg to DRDY active high and enabled + (Write CNTL2:DREN=1) */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + AMI306_REG_CNTL2, + AMI306_BIT_CNTL2_DREN | + AMI306_BIT_CNTL2_DRP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Step3. Set CNTL4 reg to for measurement speed: Write CNTL4, 0xA07E */ + result = inv_serial_write(mlsl_handle, pdata->address, + ARRAY_SIZE(regs), regs); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Step4. skipped */ + + /* Step5. Set CNTL3 reg to forced measurement period + (Write CNTL3:FORCE=1) */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + AMI306_REG_CNTL3, + AMI306_BIT_CNTL3_F0RCE); + + return result; +} + +static int ami306_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result = INV_SUCCESS; + int ii; + short val[COMPASS_NUM_AXES]; + + result = ami306_mea(mlsl_handle, pdata, val); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + for (ii = 0; ii < COMPASS_NUM_AXES; ii++) { + val[ii] -= AMI_STANDARD_OFFSET; + data[2 * ii] = val[ii] & 0xFF; + data[(2 * ii) + 1] = (val[ii] >> 8) & 0xFF; + } + return result; +} + +static int ami306_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + struct ami306_private_data *private_data; + private_data = (struct ami306_private_data *) + kzalloc(sizeof(struct ami306_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + result = ami306_set_bits8(mlsl_handle, pdata, + AMI_REG_CTRL1, + AMI_CTRL1_PC1 | AMI_CTRL1_FS1_FORCE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Read Parameters */ + result = ami306_read_param(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Read Window */ + result = ami306_initial_b0_adjust(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = ami306_start_sensor(mlsl_handle, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = ami306_read_win(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->address, + AMI306_REG_CNTL1, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return INV_SUCCESS; +} + +static int ami306_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static int ami306_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + if (!data->data) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + switch (data->key) { + case MPU_SLAVE_PARAM: + case MPU_SLAVE_WINDOW: + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + case MPU_SLAVE_CONFIG_ODR_RESUME: + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + case MPU_SLAVE_CONFIG_FSR_RESUME: + case MPU_SLAVE_CONFIG_MOT_THS: + case MPU_SLAVE_CONFIG_NMOT_THS: + case MPU_SLAVE_CONFIG_MOT_DUR: + case MPU_SLAVE_CONFIG_NMOT_DUR: + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static int ami306_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + int result; + struct ami306_private_data *private_data = pdata->private_data; + if (!data->data) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + switch (data->key) { + case MPU_SLAVE_PARAM: + if (sizeof(AMI_SENSOR_PARAMETOR) > data->len) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + if (data->apply) { + result = ami306_read_param(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + memcpy(data->data, &private_data->param, + sizeof(AMI_SENSOR_PARAMETOR)); + break; + case MPU_SLAVE_WINDOW: + if (sizeof(AMI_WIN_PARAMETER) > data->len) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + if (data->apply) { + result = ami306_read_win(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + memcpy(data->data, &private_data->win, + sizeof(AMI_WIN_PARAMETER)); + break; + case MPU_SLAVE_SEARCHOFFSET: + if (sizeof(AMI_WIN_PARAMETER) > data->len) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + if (data->apply) { + result = ami306_search_offset(mlsl_handle, + slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Start sensor */ + result = ami306_start_sensor(mlsl_handle, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = ami306_read_win(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + memcpy(data->data, &private_data->win, + sizeof(AMI_WIN_PARAMETER)); + break; + case MPU_SLAVE_READWINPARAMS: + if (sizeof(AMI_WIN_PARAMETER) > data->len) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + if (data->apply) { + result = ami306_initial_b0_adjust(mlsl_handle, + slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Start sensor */ + result = ami306_start_sensor(mlsl_handle, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = ami306_read_win(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + memcpy(data->data, &private_data->win, + sizeof(AMI_WIN_PARAMETER)); + break; + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = 0; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = 50000; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + case MPU_SLAVE_CONFIG_FSR_RESUME: + case MPU_SLAVE_CONFIG_MOT_THS: + case MPU_SLAVE_CONFIG_NMOT_THS: + case MPU_SLAVE_CONFIG_MOT_DUR: + case MPU_SLAVE_CONFIG_NMOT_DUR: + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + case MPU_SLAVE_READ_SCALE: + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static struct ext_slave_read_trigger ami306_read_trigger = { + /*.reg = */ AMI_REG_CTRL3, + /*.value = */ AMI_CTRL3_FORCE_BIT +}; + +static struct ext_slave_descr ami306_descr = { + .init = ami306_init, + .exit = ami306_exit, + .suspend = ami306_suspend, + .resume = ami306_resume, + .read = ami306_read, + .config = ami306_config, + .get_config = ami306_get_config, + .name = "ami306", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_AMI306, + .read_reg = 0x0E, + .read_len = 13, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {5461, 3333}, + .trigger = &ami306_read_trigger, +}; + +static +struct ext_slave_descr *ami306_get_slave_descr(void) +{ + return &ami306_descr; +} + +/* -------------------------------------------------------------------------- */ +struct ami306_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int ami306_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct ami306_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + ami306_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int ami306_mod_remove(struct i2c_client *client) +{ + struct ami306_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + ami306_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id ami306_mod_id[] = { + { "ami306", COMPASS_ID_AMI306 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ami306_mod_id); + +static struct i2c_driver ami306_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = ami306_mod_probe, + .remove = ami306_mod_remove, + .id_table = ami306_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "ami306_mod", + }, + .address_list = normal_i2c, +}; + +static int __init ami306_mod_init(void) +{ + int res = i2c_add_driver(&ami306_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "ami306_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit ami306_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&ami306_mod_driver); +} + +module_init(ami306_mod_init); +module_exit(ami306_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate AMI306 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ami306_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/ami30x.c b/drivers/misc/inv_mpu/compass/ami30x.c new file mode 100644 index 0000000..4cb0daa --- /dev/null +++ b/drivers/misc/inv_mpu/compass/ami30x.c @@ -0,0 +1,308 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file ami30x.c + * @brief Magnetometer setup and handling methods for Aichi AMI304 + * and AMI305 compass devices. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +/* -------------------------------------------------------------------------- */ +#define AMI30X_REG_DATAX (0x10) +#define AMI30X_REG_STAT1 (0x18) +#define AMI30X_REG_CNTL1 (0x1B) +#define AMI30X_REG_CNTL2 (0x1C) +#define AMI30X_REG_CNTL3 (0x1D) + +#define AMI30X_BIT_CNTL1_PC1 (0x80) +#define AMI30X_BIT_CNTL1_ODR1 (0x10) +#define AMI30X_BIT_CNTL1_FS1 (0x02) + +#define AMI30X_BIT_CNTL2_IEN (0x10) +#define AMI30X_BIT_CNTL2_DREN (0x08) +#define AMI30X_BIT_CNTL2_DRP (0x04) +#define AMI30X_BIT_CNTL3_F0RCE (0x40) + +/* -------------------------------------------------------------------------- */ +static int ami30x_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char reg; + result = + inv_serial_read(mlsl_handle, pdata->address, AMI30X_REG_CNTL1, + 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + reg &= ~(AMI30X_BIT_CNTL1_PC1 | AMI30X_BIT_CNTL1_FS1); + result = + inv_serial_single_write(mlsl_handle, pdata->address, + AMI30X_REG_CNTL1, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +static int ami30x_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + /* Set CNTL1 reg to power model active */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + AMI30X_REG_CNTL1, + AMI30X_BIT_CNTL1_PC1 | + AMI30X_BIT_CNTL1_FS1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Set CNTL2 reg to DRDY active high and enabled */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + AMI30X_REG_CNTL2, + AMI30X_BIT_CNTL2_DREN | + AMI30X_BIT_CNTL2_DRP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Set CNTL3 reg to forced measurement period */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + AMI30X_REG_CNTL3, AMI30X_BIT_CNTL3_F0RCE); + + return result; +} + +static int ami30x_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + unsigned char stat; + int result = INV_SUCCESS; + + /* Read status reg and check if data ready (DRDY) */ + result = + inv_serial_read(mlsl_handle, pdata->address, AMI30X_REG_STAT1, + 1, &stat); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (stat & 0x40) { + result = + inv_serial_read(mlsl_handle, pdata->address, + AMI30X_REG_DATAX, 6, (unsigned char *)data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* start another measurement */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + AMI30X_REG_CNTL3, + AMI30X_BIT_CNTL3_F0RCE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return INV_SUCCESS; + } + + return INV_ERROR_COMPASS_DATA_NOT_READY; +} + + +/* For AMI305,the range field needs to be modified to {9830.4f} */ +static struct ext_slave_descr ami30x_descr = { + .init = NULL, + .exit = NULL, + .suspend = ami30x_suspend, + .resume = ami30x_resume, + .read = ami30x_read, + .config = NULL, + .get_config = NULL, + .name = "ami30x", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_AMI30X, + .read_reg = 0x06, + .read_len = 6, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {5461, 3333}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *ami30x_get_slave_descr(void) +{ + return &ami30x_descr; +} + +/* -------------------------------------------------------------------------- */ +struct ami30x_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int ami30x_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct ami30x_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + ami30x_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int ami30x_mod_remove(struct i2c_client *client) +{ + struct ami30x_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + ami30x_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id ami30x_mod_id[] = { + { "ami30x", COMPASS_ID_AMI30X }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ami30x_mod_id); + +static struct i2c_driver ami30x_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = ami30x_mod_probe, + .remove = ami30x_mod_remove, + .id_table = ami30x_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "ami30x_mod", + }, + .address_list = normal_i2c, +}; + +static int __init ami30x_mod_init(void) +{ + int res = i2c_add_driver(&ami30x_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "ami30x_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit ami30x_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&ami30x_mod_driver); +} + +module_init(ami30x_mod_init); +module_exit(ami30x_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate AMI30X sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ami30x_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/ami_hw.h b/drivers/misc/inv_mpu/compass/ami_hw.h new file mode 100644 index 0000000..995355c --- /dev/null +++ b/drivers/misc/inv_mpu/compass/ami_hw.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2010 Information System Products Co.,Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AMI_HW_H +#define AMI_HW_H + +/* N of /dev/i2c-N. */ +#define AMI_I2C_BUS_NUM 2 + +#ifdef AMI304_MODEL +/* new Addr=0x0E(Low), old Addr=0x0F(High) */ +#define AMI_I2C_ADDRESS 0x0F +#else +/* new Addr=0x0E(Low), old Addr=0x0F(High) */ +#define AMI_I2C_ADDRESS 0x0E +#endif + +/* AMI-Sensor Internal Register Address + *(Please refer to AMI-Sensor Specifications) + */ +#define AMI_MOREINFO_CMDCODE 0x0d +#define AMI_WHOIAM_CMDCODE 0x0f +#define AMI_REG_DATAX 0x10 +#define AMI_REG_DATAY 0x12 +#define AMI_REG_DATAZ 0x14 +#define AMI_REG_STA1 0x18 +#define AMI_REG_CTRL1 0x1b +#define AMI_REG_CTRL2 0x1c +#define AMI_REG_CTRL3 0x1d +#define AMI_REG_B0X 0x20 +#define AMI_REG_B0Y 0x22 +#define AMI_REG_B0Z 0x24 +#define AMI_REG_CTRL5 0x40 +#define AMI_REG_CTRL4 0x5c +#define AMI_REG_TEMP 0x60 +#define AMI_REG_DELAYX 0x68 +#define AMI_REG_DELAYY 0x6e +#define AMI_REG_DELAYZ 0x74 +#define AMI_REG_OFFX 0x6c +#define AMI_REG_OFFY 0x72 +#define AMI_REG_OFFZ 0x78 +#define AMI_FINEOUTPUT_X 0x90 +#define AMI_FINEOUTPUT_Y 0x92 +#define AMI_FINEOUTPUT_Z 0x94 +#define AMI_REG_SENX 0x96 +#define AMI_REG_SENY 0x98 +#define AMI_REG_SENZ 0x9a +#define AMI_REG_GAINX 0x9c +#define AMI_REG_GAINY 0x9e +#define AMI_REG_GAINZ 0xa0 +#define AMI_GETVERSION_CMDCODE 0xe8 +#define AMI_SERIALNUMBER_CMDCODE 0xea +#define AMI_REG_B0OTPX 0xa2 +#define AMI_REG_B0OTPY 0xb8 +#define AMI_REG_B0OTPZ 0xce +#define AMI_REG_OFFOTPX 0xf8 +#define AMI_REG_OFFOTPY 0xfa +#define AMI_REG_OFFOTPZ 0xfc + +/* AMI-Sensor Control Bit (Please refer to AMI-Sensor Specifications) */ +#define AMI_CTRL1_PC1 0x80 +#define AMI_CTRL1_FS1_FORCE 0x02 +#define AMI_CTRL1_ODR1 0x10 +#define AMI_CTRL2_DREN 0x08 +#define AMI_CTRL2_DRP 0x04 +#define AMI_CTRL3_FORCE_BIT 0x40 +#define AMI_CTRL3_B0_LO_BIT 0x10 +#define AMI_CTRL3_SRST_BIT 0x80 +#define AMI_CTRL4_HS 0xa07e +#define AMI_CTRL4_AB 0x0001 +#define AMI_STA1_DRDY_BIT 0x40 +#define AMI_STA1_DOR_BIT 0x20 + +#endif diff --git a/drivers/misc/inv_mpu/compass/ami_sensor_def.h b/drivers/misc/inv_mpu/compass/ami_sensor_def.h new file mode 100644 index 0000000..454d5fa --- /dev/null +++ b/drivers/misc/inv_mpu/compass/ami_sensor_def.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2010 Information System Products Co.,Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Definitions for ami306 compass chip. + */ +#ifndef AMI_SENSOR_DEF_H +#define AMI_SENSOR_DEF_H + +/********************************************************************* + Constant + *********************************************************************/ +#define AMI_OK 0x00 /**< Normal */ +#define AMI_PARAM_ERR 0x01 /**< Parameter Error */ +#define AMI_SEQ_ERR 0x02 /**< Squence Error */ +#define AMI_SYSTEM_ERR 0x10 /**< System Error */ +#define AMI_BLOCK_ERR 0x20 /**< Block Error */ +#define AMI_ERROR 0x99 /**< other Error */ + +/********************************************************************* + Type definition + *********************************************************************/ +typedef signed char ami_sint8; /**< signed char */ +typedef unsigned char ami_uint8; /**< unsigned char */ +typedef signed short ami_sint16; /**< signed short */ +typedef unsigned short ami_uint16; /**< unsigned short */ +typedef signed long ami_sint32; /**< signed long */ +typedef unsigned long ami_uint32; /**< unsigned long */ +typedef double ami_double; /**< double */ + +/********************************************************************* + Struct definition + *********************************************************************/ +/** axis sensitivity(gain) calibration parameter information */ +typedef struct { + ami_sint16 x; /**< X-axis */ + ami_sint16 y; /**< Y-axis */ + ami_sint16 z; /**< Z-axis */ +} AMI_VECTOR3D; + +/** axis interference information */ +typedef struct { + ami_sint16 xy; /**< Y-axis magnetic field for X-axis correction value */ + ami_sint16 xz; /**< Z-axis magnetic field for X-axis correction value */ + ami_sint16 yx; /**< X-axis magnetic field for Y-axis correction value */ + ami_sint16 yz; /**< Z-axis magnetic field for Y-axis correction value */ + ami_sint16 zx; /**< X-axis magnetic field for Z-axis correction value */ + ami_sint16 zy; /**< Y-axis magnetic field for Z-axis correction value */ +} AMI_INTERFERENCE; + +/** sensor calibration Parameter information */ +typedef struct { + AMI_VECTOR3D m_gain; /**< geomagnetic field sensor gain */ + + /** geomagnetic field sensor gain correction parameter */ + AMI_VECTOR3D m_gain_cor; + AMI_VECTOR3D m_offset; /**< geomagnetic field sensor offset */ + /** geomagnetic field sensor axis interference parameter */ + AMI_INTERFERENCE m_interference; +#ifdef AMI_6AXIS + AMI_VECTOR3D a_gain; /**< acceleration sensor gain */ + AMI_VECTOR3D a_offset; /**< acceleration sensor offset */ +#endif +} AMI_SENSOR_PARAMETOR; + +/** G2-Sensor measurement value (voltage ADC value ) */ +typedef struct { + /** geomagnetic field sensor measurement X-axis value + (mounted position/direction reference) */ + ami_uint16 mx; + /** geomagnetic field sensor measurement Y-axis value + (mounted position/direction reference) */ + ami_uint16 my; + /** geomagnetic field sensor measurement Z-axis value + (mounted position/direction reference) */ + ami_uint16 mz; +#ifdef AMI_6AXIS + /** acceleration sensor measurement X-axis value + (mounted position/direction reference) */ + ami_uint16 ax; + /** acceleration sensor measurement Y-axis value + (mounted position/direction reference) */ + ami_uint16 ay; + /** acceleration sensor measurement Z-axis value + (mounted position/direction reference) */ + ami_uint16 az; +#endif + /** temperature sensor measurement value */ + ami_uint16 temperature; +} AMI_SENSOR_RAWVALUE; + +/** Window function Parameter information */ +typedef struct { + AMI_VECTOR3D m_fine; /**< current fine value */ + AMI_VECTOR3D m_fine_output; /**< change per 1coarse */ + AMI_VECTOR3D m_0Gauss_fine; /**< fine value at zero gauss */ +#ifdef AMI304 + AMI_VECTOR3D m_b0; /**< current b0 value */ + AMI_VECTOR3D m_coar; /**< current coarse value */ + AMI_VECTOR3D m_coar_output; /**< change per 1fine */ + AMI_VECTOR3D m_0Gauss_coar; /**< coarse value at zero gauss */ + AMI_VECTOR3D m_delay; /**< delay value */ +#endif +} AMI_WIN_PARAMETER; + +/** AMI chip information ex) 1)model 2)s/n 3)ver 4)more info in the chip */ +typedef struct { + ami_uint16 info; /* INFO 0x0d/0x0e reg. */ + ami_uint16 ver; /* VER 0xe8/0xe9 reg. */ + ami_uint16 sn; /* SN 0xea/0xeb reg. */ + ami_uint8 wia; /* WIA 0x0f reg. */ +} AMI_CHIPINFO; + +/** AMI Driver Information */ +typedef struct { + ami_uint8 remarks[40 + 1]; /* Some Information */ + ami_uint8 datetime[30 + 1]; /* compiled date&time */ + ami_uint8 ver_major; /* major version */ + ami_uint8 ver_middle; /* middle.. */ + ami_uint8 ver_minor; /* minor .. */ +} AMI_DRIVERINFO; + +#endif diff --git a/drivers/misc/inv_mpu/compass/hmc5883.c b/drivers/misc/inv_mpu/compass/hmc5883.c new file mode 100644 index 0000000..06d428e --- /dev/null +++ b/drivers/misc/inv_mpu/compass/hmc5883.c @@ -0,0 +1,391 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file hmc5883.c + * @brief Magnetometer setup and handling methods for Honeywell + * HMC5883 compass. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +/* -------------------------------------------------------------------------- */ +enum HMC_REG { + HMC_REG_CONF_A = 0x0, + HMC_REG_CONF_B = 0x1, + HMC_REG_MODE = 0x2, + HMC_REG_X_M = 0x3, + HMC_REG_X_L = 0x4, + HMC_REG_Z_M = 0x5, + HMC_REG_Z_L = 0x6, + HMC_REG_Y_M = 0x7, + HMC_REG_Y_L = 0x8, + HMC_REG_STATUS = 0x9, + HMC_REG_ID_A = 0xA, + HMC_REG_ID_B = 0xB, + HMC_REG_ID_C = 0xC +}; + +enum HMC_CONF_A { + HMC_CONF_A_DRATE_MASK = 0x1C, + HMC_CONF_A_DRATE_0_75 = 0x00, + HMC_CONF_A_DRATE_1_5 = 0x04, + HMC_CONF_A_DRATE_3 = 0x08, + HMC_CONF_A_DRATE_7_5 = 0x0C, + HMC_CONF_A_DRATE_15 = 0x10, + HMC_CONF_A_DRATE_30 = 0x14, + HMC_CONF_A_DRATE_75 = 0x18, + HMC_CONF_A_MEAS_MASK = 0x3, + HMC_CONF_A_MEAS_NORM = 0x0, + HMC_CONF_A_MEAS_POS = 0x1, + HMC_CONF_A_MEAS_NEG = 0x2 +}; + +enum HMC_CONF_B { + HMC_CONF_B_GAIN_MASK = 0xE0, + HMC_CONF_B_GAIN_0_9 = 0x00, + HMC_CONF_B_GAIN_1_2 = 0x20, + HMC_CONF_B_GAIN_1_9 = 0x40, + HMC_CONF_B_GAIN_2_5 = 0x60, + HMC_CONF_B_GAIN_4_0 = 0x80, + HMC_CONF_B_GAIN_4_6 = 0xA0, + HMC_CONF_B_GAIN_5_5 = 0xC0, + HMC_CONF_B_GAIN_7_9 = 0xE0 +}; + +enum HMC_MODE { + HMC_MODE_MASK = 0x3, + HMC_MODE_CONT = 0x0, + HMC_MODE_SINGLE = 0x1, + HMC_MODE_IDLE = 0x2, + HMC_MODE_SLEEP = 0x3 +}; + +/* -------------------------------------------------------------------------- */ +static int hmc5883_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + result = + inv_serial_single_write(mlsl_handle, pdata->address, + HMC_REG_MODE, HMC_MODE_SLEEP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(3); + + return result; +} + +static int hmc5883_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + /* Use single measurement mode. Start at sleep state. */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + HMC_REG_MODE, HMC_MODE_SLEEP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Config normal measurement */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + HMC_REG_CONF_A, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Adjust gain to 307 LSB/Gauss */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + HMC_REG_CONF_B, HMC_CONF_B_GAIN_5_5); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +static int hmc5883_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + unsigned char stat; + int result = INV_SUCCESS; + unsigned char tmp; + short axisFixed; + + /* Read status reg. to check if data is ready */ + result = + inv_serial_read(mlsl_handle, pdata->address, HMC_REG_STATUS, 1, + &stat); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (stat & 0x01) { + result = + inv_serial_read(mlsl_handle, pdata->address, + HMC_REG_X_M, 6, (unsigned char *)data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* switch YZ axis to proper position */ + tmp = data[2]; + data[2] = data[4]; + data[4] = tmp; + tmp = data[3]; + data[3] = data[5]; + data[5] = tmp; + + /*drop data if overflows */ + if ((data[0] == 0xf0) || (data[2] == 0xf0) + || (data[4] == 0xf0)) { + /* trigger next measurement read */ + result = + inv_serial_single_write(mlsl_handle, + pdata->address, + HMC_REG_MODE, + HMC_MODE_SINGLE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return INV_ERROR_COMPASS_DATA_OVERFLOW; + } + /* convert to fixed point and apply sensitivity correction for + Z-axis */ + axisFixed = + (short)((unsigned short)data[5] + + (unsigned short)data[4] * 256); + /* scale up by 1.125 (36/32) */ + axisFixed = (short)(axisFixed * 36); + data[4] = axisFixed >> 8; + data[5] = axisFixed & 0xFF; + + axisFixed = + (short)((unsigned short)data[3] + + (unsigned short)data[2] * 256); + axisFixed = (short)(axisFixed * 32); + data[2] = axisFixed >> 8; + data[3] = axisFixed & 0xFF; + + axisFixed = + (short)((unsigned short)data[1] + + (unsigned short)data[0] * 256); + axisFixed = (short)(axisFixed * 32); + data[0] = axisFixed >> 8; + data[1] = axisFixed & 0xFF; + + /* trigger next measurement read */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + HMC_REG_MODE, HMC_MODE_SINGLE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return INV_SUCCESS; + } else { + /* trigger next measurement read */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + HMC_REG_MODE, HMC_MODE_SINGLE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return INV_ERROR_COMPASS_DATA_NOT_READY; + } +} + +static struct ext_slave_descr hmc5883_descr = { + .init = NULL, + .exit = NULL, + .suspend = hmc5883_suspend, + .resume = hmc5883_resume, + .read = hmc5883_read, + .config = NULL, + .get_config = NULL, + .name = "hmc5883", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_HMC5883, + .read_reg = 0x06, + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {10673, 6156}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *hmc5883_get_slave_descr(void) +{ + return &hmc5883_descr; +} + +/* -------------------------------------------------------------------------- */ +struct hmc5883_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int hmc5883_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct hmc5883_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + hmc5883_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int hmc5883_mod_remove(struct i2c_client *client) +{ + struct hmc5883_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + hmc5883_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id hmc5883_mod_id[] = { + { "hmc5883", COMPASS_ID_HMC5883 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, hmc5883_mod_id); + +static struct i2c_driver hmc5883_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = hmc5883_mod_probe, + .remove = hmc5883_mod_remove, + .id_table = hmc5883_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "hmc5883_mod", + }, + .address_list = normal_i2c, +}; + +static int __init hmc5883_mod_init(void) +{ + int res = i2c_add_driver(&hmc5883_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "hmc5883_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit hmc5883_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&hmc5883_mod_driver); +} + +module_init(hmc5883_mod_init); +module_exit(hmc5883_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate HMC5883 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("hmc5883_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/hscdtd002b.c b/drivers/misc/inv_mpu/compass/hscdtd002b.c new file mode 100644 index 0000000..978822f --- /dev/null +++ b/drivers/misc/inv_mpu/compass/hscdtd002b.c @@ -0,0 +1,294 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file hscdtd002b.c + * @brief Magnetometer setup and handling methods for Alps HSCDTD002B + * compass. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +/* -------------------------------------------------------------------------- */ +#define COMPASS_HSCDTD002B_STAT (0x18) +#define COMPASS_HSCDTD002B_CTRL1 (0x1B) +#define COMPASS_HSCDTD002B_CTRL2 (0x1C) +#define COMPASS_HSCDTD002B_CTRL3 (0x1D) +#define COMPASS_HSCDTD002B_DATAX (0x10) + +/* -------------------------------------------------------------------------- */ +static int hscdtd002b_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + /* Power mode: stand-by */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD002B_CTRL1, 0x00); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(1); /* turn-off time */ + + return result; +} + +static int hscdtd002b_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + /* Soft reset */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD002B_CTRL3, 0x80); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Force state; Power mode: active */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD002B_CTRL1, 0x82); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Data ready enable */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD002B_CTRL2, 0x08); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(1); /* turn-on time */ + + return result; +} + +static int hscdtd002b_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + unsigned char stat; + int result = INV_SUCCESS; + int status = INV_SUCCESS; + + /* Read status reg. to check if data is ready */ + result = + inv_serial_read(mlsl_handle, pdata->address, + COMPASS_HSCDTD002B_STAT, 1, &stat); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (stat & 0x40) { + result = + inv_serial_read(mlsl_handle, pdata->address, + COMPASS_HSCDTD002B_DATAX, 6, + (unsigned char *)data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + status = INV_SUCCESS; + } else if (stat & 0x20) { + status = INV_ERROR_COMPASS_DATA_OVERFLOW; + } else { + status = INV_ERROR_COMPASS_DATA_NOT_READY; + } + /* trigger next measurement read */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD002B_CTRL3, 0x40); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return status; +} + +static struct ext_slave_descr hscdtd002b_descr = { + .init = NULL, + .exit = NULL, + .suspend = hscdtd002b_suspend, + .resume = hscdtd002b_resume, + .read = hscdtd002b_read, + .config = NULL, + .get_config = NULL, + .name = "hscdtd002b", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_HSCDTD002B, + .read_reg = 0x10, + .read_len = 6, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {9830, 4000}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *hscdtd002b_get_slave_descr(void) +{ + return &hscdtd002b_descr; +} + +/* -------------------------------------------------------------------------- */ +struct hscdtd002b_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int hscdtd002b_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct hscdtd002b_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + hscdtd002b_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int hscdtd002b_mod_remove(struct i2c_client *client) +{ + struct hscdtd002b_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + hscdtd002b_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id hscdtd002b_mod_id[] = { + { "hscdtd002b", COMPASS_ID_HSCDTD002B }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, hscdtd002b_mod_id); + +static struct i2c_driver hscdtd002b_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = hscdtd002b_mod_probe, + .remove = hscdtd002b_mod_remove, + .id_table = hscdtd002b_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "hscdtd002b_mod", + }, + .address_list = normal_i2c, +}; + +static int __init hscdtd002b_mod_init(void) +{ + int res = i2c_add_driver(&hscdtd002b_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "hscdtd002b_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit hscdtd002b_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&hscdtd002b_mod_driver); +} + +module_init(hscdtd002b_mod_init); +module_exit(hscdtd002b_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate HSCDTD002B sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("hscdtd002b_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/hscdtd004a.c b/drivers/misc/inv_mpu/compass/hscdtd004a.c new file mode 100644 index 0000000..e196032 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/hscdtd004a.c @@ -0,0 +1,294 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file hscdtd004a.c + * @brief Magnetometer setup and handling methods for Alps HSCDTD004A + * compass. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +/* -------------------------------------------------------------------------- */ +#define COMPASS_HSCDTD004A_STAT (0x18) +#define COMPASS_HSCDTD004A_CTRL1 (0x1B) +#define COMPASS_HSCDTD004A_CTRL2 (0x1C) +#define COMPASS_HSCDTD004A_CTRL3 (0x1D) +#define COMPASS_HSCDTD004A_DATAX (0x10) + +/* -------------------------------------------------------------------------- */ + +static int hscdtd004a_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + /* Power mode: stand-by */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD004A_CTRL1, 0x00); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(1); /* turn-off time */ + + return result; +} + +static int hscdtd004a_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + /* Soft reset */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD004A_CTRL3, 0x80); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Normal state; Power mode: active */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD004A_CTRL1, 0x82); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Data ready enable */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD004A_CTRL2, 0x7C); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(1); /* turn-on time */ + return result; +} + +static int hscdtd004a_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + unsigned char stat; + int result = INV_SUCCESS; + int status = INV_SUCCESS; + + /* Read status reg. to check if data is ready */ + result = + inv_serial_read(mlsl_handle, pdata->address, + COMPASS_HSCDTD004A_STAT, 1, &stat); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (stat & 0x48) { + result = + inv_serial_read(mlsl_handle, pdata->address, + COMPASS_HSCDTD004A_DATAX, 6, + (unsigned char *)data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + status = INV_SUCCESS; + } else if (stat & 0x68) { + status = INV_ERROR_COMPASS_DATA_OVERFLOW; + } else { + status = INV_ERROR_COMPASS_DATA_NOT_READY; + } + /* trigger next measurement read */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + COMPASS_HSCDTD004A_CTRL3, 0x40); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return status; + +} + +static struct ext_slave_descr hscdtd004a_descr = { + .init = NULL, + .exit = NULL, + .suspend = hscdtd004a_suspend, + .resume = hscdtd004a_resume, + .read = hscdtd004a_read, + .config = NULL, + .get_config = NULL, + .name = "hscdtd004a", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_HSCDTD004A, + .read_reg = 0x10, + .read_len = 6, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {9830, 4000}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *hscdtd004a_get_slave_descr(void) +{ + return &hscdtd004a_descr; +} + +/* -------------------------------------------------------------------------- */ +struct hscdtd004a_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int hscdtd004a_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct hscdtd004a_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + hscdtd004a_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int hscdtd004a_mod_remove(struct i2c_client *client) +{ + struct hscdtd004a_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + hscdtd004a_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id hscdtd004a_mod_id[] = { + { "hscdtd004a", COMPASS_ID_HSCDTD004A }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, hscdtd004a_mod_id); + +static struct i2c_driver hscdtd004a_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = hscdtd004a_mod_probe, + .remove = hscdtd004a_mod_remove, + .id_table = hscdtd004a_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "hscdtd004a_mod", + }, + .address_list = normal_i2c, +}; + +static int __init hscdtd004a_mod_init(void) +{ + int res = i2c_add_driver(&hscdtd004a_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "hscdtd004a_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit hscdtd004a_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&hscdtd004a_mod_driver); +} + +module_init(hscdtd004a_mod_init); +module_exit(hscdtd004a_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate HSCDTD004A sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("hscdtd004a_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/lsm303m.c b/drivers/misc/inv_mpu/compass/lsm303m.c new file mode 100644 index 0000000..3ca1a67 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/lsm303m.c @@ -0,0 +1,386 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file lsm303m.c + * @brief Magnetometer setup and handling methods for ST LSM303 + * compass. + * This magnetometer device is part of a combo chip with the + * ST LIS331DLH accelerometer and the logic in entirely based + * on the Honeywell HMC5883 magnetometer. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +/* -------------------------------------------------------------------------- */ +enum LSM_REG { + LSM_REG_CONF_A = 0x0, + LSM_REG_CONF_B = 0x1, + LSM_REG_MODE = 0x2, + LSM_REG_X_M = 0x3, + LSM_REG_X_L = 0x4, + LSM_REG_Z_M = 0x5, + LSM_REG_Z_L = 0x6, + LSM_REG_Y_M = 0x7, + LSM_REG_Y_L = 0x8, + LSM_REG_STATUS = 0x9, + LSM_REG_ID_A = 0xA, + LSM_REG_ID_B = 0xB, + LSM_REG_ID_C = 0xC +}; + +enum LSM_CONF_A { + LSM_CONF_A_DRATE_MASK = 0x1C, + LSM_CONF_A_DRATE_0_75 = 0x00, + LSM_CONF_A_DRATE_1_5 = 0x04, + LSM_CONF_A_DRATE_3 = 0x08, + LSM_CONF_A_DRATE_7_5 = 0x0C, + LSM_CONF_A_DRATE_15 = 0x10, + LSM_CONF_A_DRATE_30 = 0x14, + LSM_CONF_A_DRATE_75 = 0x18, + LSM_CONF_A_MEAS_MASK = 0x3, + LSM_CONF_A_MEAS_NORM = 0x0, + LSM_CONF_A_MEAS_POS = 0x1, + LSM_CONF_A_MEAS_NEG = 0x2 +}; + +enum LSM_CONF_B { + LSM_CONF_B_GAIN_MASK = 0xE0, + LSM_CONF_B_GAIN_0_9 = 0x00, + LSM_CONF_B_GAIN_1_2 = 0x20, + LSM_CONF_B_GAIN_1_9 = 0x40, + LSM_CONF_B_GAIN_2_5 = 0x60, + LSM_CONF_B_GAIN_4_0 = 0x80, + LSM_CONF_B_GAIN_4_6 = 0xA0, + LSM_CONF_B_GAIN_5_5 = 0xC0, + LSM_CONF_B_GAIN_7_9 = 0xE0 +}; + +enum LSM_MODE { + LSM_MODE_MASK = 0x3, + LSM_MODE_CONT = 0x0, + LSM_MODE_SINGLE = 0x1, + LSM_MODE_IDLE = 0x2, + LSM_MODE_SLEEP = 0x3 +}; + +/* -------------------------------------------------------------------------- */ + +static int lsm303dlhm_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + result = + inv_serial_single_write(mlsl_handle, pdata->address, + LSM_REG_MODE, LSM_MODE_SLEEP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(3); + + return result; +} + +static int lsm303dlhm_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + /* Use single measurement mode. Start at sleep state. */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + LSM_REG_MODE, LSM_MODE_SLEEP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Config normal measurement */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + LSM_REG_CONF_A, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Adjust gain to 320 LSB/Gauss */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + LSM_REG_CONF_B, LSM_CONF_B_GAIN_5_5); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +static int lsm303dlhm_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + unsigned char stat; + int result = INV_SUCCESS; + short axisFixed; + + /* Read status reg. to check if data is ready */ + result = + inv_serial_read(mlsl_handle, pdata->address, LSM_REG_STATUS, 1, + &stat); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (stat & 0x01) { + result = + inv_serial_read(mlsl_handle, pdata->address, + LSM_REG_X_M, 6, (unsigned char *)data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /*drop data if overflows */ + if ((data[0] == 0xf0) || (data[2] == 0xf0) + || (data[4] == 0xf0)) { + /* trigger next measurement read */ + result = + inv_serial_single_write(mlsl_handle, + pdata->address, + LSM_REG_MODE, + LSM_MODE_SINGLE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return INV_ERROR_COMPASS_DATA_OVERFLOW; + } + /* convert to fixed point and apply sensitivity correction for + Z-axis */ + axisFixed = + (short)((unsigned short)data[5] + + (unsigned short)data[4] * 256); + /* scale up by 1.125 (36/32) approximate of 1.122 (320/285) */ + axisFixed = (short)(axisFixed * 36); + data[4] = axisFixed >> 8; + data[5] = axisFixed & 0xFF; + + axisFixed = + (short)((unsigned short)data[3] + + (unsigned short)data[2] * 256); + axisFixed = (short)(axisFixed * 32); + data[2] = axisFixed >> 8; + data[3] = axisFixed & 0xFF; + + axisFixed = + (short)((unsigned short)data[1] + + (unsigned short)data[0] * 256); + axisFixed = (short)(axisFixed * 32); + data[0] = axisFixed >> 8; + data[1] = axisFixed & 0xFF; + + /* trigger next measurement read */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + LSM_REG_MODE, LSM_MODE_SINGLE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return INV_SUCCESS; + } else { + /* trigger next measurement read */ + result = + inv_serial_single_write(mlsl_handle, pdata->address, + LSM_REG_MODE, LSM_MODE_SINGLE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return INV_ERROR_COMPASS_DATA_NOT_READY; + } +} + +static struct ext_slave_descr lsm303dlhm_descr = { + .init = NULL, + .exit = NULL, + .suspend = lsm303dlhm_suspend, + .resume = lsm303dlhm_resume, + .read = lsm303dlhm_read, + .config = NULL, + .get_config = NULL, + .name = "lsm303dlhm", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_LSM303M, + .read_reg = 0x06, + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {10240, 0}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *lsm303m_get_slave_descr(void) +{ + return &lsm303dlhm_descr; +} + +/* -------------------------------------------------------------------------- */ +struct lsm303m_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int lsm303m_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct lsm303m_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + lsm303m_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int lsm303m_mod_remove(struct i2c_client *client) +{ + struct lsm303m_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + lsm303m_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id lsm303m_mod_id[] = { + { "lsm303m", COMPASS_ID_LSM303M }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lsm303m_mod_id); + +static struct i2c_driver lsm303m_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = lsm303m_mod_probe, + .remove = lsm303m_mod_remove, + .id_table = lsm303m_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "lsm303m_mod", + }, + .address_list = normal_i2c, +}; + +static int __init lsm303m_mod_init(void) +{ + int res = i2c_add_driver(&lsm303m_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "lsm303m_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit lsm303m_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&lsm303m_mod_driver); +} + +module_init(lsm303m_mod_init); +module_exit(lsm303m_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate LSM303M sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("lsm303m_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/mmc314x.c b/drivers/misc/inv_mpu/compass/mmc314x.c new file mode 100644 index 0000000..f1b300e --- /dev/null +++ b/drivers/misc/inv_mpu/compass/mmc314x.c @@ -0,0 +1,313 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file mmc314x.c + * @brief Magnetometer setup and handling methods for the + * MEMSIC MMC314x compass. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +/* -------------------------------------------------------------------------- */ + +static int reset_int = 1000; +static int read_count = 1; +static char reset_mode; /* in Z-init section */ + +/* -------------------------------------------------------------------------- */ +#define MMC314X_REG_ST (0x00) +#define MMC314X_REG_X_MSB (0x01) + +#define MMC314X_CNTL_MODE_WAKE_UP (0x01) +#define MMC314X_CNTL_MODE_SET (0x02) +#define MMC314X_CNTL_MODE_RESET (0x04) + +/* -------------------------------------------------------------------------- */ + +static int mmc314x_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + return result; +} + +static int mmc314x_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + + int result; + result = + inv_serial_single_write(mlsl_handle, pdata->address, + MMC314X_REG_ST, MMC314X_CNTL_MODE_RESET); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(10); + result = + inv_serial_single_write(mlsl_handle, pdata->address, + MMC314X_REG_ST, MMC314X_CNTL_MODE_SET); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(10); + read_count = 1; + return INV_SUCCESS; +} + +static int mmc314x_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result, ii; + short tmp[3]; + unsigned char tmpdata[6]; + + if (read_count > 1000) + read_count = 1; + + result = + inv_serial_read(mlsl_handle, pdata->address, MMC314X_REG_X_MSB, + 6, (unsigned char *)data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + for (ii = 0; ii < 6; ii++) + tmpdata[ii] = data[ii]; + + for (ii = 0; ii < 3; ii++) { + tmp[ii] = (short)((tmpdata[2 * ii] << 8) + tmpdata[2 * ii + 1]); + tmp[ii] = tmp[ii] - 4096; + tmp[ii] = tmp[ii] * 16; + } + + for (ii = 0; ii < 3; ii++) { + data[2 * ii] = (unsigned char)(tmp[ii] >> 8); + data[2 * ii + 1] = (unsigned char)(tmp[ii]); + } + + if (read_count % reset_int == 0) { + if (reset_mode) { + result = + inv_serial_single_write(mlsl_handle, + pdata->address, + MMC314X_REG_ST, + MMC314X_CNTL_MODE_RESET); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reset_mode = 0; + return INV_ERROR_COMPASS_DATA_NOT_READY; + } else { + result = + inv_serial_single_write(mlsl_handle, + pdata->address, + MMC314X_REG_ST, + MMC314X_CNTL_MODE_SET); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reset_mode = 1; + read_count++; + return INV_ERROR_COMPASS_DATA_NOT_READY; + } + } + result = + inv_serial_single_write(mlsl_handle, pdata->address, + MMC314X_REG_ST, MMC314X_CNTL_MODE_WAKE_UP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + read_count++; + + return INV_SUCCESS; +} + +static struct ext_slave_descr mmc314x_descr = { + .init = NULL, + .exit = NULL, + .suspend = mmc314x_suspend, + .resume = mmc314x_resume, + .read = mmc314x_read, + .config = NULL, + .get_config = NULL, + .name = "mmc314x", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_MMC314X, + .read_reg = 0x01, + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {400, 0}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *mmc314x_get_slave_descr(void) +{ + return &mmc314x_descr; +} + +/* -------------------------------------------------------------------------- */ +struct mmc314x_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int mmc314x_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct mmc314x_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + mmc314x_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int mmc314x_mod_remove(struct i2c_client *client) +{ + struct mmc314x_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + mmc314x_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id mmc314x_mod_id[] = { + { "mmc314x", COMPASS_ID_MMC314X }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mmc314x_mod_id); + +static struct i2c_driver mmc314x_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = mmc314x_mod_probe, + .remove = mmc314x_mod_remove, + .id_table = mmc314x_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "mmc314x_mod", + }, + .address_list = normal_i2c, +}; + +static int __init mmc314x_mod_init(void) +{ + int res = i2c_add_driver(&mmc314x_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "mmc314x_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit mmc314x_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&mmc314x_mod_driver); +} + +module_init(mmc314x_mod_init); +module_exit(mmc314x_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate MMC314X sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("mmc314x_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/yas529-kernel.c b/drivers/misc/inv_mpu/compass/yas529-kernel.c new file mode 100644 index 0000000..4758d63 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/yas529-kernel.c @@ -0,0 +1,611 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/*----- YAMAHA YAS529 Registers ------*/ +enum YAS_REG { + YAS_REG_CMDR = 0x00, /* 000 < 5 */ + YAS_REG_XOFFSETR = 0x20, /* 001 < 5 */ + YAS_REG_Y1OFFSETR = 0x40, /* 010 < 5 */ + YAS_REG_Y2OFFSETR = 0x60, /* 011 < 5 */ + YAS_REG_ICOILR = 0x80, /* 100 < 5 */ + YAS_REG_CAL = 0xA0, /* 101 < 5 */ + YAS_REG_CONFR = 0xC0, /* 110 < 5 */ + YAS_REG_DOUTR = 0xE0 /* 111 < 5 */ +}; + +/* -------------------------------------------------------------------------- */ + +static long a1; +static long a2; +static long a3; +static long a4; +static long a5; +static long a6; +static long a7; +static long a8; +static long a9; + +/* -------------------------------------------------------------------------- */ +static int yas529_sensor_i2c_write(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned int len, unsigned char *data) +{ + struct i2c_msg msgs[1]; + int res; + + if (NULL == data || NULL == i2c_adap) + return -EINVAL; + + msgs[0].addr = address; + msgs[0].flags = 0; /* write */ + msgs[0].buf = (unsigned char *)data; + msgs[0].len = len; + + res = i2c_transfer(i2c_adap, msgs, 1); + if (res < 1) + return res; + else + return 0; +} + +static int yas529_sensor_i2c_read(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned char reg, + unsigned int len, unsigned char *data) +{ + struct i2c_msg msgs[2]; + int res; + + if (NULL == data || NULL == i2c_adap) + return -EINVAL; + + msgs[0].addr = address; + msgs[0].flags = I2C_M_RD; + msgs[0].buf = data; + msgs[0].len = len; + + res = i2c_transfer(i2c_adap, msgs, 1); + if (res < 1) + return res; + else + return 0; +} + +static int yas529_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + return result; +} + +static int yas529_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + unsigned char dummyData[1] = { 0 }; + unsigned char dummyRegister = 0; + unsigned char rawData[6]; + unsigned char calData[9]; + + short xoffset, y1offset, y2offset; + short d2, d3, d4, d5, d6, d7, d8, d9; + + /* YAS529 Application Manual MS-3C - Section 4.4.5 */ + /* =============================================== */ + /* Step 1 - register initialization */ + /* zero initialization coil register - "100 00 000" */ + dummyData[0] = YAS_REG_ICOILR | 0x00; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* zero config register - "110 00 000" */ + dummyData[0] = YAS_REG_CONFR | 0x00; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Step 2 - initialization coil operation */ + dummyData[0] = YAS_REG_ICOILR | 0x11; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x01; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x12; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x02; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x13; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x03; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x14; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x04; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x15; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x05; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x16; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x06; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x17; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x07; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x10; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_ICOILR | 0x00; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Step 3 - rough offset measurement */ + /* Config register - Measurements results - "110 00 000" */ + dummyData[0] = YAS_REG_CONFR | 0x00; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Measurements command register - Rough offset measurement - + "000 00001" */ + dummyData[0] = YAS_REG_CMDR | 0x01; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(2); /* wait at least 1.5ms */ + + /* Measurement data read */ + result = + yas529_sensor_i2c_read(mlsl_handle, pdata->address, + dummyRegister, 6, rawData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + xoffset = + (short)((unsigned short)rawData[5] + + ((unsigned short)rawData[4] & 0x7) * 256) - 5; + if (xoffset < 0) + xoffset = 0; + y1offset = + (short)((unsigned short)rawData[3] + + ((unsigned short)rawData[2] & 0x7) * 256) - 5; + if (y1offset < 0) + y1offset = 0; + y2offset = + (short)((unsigned short)rawData[1] + + ((unsigned short)rawData[0] & 0x7) * 256) - 5; + if (y2offset < 0) + y2offset = 0; + + /* Step 4 - rough offset setting */ + /* Set rough offset register values */ + dummyData[0] = YAS_REG_XOFFSETR | xoffset; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_Y1OFFSETR | y1offset; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + dummyData[0] = YAS_REG_Y2OFFSETR | y2offset; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* CAL matrix read (first read is invalid) */ + /* Config register - CAL register read - "110 01 000" */ + dummyData[0] = YAS_REG_CONFR | 0x08; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* CAL data read */ + result = + yas529_sensor_i2c_read(mlsl_handle, pdata->address, + dummyRegister, 9, calData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Config register - CAL register read - "110 01 000" */ + dummyData[0] = YAS_REG_CONFR | 0x08; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* CAL data read */ + result = + yas529_sensor_i2c_read(mlsl_handle, pdata->address, + dummyRegister, 9, calData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Calculate coefficients of the sensitivity correction matrix */ + a1 = 100; + d2 = (calData[0] & 0xFC) >> 2; /* [71..66] 6bit */ + a2 = (short)(d2 - 32); + /* [65..62] 4bit */ + d3 = ((calData[0] & 0x03) << 2) | ((calData[1] & 0xC0) >> 6); + a3 = (short)(d3 - 8); + d4 = (calData[1] & 0x3F); /* [61..56] 6bit */ + a4 = (short)(d4 - 32); + d5 = (calData[2] & 0xFC) >> 2; /* [55..50] 6bit */ + a5 = (short)(d5 - 32) + 70; + /* [49..44] 6bit */ + d6 = ((calData[2] & 0x03) << 4) | ((calData[3] & 0xF0) >> 4); + a6 = (short)(d6 - 32); + /* [43..38] 6bit */ + d7 = ((calData[3] & 0x0F) << 2) | ((calData[4] & 0xC0) >> 6); + a7 = (short)(d7 - 32); + d8 = (calData[4] & 0x3F); /* [37..32] 6bit */ + a8 = (short)(d8 - 32); + d9 = (calData[5] & 0xFE) >> 1; /* [31..25] 7bit */ + a9 = (short)(d9 - 64) + 130; + + return result; +} + +static int yas529_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + unsigned char stat; + unsigned char rawData[6]; + unsigned char dummyData[1] = { 0 }; + unsigned char dummyRegister = 0; + int result = INV_SUCCESS; + short SX, SY1, SY2, SY, SZ; + short row1fixed, row2fixed, row3fixed; + + /* Config register - Measurements results - "110 00 000" */ + dummyData[0] = YAS_REG_CONFR | 0x00; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Measurements command register - Normal magnetic field measurement - + "000 00000" */ + dummyData[0] = YAS_REG_CMDR | 0x00; + result = + yas529_sensor_i2c_write(mlsl_handle, pdata->address, 1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(10); + /* Measurement data read */ + result = + yas529_sensor_i2c_read(mlsl_handle, pdata->address, + dummyRegister, 6, (unsigned char *)&rawData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + stat = rawData[0] & 0x80; + if (stat == 0x00) { + /* Extract raw data */ + SX = (short)((unsigned short)rawData[5] + + ((unsigned short)rawData[4] & 0x7) * 256); + SY1 = + (short)((unsigned short)rawData[3] + + ((unsigned short)rawData[2] & 0x7) * 256); + SY2 = + (short)((unsigned short)rawData[1] + + ((unsigned short)rawData[0] & 0x7) * 256); + if ((SX <= 1) || (SY1 <= 1) || (SY2 <= 1)) + return INV_ERROR_COMPASS_DATA_UNDERFLOW; + if ((SX >= 1024) || (SY1 >= 1024) || (SY2 >= 1024)) + return INV_ERROR_COMPASS_DATA_OVERFLOW; + /* Convert to XYZ axis */ + SX = -1 * SX; + SY = SY2 - SY1; + SZ = SY1 + SY2; + + /* Apply sensitivity correction matrix */ + row1fixed = (short)((a1 * SX + a2 * SY + a3 * SZ) >> 7) * 41; + row2fixed = (short)((a4 * SX + a5 * SY + a6 * SZ) >> 7) * 41; + row3fixed = (short)((a7 * SX + a8 * SY + a9 * SZ) >> 7) * 41; + + data[0] = row1fixed >> 8; + data[1] = row1fixed & 0xFF; + data[2] = row2fixed >> 8; + data[3] = row2fixed & 0xFF; + data[4] = row3fixed >> 8; + data[5] = row3fixed & 0xFF; + + return INV_SUCCESS; + } else { + return INV_ERROR_COMPASS_DATA_NOT_READY; + } +} + +static struct ext_slave_descr yas529_descr = { + .init = NULL, + .exit = NULL, + .suspend = yas529_suspend, + .resume = yas529_resume, + .read = yas529_read, + .config = NULL, + .get_config = NULL, + .name = "yas529", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_YAS529, + .read_reg = 0x06, + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {19660, 8000}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *yas529_get_slave_descr(void) +{ + return &yas529_descr; +} + +/* -------------------------------------------------------------------------- */ +struct yas529_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int yas529_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct yas529_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + yas529_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int yas529_mod_remove(struct i2c_client *client) +{ + struct yas529_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + yas529_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id yas529_mod_id[] = { + { "yas529", COMPASS_ID_YAS529 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, yas529_mod_id); + +static struct i2c_driver yas529_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = yas529_mod_probe, + .remove = yas529_mod_remove, + .id_table = yas529_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "yas529_mod", + }, + .address_list = normal_i2c, +}; + +static int __init yas529_mod_init(void) +{ + int res = i2c_add_driver(&yas529_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "yas529_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit yas529_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&yas529_mod_driver); +} + +module_init(yas529_mod_init); +module_exit(yas529_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate YAS529 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("yas529_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/yas530.c b/drivers/misc/inv_mpu/compass/yas530.c new file mode 100644 index 0000000..fbe7d7f --- /dev/null +++ b/drivers/misc/inv_mpu/compass/yas530.c @@ -0,0 +1,784 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file yas530.c + * @brief Magnetometer setup and handling methods for Yamaha YAS530 + * compass when used in a user-space solution (no kernel driver). + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> + +#include <linux/module.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include "log.h" +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +enum { + FLAG_OFFSETS_VALID = 0x00000001, + FLAG_RESUMED = 0x00000002, +}; + +struct yas530_private_data { + int flags; + char offsets[3]; + const int *correction_matrix; +}; + +/* -------------------------------------------------------------------------- */ +#define YAS530_REGADDR_DEVICE_ID (0x80) +#define YAS530_REGADDR_ACTUATE_INIT_COIL (0x81) +#define YAS530_REGADDR_MEASURE_COMMAND (0x82) +#define YAS530_REGADDR_CONFIG (0x83) +#define YAS530_REGADDR_MEASURE_INTERVAL (0x84) +#define YAS530_REGADDR_OFFSET_X (0x85) +#define YAS530_REGADDR_OFFSET_Y1 (0x86) +#define YAS530_REGADDR_OFFSET_Y2 (0x87) +#define YAS530_REGADDR_TEST1 (0x88) +#define YAS530_REGADDR_TEST2 (0x89) +#define YAS530_REGADDR_CAL (0x90) +#define YAS530_REGADDR_MEASURE_DATA (0xb0) + +/* -------------------------------------------------------------------------- */ +static int Cx, Cy1, Cy2; +static int /*a1, */ a2, a3, a4, a5, a6, a7, a8, a9; +static int k; + +static unsigned char dx, dy1, dy2; +static unsigned char d2, d3, d4, d5, d6, d7, d8, d9, d0; +static unsigned char dck; + +/* -------------------------------------------------------------------------- */ + +static int is_overunderflow(short xy1y2) +{ + return (xy1y2 == 0 || xy1y2 == 4095); +} + +static int set_hardware_offset(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + char offset_x, char offset_y1, char offset_y2) +{ + char data; + int result = INV_SUCCESS; + + data = offset_x & 0x3f; + result = inv_serial_single_write(mlsl_handle, pdata->address, + YAS530_REGADDR_OFFSET_X, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + data = offset_y1 & 0x3f; + result = inv_serial_single_write(mlsl_handle, pdata->address, + YAS530_REGADDR_OFFSET_Y1, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + data = offset_y2 & 0x3f; + result = inv_serial_single_write(mlsl_handle, pdata->address, + YAS530_REGADDR_OFFSET_Y2, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +static int set_measure_command(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + int ldtc, int fors, int dlymes) +{ + int result = INV_SUCCESS; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + YAS530_REGADDR_MEASURE_COMMAND, 0x01); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + +static int measure_normal(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + int *busy, unsigned short *t, + unsigned short *x, unsigned short *y1, + unsigned short *y2) +{ + unsigned char data[8]; + unsigned short b, to, xo, y1o, y2o; + int result; + ktime_t sleeptime; + result = set_measure_command(mlsl_handle, slave, pdata, 0, 0, 0); + sleeptime = ktime_set(0, 2 * NSEC_PER_MSEC); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_hrtimeout(&sleeptime, HRTIMER_MODE_REL); + + result = inv_serial_read(mlsl_handle, pdata->address, + YAS530_REGADDR_MEASURE_DATA, 8, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* msleep(2); */ + + b = (data[0] >> 7) & 0x01; + to = ((data[0] << 2) & 0x1fc) | ((data[1] >> 6) & 0x03); + xo = ((data[2] << 5) & 0xfe0) | ((data[3] >> 3) & 0x1f); + y1o = ((data[4] << 5) & 0xfe0) | ((data[5] >> 3) & 0x1f); + y2o = ((data[6] << 5) & 0xfe0) | ((data[7] >> 3) & 0x1f); + + *busy = b; + *t = to; + *x = xo; + *y1 = y1o; + *y2 = y2o; + + return result; +} + +static int check_offset(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + char offset_x, char offset_y1, char offset_y2, + int *flag_x, int *flag_y1, int *flag_y2) +{ + int result; + int busy; + short t, x, y1, y2; + + result = set_hardware_offset(mlsl_handle, slave, pdata, + offset_x, offset_y1, offset_y2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = measure_normal(mlsl_handle, slave, pdata, + &busy, &t, &x, &y1, &y2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + *flag_x = 0; + *flag_y1 = 0; + *flag_y2 = 0; + + if (x > 2048) + *flag_x = 1; + if (y1 > 2048) + *flag_y1 = 1; + if (y2 > 2048) + *flag_y2 = 1; + if (x < 2048) + *flag_x = -1; + if (y1 < 2048) + *flag_y1 = -1; + if (y2 < 2048) + *flag_y2 = -1; + + return result; +} + +static int measure_and_set_offset(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + char *offset) +{ + int i; + int result = INV_SUCCESS; + char offset_x = 0, offset_y1 = 0, offset_y2 = 0; + int flag_x = 0, flag_y1 = 0, flag_y2 = 0; + static const int correct[5] = { 16, 8, 4, 2, 1 }; + + for (i = 0; i < 5; i++) { + result = check_offset(mlsl_handle, slave, pdata, + offset_x, offset_y1, offset_y2, + &flag_x, &flag_y1, &flag_y2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (flag_x) + offset_x += flag_x * correct[i]; + if (flag_y1) + offset_y1 += flag_y1 * correct[i]; + if (flag_y2) + offset_y2 += flag_y2 * correct[i]; + } + + result = set_hardware_offset(mlsl_handle, slave, pdata, + offset_x, offset_y1, offset_y2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + offset[0] = offset_x; + offset[1] = offset_y1; + offset[2] = offset_y2; + + return result; +} + +static void coordinate_conversion(short x, short y1, short y2, short t, + int32_t *xo, int32_t *yo, int32_t *zo) +{ + int32_t sx, sy1, sy2, sy, sz; + int32_t hx, hy, hz; + + sx = x - (Cx * t) / 100; + sy1 = y1 - (Cy1 * t) / 100; + sy2 = y2 - (Cy2 * t) / 100; + + sy = sy1 - sy2; + sz = -sy1 - sy2; + + hx = k * ((100 * sx + a2 * sy + a3 * sz) / 10); + hy = k * ((a4 * sx + a5 * sy + a6 * sz) / 10); + hz = k * ((a7 * sx + a8 * sy + a9 * sz) / 10); + + *xo = hx; + *yo = hy; + *zo = hz; +} + +static int power_up(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + unsigned char dummyData = 0x00; + unsigned char data[16]; + unsigned char read_reg[1]; + + /* =============================================== */ + + /* Step 1 - Test register initialization */ + dummyData = 0x00; + result = inv_serial_single_write(mlsl_handle, pdata->address, + YAS530_REGADDR_TEST1, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = + inv_serial_single_write(mlsl_handle, pdata->address, + YAS530_REGADDR_TEST2, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Device ID read */ + result = inv_serial_read(mlsl_handle, pdata->address, + YAS530_REGADDR_DEVICE_ID, 1, read_reg); + + /*Step 2 Read the CAL register */ + /* CAL data read */ + result = inv_serial_read(mlsl_handle, pdata->address, + YAS530_REGADDR_CAL, 16, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* CAL data Second Read */ + result = inv_serial_read(mlsl_handle, pdata->address, + YAS530_REGADDR_CAL, 16, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /*Cal data */ + dx = data[0]; + dy1 = data[1]; + dy2 = data[2]; + d2 = (data[3] >> 2) & 0x03f; + d3 = ((data[3] << 2) & 0x0c) | ((data[4] >> 6) & 0x03); + d4 = data[4] & 0x3f; + d5 = (data[5] >> 2) & 0x3f; + d6 = ((data[5] << 4) & 0x30) | ((data[6] >> 4) & 0x0f); + d7 = ((data[6] << 3) & 0x78) | ((data[7] >> 5) & 0x07); + d8 = ((data[7] << 1) & 0x3e) | ((data[8] >> 7) & 0x01); + d9 = ((data[8] << 1) & 0xfe) | ((data[9] >> 7) & 0x01); + d0 = (data[9] >> 2) & 0x1f; + dck = ((data[9] << 1) & 0x06) | ((data[10] >> 7) & 0x01); + + /*Correction Data */ + Cx = (int)dx * 6 - 768; + Cy1 = (int)dy1 * 6 - 768; + Cy2 = (int)dy2 * 6 - 768; + a2 = (int)d2 - 32; + a3 = (int)d3 - 8; + a4 = (int)d4 - 32; + a5 = (int)d5 + 38; + a6 = (int)d6 - 32; + a7 = (int)d7 - 64; + a8 = (int)d8 - 32; + a9 = (int)d9; + k = (int)d0 + 10; + + /*Obtain the [49:47] bits */ + dck &= 0x07; + + /*Step 3 : Storing the CONFIG with the CLK value */ + dummyData = 0x00 | (dck << 2); + result = inv_serial_single_write(mlsl_handle, pdata->address, + YAS530_REGADDR_CONFIG, dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /*Step 4 : Set Acquisition Interval Register */ + dummyData = 0x00; + result = inv_serial_single_write(mlsl_handle, pdata->address, + YAS530_REGADDR_MEASURE_INTERVAL, + dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /*Step 5 : Reset Coil */ + dummyData = 0x00; + result = inv_serial_single_write(mlsl_handle, pdata->address, + YAS530_REGADDR_ACTUATE_INIT_COIL, + dummyData); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; + +} + +static int yas530_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + return result; +} + + +static int yas530_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + + struct yas530_private_data *private_data = pdata->private_data; + + result = power_up(mlsl_handle, slave, pdata); + + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + private_data->flags |= FLAG_RESUMED; + + return result; +} + +static int yas530_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result = INV_SUCCESS; + + int busy; + short t, x, y1, y2; + int32_t xyz[3]; + int32_t tmp[3]; + int i; + short rawfixed[3]; + struct yas530_private_data *private_data = pdata->private_data; + const int *correction_matrix = private_data->correction_matrix; + + result = measure_normal(mlsl_handle, slave, pdata, + &busy, &t, &x, &y1, &y2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + coordinate_conversion(x, y1, y2, t, &xyz[0], &xyz[1], &xyz[2]); + if (correction_matrix) { + for (i = 0; i < 3; i++) { + tmp[i] = (correction_matrix[i * 3 + 0] + * (xyz[0] / 10)) / 100 + + (correction_matrix[i * 3 + 1] + * (xyz[1] / 10)) / 100 + + (correction_matrix[i * 3 + 2] + * (xyz[2] / 10)) / 100; + } + for (i = 0; i < 3; i++) + xyz[i] = tmp[i]; + } + + + rawfixed[0] = (short)(xyz[0] / 100); + rawfixed[1] = (short)(xyz[1] / 100); + rawfixed[2] = (short)(xyz[2] / 100); + + data[0] = rawfixed[0] >> 8; + data[1] = rawfixed[0] & 0xFF; + data[2] = rawfixed[1] >> 8; + data[3] = rawfixed[1] & 0xFF; + data[4] = rawfixed[2] >> 8; + data[5] = rawfixed[2] & 0xFF; + data[6] = is_overunderflow(x) + || is_overunderflow(y1) + || is_overunderflow(y2); + + return result; +} + +static int yas530_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + int result = INV_SUCCESS; + struct yas530_private_data *private_data = pdata->private_data; + switch (data->key) { + case MPU_SLAVE_OFFSET_VALS: { + char offs_x, offs_y1, offs_y2; + offs_x = ((char *)(data->data))[0]; + offs_y1 = ((char *)(data->data))[1]; + offs_y2 = ((char *)(data->data))[2]; + result = set_hardware_offset(mlsl_handle, slave, pdata, + offs_x, offs_y1, offs_y2); + if (result == INV_SUCCESS) { + private_data->flags |= FLAG_OFFSETS_VALID; + private_data->offsets[0] = offs_x; + private_data->offsets[1] = offs_y1; + private_data->offsets[2] = offs_y2; + } + break; + } + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + } + + return result; + +} + +static int yas530_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + int result = INV_SUCCESS; + struct yas530_private_data *private_data = pdata->private_data; + + switch (data->key) { + case MPU_SLAVE_OFFSET_VALS: { + if (!(private_data->flags & FLAG_RESUMED)) { + result = power_up(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } else { + result = inv_serial_single_write(mlsl_handle, + pdata->address, + YAS530_REGADDR_ACTUATE_INIT_COIL, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = measure_and_set_offset(mlsl_handle, slave, pdata, + (char *)(data->data)); + if (result == INV_SUCCESS) { + private_data->flags |= FLAG_OFFSETS_VALID; + private_data->offsets[0] = ((char *)(data->data))[0]; + private_data->offsets[1] = ((char *)(data->data))[1]; + private_data->offsets[2] = ((char *)(data->data))[2]; + } + break; + } + case MPU_SLAVE_RANGE_CHECK: { + int busy; + short t, x, y1, y2; + char flag_x = 0, flag_y1 = 0, flag_y2 = 0; + + if (!(private_data->flags & FLAG_OFFSETS_VALID)) + return INV_ERROR_INVALID_CONFIGURATION; + + if (!(private_data->flags & FLAG_RESUMED)) { + result = power_up(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + result = measure_normal(mlsl_handle, slave, pdata, + &busy, &t, &x, &y1, &y2); + + if (x < 1024) + flag_x = -1; + if (x > 3072) + flag_x = 1; + if (y1 < 1024) + flag_y1 = -1; + if (y1 > 3072) + flag_y1 = 1; + if (y2 < 1024) + flag_y2 = -1; + if (y2 > 3072) + flag_y2 = 1; + + ((char *) (data->data))[0] = (char) flag_x; + ((char *) (data->data))[1] = (char) flag_y1; + ((char *) (data->data))[2] = (char) flag_y2; + break; + } + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + } + + return result; + +} + +static int yas530_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + + struct yas530_private_data *private_data; + int result = INV_SUCCESS; + char offset[3] = {0, 0, 0}; + + private_data = (struct yas530_private_data *) + kzalloc(sizeof(struct yas530_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + private_data->correction_matrix = pdata->private_data; + + pdata->private_data = private_data; + + result = power_up(mlsl_handle, slave, pdata); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = measure_and_set_offset(mlsl_handle, slave, pdata, offset); + if (result == INV_SUCCESS) { + private_data->flags |= FLAG_OFFSETS_VALID; + private_data->offsets[0] = offset[0]; + private_data->offsets[1] = offset[1]; + private_data->offsets[2] = offset[2]; + } + + return result; +} + +static int yas530_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static struct ext_slave_descr yas530_descr = { + .init = yas530_init, + .exit = yas530_exit, + .suspend = yas530_suspend, + .resume = yas530_resume, + .read = yas530_read, + .config = yas530_config, + .get_config = yas530_get_config, + .name = "yas530", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_YAS530, + .read_reg = 0x06, + .read_len = 7, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {3276, 8001}, + .trigger = NULL, +}; + +static +struct ext_slave_descr *yas530_get_slave_descr(void) +{ + return &yas530_descr; +} + +/* -------------------------------------------------------------------------- */ +struct yas530_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int yas530_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct yas530_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + yas530_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int yas530_mod_remove(struct i2c_client *client) +{ + struct yas530_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + yas530_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id yas530_mod_id[] = { + { "yas530", COMPASS_ID_YAS530 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, yas530_mod_id); + +static struct i2c_driver yas530_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = yas530_mod_probe, + .remove = yas530_mod_remove, + .id_table = yas530_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "yas530_mod", + }, + .address_list = normal_i2c, +}; + +static int __init yas530_mod_init(void) +{ + int res = i2c_add_driver(&yas530_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "yas530_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit yas530_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&yas530_mod_driver); +} + +module_init(yas530_mod_init); +module_exit(yas530_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate YAS530 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("yas530_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/log.h b/drivers/misc/inv_mpu/log.h new file mode 100644 index 0000000..99e6b4f --- /dev/null +++ b/drivers/misc/inv_mpu/log.h @@ -0,0 +1,287 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/* + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * C/C++ logging functions. See the logging documentation for API details. + * + * We'd like these to be available from C code (in case we import some from + * somewhere), so this has a C interface. + * + * The output will be correct when the log file is shared between multiple + * threads and/or multiple processes so long as the operating system + * supports O_APPEND. These calls have mutex-protected data structures + * and so are NOT reentrant. Do not use MPL_LOG in a signal handler. + */ +#ifndef _LIBS_CUTILS_MPL_LOG_H +#define _LIBS_CUTILS_MPL_LOG_H + +#include "mltypes.h" +#include <stdarg.h> + + +#include <linux/kernel.h> + + +/* --------------------------------------------------------------------- */ + +/* + * Normally we strip MPL_LOGV (VERBOSE messages) from release builds. + * You can modify this (for example with "#define MPL_LOG_NDEBUG 0" + * at the top of your source file) to change that behavior. + */ +#ifndef MPL_LOG_NDEBUG +#ifdef NDEBUG +#define MPL_LOG_NDEBUG 1 +#else +#define MPL_LOG_NDEBUG 0 +#endif +#endif + +#define MPL_LOG_UNKNOWN MPL_LOG_VERBOSE +#define MPL_LOG_DEFAULT KERN_DEFAULT +#define MPL_LOG_VERBOSE KERN_CONT +#define MPL_LOG_DEBUG KERN_NOTICE +#define MPL_LOG_INFO KERN_INFO +#define MPL_LOG_WARN KERN_WARNING +#define MPL_LOG_ERROR KERN_ERR +#define MPL_LOG_SILENT MPL_LOG_VERBOSE + + + +/* + * This is the local tag used for the following simplified + * logging macros. You can change this preprocessor definition + * before using the other macros to change the tag. + */ +#ifndef MPL_LOG_TAG +#define MPL_LOG_TAG +#endif + +/* --------------------------------------------------------------------- */ + +/* + * Simplified macro to send a verbose log message using the current MPL_LOG_TAG. + */ +#ifndef MPL_LOGV +#if MPL_LOG_NDEBUG +#define MPL_LOGV(fmt, ...) \ + do { \ + if (0) \ + MPL_LOG(LOG_VERBOSE, MPL_LOG_TAG, fmt, ##__VA_ARGS__);\ + } while (0) +#else +#define MPL_LOGV(fmt, ...) MPL_LOG(LOG_VERBOSE, MPL_LOG_TAG, fmt, ##__VA_ARGS__) +#endif +#endif + +#ifndef CONDITION +#define CONDITION(cond) ((cond) != 0) +#endif + +#ifndef MPL_LOGV_IF +#if MPL_LOG_NDEBUG +#define MPL_LOGV_IF(cond, fmt, ...) \ + do { if (0) MPL_LOG(fmt, ##__VA_ARGS__); } while (0) +#else +#define MPL_LOGV_IF(cond, fmt, ...) \ + ((CONDITION(cond)) \ + ? MPL_LOG(LOG_VERBOSE, MPL_LOG_TAG, fmt, ##__VA_ARGS__) \ + : (void)0) +#endif +#endif + +/* + * Simplified macro to send a debug log message using the current MPL_LOG_TAG. + */ +#ifndef MPL_LOGD +#define MPL_LOGD(fmt, ...) MPL_LOG(LOG_DEBUG, MPL_LOG_TAG, fmt, ##__VA_ARGS__) +#endif + +#ifndef MPL_LOGD_IF +#define MPL_LOGD_IF(cond, fmt, ...) \ + ((CONDITION(cond)) \ + ? MPL_LOG(LOG_DEBUG, MPL_LOG_TAG, fmt, ##__VA_ARGS__) \ + : (void)0) +#endif + +/* + * Simplified macro to send an info log message using the current MPL_LOG_TAG. + */ +#ifndef MPL_LOGI +#define MPL_LOGI(fmt, ...) MPL_LOG(LOG_INFO, MPL_LOG_TAG, fmt, ##__VA_ARGS__) +#endif + +#ifndef MPL_LOGI_IF +#define MPL_LOGI_IF(cond, fmt, ...) \ + ((CONDITION(cond)) \ + ? MPL_LOG(LOG_INFO, MPL_LOG_TAG, fmt, ##__VA_ARGS__) \ + : (void)0) +#endif + +/* + * Simplified macro to send a warning log message using the current MPL_LOG_TAG. + */ +#ifndef MPL_LOGW +#define MPL_LOGW(fmt, ...) printk(KERN_WARNING MPL_LOG_TAG fmt, ##__VA_ARGS__) +#endif + +#ifndef MPL_LOGW_IF +#define MPL_LOGW_IF(cond, fmt, ...) \ + ((CONDITION(cond)) \ + ? MPL_LOG(LOG_WARN, MPL_LOG_TAG, fmt, ##__VA_ARGS__) \ + : (void)0) +#endif + +/* + * Simplified macro to send an error log message using the current MPL_LOG_TAG. + */ +#ifndef MPL_LOGE +#define MPL_LOGE(fmt, ...) printk(KERN_ERR MPL_LOG_TAG fmt, ##__VA_ARGS__) +#endif + +#ifndef MPL_LOGE_IF +#define MPL_LOGE_IF(cond, fmt, ...) \ + ((CONDITION(cond)) \ + ? MPL_LOG(LOG_ERROR, MPL_LOG_TAG, fmt, ##__VA_ARGS__) \ + : (void)0) +#endif + +/* --------------------------------------------------------------------- */ + +/* + * Log a fatal error. If the given condition fails, this stops program + * execution like a normal assertion, but also generating the given message. + * It is NOT stripped from release builds. Note that the condition test + * is -inverted- from the normal assert() semantics. + */ +#define MPL_LOG_ALWAYS_FATAL_IF(cond, fmt, ...) \ + ((CONDITION(cond)) \ + ? ((void)android_printAssert(#cond, MPL_LOG_TAG, \ + fmt, ##__VA_ARGS__)) \ + : (void)0) + +#define MPL_LOG_ALWAYS_FATAL(fmt, ...) \ + (((void)android_printAssert(NULL, MPL_LOG_TAG, fmt, ##__VA_ARGS__))) + +/* + * Versions of MPL_LOG_ALWAYS_FATAL_IF and MPL_LOG_ALWAYS_FATAL that + * are stripped out of release builds. + */ +#if MPL_LOG_NDEBUG +#define MPL_LOG_FATAL_IF(cond, fmt, ...) \ + do { \ + if (0) \ + MPL_LOG_ALWAYS_FATAL_IF(cond, fmt, ##__VA_ARGS__); \ + } while (0) +#define MPL_LOG_FATAL(fmt, ...) \ + do { \ + if (0) \ + MPL_LOG_ALWAYS_FATAL(fmt, ##__VA_ARGS__) \ + } while (0) +#else +#define MPL_LOG_FATAL_IF(cond, fmt, ...) \ + MPL_LOG_ALWAYS_FATAL_IF(cond, fmt, ##__VA_ARGS__) +#define MPL_LOG_FATAL(fmt, ...) \ + MPL_LOG_ALWAYS_FATAL(fmt, ##__VA_ARGS__) +#endif + +/* + * Assertion that generates a log message when the assertion fails. + * Stripped out of release builds. Uses the current MPL_LOG_TAG. + */ +#define MPL_LOG_ASSERT(cond, fmt, ...) \ + MPL_LOG_FATAL_IF(!(cond), fmt, ##__VA_ARGS__) + +/* --------------------------------------------------------------------- */ + +/* + * Basic log message macro. + * + * Example: + * MPL_LOG(MPL_LOG_WARN, NULL, "Failed with error %d", errno); + * + * The second argument may be NULL or "" to indicate the "global" tag. + */ +#ifndef MPL_LOG +#define MPL_LOG(priority, tag, fmt, ...) \ + MPL_LOG_PRI(priority, tag, fmt, ##__VA_ARGS__) +#endif + +/* + * Log macro that allows you to specify a number for the priority. + */ +#ifndef MPL_LOG_PRI +#define MPL_LOG_PRI(priority, tag, fmt, ...) \ + pr_debug(MPL_##priority tag fmt, ##__VA_ARGS__) +#endif + +/* + * Log macro that allows you to pass in a varargs ("args" is a va_list). + */ +#ifndef MPL_LOG_PRI_VA +/* not allowed in the Kernel because there is no dev_dbg that takes a va_list */ +#endif + +/* --------------------------------------------------------------------- */ + +/* + * =========================================================================== + * + * The stuff in the rest of this file should not be used directly. + */ + +int _MLPrintLog(int priority, const char *tag, const char *fmt, ...); +int _MLPrintVaLog(int priority, const char *tag, const char *fmt, va_list args); +/* Final implementation of actual writing to a character device */ +int _MLWriteLog(const char *buf, int buflen); + +static inline void __print_result_location(int result, + const char *file, + const char *func, int line) +{ + MPL_LOGE("%s|%s|%d returning %d\n", file, func, line, result); +} + +#define LOG_RESULT_LOCATION(condition) \ + do { \ + __print_result_location((int)(condition), __FILE__, \ + __func__, __LINE__); \ + } while (0) + + +#endif /* _LIBS_CUTILS_MPL_LOG_H */ diff --git a/drivers/misc/inv_mpu/mldl_cfg.c b/drivers/misc/inv_mpu/mldl_cfg.c new file mode 100644 index 0000000..2ae894e --- /dev/null +++ b/drivers/misc/inv_mpu/mldl_cfg.c @@ -0,0 +1,1808 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup MLDL + * + * @{ + * @file mldl_cfg.c + * @brief The Motion Library Driver Layer. + */ + +/* -------------------------------------------------------------------------- */ +#include <linux/delay.h> + +#include <stddef.h> + +#include "mldl_cfg.h" +#include <linux/mpu.h> +# include "mpu3050.h" + +#include "mlsl.h" + +#include "log.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "mldl_cfg:" + +/* -------------------------------------------------------------------------- */ + +#define SLEEP 1 +#define WAKE_UP 0 +#define RESET 1 +#define STANDBY 1 + +/* -------------------------------------------------------------------------- */ + +/** + * @brief Stop the DMP running + * + * @return INV_SUCCESS or non-zero error code + */ +static int dmp_stop(struct mldl_cfg *mldl_cfg, void *gyro_handle) +{ + unsigned char user_ctrl_reg; + int result; + + if (!mldl_cfg->dmp_is_running) + return INV_SUCCESS; + + result = inv_serial_read(gyro_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, 1, &user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + user_ctrl_reg = (user_ctrl_reg & (~BIT_FIFO_EN)) | BIT_FIFO_RST; + user_ctrl_reg = (user_ctrl_reg & (~BIT_DMP_EN)) | BIT_DMP_RST; + + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->dmp_is_running = FALSE; + + return result; +} + +/** + * @brief Starts the DMP running + * + * @return INV_SUCCESS or non-zero error code + */ +static int dmp_start(struct mldl_cfg *pdata, void *mlsl_handle) +{ + unsigned char user_ctrl_reg; + int result; + + if (pdata->dmp_is_running == pdata->dmp_enable) + return INV_SUCCESS; + + result = inv_serial_read(mlsl_handle, pdata->addr, + MPUREG_USER_CTRL, 1, &user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->addr, + MPUREG_USER_CTRL, + ((user_ctrl_reg & (~BIT_FIFO_EN)) + | BIT_FIFO_RST)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write(mlsl_handle, pdata->addr, + MPUREG_USER_CTRL, user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_read(mlsl_handle, pdata->addr, + MPUREG_USER_CTRL, 1, &user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (pdata->dmp_enable) + user_ctrl_reg |= BIT_DMP_EN; + else + user_ctrl_reg &= ~BIT_DMP_EN; + + if (pdata->fifo_enable) + user_ctrl_reg |= BIT_FIFO_EN; + else + user_ctrl_reg &= ~BIT_FIFO_EN; + + user_ctrl_reg |= BIT_DMP_RST; + + result = inv_serial_single_write(mlsl_handle, pdata->addr, + MPUREG_USER_CTRL, user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + pdata->dmp_is_running = pdata->dmp_enable; + + return result; +} + + + +static int mpu3050_set_i2c_bypass(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, unsigned char enable) +{ + unsigned char b; + int result; + + if ((mldl_cfg->gyro_is_bypassed && enable) || + (!mldl_cfg->gyro_is_bypassed && !enable)) + return INV_SUCCESS; + + /*---- get current 'USER_CTRL' into b ----*/ + result = inv_serial_read(mlsl_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, 1, &b); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + b &= ~BIT_AUX_IF_EN; + + if (!enable) { + result = inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, + (b | BIT_AUX_IF_EN)); + if (result) { + LOG_RESULT_LOCATION(result); + 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_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_AUX_SLV_ADDR, 0x7F); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* + * 2) wait enough time for a nack to occur, then go into + * bypass mode: + */ + msleep(2); + result = inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, (b)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* + * 3) wait for up to one MPU cycle then restore the slave + * address + */ + msleep(inv_mpu_get_sampling_period_us(mldl_cfg) / 1000); + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->addr, + MPUREG_AUX_SLV_ADDR, + mldl_cfg->pdata->accel.address); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* + * 4) reset the ime interface + */ + + result = inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, + (b | BIT_AUX_IF_RST)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(2); + } + mldl_cfg->gyro_is_bypassed = enable; + + return result; +} + +/** + * @brief enables/disables the I2C bypass to an external device + * connected to MPU's secondary I2C bus. + * @param enable + * Non-zero to enable pass through. + * @return INV_SUCCESS if successful, a non-zero error code otherwise. + */ +static int mpu_set_i2c_bypass(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, unsigned char enable) +{ + return mpu3050_set_i2c_bypass(mldl_cfg, mlsl_handle, enable); +} + + +#define NUM_OF_PROD_REVS (ARRAY_SIZE(prod_rev_map)) + +/* NOTE : when not indicated, product revision + is considered an 'npp'; non production part */ + +struct prod_rev_map_t { + unsigned char silicon_rev; + unsigned short gyro_trim; +}; + +#define OLDEST_PROD_REV_SUPPORTED 11 +static struct prod_rev_map_t prod_rev_map[] = { + {0, 0}, + {MPU_SILICON_REV_A4, 131}, /* 1 A? OBSOLETED */ + {MPU_SILICON_REV_A4, 131}, /* 2 | */ + {MPU_SILICON_REV_A4, 131}, /* 3 | */ + {MPU_SILICON_REV_A4, 131}, /* 4 | */ + {MPU_SILICON_REV_A4, 131}, /* 5 | */ + {MPU_SILICON_REV_A4, 131}, /* 6 | */ + {MPU_SILICON_REV_A4, 131}, /* 7 | */ + {MPU_SILICON_REV_A4, 131}, /* 8 | */ + {MPU_SILICON_REV_A4, 131}, /* 9 | */ + {MPU_SILICON_REV_A4, 131}, /* 10 V */ + {MPU_SILICON_REV_B1, 131}, /* 11 B1 */ + {MPU_SILICON_REV_B1, 131}, /* 12 | */ + {MPU_SILICON_REV_B1, 131}, /* 13 | */ + {MPU_SILICON_REV_B1, 131}, /* 14 V */ + {MPU_SILICON_REV_B4, 131}, /* 15 B4 */ + {MPU_SILICON_REV_B4, 131}, /* 16 | */ + {MPU_SILICON_REV_B4, 131}, /* 17 | */ + {MPU_SILICON_REV_B4, 131}, /* 18 | */ + {MPU_SILICON_REV_B4, 115}, /* 19 | */ + {MPU_SILICON_REV_B4, 115}, /* 20 V */ + {MPU_SILICON_REV_B6, 131}, /* 21 B6 (B6/A9) */ + {MPU_SILICON_REV_B4, 115}, /* 22 B4 (B7/A10) */ + {MPU_SILICON_REV_B6, 0}, /* 23 B6 */ + {MPU_SILICON_REV_B6, 0}, /* 24 | */ + {MPU_SILICON_REV_B6, 0}, /* 25 | */ + {MPU_SILICON_REV_B6, 131}, /* 26 V (B6/A11) */ +}; + +/** + * @internal + * @brief Get the silicon revision ID from OTP for MPU3050. + * The silicon revision number is in read from OTP bank 0, + * ADDR6[7:2]. The corresponding ID is retrieved by lookup + * in a map. + * + * @param mldl_cfg + * a pointer to the mldl config data structure. + * @param mlsl_handle + * an file handle to the serial communication device the + * device is connected to. + * + * @return 0 on success, a non-zero error code otherwise. + */ +static int inv_get_silicon_rev_mpu3050( + struct mldl_cfg *mldl_cfg, void *mlsl_handle) +{ + int result; + unsigned char index = 0x00; + unsigned char bank = + (BIT_PRFTCH_EN | BIT_CFG_USER_BANK | MPU_MEM_OTP_BANK_0); + unsigned short mem_addr = ((bank << 8) | 0x06); + + result = inv_serial_read(mlsl_handle, mldl_cfg->addr, + MPUREG_PRODUCT_ID, 1, &mldl_cfg->product_id); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_read_mem(mlsl_handle, mldl_cfg->addr, + mem_addr, 1, &index); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + index >>= 2; + + /* clean the prefetch and cfg user bank bits */ + result = inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_BANK_SEL, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (index < OLDEST_PROD_REV_SUPPORTED || index >= NUM_OF_PROD_REVS) { + mldl_cfg->silicon_revision = 0; + mldl_cfg->gyro_sens_trim = 0; + MPL_LOGE("Unsupported Product Revision Detected : %d\n", index); + return INV_ERROR_INVALID_MODULE; + } + + mldl_cfg->product_revision = index; + mldl_cfg->silicon_revision = prod_rev_map[index].silicon_rev; + mldl_cfg->gyro_sens_trim = prod_rev_map[index].gyro_trim; + if (mldl_cfg->gyro_sens_trim == 0) { + MPL_LOGE("gyro sensitivity trim is 0" + " - unsupported non production part.\n"); + return INV_ERROR_INVALID_MODULE; + } + + return result; +} +#define inv_get_silicon_rev inv_get_silicon_rev_mpu3050 + + +/** + * @brief Enable / Disable the use MPU's secondary I2C interface level + * shifters. + * When enabled the secondary I2C interface to which the external + * device is connected runs at VDD voltage (main supply). + * When disabled the 2nd interface runs at VDDIO voltage. + * See the device specification for more details. + * + * @note using this API may produce unpredictable results, depending on how + * the MPU and slave device are setup on the target platform. + * Use of this API should entirely be restricted to system + * integrators. Once the correct value is found, there should be no + * need to change the level shifter at runtime. + * + * @pre Must be called after inv_serial_start(). + * @note Typically called before inv_dmp_open(). + * + * @param[in] enable: + * 0 to run at VDDIO (default), + * 1 to run at VDD. + * + * @return INV_SUCCESS if successfull, a non-zero error code otherwise. + */ +static int inv_mpu_set_level_shifter_bit(struct mldl_cfg *pdata, + void *mlsl_handle, unsigned char enable) +{ + int result; + unsigned char reg; + unsigned char mask; + unsigned char regval; + + if (0 == pdata->silicon_revision) + return INV_ERROR_INVALID_PARAMETER; + + /*-- on parts before B6 the VDDIO bit is bit 7 of ACCEL_BURST_ADDR -- + NOTE: this is incompatible with ST accelerometers where the VDDIO + bit MUST be set to enable ST's internal logic to autoincrement + the register address on burst reads --*/ + if ((pdata->silicon_revision & 0xf) < MPU_SILICON_REV_B6) { + reg = MPUREG_ACCEL_BURST_ADDR; + mask = 0x80; + } else { + /*-- on B6 parts the VDDIO bit was moved to FIFO_EN2 => + the mask is always 0x04 --*/ + reg = MPUREG_FIFO_EN2; + mask = 0x04; + } + + result = inv_serial_read(mlsl_handle, pdata->addr, reg, 1, ®val); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (enable) + regval |= mask; + else + regval &= ~mask; + + result = inv_serial_single_write(mlsl_handle, pdata->addr, reg, regval); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; + return INV_SUCCESS; +} + + +/** + * @internal + * @brief This function controls the power management on the MPU device. + * The entire chip can be put to low power sleep mode, or individual + * gyros can be turned on/off. + * + * Putting the device into sleep mode depending upon the changing needs + * of the associated applications is a recommended method for reducing + * power consuption. It is a safe opearation in that sleep/wake up of + * gyros while running will not result in any interruption of data. + * + * Although it is entirely allowed to put the device into full sleep + * while running the DMP, it is not recomended because it will disrupt + * the ongoing calculations carried on inside the DMP and consequently + * the sensor fusion algorithm. Furthermore, while in sleep mode + * read & write operation from the app processor on both registers and + * memory are disabled and can only regained by restoring the MPU in + * normal power mode. + * Disabling any of the gyro axis will reduce the associated power + * consuption from the PLL but will not stop the DMP from running + * state. + * + * @param reset + * Non-zero to reset the device. Note that this setting + * is volatile and the corresponding register bit will + * clear itself right after being applied. + * @param sleep + * Non-zero to put device into full sleep. + * @param disable_gx + * Non-zero to disable gyro X. + * @param disable_gy + * Non-zero to disable gyro Y. + * @param disable_gz + * Non-zero to disable gyro Z. + * + * @return INV_SUCCESS if successfull; a non-zero error code otherwise. + */ +static int mpu3050_pwr_mgmt(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, + unsigned char reset, + unsigned char sleep, + unsigned char disable_gx, + unsigned char disable_gy, + unsigned char disable_gz) +{ + unsigned char b; + int result; + + result = + inv_serial_read(mlsl_handle, mldl_cfg->addr, MPUREG_PWR_MGM, 1, &b); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* If we are awake, we need to put it in bypass before resetting */ + if ((!(b & BIT_SLEEP)) && reset) + result = mpu_set_i2c_bypass(mldl_cfg, mlsl_handle, 1); + + /* Reset if requested */ + if (reset) { + MPL_LOGV("Reset MPU3050\n"); + result = inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, + b | BIT_H_RESET); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + msleep(5); + mldl_cfg->gyro_needs_reset = FALSE; + /* Some chips are awake after reset and some are asleep, + * check the status */ + result = inv_serial_read(mlsl_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, 1, &b); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + /* Update the suspended state just in case we return early */ + if (b & BIT_SLEEP) + mldl_cfg->gyro_is_suspended = TRUE; + else + mldl_cfg->gyro_is_suspended = FALSE; + + /* if power status match requested, nothing else's left to do */ + if ((b & (BIT_SLEEP | BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG)) == + (((sleep != 0) * BIT_SLEEP) | + ((disable_gx != 0) * BIT_STBY_XG) | + ((disable_gy != 0) * BIT_STBY_YG) | + ((disable_gz != 0) * BIT_STBY_ZG))) { + return INV_SUCCESS; + } + + /* + * This specific transition between states needs to be reinterpreted: + * (1,1,1,1) -> (0,1,1,1) has to become + * (1,1,1,1) -> (1,0,0,0) -> (0,1,1,1) + * where + * (1,1,1,1) is (sleep=1,disable_gx=1,disable_gy=1,disable_gz=1) + */ + if ((b & (BIT_SLEEP | BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG)) == + (BIT_SLEEP | BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG) + && ((!sleep) && disable_gx && disable_gy && disable_gz)) { + result = mpu3050_pwr_mgmt(mldl_cfg, mlsl_handle, 0, 1, 0, 0, 0); + if (result) + return result; + b |= BIT_SLEEP; + b &= ~(BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG); + } + + if ((b & BIT_SLEEP) != ((sleep != 0) * BIT_SLEEP)) { + if (sleep) { + result = mpu_set_i2c_bypass(mldl_cfg, mlsl_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + b |= BIT_SLEEP; + result = + inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, b); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->gyro_is_suspended = TRUE; + } else { + b &= ~BIT_SLEEP; + result = + inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, b); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->gyro_is_suspended = FALSE; + msleep(5); + } + } + /*--- + WORKAROUND FOR PUTTING GYRO AXIS in STAND-BY MODE + 1) put one axis at a time in stand-by + ---*/ + if ((b & BIT_STBY_XG) != ((disable_gx != 0) * BIT_STBY_XG)) { + b ^= BIT_STBY_XG; + result = inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, b); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + if ((b & BIT_STBY_YG) != ((disable_gy != 0) * BIT_STBY_YG)) { + b ^= BIT_STBY_YG; + result = inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, b); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + if ((b & BIT_STBY_ZG) != ((disable_gz != 0) * BIT_STBY_ZG)) { + b ^= BIT_STBY_ZG; + result = inv_serial_single_write(mlsl_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, b); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + return INV_SUCCESS; +} + + +/** + * @brief sets the clock source for the gyros. + * @param mldl_cfg + * a pointer to the struct mldl_cfg data structure. + * @param gyro_handle + * an handle to the serial device the gyro is assigned to. + * @return ML_SUCCESS if successful, a non-zero error code otherwise. + */ +static int mpu_set_clock_source(void *gyro_handle, struct mldl_cfg *mldl_cfg) +{ + int result; + unsigned char cur_clk_src; + unsigned char reg; + + /* clock source selection */ + result = inv_serial_read(gyro_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + cur_clk_src = reg & BITS_CLKSEL; + reg &= ~BITS_CLKSEL; + + + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, + mldl_cfg->clk_src | reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* TODO : workarounds to be determined and implemented */ + + return result; +} + +void mpu_print_cfg(struct mldl_cfg *mldl_cfg) +{ + struct mpu_platform_data *pdata = mldl_cfg->pdata; + struct ext_slave_platform_data *accel = &mldl_cfg->pdata->accel; + struct ext_slave_platform_data *compass = &mldl_cfg->pdata->compass; + struct ext_slave_platform_data *pressure = &mldl_cfg->pdata->pressure; + + MPL_LOGD("mldl_cfg.addr = %02x\n", mldl_cfg->addr); + MPL_LOGD("mldl_cfg.int_config = %02x\n", mldl_cfg->int_config); + MPL_LOGD("mldl_cfg.ext_sync = %02x\n", mldl_cfg->ext_sync); + MPL_LOGD("mldl_cfg.full_scale = %02x\n", mldl_cfg->full_scale); + MPL_LOGD("mldl_cfg.lpf = %02x\n", mldl_cfg->lpf); + MPL_LOGD("mldl_cfg.clk_src = %02x\n", mldl_cfg->clk_src); + MPL_LOGD("mldl_cfg.divider = %02x\n", mldl_cfg->divider); + MPL_LOGD("mldl_cfg.dmp_enable = %02x\n", mldl_cfg->dmp_enable); + MPL_LOGD("mldl_cfg.fifo_enable = %02x\n", mldl_cfg->fifo_enable); + MPL_LOGD("mldl_cfg.dmp_cfg1 = %02x\n", mldl_cfg->dmp_cfg1); + MPL_LOGD("mldl_cfg.dmp_cfg2 = %02x\n", mldl_cfg->dmp_cfg2); + MPL_LOGD("mldl_cfg.offset_tc[0] = %02x\n", mldl_cfg->offset_tc[0]); + MPL_LOGD("mldl_cfg.offset_tc[1] = %02x\n", mldl_cfg->offset_tc[1]); + MPL_LOGD("mldl_cfg.offset_tc[2] = %02x\n", mldl_cfg->offset_tc[2]); + MPL_LOGD("mldl_cfg.silicon_revision = %02x\n", + mldl_cfg->silicon_revision); + MPL_LOGD("mldl_cfg.product_revision = %02x\n", + mldl_cfg->product_revision); + MPL_LOGD("mldl_cfg.product_id = %02x\n", mldl_cfg->product_id); + MPL_LOGD("mldl_cfg.gyro_sens_trim = %02x\n", + mldl_cfg->gyro_sens_trim); + MPL_LOGD("mldl_cfg.requested_sensors= %04lx\n", + mldl_cfg->requested_sensors); + + if (mldl_cfg->accel) { + MPL_LOGD("slave_accel->suspend = %02x\n", + (int)mldl_cfg->accel->suspend); + MPL_LOGD("slave_accel->resume = %02x\n", + (int)mldl_cfg->accel->resume); + MPL_LOGD("slave_accel->read = %02x\n", + (int)mldl_cfg->accel->read); + MPL_LOGD("slave_accel->type = %02x\n", + mldl_cfg->accel->type); + MPL_LOGD("slave_accel->reg = %02x\n", + mldl_cfg->accel->read_reg); + MPL_LOGD("slave_accel->len = %02x\n", + mldl_cfg->accel->read_len); + MPL_LOGD("slave_accel->endian = %02x\n", + mldl_cfg->accel->endian); + MPL_LOGD("slave_accel->range.mantissa= %02lx\n", + mldl_cfg->accel->range.mantissa); + MPL_LOGD("slave_accel->range.fraction= %02lx\n", + mldl_cfg->accel->range.fraction); + } else { + MPL_LOGD("slave_accel = NULL\n"); + } + + if (mldl_cfg->compass) { + MPL_LOGD("slave_compass->suspend = %02x\n", + (int)mldl_cfg->compass->suspend); + MPL_LOGD("slave_compass->resume = %02x\n", + (int)mldl_cfg->compass->resume); + MPL_LOGD("slave_compass->read = %02x\n", + (int)mldl_cfg->compass->read); + MPL_LOGD("slave_compass->type = %02x\n", + mldl_cfg->compass->type); + MPL_LOGD("slave_compass->reg = %02x\n", + mldl_cfg->compass->read_reg); + MPL_LOGD("slave_compass->len = %02x\n", + mldl_cfg->compass->read_len); + MPL_LOGD("slave_compass->endian = %02x\n", + mldl_cfg->compass->endian); + MPL_LOGD("slave_compass->range.mantissa= %02lx\n", + mldl_cfg->compass->range.mantissa); + MPL_LOGD("slave_compass->range.fraction= %02lx\n", + mldl_cfg->compass->range.fraction); + + } else { + MPL_LOGD("slave_compass = NULL\n"); + } + + if (mldl_cfg->pressure) { + MPL_LOGD("slave_pressure->suspend = %02x\n", + (int)mldl_cfg->pressure->suspend); + MPL_LOGD("slave_pressure->resume = %02x\n", + (int)mldl_cfg->pressure->resume); + MPL_LOGD("slave_pressure->read = %02x\n", + (int)mldl_cfg->pressure->read); + MPL_LOGD("slave_pressure->type = %02x\n", + mldl_cfg->pressure->type); + MPL_LOGD("slave_pressure->reg = %02x\n", + mldl_cfg->pressure->read_reg); + MPL_LOGD("slave_pressure->len = %02x\n", + mldl_cfg->pressure->read_len); + MPL_LOGD("slave_pressure->endian = %02x\n", + mldl_cfg->pressure->endian); + MPL_LOGD("slave_pressure->range.mantissa= %02lx\n", + mldl_cfg->pressure->range.mantissa); + MPL_LOGD("slave_pressure->range.fraction= %02lx\n", + mldl_cfg->pressure->range.fraction); + + } else { + MPL_LOGD("slave_pressure = NULL\n"); + } + MPL_LOGD("accel->get_slave_descr = %x\n", + (unsigned int)accel->get_slave_descr); + MPL_LOGD("accel->irq = %02x\n", accel->irq); + MPL_LOGD("accel->adapt_num = %02x\n", accel->adapt_num); + MPL_LOGD("accel->bus = %02x\n", accel->bus); + MPL_LOGD("accel->address = %02x\n", accel->address); + MPL_LOGD("accel->orientation =\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n", + accel->orientation[0], accel->orientation[1], + accel->orientation[2], accel->orientation[3], + accel->orientation[4], accel->orientation[5], + accel->orientation[6], accel->orientation[7], + accel->orientation[8]); + MPL_LOGD("compass->get_slave_descr = %x\n", + (unsigned int)compass->get_slave_descr); + MPL_LOGD("compass->irq = %02x\n", compass->irq); + MPL_LOGD("compass->adapt_num = %02x\n", compass->adapt_num); + MPL_LOGD("compass->bus = %02x\n", compass->bus); + MPL_LOGD("compass->address = %02x\n", compass->address); + MPL_LOGD("compass->orientation =\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n", + compass->orientation[0], compass->orientation[1], + compass->orientation[2], compass->orientation[3], + compass->orientation[4], compass->orientation[5], + compass->orientation[6], compass->orientation[7], + compass->orientation[8]); + MPL_LOGD("pressure->get_slave_descr = %x\n", + (unsigned int)pressure->get_slave_descr); + MPL_LOGD("pressure->irq = %02x\n", pressure->irq); + MPL_LOGD("pressure->adapt_num = %02x\n", pressure->adapt_num); + MPL_LOGD("pressure->bus = %02x\n", pressure->bus); + MPL_LOGD("pressure->address = %02x\n", pressure->address); + MPL_LOGD("pressure->orientation =\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n", + pressure->orientation[0], pressure->orientation[1], + pressure->orientation[2], pressure->orientation[3], + pressure->orientation[4], pressure->orientation[5], + pressure->orientation[6], pressure->orientation[7], + pressure->orientation[8]); + + MPL_LOGD("pdata->int_config = %02x\n", pdata->int_config); + MPL_LOGD("pdata->level_shifter = %02x\n", pdata->level_shifter); + MPL_LOGD("pdata->orientation =\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n", + pdata->orientation[0], pdata->orientation[1], + pdata->orientation[2], pdata->orientation[3], + pdata->orientation[4], pdata->orientation[5], + pdata->orientation[6], pdata->orientation[7], + pdata->orientation[8]); + + MPL_LOGD("Struct sizes: mldl_cfg: %d, " + "ext_slave_descr:%d, " + "mpu_platform_data:%d: RamOffset: %d\n", + sizeof(struct mldl_cfg), sizeof(struct ext_slave_descr), + sizeof(struct mpu_platform_data), + offsetof(struct mldl_cfg, ram)); +} + +/** + * Configures the MPU I2C Master + * + * @mldl_cfg Handle to the configuration data + * @gyro_handle handle to the gyro communictation interface + * @slave Can be Null if turning off the slave + * @slave_pdata Can be null if turning off the slave + * @slave_id enum ext_slave_type to determine which index to use + * + * + * This fucntion configures the slaves by: + * 1) Setting up the read + * a) Read Register + * b) Read Length + * 2) Set up the data trigger (MPU6050 only) + * a) Set trigger write register + * b) Set Trigger write value + * 3) Set up the divider (MPU6050 only) + * 4) Set the slave bypass mode depending on slave + * + * returns INV_SUCCESS or non-zero error code + */ +static int mpu_set_slave_mpu3050(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *slave_pdata, + int slave_id) +{ + int result; + unsigned char reg; + unsigned char slave_reg; + unsigned char slave_len; + unsigned char slave_endian; + unsigned char slave_address; + + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, TRUE); + + if (NULL == slave || NULL == slave_pdata) { + slave_reg = 0; + slave_len = 0; + slave_endian = 0; + slave_address = 0; + mldl_cfg->i2c_slaves_enabled = 0; + } else { + slave_reg = slave->read_reg; + slave_len = slave->read_len; + slave_endian = slave->endian; + slave_address = slave_pdata->address; + mldl_cfg->i2c_slaves_enabled = 1; + } + + /* Address */ + result = inv_serial_single_write(gyro_handle, + mldl_cfg->addr, + MPUREG_AUX_SLV_ADDR, slave_address); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Register */ + result = inv_serial_read(gyro_handle, mldl_cfg->addr, + MPUREG_ACCEL_BURST_ADDR, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg = ((reg & 0x80) | slave_reg); + result = inv_serial_single_write(gyro_handle, + mldl_cfg->addr, + MPUREG_ACCEL_BURST_ADDR, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Length */ + result = inv_serial_read(gyro_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg = (reg & ~BIT_AUX_RD_LENG); + result = inv_serial_single_write(gyro_handle, + mldl_cfg->addr, MPUREG_USER_CTRL, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return result; +} + + +static int mpu_set_slave(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *slave_pdata, + int slave_id) +{ + return mpu_set_slave_mpu3050(mldl_cfg, gyro_handle, slave, + slave_pdata, slave_id); +} +/** + * Check to see if the gyro was reset by testing a couple of registers known + * to change on reset. + * + * @mldl_cfg mldl configuration structure + * @gyro_handle handle used to communicate with the gyro + * + * @return INV_SUCCESS or non-zero error code + */ +static int mpu_was_reset(struct mldl_cfg *mldl_cfg, void *gyro_handle) +{ + int result = INV_SUCCESS; + unsigned char reg; + + result = inv_serial_read(gyro_handle, mldl_cfg->addr, + MPUREG_DMP_CFG_2, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (mldl_cfg->dmp_cfg2 != reg) + return TRUE; + + if (0 != mldl_cfg->dmp_cfg1) + return FALSE; + + result = inv_serial_read(gyro_handle, mldl_cfg->addr, + MPUREG_SMPLRT_DIV, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (reg != mldl_cfg->divider) + return TRUE; + + if (0 != mldl_cfg->divider) + return FALSE; + + /* Inconclusive assume it was reset */ + return TRUE; +} + +static int gyro_resume(struct mldl_cfg *mldl_cfg, void *gyro_handle, + unsigned long sensors) +{ + int result; + int ii; + int jj; + unsigned char reg; + unsigned char regs[7]; + + /* Wake up the part */ + result = mpu3050_pwr_mgmt(mldl_cfg, gyro_handle, FALSE, FALSE, + !(sensors & INV_X_GYRO), + !(sensors & INV_Y_GYRO), + !(sensors & INV_Z_GYRO)); + + if (!mldl_cfg->gyro_needs_reset && + !mpu_was_reset(mldl_cfg, gyro_handle)) { + return INV_SUCCESS; + } + + result = mpu3050_pwr_mgmt(mldl_cfg, gyro_handle, TRUE, FALSE, + !(sensors & INV_X_GYRO), + !(sensors & INV_Y_GYRO), + !(sensors & INV_Z_GYRO)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_INT_CFG, + (mldl_cfg->int_config | + mldl_cfg->pdata->int_config)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu_set_clock_source(gyro_handle, mldl_cfg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_SMPLRT_DIV, mldl_cfg->divider); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + reg = DLPF_FS_SYNC_VALUE(mldl_cfg->ext_sync, + mldl_cfg->full_scale, mldl_cfg->lpf); + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_DLPF_FS_SYNC, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_DMP_CFG_1, mldl_cfg->dmp_cfg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_DMP_CFG_2, mldl_cfg->dmp_cfg2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Write and verify memory */ + for (ii = 0; ii < MPU_MEM_NUM_RAM_BANKS; ii++) { + unsigned char read[MPU_MEM_BANK_SIZE]; + + result = inv_serial_write_mem(gyro_handle, + mldl_cfg->addr, + ((ii << 8) | 0x00), + MPU_MEM_BANK_SIZE, + mldl_cfg->ram[ii]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read_mem(gyro_handle, mldl_cfg->addr, + ((ii << 8) | 0x00), + MPU_MEM_BANK_SIZE, read); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + +#define ML_SKIP_CHECK 20 + for (jj = 0; jj < MPU_MEM_BANK_SIZE; jj++) { + /* skip the register memory locations */ + if (ii == 0 && jj < ML_SKIP_CHECK) + continue; + if (mldl_cfg->ram[ii][jj] != read[jj]) { + result = INV_ERROR_SERIAL_WRITE; + break; + } + } + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_XG_OFFS_TC, + mldl_cfg->offset_tc[0]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_YG_OFFS_TC, + mldl_cfg->offset_tc[1]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(gyro_handle, mldl_cfg->addr, + MPUREG_ZG_OFFS_TC, + mldl_cfg->offset_tc[2]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + regs[0] = MPUREG_X_OFFS_USRH; + for (ii = 0; ii < ARRAY_SIZE(mldl_cfg->offset); ii++) { + regs[1 + ii * 2] = (unsigned char)(mldl_cfg->offset[ii] >> 8) + & 0xff; + regs[1 + ii * 2 + 1] = + (unsigned char)(mldl_cfg->offset[ii] & 0xff); + } + result = inv_serial_write(gyro_handle, mldl_cfg->addr, 7, regs); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Configure slaves */ + result = inv_mpu_set_level_shifter_bit(mldl_cfg, gyro_handle, + mldl_cfg->pdata->level_shifter); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +/******************************************************************************* + ******************************************************************************* + * Exported functions + ******************************************************************************* + ******************************************************************************/ + +/** + * Initializes the pdata structure to defaults. + * + * Opens the device to read silicon revision, product id and whoami. + * + * @mldl_cfg + * The internal device configuration data structure. + * @mlsl_handle + * The serial communication handle. + * + * @return INV_SUCCESS if silicon revision, product id and woami are supported + * by this software. + */ +int inv_mpu_open(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, + void *accel_handle, + void *compass_handle, void *pressure_handle) +{ + int result; + /* Default is Logic HIGH, pushpull, latch disabled, anyread to clear */ + mldl_cfg->ignore_system_suspend = FALSE; + mldl_cfg->int_config = BIT_DMP_INT_EN; + mldl_cfg->clk_src = MPU_CLK_SEL_PLLGYROZ; + mldl_cfg->lpf = MPU_FILTER_42HZ; + mldl_cfg->full_scale = MPU_FS_2000DPS; + mldl_cfg->divider = 4; + mldl_cfg->dmp_enable = 1; + mldl_cfg->fifo_enable = 1; + mldl_cfg->ext_sync = 0; + mldl_cfg->dmp_cfg1 = 0; + mldl_cfg->dmp_cfg2 = 0; + mldl_cfg->i2c_slaves_enabled = 0; + mldl_cfg->dmp_is_running = FALSE; + mldl_cfg->gyro_is_suspended = TRUE; + mldl_cfg->accel_is_suspended = TRUE; + mldl_cfg->compass_is_suspended = TRUE; + mldl_cfg->pressure_is_suspended = TRUE; + mldl_cfg->gyro_needs_reset = FALSE; + if (mldl_cfg->addr == 0) + return INV_ERROR_INVALID_PARAMETER; + + /* + * Reset, + * Take the DMP out of sleep, and + * read the product_id, sillicon rev and whoami + */ + mldl_cfg->gyro_is_bypassed = TRUE; + result = mpu3050_pwr_mgmt(mldl_cfg, mlsl_handle, RESET, 0, 0, 0, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_get_silicon_rev(mldl_cfg, mlsl_handle); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Get the factory temperature compensation offsets */ + result = inv_serial_read(mlsl_handle, mldl_cfg->addr, + MPUREG_XG_OFFS_TC, 1, &mldl_cfg->offset_tc[0]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, mldl_cfg->addr, + MPUREG_YG_OFFS_TC, 1, &mldl_cfg->offset_tc[1]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(mlsl_handle, mldl_cfg->addr, + MPUREG_ZG_OFFS_TC, 1, &mldl_cfg->offset_tc[2]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Into bypass mode before sleeping and calling the slaves init */ + result = mpu_set_i2c_bypass(mldl_cfg, mlsl_handle, TRUE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = mpu3050_pwr_mgmt(mldl_cfg, mlsl_handle, 0, SLEEP, 0, 0, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (mldl_cfg->accel && mldl_cfg->accel->init) { + result = mldl_cfg->accel->init(accel_handle, + mldl_cfg->accel, + &mldl_cfg->pdata->accel); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + if (mldl_cfg->compass && mldl_cfg->compass->init) { + result = mldl_cfg->compass->init(compass_handle, + mldl_cfg->compass, + &mldl_cfg->pdata->compass); + if (INV_SUCCESS != result) { + MPL_LOGE("mldl_cfg->compass->init returned %d\n", + result); + goto out_accel; + } + } + if (mldl_cfg->pressure && mldl_cfg->pressure->init) { + result = mldl_cfg->pressure->init(pressure_handle, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure); + if (INV_SUCCESS != result) { + MPL_LOGE("mldl_cfg->pressure->init returned %d\n", + result); + goto out_compass; + } + } + + mldl_cfg->requested_sensors = INV_THREE_AXIS_GYRO; + if (mldl_cfg->accel && mldl_cfg->accel->resume) + mldl_cfg->requested_sensors |= INV_THREE_AXIS_ACCEL; + + if (mldl_cfg->compass && mldl_cfg->compass->resume) + mldl_cfg->requested_sensors |= INV_THREE_AXIS_COMPASS; + + if (mldl_cfg->pressure && mldl_cfg->pressure->resume) + mldl_cfg->requested_sensors |= INV_THREE_AXIS_PRESSURE; + + return result; + + out_compass: + if (mldl_cfg->compass->init) + mldl_cfg->compass->exit(compass_handle, + mldl_cfg->compass, + &mldl_cfg->pdata->compass); + out_accel: + if (mldl_cfg->accel->init) + mldl_cfg->accel->exit(accel_handle, + mldl_cfg->accel, &mldl_cfg->pdata->accel); + return result; +} + +/** + * Close the mpu interface + * + * @mldl_cfg pointer to the configuration structure + * @mlsl_handle pointer to the serial layer handle + * + * @return INV_SUCCESS or non-zero error code + */ +int inv_mpu_close(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle) +{ + int result = INV_SUCCESS; + int ret_result = INV_SUCCESS; + + if (mldl_cfg->accel && mldl_cfg->accel->exit) { + result = mldl_cfg->accel->exit(accel_handle, + mldl_cfg->accel, + &mldl_cfg->pdata->accel); + if (INV_SUCCESS != result) + MPL_LOGE("Accel exit failed %d\n", result); + ret_result = result; + } + if (INV_SUCCESS == ret_result) + ret_result = result; + + if (mldl_cfg->compass && mldl_cfg->compass->exit) { + result = mldl_cfg->compass->exit(compass_handle, + mldl_cfg->compass, + &mldl_cfg->pdata->compass); + if (INV_SUCCESS != result) + MPL_LOGE("Compass exit failed %d\n", result); + } + if (INV_SUCCESS == ret_result) + ret_result = result; + + if (mldl_cfg->pressure && mldl_cfg->pressure->exit) { + result = mldl_cfg->pressure->exit(pressure_handle, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure); + if (INV_SUCCESS != result) + MPL_LOGE("Pressure exit failed %d\n", result); + } + if (INV_SUCCESS == ret_result) + ret_result = result; + + return ret_result; +} + +/** + * @brief resume the MPU device and all the other sensor + * devices from their low power state. + * + * @mldl_cfg + * pointer to the configuration structure + * @gyro_handle + * the main file handle to the MPU device. + * @accel_handle + * an handle to the accelerometer device, if sitting + * onto a separate bus. Can match mlsl_handle if + * the accelerometer device operates on the same + * primary bus of MPU. + * @compass_handle + * an handle to the compass device, if sitting + * onto a separate bus. Can match mlsl_handle if + * the compass device operates on the same + * primary bus of MPU. + * @pressure_handle + * an handle to the pressure sensor device, if sitting + * onto a separate bus. Can match mlsl_handle if + * the pressure sensor device operates on the same + * primary bus of MPU. + * @resume_gyro + * whether resuming the gyroscope device is + * actually needed (if the device supports low power + * mode of some sort). + * @resume_accel + * whether resuming the accelerometer device is + * actually needed (if the device supports low power + * mode of some sort). + * @resume_compass + * whether resuming the compass device is + * actually needed (if the device supports low power + * mode of some sort). + * @resume_pressure + * whether resuming the pressure sensor device is + * actually needed (if the device supports low power + * mode of some sort). + * @return INV_SUCCESS or a non-zero error code. + */ +int inv_mpu_resume(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle, + unsigned long sensors) +{ + bool resume_dmp = sensors & INV_DMP_PROCESSOR; + bool resume_gyro = sensors & INV_THREE_AXIS_GYRO; + bool resume_accel = sensors & INV_THREE_AXIS_ACCEL; + bool resume_compass = sensors & INV_THREE_AXIS_COMPASS; + bool resume_pressure = sensors & INV_THREE_AXIS_PRESSURE; + int result = INV_SUCCESS; + +#ifdef CONFIG_MPU_SENSORS_DEBUG + mpu_print_cfg(mldl_cfg); +#endif + + if (resume_accel && ((!mldl_cfg->accel) || (!mldl_cfg->accel->resume))) + return INV_ERROR_INVALID_PARAMETER; + if (resume_compass && + ((!mldl_cfg->compass) || (!mldl_cfg->compass->resume))) + return INV_ERROR_INVALID_PARAMETER; + if (resume_pressure && + ((!mldl_cfg->pressure) || (!mldl_cfg->pressure->resume))) + return INV_ERROR_INVALID_PARAMETER; + + if (resume_gyro && mldl_cfg->gyro_is_suspended) { + result = gyro_resume(mldl_cfg, gyro_handle, sensors); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + if (resume_accel && mldl_cfg->accel_is_suspended) { + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->accel.bus) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, + TRUE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = mldl_cfg->accel->resume(accel_handle, + mldl_cfg->accel, + &mldl_cfg->pdata->accel); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->accel_is_suspended = FALSE; + } + + if (resume_dmp && !mldl_cfg->accel_is_suspended && + EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->accel.bus) { + result = mpu_set_slave(mldl_cfg, + gyro_handle, + mldl_cfg->accel, + &mldl_cfg->pdata->accel, + mldl_cfg->accel->type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + if (resume_compass && mldl_cfg->compass_is_suspended) { + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->compass.bus) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, + TRUE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = mldl_cfg->compass->resume(compass_handle, + mldl_cfg->compass, + &mldl_cfg->pdata->compass); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->compass_is_suspended = FALSE; + } + + if (resume_dmp && !mldl_cfg->compass_is_suspended && + EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->compass.bus) { + result = mpu_set_slave(mldl_cfg, + gyro_handle, + mldl_cfg->compass, + &mldl_cfg->pdata->compass, + mldl_cfg->compass->type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + if (resume_pressure && mldl_cfg->pressure_is_suspended) { + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->pressure.bus) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, + TRUE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = mldl_cfg->pressure->resume(pressure_handle, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->pressure_is_suspended = FALSE; + } + + if (resume_dmp && !mldl_cfg->pressure_is_suspended && + EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->pressure.bus) { + result = mpu_set_slave(mldl_cfg, + gyro_handle, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure, + mldl_cfg->pressure->type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + /* Turn on the master i2c iterface if necessary */ + if (resume_dmp) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, + !(mldl_cfg->i2c_slaves_enabled)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + /* Now start */ + if (resume_dmp) { + result = dmp_start(mldl_cfg, gyro_handle); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + return result; +} + +/** + * @brief suspend the MPU device and all the other sensor + * devices into their low power state. + * @gyro_handle + * the main file handle to the MPU device. + * @accel_handle + * an handle to the accelerometer device, if sitting + * onto a separate bus. Can match gyro_handle if + * the accelerometer device operates on the same + * primary bus of MPU. + * @compass_handle + * an handle to the compass device, if sitting + * onto a separate bus. Can match gyro_handle if + * the compass device operates on the same + * primary bus of MPU. + * @pressure_handle + * an handle to the pressure sensor device, if sitting + * onto a separate bus. Can match gyro_handle if + * the pressure sensor device operates on the same + * primary bus of MPU. + * @accel + * whether suspending the accelerometer device is + * actually needed (if the device supports low power + * mode of some sort). + * @compass + * whether suspending the compass device is + * actually needed (if the device supports low power + * mode of some sort). + * @pressure + * whether suspending the pressure sensor device is + * actually needed (if the device supports low power + * mode of some sort). + * @return INV_SUCCESS or a non-zero error code. + */ +int inv_mpu_suspend(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle, + unsigned long sensors) +{ + int result = INV_SUCCESS; + bool suspend_dmp = ((sensors & INV_DMP_PROCESSOR) == INV_DMP_PROCESSOR); + bool suspend_gyro = ((sensors & (INV_X_GYRO | INV_Y_GYRO | INV_Z_GYRO)) + == (INV_X_GYRO | INV_Y_GYRO | INV_Z_GYRO)); + bool suspend_accel = ((sensors & INV_THREE_AXIS_ACCEL) == + INV_THREE_AXIS_ACCEL); + bool suspend_compass = ((sensors & INV_THREE_AXIS_COMPASS) == + INV_THREE_AXIS_COMPASS); + bool suspend_pressure = ((sensors & INV_THREE_AXIS_PRESSURE) == + INV_THREE_AXIS_PRESSURE); + + if (suspend_dmp) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = dmp_stop(mldl_cfg, gyro_handle); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + /* Gyro */ + if (suspend_gyro && !mldl_cfg->gyro_is_suspended) { + result = mpu3050_pwr_mgmt(mldl_cfg, gyro_handle, + 0, suspend_dmp && suspend_gyro, + (sensors & INV_X_GYRO), + (sensors & INV_Y_GYRO), + (sensors & INV_Z_GYRO)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + /* Accel */ + if (!mldl_cfg->accel_is_suspended && suspend_accel && + mldl_cfg->accel && mldl_cfg->accel->suspend) { + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->accel.bus) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = mldl_cfg->accel->suspend(accel_handle, + mldl_cfg->accel, + &mldl_cfg->pdata->accel); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->accel.bus) { + result = mpu_set_slave(mldl_cfg, gyro_handle, + NULL, NULL, + mldl_cfg->accel->type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + mldl_cfg->accel_is_suspended = TRUE; + } + + /* Compass */ + if (!mldl_cfg->compass_is_suspended && suspend_compass && + mldl_cfg->compass && mldl_cfg->compass->suspend) { + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->compass.bus) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = mldl_cfg->compass->suspend(compass_handle, + mldl_cfg->compass, + &mldl_cfg->pdata->compass); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->compass.bus) { + result = mpu_set_slave(mldl_cfg, gyro_handle, + NULL, NULL, + mldl_cfg->compass->type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + mldl_cfg->compass_is_suspended = TRUE; + } + /* Pressure */ + if (!mldl_cfg->pressure_is_suspended && suspend_pressure && + mldl_cfg->pressure && mldl_cfg->pressure->suspend) { + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->pressure.bus) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = mldl_cfg->pressure->suspend( + pressure_handle, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->pressure.bus) { + result = mpu_set_slave(mldl_cfg, gyro_handle, + NULL, NULL, + mldl_cfg->pressure->type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + mldl_cfg->pressure_is_suspended = TRUE; + } + + /* Re-enable the i2c master if there are configured slaves and DMP */ + if (!suspend_dmp) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, + !(mldl_cfg->i2c_slaves_enabled)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +int inv_mpu_slave_read(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *slave_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result; + int bypass_result; + int remain_bypassed = TRUE; + + if (NULL == slave || NULL == slave->read) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION); + return INV_ERROR_INVALID_CONFIGURATION; + } + + if ((EXT_SLAVE_BUS_SECONDARY == pdata->bus) + && (!mldl_cfg->gyro_is_bypassed)) { + remain_bypassed = FALSE; + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + result = slave->read(slave_handle, slave, pdata, data); + + if (!remain_bypassed) { + bypass_result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 0); + if (bypass_result) { + LOG_RESULT_LOCATION(bypass_result); + return bypass_result; + } + } + return result; +} + +int inv_mpu_slave_config(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *slave_handle, + struct ext_slave_config *data, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + int remain_bypassed = TRUE; + + if (NULL == slave || NULL == slave->config) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION); + return INV_ERROR_INVALID_CONFIGURATION; + } + + if (data->apply && (EXT_SLAVE_BUS_SECONDARY == pdata->bus) + && (!mldl_cfg->gyro_is_bypassed)) { + remain_bypassed = FALSE; + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + result = slave->config(slave_handle, slave, pdata, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (!remain_bypassed) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +int inv_mpu_get_slave_config(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *slave_handle, + struct ext_slave_config *data, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + int remain_bypassed = TRUE; + + if (NULL == slave || NULL == slave->get_config) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION); + return INV_ERROR_INVALID_CONFIGURATION; + } + + if (data->apply && (EXT_SLAVE_BUS_SECONDARY == pdata->bus) + && (!mldl_cfg->gyro_is_bypassed)) { + remain_bypassed = FALSE; + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + result = slave->get_config(slave_handle, slave, pdata, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (!remain_bypassed) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/mldl_cfg.h b/drivers/misc/inv_mpu/mldl_cfg.h new file mode 100644 index 0000000..9306a26 --- /dev/null +++ b/drivers/misc/inv_mpu/mldl_cfg.h @@ -0,0 +1,329 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup MLDL + * + * @{ + * @file mldl_cfg.h + * @brief The Motion Library Driver Layer Configuration header file. + */ + +#ifndef __MLDL_CFG_H__ +#define __MLDL_CFG_H__ + +#include "mltypes.h" +#include "mlsl.h" +#include <linux/mpu.h> +# include "mpu3050.h" + +#include "log.h" + +/************************************************************************* + * Sensors + *************************************************************************/ + +#define INV_X_GYRO (0x0001) +#define INV_Y_GYRO (0x0002) +#define INV_Z_GYRO (0x0004) +#define INV_DMP_PROCESSOR (0x0008) + +#define INV_X_ACCEL (0x0010) +#define INV_Y_ACCEL (0x0020) +#define INV_Z_ACCEL (0x0040) + +#define INV_X_COMPASS (0x0080) +#define INV_Y_COMPASS (0x0100) +#define INV_Z_COMPASS (0x0200) + +#define INV_X_PRESSURE (0x0300) +#define INV_Y_PRESSURE (0x0800) +#define INV_Z_PRESSURE (0x1000) + +#define INV_TEMPERATURE (0x2000) +#define INV_TIME (0x4000) + +#define INV_THREE_AXIS_GYRO (0x000F) +#define INV_THREE_AXIS_ACCEL (0x0070) +#define INV_THREE_AXIS_COMPASS (0x0380) +#define INV_THREE_AXIS_PRESSURE (0x1C00) + +#define INV_FIVE_AXIS (0x007B) +#define INV_SIX_AXIS_GYRO_ACCEL (0x007F) +#define INV_SIX_AXIS_ACCEL_COMPASS (0x03F0) +#define INV_NINE_AXIS (0x03FF) +#define INV_ALL_SENSORS (0x7FFF) + +#define MPL_PROD_KEY(ver, rev) (ver * 100 + rev) + +/* -------------------------------------------------------------------------- */ + +/* Platform data for the MPU */ +struct mldl_cfg { + /* MPU related configuration */ + unsigned long requested_sensors; + unsigned char ignore_system_suspend; + unsigned char addr; + unsigned char int_config; + unsigned char ext_sync; + unsigned char full_scale; + unsigned char lpf; + unsigned char clk_src; + unsigned char divider; + unsigned char dmp_enable; + unsigned char fifo_enable; + unsigned char dmp_cfg1; + unsigned char dmp_cfg2; + unsigned char offset_tc[GYRO_NUM_AXES]; + unsigned short offset[GYRO_NUM_AXES]; + unsigned char ram[MPU_MEM_NUM_RAM_BANKS][MPU_MEM_BANK_SIZE]; + + /* MPU Related stored status and info */ + unsigned char product_revision; + unsigned char silicon_revision; + unsigned char product_id; + unsigned short gyro_sens_trim; +#if defined CONFIG_MPU_SENSORS_MPU6050A2 || \ + defined CONFIG_MPU_SENSORS_MPU6050B1 + unsigned short accel_sens_trim; +#endif + + /* Driver/Kernel related state information */ + int gyro_is_bypassed; + int i2c_slaves_enabled; + int dmp_is_running; + int gyro_is_suspended; + int accel_is_suspended; + int compass_is_suspended; + int pressure_is_suspended; + int gyro_needs_reset; + + /* Slave related information */ + struct ext_slave_descr *accel; + struct ext_slave_descr *compass; + struct ext_slave_descr *pressure; + + /* Platform Data */ + struct mpu_platform_data *pdata; +}; + +/* -------------------------------------------------------------------------- */ + +int inv_mpu_open(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle); +int inv_mpu_close(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle); +int inv_mpu_resume(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle, + unsigned long sensors); +int inv_mpu_suspend(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle, + unsigned long sensors); + +/* Slave Read functions */ +int inv_mpu_slave_read(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *slave_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data); +static inline int inv_mpu_read_accel(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, unsigned char *data) +{ + if (!mldl_cfg || !(mldl_cfg->pdata)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + return inv_mpu_slave_read(mldl_cfg, gyro_handle, accel_handle, + mldl_cfg->accel, &mldl_cfg->pdata->accel, + data); +} + +static inline int inv_mpu_read_compass(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *compass_handle, + unsigned char *data) +{ + if (!mldl_cfg || !(mldl_cfg->pdata)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + return inv_mpu_slave_read(mldl_cfg, gyro_handle, compass_handle, + mldl_cfg->compass, &mldl_cfg->pdata->compass, + data); +} + +static inline int inv_mpu_read_pressure(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *pressure_handle, + unsigned char *data) +{ + if (!mldl_cfg || !(mldl_cfg->pdata)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + return inv_mpu_slave_read(mldl_cfg, gyro_handle, pressure_handle, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure, data); +} + +/* Slave Config functions */ +int inv_mpu_slave_config(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *slave_handle, + struct ext_slave_config *data, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata); +static inline int inv_mpu_config_accel(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + struct ext_slave_config *data) +{ + if (!mldl_cfg || !(mldl_cfg->pdata)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + return inv_mpu_slave_config(mldl_cfg, gyro_handle, accel_handle, data, + mldl_cfg->accel, &mldl_cfg->pdata->accel); +} + +static inline int inv_mpu_config_compass(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *compass_handle, + struct ext_slave_config *data) +{ + if (!mldl_cfg || !(mldl_cfg->pdata)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + return inv_mpu_slave_config(mldl_cfg, gyro_handle, compass_handle, data, + mldl_cfg->compass, + &mldl_cfg->pdata->compass); +} + +static inline int inv_mpu_config_pressure(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *pressure_handle, + struct ext_slave_config *data) +{ + if (!mldl_cfg || !(mldl_cfg->pdata)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + return inv_mpu_slave_config(mldl_cfg, gyro_handle, pressure_handle, + data, mldl_cfg->pressure, + &mldl_cfg->pdata->pressure); +} + +/* Slave get config functions */ +int inv_mpu_get_slave_config(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *slave_handle, + struct ext_slave_config *data, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata); + +static inline int inv_mpu_get_accel_config(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + struct ext_slave_config *data) +{ + if (!mldl_cfg || !(mldl_cfg->pdata)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + return inv_mpu_get_slave_config(mldl_cfg, gyro_handle, accel_handle, + data, mldl_cfg->accel, + &mldl_cfg->pdata->accel); +} + +static inline int inv_mpu_get_compass_config(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *compass_handle, + struct ext_slave_config *data) +{ + if (!mldl_cfg || !(mldl_cfg->pdata)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + return inv_mpu_get_slave_config(mldl_cfg, gyro_handle, compass_handle, + data, mldl_cfg->compass, + &mldl_cfg->pdata->compass); +} + +static inline int inv_mpu_get_pressure_config(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *pressure_handle, + struct ext_slave_config *data) +{ + if (!mldl_cfg || !(mldl_cfg->pdata)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + return inv_mpu_get_slave_config(mldl_cfg, gyro_handle, + pressure_handle, data, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure); +} + +/* -------------------------------------------------------------------------- */ + +static inline long inv_mpu_get_sampling_rate_hz(struct mldl_cfg *mldl_cfg) +{ + if (((mldl_cfg->lpf) == 0) || ((mldl_cfg->lpf) == 7)) + return 8000L / (mldl_cfg->divider + 1); + else + return 1000L / (mldl_cfg->divider + 1); +} + +static inline long inv_mpu_get_sampling_period_us(struct mldl_cfg *mldl_cfg) +{ + if (((mldl_cfg->lpf) == 0) || ((mldl_cfg->lpf) == 7)) + return (long) (1000000L * (mldl_cfg->divider + 1)) / 8000L; + else + return (long) (1000000L * (mldl_cfg->divider + 1)) / 1000L; +} + +#endif /* __MLDL_CFG_H__ */ + +/** + *@} + */ diff --git a/drivers/misc/inv_mpu/mlsl-kernel.c b/drivers/misc/inv_mpu/mlsl-kernel.c new file mode 100644 index 0000000..dd3186b --- /dev/null +++ b/drivers/misc/inv_mpu/mlsl-kernel.c @@ -0,0 +1,389 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +#include "mlsl.h" +#include <linux/i2c.h> +# include "mpu3050.h" + +static int inv_i2c_write(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned int len, unsigned char const *data) +{ + struct i2c_msg msgs[1]; + int res; + + if (!data || !i2c_adap) + return -EINVAL; + + msgs[0].addr = address; + msgs[0].flags = 0; /* write */ + msgs[0].buf = (unsigned char *)data; + msgs[0].len = len; + + res = i2c_transfer(i2c_adap, msgs, 1); + if (res < 1) { + if (res == 0) + res = -EIO; + return res; + } else + return 0; +} + +static int inv_i2c_write_register(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned char reg, unsigned char value) +{ + unsigned char data[2]; + + data[0] = reg; + data[1] = value; + return inv_i2c_write(i2c_adap, address, 2, data); +} + +static int inv_i2c_read(struct i2c_adapter *i2c_adap, + unsigned char address, unsigned char reg, + unsigned int len, unsigned char *data) +{ + struct i2c_msg msgs[2]; + int res; + + if (!data || !i2c_adap) + return -EINVAL; + + msgs[0].addr = address; + msgs[0].flags = 0; /* write */ + msgs[0].buf = ® + msgs[0].len = 1; + + msgs[1].addr = address; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = data; + msgs[1].len = len; + + res = i2c_transfer(i2c_adap, msgs, 2); + if (res < 2) { + if (res >= 0) + res = -EIO; + return res; + } else + return 0; +} + +static int mpu_memory_read(struct i2c_adapter *i2c_adap, + unsigned char mpu_addr, + unsigned short mem_addr, + unsigned int len, unsigned char *data) +{ + unsigned char bank[2]; + unsigned char addr[2]; + unsigned char buf; + + struct i2c_msg msgs[4]; + int res; + + if (!data || !i2c_adap) + return -EINVAL; + + bank[0] = MPUREG_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = MPUREG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf = MPUREG_MEM_R_W; + + /* 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; +} + +static int mpu_memory_write(struct i2c_adapter *i2c_adap, + unsigned char mpu_addr, + unsigned short mem_addr, + unsigned int len, unsigned char const *data) +{ + unsigned char bank[2]; + unsigned char addr[2]; + unsigned char buf[513]; + + struct i2c_msg msgs[3]; + int res; + + if (!data || !i2c_adap) + return -EINVAL; + if (len >= (sizeof(buf) - 1)) + return -ENOMEM; + + bank[0] = MPUREG_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = MPUREG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf[0] = MPUREG_MEM_R_W; + 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 = (unsigned char *)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 inv_serial_single_write( + void *sl_handle, + unsigned char slave_addr, + unsigned char register_addr, + unsigned char data) +{ + return inv_i2c_write_register((struct i2c_adapter *)sl_handle, + slave_addr, register_addr, data); +} +EXPORT_SYMBOL(inv_serial_single_write); + +int inv_serial_write( + void *sl_handle, + unsigned char slave_addr, + unsigned short length, + unsigned char const *data) +{ + int result; + const unsigned short data_length = length - 1; + const unsigned char start_reg_addr = data[0]; + unsigned char i2c_write[SERIAL_MAX_TRANSFER_SIZE + 1]; + unsigned short bytes_written = 0; + + while (bytes_written < data_length) { + unsigned short this_len = min(SERIAL_MAX_TRANSFER_SIZE, + data_length - bytes_written); + if (bytes_written == 0) { + result = inv_i2c_write((struct i2c_adapter *) + sl_handle, slave_addr, + 1 + this_len, data); + } else { + /* manually increment register addr between chunks */ + i2c_write[0] = start_reg_addr + bytes_written; + memcpy(&i2c_write[1], &data[1 + bytes_written], + this_len); + result = inv_i2c_write((struct i2c_adapter *) + sl_handle, slave_addr, + 1 + this_len, i2c_write); + } + if (result) + return result; + bytes_written += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_write); + +int inv_serial_read( + void *sl_handle, + unsigned char slave_addr, + unsigned char register_addr, + unsigned short length, + unsigned char *data) +{ + int result; + unsigned short bytes_read = 0; + + if (register_addr == MPUREG_FIFO_R_W || register_addr == MPUREG_MEM_R_W) + return INV_ERROR_INVALID_PARAMETER; + + while (bytes_read < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_read); + result = inv_i2c_read((struct i2c_adapter *)sl_handle, + slave_addr, register_addr + bytes_read, + this_len, &data[bytes_read]); + if (result) + return result; + bytes_read += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_read); + +int inv_serial_write_mem( + void *sl_handle, + unsigned char slave_addr, + unsigned short mem_addr, + unsigned short length, + unsigned char const *data) +{ + int result; + unsigned short bytes_written = 0; + + if ((mem_addr & 0xFF) + length > MPU_MEM_BANK_SIZE) { + pr_err("memory read length (%d B) extends beyond its" + " limits (%d) if started at location %d\n", length, + MPU_MEM_BANK_SIZE, mem_addr & 0xFF); + return INV_ERROR_INVALID_PARAMETER; + } + while (bytes_written < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_written); + result = mpu_memory_write((struct i2c_adapter *)sl_handle, + slave_addr, mem_addr + bytes_written, + this_len, &data[bytes_written]); + if (result) + return result; + bytes_written += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_write_mem); + +int inv_serial_read_mem( + void *sl_handle, + unsigned char slave_addr, + unsigned short mem_addr, + unsigned short length, + unsigned char *data) +{ + int result; + unsigned short bytes_read = 0; + + if ((mem_addr & 0xFF) + length > MPU_MEM_BANK_SIZE) { + printk + ("memory read length (%d B) extends beyond its limits (%d) " + "if started at location %d\n", length, + MPU_MEM_BANK_SIZE, mem_addr & 0xFF); + return INV_ERROR_INVALID_PARAMETER; + } + while (bytes_read < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_read); + result = + mpu_memory_read((struct i2c_adapter *)sl_handle, + slave_addr, mem_addr + bytes_read, + this_len, &data[bytes_read]); + if (result) + return result; + bytes_read += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_read_mem); + +int inv_serial_write_fifo( + void *sl_handle, + unsigned char slave_addr, + unsigned short length, + unsigned char const *data) +{ + int result; + unsigned char i2c_write[SERIAL_MAX_TRANSFER_SIZE + 1]; + unsigned short bytes_written = 0; + + if (length > FIFO_HW_SIZE) { + printk(KERN_ERR + "maximum fifo write length is %d\n", FIFO_HW_SIZE); + return INV_ERROR_INVALID_PARAMETER; + } + while (bytes_written < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_written); + i2c_write[0] = MPUREG_FIFO_R_W; + memcpy(&i2c_write[1], &data[bytes_written], this_len); + result = inv_i2c_write((struct i2c_adapter *)sl_handle, + slave_addr, this_len + 1, i2c_write); + if (result) + return result; + bytes_written += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_write_fifo); + +int inv_serial_read_fifo( + void *sl_handle, + unsigned char slave_addr, + unsigned short length, + unsigned char *data) +{ + int result; + unsigned short bytes_read = 0; + + if (length > FIFO_HW_SIZE) { + printk(KERN_ERR + "maximum fifo read length is %d\n", FIFO_HW_SIZE); + return INV_ERROR_INVALID_PARAMETER; + } + while (bytes_read < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_read); + result = inv_i2c_read((struct i2c_adapter *)sl_handle, + slave_addr, MPUREG_FIFO_R_W, this_len, + &data[bytes_read]); + if (result) + return result; + bytes_read += this_len; + } + + return 0; +} +EXPORT_SYMBOL(inv_serial_read_fifo); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/mlsl.h b/drivers/misc/inv_mpu/mlsl.h new file mode 100644 index 0000000..9e29ce6 --- /dev/null +++ b/drivers/misc/inv_mpu/mlsl.h @@ -0,0 +1,186 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +#ifndef __MLSL_H__ +#define __MLSL_H__ + +/** + * @defgroup MLSL + * @brief Motion Library - Serial Layer. + * The Motion Library System Layer provides the Motion Library + * with the communication interface to the hardware. + * + * The communication interface is assumed to support serial + * transfers in burst of variable length up to + * SERIAL_MAX_TRANSFER_SIZE. + * The default value for SERIAL_MAX_TRANSFER_SIZE is 128 bytes. + * Transfers of length greater than SERIAL_MAX_TRANSFER_SIZE, will + * be subdivided in smaller transfers of length <= + * SERIAL_MAX_TRANSFER_SIZE. + * The SERIAL_MAX_TRANSFER_SIZE definition can be modified to + * overcome any host processor transfer size limitation down to + * 1 B, the minimum. + * An higher value for SERIAL_MAX_TRANSFER_SIZE will favor + * performance and efficiency while requiring higher resource usage + * (mostly buffering). A smaller value will increase overhead and + * decrease efficiency but allows to operate with more resource + * constrained processor and master serial controllers. + * The SERIAL_MAX_TRANSFER_SIZE definition can be found in the + * mlsl.h header file and master serial controllers. + * The SERIAL_MAX_TRANSFER_SIZE definition can be found in the + * mlsl.h header file. + * + * @{ + * @file mlsl.h + * @brief The Motion Library System Layer. + * + */ + +#include "mltypes.h" +#include <linux/mpu.h> + + +/* + * NOTE : to properly support Yamaha compass reads, + * the max transfer size should be at least 9 B. + * Length in bytes, typically a power of 2 >= 2 + */ +#define SERIAL_MAX_TRANSFER_SIZE 128 + + +/** + * inv_serial_single_write() - used to write a single byte of data. + * @sl_handle pointer to the serial device used for the communication. + * @slave_addr I2C slave address of device. + * @register_addr Register address to write. + * @data Single byte of data to write. + * + * It is called by the MPL to write a single byte of data to the MPU. + * + * returns INV_SUCCESS if successful, a non-zero error code otherwise. + */ +int inv_serial_single_write( + void *sl_handle, + unsigned char slave_addr, + unsigned char register_addr, + unsigned char data); + +/** + * inv_serial_write() - used to write multiple bytes of data to registers. + * @sl_handle a file handle to the serial device used for the communication. + * @slave_addr I2C slave address of device. + * @register_addr Register address to write. + * @length Length of burst of data. + * @data Pointer to block of data. + * + * returns INV_SUCCESS if successful, a non-zero error code otherwise. + */ +int inv_serial_write( + void *sl_handle, + unsigned char slave_addr, + unsigned short length, + unsigned char const *data); + +/** + * inv_serial_read() - used to read multiple bytes of data from registers. + * @sl_handle a file handle to the serial device used for the communication. + * @slave_addr I2C slave address of device. + * @register_addr Register address to read. + * @length Length of burst of data. + * @data Pointer to block of data. + * + * returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise. + */ +int inv_serial_read( + void *sl_handle, + unsigned char slave_addr, + unsigned char register_addr, + unsigned short length, + unsigned char *data); + +/** + * inv_serial_read_mem() - used to read multiple bytes of data from the memory. + * This should be sent by I2C or SPI. + * + * @sl_handle a file handle to the serial device used for the communication. + * @slave_addr I2C slave address of device. + * @mem_addr The location in the memory to read from. + * @length Length of burst data. + * @data Pointer to block of data. + * + * returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise. + */ +int inv_serial_read_mem( + void *sl_handle, + unsigned char slave_addr, + unsigned short mem_addr, + unsigned short length, + unsigned char *data); + +/** + * inv_serial_write_mem() - used to write multiple bytes of data to the memory. + * @sl_handle a file handle to the serial device used for the communication. + * @slave_addr I2C slave address of device. + * @mem_addr The location in the memory to write to. + * @length Length of burst data. + * @data Pointer to block of data. + * + * returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise. + */ +int inv_serial_write_mem( + void *sl_handle, + unsigned char slave_addr, + unsigned short mem_addr, + unsigned short length, + unsigned char const *data); + +/** + * inv_serial_read_fifo() - used to read multiple bytes of data from the fifo. + * @sl_handle a file handle to the serial device used for the communication. + * @slave_addr I2C slave address of device. + * @length Length of burst of data. + * @data Pointer to block of data. + * + * returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise. + */ +int inv_serial_read_fifo( + void *sl_handle, + unsigned char slave_addr, + unsigned short length, + unsigned char *data); + +/** + * inv_serial_write_fifo() - used to write multiple bytes of data to the fifo. + * @sl_handle a file handle to the serial device used for the communication. + * @slave_addr I2C slave address of device. + * @length Length of burst of data. + * @data Pointer to block of data. + * + * returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise. + */ +int inv_serial_write_fifo( + void *sl_handle, + unsigned char slave_addr, + unsigned short length, + unsigned char const *data); + +/** + * @} + */ +#endif /* __MLSL_H__ */ diff --git a/drivers/misc/inv_mpu/mltypes.h b/drivers/misc/inv_mpu/mltypes.h new file mode 100644 index 0000000..b89087c --- /dev/null +++ b/drivers/misc/inv_mpu/mltypes.h @@ -0,0 +1,229 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @defgroup MLERROR + * @brief Motion Library - Error definitions. + * Definition of the error codes used within the MPL and + * returned to the user. + * Every function tries to return a meaningful error code basing + * on the occuring error condition. The error code is numeric. + * + * The available error codes and their associated values are: + * - (0) INV_SUCCESS + * - (1) INV_ERROR + * - (2) INV_ERROR_INVALID_PARAMETER + * - (3) INV_ERROR_FEATURE_NOT_ENABLED + * - (4) INV_ERROR_FEATURE_NOT_IMPLEMENTED + * - (6) INV_ERROR_DMP_NOT_STARTED + * - (7) INV_ERROR_DMP_STARTED + * - (8) INV_ERROR_NOT_OPENED + * - (9) INV_ERROR_OPENED + * - (10) INV_ERROR_INVALID_MODULE + * - (11) INV_ERROR_MEMORY_EXAUSTED + * - (12) INV_ERROR_DIVIDE_BY_ZERO + * - (13) INV_ERROR_ASSERTION_FAILURE + * - (14) INV_ERROR_FILE_OPEN + * - (15) INV_ERROR_FILE_READ + * - (16) INV_ERROR_FILE_WRITE + * - (17) INV_ERROR_INVALID_CONFIGURATION + * - (20) INV_ERROR_SERIAL_CLOSED + * - (21) INV_ERROR_SERIAL_OPEN_ERROR + * - (22) INV_ERROR_SERIAL_READ + * - (23) INV_ERROR_SERIAL_WRITE + * - (24) INV_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED + * - (25) INV_ERROR_SM_TRANSITION + * - (26) INV_ERROR_SM_IMPROPER_STATE + * - (30) INV_ERROR_FIFO_OVERFLOW + * - (31) INV_ERROR_FIFO_FOOTER + * - (32) INV_ERROR_FIFO_READ_COUNT + * - (33) INV_ERROR_FIFO_READ_DATA + * - (40) INV_ERROR_MEMORY_SET + * - (50) INV_ERROR_LOG_MEMORY_ERROR + * - (51) INV_ERROR_LOG_OUTPUT_ERROR + * - (60) INV_ERROR_OS_BAD_PTR + * - (61) INV_ERROR_OS_BAD_HANDLE + * - (62) INV_ERROR_OS_CREATE_FAILED + * - (63) INV_ERROR_OS_LOCK_FAILED + * - (70) INV_ERROR_COMPASS_DATA_OVERFLOW + * - (71) INV_ERROR_COMPASS_DATA_UNDERFLOW + * - (72) INV_ERROR_COMPASS_DATA_NOT_READY + * - (73) INV_ERROR_COMPASS_DATA_ERROR + * - (75) INV_ERROR_CALIBRATION_LOAD + * - (76) INV_ERROR_CALIBRATION_STORE + * - (77) INV_ERROR_CALIBRATION_LEN + * - (78) INV_ERROR_CALIBRATION_CHECKSUM + * - (79) INV_ERROR_ACCEL_DATA_OVERFLOW + * - (80) INV_ERROR_ACCEL_DATA_UNDERFLOW + * - (81) INV_ERROR_ACCEL_DATA_NOT_READY + * - (82) INV_ERROR_ACCEL_DATA_ERROR + * + * @{ + * @file mltypes.h + * @} + */ + +#ifndef MLTYPES_H +#define MLTYPES_H + +#include <linux/types.h> + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +/* - ML Errors. - */ +#define ERROR_NAME(x) (#x) +#define ERROR_CHECK_FIRST(first, x) \ + { if (INV_SUCCESS == first) first = x; } + +#define INV_SUCCESS (0) +/* Generic Error code. Proprietary Error Codes only */ +#define INV_ERROR (1) + +/* Compatibility and other generic error codes */ +#define INV_ERROR_INVALID_PARAMETER (2) +#define INV_ERROR_FEATURE_NOT_ENABLED (3) +#define INV_ERROR_FEATURE_NOT_IMPLEMENTED (4) +#define INV_ERROR_DMP_NOT_STARTED (6) +#define INV_ERROR_DMP_STARTED (7) +#define INV_ERROR_NOT_OPENED (8) +#define INV_ERROR_OPENED (9) +#define INV_ERROR_INVALID_MODULE (10) +#define INV_ERROR_MEMORY_EXAUSTED (11) +#define INV_ERROR_DIVIDE_BY_ZERO (12) +#define INV_ERROR_ASSERTION_FAILURE (13) +#define INV_ERROR_FILE_OPEN (14) +#define INV_ERROR_FILE_READ (15) +#define INV_ERROR_FILE_WRITE (16) +#define INV_ERROR_INVALID_CONFIGURATION (17) + +/* Serial Communication */ +#define INV_ERROR_SERIAL_CLOSED (20) +#define INV_ERROR_SERIAL_OPEN_ERROR (21) +#define INV_ERROR_SERIAL_READ (22) +#define INV_ERROR_SERIAL_WRITE (23) +#define INV_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED (24) + +/* SM = State Machine */ +#define INV_ERROR_SM_TRANSITION (25) +#define INV_ERROR_SM_IMPROPER_STATE (26) + +/* Fifo */ +#define INV_ERROR_FIFO_OVERFLOW (30) +#define INV_ERROR_FIFO_FOOTER (31) +#define INV_ERROR_FIFO_READ_COUNT (32) +#define INV_ERROR_FIFO_READ_DATA (33) + +/* Memory & Registers, Set & Get */ +#define INV_ERROR_MEMORY_SET (40) + +#define INV_ERROR_LOG_MEMORY_ERROR (50) +#define INV_ERROR_LOG_OUTPUT_ERROR (51) + +/* OS interface errors */ +#define INV_ERROR_OS_BAD_PTR (60) +#define INV_ERROR_OS_BAD_HANDLE (61) +#define INV_ERROR_OS_CREATE_FAILED (62) +#define INV_ERROR_OS_LOCK_FAILED (63) + +/* Compass errors */ +#define INV_ERROR_COMPASS_DATA_OVERFLOW (70) +#define INV_ERROR_COMPASS_DATA_UNDERFLOW (71) +#define INV_ERROR_COMPASS_DATA_NOT_READY (72) +#define INV_ERROR_COMPASS_DATA_ERROR (73) + +/* Load/Store calibration */ +#define INV_ERROR_CALIBRATION_LOAD (75) +#define INV_ERROR_CALIBRATION_STORE (76) +#define INV_ERROR_CALIBRATION_LEN (77) +#define INV_ERROR_CALIBRATION_CHECKSUM (78) + +/* Accel errors */ +#define INV_ERROR_ACCEL_DATA_OVERFLOW (79) +#define INV_ERROR_ACCEL_DATA_UNDERFLOW (80) +#define INV_ERROR_ACCEL_DATA_NOT_READY (81) +#define INV_ERROR_ACCEL_DATA_ERROR (82) + +#ifdef INV_USE_LEGACY_NAMES +#define ML_SUCCESS INV_SUCCESS +#define ML_ERROR INV_ERROR +#define ML_ERROR_INVALID_PARAMETER INV_ERROR_INVALID_PARAMETER +#define ML_ERROR_FEATURE_NOT_ENABLED INV_ERROR_FEATURE_NOT_ENABLED +#define ML_ERROR_FEATURE_NOT_IMPLEMENTED INV_ERROR_FEATURE_NOT_IMPLEMENTED +#define ML_ERROR_DMP_NOT_STARTED INV_ERROR_DMP_NOT_STARTED +#define ML_ERROR_DMP_STARTED INV_ERROR_DMP_STARTED +#define ML_ERROR_NOT_OPENED INV_ERROR_NOT_OPENED +#define ML_ERROR_OPENED INV_ERROR_OPENED +#define ML_ERROR_INVALID_MODULE INV_ERROR_INVALID_MODULE +#define ML_ERROR_MEMORY_EXAUSTED INV_ERROR_MEMORY_EXAUSTED +#define ML_ERROR_DIVIDE_BY_ZERO INV_ERROR_DIVIDE_BY_ZERO +#define ML_ERROR_ASSERTION_FAILURE INV_ERROR_ASSERTION_FAILURE +#define ML_ERROR_FILE_OPEN INV_ERROR_FILE_OPEN +#define ML_ERROR_FILE_READ INV_ERROR_FILE_READ +#define ML_ERROR_FILE_WRITE INV_ERROR_FILE_WRITE +#define ML_ERROR_INVALID_CONFIGURATION INV_ERROR_INVALID_CONFIGURATION +#define ML_ERROR_SERIAL_CLOSED INV_ERROR_SERIAL_CLOSED +#define ML_ERROR_SERIAL_OPEN_ERROR INV_ERROR_SERIAL_OPEN_ERROR +#define ML_ERROR_SERIAL_READ INV_ERROR_SERIAL_READ +#define ML_ERROR_SERIAL_WRITE INV_ERROR_SERIAL_WRITE +#define ML_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED \ + INV_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED +#define ML_ERROR_SM_TRANSITION INV_ERROR_SM_TRANSITION +#define ML_ERROR_SM_IMPROPER_STATE INV_ERROR_SM_IMPROPER_STATE +#define ML_ERROR_FIFO_OVERFLOW INV_ERROR_FIFO_OVERFLOW +#define ML_ERROR_FIFO_FOOTER INV_ERROR_FIFO_FOOTER +#define ML_ERROR_FIFO_READ_COUNT INV_ERROR_FIFO_READ_COUNT +#define ML_ERROR_FIFO_READ_DATA INV_ERROR_FIFO_READ_DATA +#define ML_ERROR_MEMORY_SET INV_ERROR_MEMORY_SET +#define ML_ERROR_LOG_MEMORY_ERROR INV_ERROR_LOG_MEMORY_ERROR +#define ML_ERROR_LOG_OUTPUT_ERROR INV_ERROR_LOG_OUTPUT_ERROR +#define ML_ERROR_OS_BAD_PTR INV_ERROR_OS_BAD_PTR +#define ML_ERROR_OS_BAD_HANDLE INV_ERROR_OS_BAD_HANDLE +#define ML_ERROR_OS_CREATE_FAILED INV_ERROR_OS_CREATE_FAILED +#define ML_ERROR_OS_LOCK_FAILED INV_ERROR_OS_LOCK_FAILED +#define ML_ERROR_COMPASS_DATA_OVERFLOW INV_ERROR_COMPASS_DATA_OVERFLOW +#define ML_ERROR_COMPASS_DATA_UNDERFLOW INV_ERROR_COMPASS_DATA_UNDERFLOW +#define ML_ERROR_COMPASS_DATA_NOT_READY INV_ERROR_COMPASS_DATA_NOT_READY +#define ML_ERROR_COMPASS_DATA_ERROR INV_ERROR_COMPASS_DATA_ERROR +#define ML_ERROR_CALIBRATION_LOAD INV_ERROR_CALIBRATION_LOAD +#define ML_ERROR_CALIBRATION_STORE INV_ERROR_CALIBRATION_STORE +#define ML_ERROR_CALIBRATION_LEN INV_ERROR_CALIBRATION_LEN +#define ML_ERROR_CALIBRATION_CHECKSUM INV_ERROR_CALIBRATION_CHECKSUM +#define ML_ERROR_ACCEL_DATA_OVERFLOW INV_ERROR_ACCEL_DATA_OVERFLOW +#define ML_ERROR_ACCEL_DATA_UNDERFLOW INV_ERROR_ACCEL_DATA_UNDERFLOW +#define ML_ERROR_ACCEL_DATA_NOT_READY INV_ERROR_ACCEL_DATA_NOT_READY +#define ML_ERROR_ACCEL_DATA_ERROR INV_ERROR_ACCEL_DATA_ERROR +#endif + +/* For Linux coding compliance */ + +/*--------------------------- + p-Types +---------------------------*/ + +#endif /* MLTYPES_H */ diff --git a/drivers/misc/inv_mpu/mpu-dev.c b/drivers/misc/inv_mpu/mpu-dev.c new file mode 100644 index 0000000..74b930c --- /dev/null +++ b/drivers/misc/inv_mpu/mpu-dev.c @@ -0,0 +1,1309 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/signal.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/version.h> +#include <linux/pm.h> +#include <linux/mutex.h> +#include <linux/suspend.h> +#include <linux/poll.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include "mpuirq.h" +#include "slaveirq.h" +#include "mlsl.h" +#include "mldl_cfg.h" +#include <linux/mpu.h> + + +/* Platform data for the MPU */ +struct mpu_private_data { + struct miscdevice dev; + struct i2c_client *client; + struct mldl_cfg mldl_cfg; + + struct mutex mutex; + wait_queue_head_t mpu_event_wait; + struct completion completion; + struct timer_list timeout; + struct notifier_block nb; + struct mpuirq_data mpu_pm_event; + int response_timeout; /* In seconds */ + unsigned long event; + int pid; + struct module *slave_modules[EXT_SLAVE_NUM_TYPES]; +}; + +struct mpu_private_data *mpu_private_data; + +static void mpu_pm_timeout(u_long data) +{ + struct mpu_private_data *mpu = (struct mpu_private_data *)data; + struct i2c_client *client = mpu->client; + dev_dbg(&client->adapter->dev, "%s\n", __func__); + complete(&mpu->completion); +} + +static int mpu_pm_notifier_callback(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct mpu_private_data *mpu = + container_of(nb, struct mpu_private_data, nb); + struct i2c_client *client = mpu->client; + dev_dbg(&client->adapter->dev, "%s: %ld\n", __func__, event); + + /* Prevent the file handle from being closed before we initialize + the completion event */ + mutex_lock(&mpu->mutex); + if (!(mpu->pid) || + (event != PM_SUSPEND_PREPARE && event != PM_POST_SUSPEND)) { + mutex_unlock(&mpu->mutex); + return NOTIFY_OK; + } + + if (event == PM_SUSPEND_PREPARE) + mpu->event = MPU_PM_EVENT_SUSPEND_PREPARE; + if (event == PM_POST_SUSPEND) + mpu->event = MPU_PM_EVENT_POST_SUSPEND; + + mpu->mpu_pm_event.irqtime = ktime_to_ns(ktime_get()); + mpu->mpu_pm_event.interruptcount++; + mpu->mpu_pm_event.data_type = MPUIRQ_DATA_TYPE_PM_EVENT; + mpu->mpu_pm_event.data = mpu->event; + + if (mpu->response_timeout > 0) { + mpu->timeout.expires = jiffies + mpu->response_timeout * HZ; + add_timer(&mpu->timeout); + } + INIT_COMPLETION(mpu->completion); + mutex_unlock(&mpu->mutex); + + wake_up_interruptible(&mpu->mpu_event_wait); + wait_for_completion(&mpu->completion); + del_timer_sync(&mpu->timeout); + dev_dbg(&client->adapter->dev, "%s: %ld DONE\n", __func__, event); + return NOTIFY_OK; +} + +static int mpu_dev_open(struct inode *inode, struct file *file) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + int result; + int ii; + dev_dbg(&client->adapter->dev, "%s\n", __func__); + dev_dbg(&client->adapter->dev, "current->pid %d\n", current->pid); + + result = mutex_lock_interruptible(&mpu->mutex); + if (mpu->pid) { + mutex_unlock(&mpu->mutex); + return -EBUSY; + } + mpu->pid = current->pid; + + /* Reset the sensors to the default */ + if (result) { + dev_err(&client->adapter->dev, + "%s: mutex_lock_interruptible returned %d\n", + __func__, result); + return result; + } + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) + __module_get(mpu->slave_modules[ii]); + + mldl_cfg->requested_sensors = INV_THREE_AXIS_GYRO; + if (mldl_cfg->accel && mldl_cfg->accel->resume) + mldl_cfg->requested_sensors |= INV_THREE_AXIS_ACCEL; + + if (mldl_cfg->compass && mldl_cfg->compass->resume) + mldl_cfg->requested_sensors |= INV_THREE_AXIS_COMPASS; + + if (mldl_cfg->pressure && mldl_cfg->pressure->resume) + mldl_cfg->requested_sensors |= INV_THREE_AXIS_PRESSURE; + mutex_unlock(&mpu->mutex); + return 0; +} + +/* close function - called when the "file" /dev/mpu is closed in userspace */ +static int mpu_release(struct inode *inode, struct file *file) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + int result = 0; + int ii; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + mutex_lock(&mpu->mutex); + mldl_cfg->requested_sensors = 0; + result = inv_mpu_suspend(mldl_cfg, client->adapter, + accel_adapter, compass_adapter, + pressure_adapter, INV_ALL_SENSORS); + mpu->pid = 0; + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) + module_put(mpu->slave_modules[ii]); + + mutex_unlock(&mpu->mutex); + complete(&mpu->completion); + dev_dbg(&client->adapter->dev, "mpu_release\n"); + + return result; +} + +/* read function called when from /dev/mpu is read. Read from the FIFO */ +static ssize_t mpu_read(struct file *file, + char __user *buf, size_t count, loff_t *offset) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + size_t len = sizeof(mpu->mpu_pm_event) + sizeof(unsigned long); + int err; + + if (!mpu->event && (!(file->f_flags & O_NONBLOCK))) + wait_event_interruptible(mpu->mpu_event_wait, mpu->event); + + if (!mpu->event || !buf + || count < sizeof(mpu->mpu_pm_event)) + return 0; + + err = copy_to_user(buf, &mpu->mpu_pm_event, sizeof(mpu->mpu_pm_event)); + if (err) { + dev_err(&client->adapter->dev, + "Copy to user returned %d\n", err); + return -EFAULT; + } + mpu->event = 0; + return len; +} + +static unsigned int mpu_poll(struct file *file, struct poll_table_struct *poll) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + int mask = 0; + + poll_wait(file, &mpu->mpu_event_wait, poll); + if (mpu->event) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +static int +mpu_dev_ioctl_set_mpu_pdata(struct i2c_client *client, unsigned long arg) +{ + int ii; + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mpu_platform_data *pdata = mpu->mldl_cfg.pdata; + struct mpu_platform_data local_pdata; + + if (copy_from_user(&local_pdata, (unsigned char __user *)arg, + sizeof(local_pdata))) + return -EFAULT; + + pdata->int_config = local_pdata.int_config; + for (ii = 0; ii < ARRAY_SIZE(pdata->orientation); ii++) + pdata->orientation[ii] = local_pdata.orientation[ii]; + pdata->level_shifter = local_pdata.level_shifter; + + pdata->accel.address = local_pdata.accel.address; + for (ii = 0; ii < ARRAY_SIZE(pdata->accel.orientation); ii++) + pdata->accel.orientation[ii] = + local_pdata.accel.orientation[ii]; + + pdata->compass.address = local_pdata.compass.address; + for (ii = 0; ii < ARRAY_SIZE(pdata->compass.orientation); ii++) + pdata->compass.orientation[ii] = + local_pdata.compass.orientation[ii]; + + pdata->pressure.address = local_pdata.pressure.address; + for (ii = 0; ii < ARRAY_SIZE(pdata->pressure.orientation); ii++) + pdata->pressure.orientation[ii] = + local_pdata.pressure.orientation[ii]; + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + return 0; +} + +static int +mpu_dev_ioctl_set_mpu_config(struct i2c_client *client, unsigned long arg) +{ + int ii; + int result = 0; + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct mldl_cfg *temp_mldl_cfg; + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + temp_mldl_cfg = kmalloc(offsetof(struct mldl_cfg, silicon_revision), + GFP_KERNEL); + if (!temp_mldl_cfg) + return -ENOMEM; + + /* + * User space is not allowed to modify accel compass pressure or + * pdata structs, as well as silicon_revision product_id or trim + */ + if (copy_from_user(temp_mldl_cfg, (struct mldl_cfg __user *)arg, + offsetof(struct mldl_cfg, silicon_revision))) { + result = -EFAULT; + goto out; + } + + if (mldl_cfg->gyro_is_suspended) { + if (mldl_cfg->addr != temp_mldl_cfg->addr) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->int_config != temp_mldl_cfg->int_config) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->ext_sync != temp_mldl_cfg->ext_sync) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->full_scale != temp_mldl_cfg->full_scale) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->lpf != temp_mldl_cfg->lpf) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->clk_src != temp_mldl_cfg->clk_src) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->divider != temp_mldl_cfg->divider) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->dmp_enable != temp_mldl_cfg->dmp_enable) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->fifo_enable != temp_mldl_cfg->fifo_enable) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->dmp_cfg1 != temp_mldl_cfg->dmp_cfg1) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->dmp_cfg2 != temp_mldl_cfg->dmp_cfg2) + mldl_cfg->gyro_needs_reset = TRUE; + + for (ii = 0; ii < GYRO_NUM_AXES; ii++) + if (mldl_cfg->offset_tc[ii] != + temp_mldl_cfg->offset_tc[ii]) + mldl_cfg->gyro_needs_reset = TRUE; + + for (ii = 0; ii < GYRO_NUM_AXES; ii++) + if (mldl_cfg->offset[ii] != temp_mldl_cfg->offset[ii]) + mldl_cfg->gyro_needs_reset = TRUE; + + if (memcmp(mldl_cfg->ram, temp_mldl_cfg->ram, + MPU_MEM_NUM_RAM_BANKS * MPU_MEM_BANK_SIZE * + sizeof(unsigned char))) + mldl_cfg->gyro_needs_reset = TRUE; + } + + memcpy(mldl_cfg, temp_mldl_cfg, + offsetof(struct mldl_cfg, silicon_revision)); + + out: + kfree(temp_mldl_cfg); + return result; +} + +static int +mpu_dev_ioctl_get_mpu_config(struct i2c_client *client, + struct mldl_cfg __user *arg) +{ + /* Have to be careful as there are 3 pointers in the mldl_cfg + * structure */ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct mldl_cfg *local_mldl_cfg; + int retval = 0; + + local_mldl_cfg = kmalloc(sizeof(struct mldl_cfg), GFP_KERNEL); + if (!local_mldl_cfg) + return -ENOMEM; + + retval = + copy_from_user(local_mldl_cfg, arg, sizeof(*arg)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on arg\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + + /* Fill in the accel, compass, pressure and pdata pointers */ + if (mldl_cfg->accel) { + retval = copy_to_user((void __user *)local_mldl_cfg->accel, + mldl_cfg->accel, + sizeof(*mldl_cfg->accel)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on accel\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + } + + if (mldl_cfg->compass) { + retval = copy_to_user((void __user *)local_mldl_cfg->compass, + mldl_cfg->compass, + sizeof(*mldl_cfg->compass)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on compass\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + } + + if (mldl_cfg->pressure) { + retval = copy_to_user((void __user *)local_mldl_cfg->pressure, + mldl_cfg->pressure, + sizeof(*mldl_cfg->pressure)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on pressure\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + } + + if (mldl_cfg->pdata) { + retval = copy_to_user((void __user *)local_mldl_cfg->pdata, + mldl_cfg->pdata, + sizeof(*mldl_cfg->pdata)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on pdata\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + } + + /* Do not modify the accel, compass, pressure and pdata pointers */ + retval = copy_to_user(arg, mldl_cfg, offsetof(struct mldl_cfg, accel)); + + if (retval) + retval = -EFAULT; + out: + kfree(local_mldl_cfg); + return retval; +} + +/** + * slave_config() - Pass a requested slave configuration to the slave sensor + * + * @adapter the adaptor to use to communicate with the slave + * @mldl_cfg the mldl configuration structuer + * @slave pointer to the slave descriptor + * @usr_config The configuration to pass to the slave sensor + * + * returns 0 or non-zero error code + */ +static int slave_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + void *slave_adapter, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + if ((!slave) || (!slave->config)) + return -ENODEV; + + retval = copy_from_user(&config, usr_config, sizeof(config)); + if (retval) + return -EFAULT; + + if (config.len && config.data) { + void *data; + data = kmalloc(config.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)config.data, config.len); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + config.data = data; + } + retval = inv_mpu_slave_config(mldl_cfg, gyro_adapter, slave_adapter, + &config, slave, pdata); + kfree(config.data); + return retval; +} + +/** + * slave_get_config() - Get requested slave configuration from the slave sensor + * + * @adapter the adaptor to use to communicate with the slave + * @mldl_cfg the mldl configuration structuer + * @slave pointer to the slave descriptor + * @usr_config The configuration for the slave to fill out + * + * returns 0 or non-zero error code + */ +static int slave_get_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + void *slave_adapter, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + void *user_data; + if (!(slave) || !(slave->get_config)) + return -ENODEV; + + retval = copy_from_user(&config, usr_config, sizeof(config)); + if (retval) + return -EFAULT; + + user_data = config.data; + if (config.len && config.data) { + void *data; + data = kmalloc(config.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)config.data, config.len); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + config.data = data; + } + retval = inv_mpu_get_slave_config(mldl_cfg, gyro_adapter, + slave_adapter, &config, slave, pdata); + if (retval) { + kfree(config.data); + return retval; + } + retval = copy_to_user((unsigned char __user *)user_data, + config.data, config.len); + kfree(config.data); + return retval; +} + +static int inv_slave_read(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + void *slave_adapter, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + void __user *usr_data) +{ + int retval; + unsigned char *data; + data = kzalloc(slave->read_len, GFP_KERNEL); + if (!data) + return -EFAULT; + + retval = inv_mpu_slave_read(mldl_cfg, gyro_adapter, slave_adapter, + slave, pdata, data); + + if ((!retval) && + (copy_to_user((unsigned char __user *)usr_data, + data, slave->read_len))) + retval = -EFAULT; + + kfree(data); + return retval; +} + +static int mpu_handle_mlsl(void *sl_handle, + unsigned char addr, + unsigned int cmd, + struct mpu_read_write __user *usr_msg) +{ + int retval = 0; + struct mpu_read_write msg; + unsigned char *user_data; + retval = copy_from_user(&msg, usr_msg, sizeof(msg)); + if (retval) + return -EFAULT; + + user_data = msg.data; + if (msg.length && msg.data) { + unsigned char *data; + data = kmalloc(msg.length, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)msg.data, msg.length); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + msg.data = data; + } else { + return -EPERM; + } + + switch (cmd) { + case MPU_READ: + retval = inv_serial_read(sl_handle, addr, + msg.address, msg.length, msg.data); + break; + case MPU_WRITE: + retval = inv_serial_write(sl_handle, addr, + msg.length, msg.data); + break; + case MPU_READ_MEM: + retval = inv_serial_read_mem(sl_handle, addr, + msg.address, msg.length, msg.data); + break; + case MPU_WRITE_MEM: + retval = inv_serial_write_mem(sl_handle, addr, + msg.address, msg.length, + msg.data); + break; + case MPU_READ_FIFO: + retval = inv_serial_read_fifo(sl_handle, addr, + msg.length, msg.data); + break; + case MPU_WRITE_FIFO: + retval = inv_serial_write_fifo(sl_handle, addr, + msg.length, msg.data); + break; + + }; + if (retval) { + dev_err(&((struct i2c_adapter *)sl_handle)->dev, + "%s: i2c %d error %d\n", + __func__, cmd, retval); + kfree(msg.data); + return retval; + } + retval = copy_to_user((unsigned char __user *)user_data, + msg.data, msg.length); + kfree(msg.data); + return retval; +} + +/* ioctl - I/O control */ +static long mpu_dev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + int retval = 0; + struct i2c_adapter *gyro_adapter; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + + gyro_adapter = client->adapter; + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + retval = mutex_lock_interruptible(&mpu->mutex); + if (retval) { + dev_err(&client->adapter->dev, + "%s: mutex_lock_interruptible returned %d\n", + __func__, retval); + return retval; + } + + switch (cmd) { + case MPU_SET_MPU_CONFIG: + retval = mpu_dev_ioctl_set_mpu_config(client, arg); + break; + case MPU_SET_PLATFORM_DATA: + retval = mpu_dev_ioctl_set_mpu_pdata(client, arg); + break; + case MPU_GET_MPU_CONFIG: + retval = mpu_dev_ioctl_get_mpu_config(client, + (struct mldl_cfg __user *)arg); + break; + case MPU_READ: + case MPU_WRITE: + case MPU_READ_MEM: + case MPU_WRITE_MEM: + case MPU_READ_FIFO: + case MPU_WRITE_FIFO: + retval = mpu_handle_mlsl(gyro_adapter, mldl_cfg->addr, cmd, + (struct mpu_read_write __user *)arg); + break; + case MPU_CONFIG_ACCEL: + retval = slave_config(mldl_cfg, + gyro_adapter, + accel_adapter, + mldl_cfg->accel, + &mldl_cfg->pdata->accel, + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_COMPASS: + retval = slave_config(mldl_cfg, + gyro_adapter, + compass_adapter, + mldl_cfg->compass, + &mldl_cfg->pdata->compass, + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_PRESSURE: + retval = slave_config(mldl_cfg, + gyro_adapter, + pressure_adapter, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure, + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_ACCEL: + retval = slave_get_config(mldl_cfg, + gyro_adapter, + accel_adapter, + mldl_cfg->accel, + &mldl_cfg->pdata->accel, + (struct ext_slave_config __user *) + arg); + break; + case MPU_GET_CONFIG_COMPASS: + retval = slave_get_config(mldl_cfg, + gyro_adapter, + compass_adapter, + mldl_cfg->compass, + &mldl_cfg->pdata->compass, + (struct ext_slave_config __user *) + arg); + break; + case MPU_GET_CONFIG_PRESSURE: + retval = slave_get_config(mldl_cfg, + gyro_adapter, + pressure_adapter, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure, + (struct ext_slave_config __user *) + arg); + break; + case MPU_SUSPEND: + retval = inv_mpu_suspend(mldl_cfg, + gyro_adapter, + accel_adapter, + compass_adapter, + pressure_adapter, + (~(mldl_cfg->requested_sensors)) + & INV_ALL_SENSORS); + break; + case MPU_RESUME: + retval = inv_mpu_resume(mldl_cfg, + gyro_adapter, + accel_adapter, + compass_adapter, + pressure_adapter, + mldl_cfg->requested_sensors); + break; + case MPU_PM_EVENT_HANDLED: + dev_dbg(&client->adapter->dev, "%s: %d\n", __func__, cmd); + complete(&mpu->completion); + break; + case MPU_READ_ACCEL: + retval = inv_slave_read(mldl_cfg, + gyro_adapter, + accel_adapter, + mldl_cfg->accel, + &mldl_cfg->pdata->accel, + (unsigned char __user *)arg); + break; + case MPU_READ_COMPASS: + retval = inv_slave_read(mldl_cfg, + gyro_adapter, + compass_adapter, + mldl_cfg->compass, + &mldl_cfg->pdata->compass, + (unsigned char __user *)arg); + break; + case MPU_READ_PRESSURE: + retval = inv_slave_read(mldl_cfg, + gyro_adapter, + pressure_adapter, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure, + (unsigned char __user *)arg); + break; + default: + dev_err(&client->adapter->dev, + "%s: Unknown cmd %x, arg %lu: MIN %x MAX %x\n", + __func__, cmd, arg, + MPU_SET_MPU_CONFIG, MPU_SET_MPU_CONFIG); + retval = -EINVAL; + }; + + mutex_unlock(&mpu->mutex); + return retval; +} + +void mpu_shutdown(struct i2c_client *client) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + mutex_lock(&mpu->mutex); + (void)inv_mpu_suspend(mldl_cfg, client->adapter, + accel_adapter, compass_adapter, pressure_adapter, + INV_ALL_SENSORS); + mutex_unlock(&mpu->mutex); + dev_dbg(&client->adapter->dev, "%s\n", __func__); +} + +int mpu_dev_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + mutex_lock(&mpu->mutex); + if (!mldl_cfg->ignore_system_suspend) { + dev_dbg(&client->adapter->dev, + "%s: suspending on event %d\n", __func__, mesg.event); + (void)inv_mpu_suspend(mldl_cfg, client->adapter, + accel_adapter, compass_adapter, + pressure_adapter, INV_ALL_SENSORS); + } else { + dev_dbg(&client->adapter->dev, + "%s: Already suspended %d\n", __func__, mesg.event); + } + mutex_unlock(&mpu->mutex); + return 0; +} + +int mpu_dev_resume(struct i2c_client *client) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + mutex_lock(&mpu->mutex); + if (mpu->pid && !mldl_cfg->ignore_system_suspend) { + (void)inv_mpu_resume(mldl_cfg, client->adapter, + accel_adapter, + compass_adapter, + pressure_adapter, + mldl_cfg->requested_sensors); + dev_dbg(&client->adapter->dev, + "%s for pid %d\n", __func__, mpu->pid); + } + mutex_unlock(&mpu->mutex); + return 0; +} + +/* define which file operations are supported */ +static const struct file_operations mpu_fops = { + .owner = THIS_MODULE, + .read = mpu_read, + .poll = mpu_poll, + .unlocked_ioctl = mpu_dev_ioctl, + .open = mpu_dev_open, + .release = mpu_release, +}; + +int inv_mpu_register_slave(struct module *slave_module, + struct i2c_client *slave_client, + struct ext_slave_platform_data *slave_pdata, + struct ext_slave_descr *(*get_slave_descr)(void)) +{ + struct mpu_private_data *mpu = mpu_private_data; + struct mldl_cfg *mldl_cfg; + struct mpu_platform_data *pdata; + struct ext_slave_descr *slave_descr; + int result = 0; + + if (!slave_client || !slave_pdata || !get_slave_descr) + return -EINVAL; + + if (!mpu) { + dev_err(&slave_client->adapter->dev, + "%s: Null mpu_private_data\n", __func__); + return -EINVAL; + } + mldl_cfg = &mpu->mldl_cfg; + pdata = mldl_cfg->pdata; + + slave_descr = get_slave_descr(); + if (!slave_descr) { + dev_err(&slave_client->adapter->dev, + "%s: Null ext_slave_descr\n", __func__); + return -EINVAL; + } + + mutex_lock(&mpu->mutex); + if (mpu->pid) { + mutex_unlock(&mpu->mutex); + return -EBUSY; + } + + mpu->slave_modules[slave_descr->type] = slave_module; + + switch (slave_descr->type) { + case EXT_SLAVE_TYPE_ACCELEROMETER: + if (pdata->accel.get_slave_descr) { + result = -EBUSY; + break; + } + + pdata->accel.address = slave_client->addr; + pdata->accel.irq = slave_client->irq; + pdata->accel.adapt_num = i2c_adapter_id(slave_client->adapter); + + if (pdata->accel.irq > 0) { + dev_info(&slave_client->adapter->dev, + "Installing Accel irq using %d\n", + pdata->accel.irq); + result = slaveirq_init(slave_client->adapter, + &pdata->accel, "accelirq"); + if (result) + break; + } else { + dev_WARN(&slave_client->adapter->dev, + "Accel irq not assigned\n"); + } + + if (slave_descr->init) { + result = slave_descr->init(slave_client->adapter, + slave_descr, + &pdata->accel); + if (result) { + dev_err(&slave_client->adapter->dev, + "Accel init failed %d\n", result); + if (pdata->accel.irq > 0) + slaveirq_exit(&pdata->accel); + break; + } + } + + pdata->accel.get_slave_descr = get_slave_descr; + mldl_cfg->accel = slave_descr; + dev_info(&slave_client->adapter->dev, + "%s: +%s\n", MPU_NAME, mldl_cfg->accel->name); + break; + case EXT_SLAVE_TYPE_COMPASS: + if (pdata->compass.get_slave_descr) { + result = -EBUSY; + break; + } + + pdata->compass.address = slave_client->addr; + pdata->compass.irq = slave_client->irq; + pdata->compass.adapt_num = + i2c_adapter_id(slave_client->adapter); + if (pdata->compass.irq > 0) { + dev_info(&slave_client->adapter->dev, + "Installing Compass irq using %d\n", + pdata->compass.irq); + result = slaveirq_init(slave_client->adapter, + &pdata->compass, + "compassirq"); + if (result) + break; + } else { + dev_warn(&slave_client->adapter->dev, + "Compass irq not assigned\n"); + } + + if (slave_descr->init) { + result = slave_descr->init(slave_client->adapter, + slave_descr, + &pdata->compass); + if (result) { + dev_err(&slave_client->adapter->dev, + "Compass init failed %d\n", result); + if (pdata->compass.irq > 0) + slaveirq_exit(&pdata->compass); + break; + } + } + + pdata->compass.get_slave_descr = get_slave_descr; + mldl_cfg->compass = pdata->compass.get_slave_descr(); + dev_info(&slave_client->adapter->dev, + "%s: +%s\n", MPU_NAME, + mldl_cfg->compass->name); + break; + case EXT_SLAVE_TYPE_PRESSURE: + if (pdata->pressure.get_slave_descr) { + result = -EBUSY; + break; + } + + pdata->pressure.address = slave_client->addr; + pdata->pressure.irq = slave_client->irq; + pdata->pressure.adapt_num = + i2c_adapter_id(slave_client->adapter); + if (pdata->pressure.irq > 0) { + dev_info(&slave_client->adapter->dev, + "Installing Pressure irq using %d\n", + pdata->pressure.irq); + result = slaveirq_init(slave_client->adapter, + &pdata->pressure, + "pressureirq"); + if (result) + break; + } else { + dev_warn(&slave_client->adapter->dev, + "Pressure irq not assigned\n"); + } + + if (slave_descr->init) { + result = slave_descr->init(slave_client->adapter, + slave_descr, + &pdata->pressure); + if (result) { + dev_err(&slave_client->adapter->dev, + "Pressure init failed %d\n", result); + if (pdata->pressure.irq > 0) + slaveirq_exit(&pdata->pressure); + break; + } + } + + pdata->pressure.get_slave_descr = get_slave_descr; + mldl_cfg->pressure = pdata->pressure.get_slave_descr(); + dev_info(&slave_client->adapter->dev, + "%s: +%s\n", MPU_NAME, + mldl_cfg->pressure->name); + break; + default: + dev_err(&slave_client->adapter->dev, + "Invalid slave type %d\n", slave_descr->type); + result = -EINVAL; + break; + }; + + mutex_unlock(&mpu->mutex); + return result; +} +EXPORT_SYMBOL(inv_mpu_register_slave); + +void inv_mpu_unregister_slave(struct i2c_client *slave_client, + struct ext_slave_platform_data *slave_pdata, + struct ext_slave_descr *(*get_slave_descr)(void)) +{ + struct mpu_private_data *mpu = mpu_private_data; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct mpu_platform_data *pdata; + struct ext_slave_descr *slave_descr; + int result; + + dev_info(&slave_client->adapter->dev, "%s\n", __func__); + + if (!slave_client || !slave_pdata || !get_slave_descr) + return; + + slave_descr = get_slave_descr(); + if (!slave_descr) + return; + + pdata = mldl_cfg->pdata; + if (!pdata) + return; + + mutex_lock(&mpu->mutex); + + if (slave_descr->exit) { + result = slave_descr->exit(slave_client->adapter, + slave_descr, + slave_pdata); + if (INV_SUCCESS != result) + MPL_LOGE("Accel exit failed %d\n", result); + } + + if (slave_pdata->irq) + slaveirq_exit(slave_pdata); + + switch (slave_descr->type) { + case EXT_SLAVE_TYPE_ACCELEROMETER: + mldl_cfg->accel = NULL; + pdata->accel.get_slave_descr = NULL; + break; + case EXT_SLAVE_TYPE_COMPASS: + mldl_cfg->compass = NULL; + pdata->compass.get_slave_descr = NULL; + break; + case EXT_SLAVE_TYPE_PRESSURE: + mldl_cfg->pressure = NULL; + pdata->pressure.get_slave_descr = NULL; + break; + default: + break; + }; + mpu->slave_modules[slave_descr->type] = NULL; + mutex_unlock(&mpu->mutex); +} +EXPORT_SYMBOL(inv_mpu_unregister_slave); + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static const struct i2c_device_id mpu_id[] = { + {"mpu3050", 0}, + {"mpu6050", 0}, + {"mpu6050_no_accel", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mpu_id); + +int mpu_probe(struct i2c_client *client, const struct i2c_device_id *devid) +{ + struct mpu_platform_data *pdata; + struct mpu_private_data *mpu; + struct mldl_cfg *mldl_cfg; + int res = 0; + struct i2c_adapter *accel_adapter = NULL; + struct i2c_adapter *compass_adapter = NULL; + struct i2c_adapter *pressure_adapter = NULL; + int ii = 0; + + dev_info(&client->adapter->dev, "%s: %d\n", __func__, ii++); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + res = -ENODEV; + goto out_check_functionality_failed; + } + + mpu = kzalloc(sizeof(struct mpu_private_data), GFP_KERNEL); + if (!mpu) { + res = -ENOMEM; + goto out_alloc_data_failed; + } + mpu_private_data = mpu; + i2c_set_clientdata(client, mpu); + mpu->client = client; + mldl_cfg = &mpu->mldl_cfg; + + init_waitqueue_head(&mpu->mpu_event_wait); + + mutex_init(&mpu->mutex); + init_completion(&mpu->completion); + + mpu->response_timeout = 60; /* Seconds */ + mpu->timeout.function = mpu_pm_timeout; + mpu->timeout.data = (u_long) mpu; + init_timer(&mpu->timeout); + + mpu->nb.notifier_call = mpu_pm_notifier_callback; + mpu->nb.priority = 0; + register_pm_notifier(&mpu->nb); + + pdata = (struct mpu_platform_data *)client->dev.platform_data; + if (!pdata) { + dev_WARN(&client->adapter->dev, + "Missing platform data for mpu\n"); + } + mldl_cfg->pdata = pdata; + + mldl_cfg->addr = client->addr; + res = inv_mpu_open(&mpu->mldl_cfg, client->adapter, + accel_adapter, compass_adapter, pressure_adapter); + + if (res) { + dev_err(&client->adapter->dev, + "Unable to open %s %d\n", MPU_NAME, res); + res = -ENODEV; + goto out_whoami_failed; + } + + mpu->dev.minor = MISC_DYNAMIC_MINOR; + mpu->dev.name = "mpu"; /* Same for both 3050 and 6000 */ + mpu->dev.fops = &mpu_fops; + res = misc_register(&mpu->dev); + if (res < 0) { + dev_err(&client->adapter->dev, + "ERROR: misc_register returned %d\n", res); + goto out_misc_register_failed; + } + + if (client->irq) { + dev_info(&client->adapter->dev, + "Installing irq using %d\n", client->irq); + res = mpuirq_init(client, mldl_cfg); + if (res) + goto out_mpuirq_failed; + } else { + dev_WARN(&client->adapter->dev, + "Missing %s IRQ\n", MPU_NAME); + } + + return res; + + out_mpuirq_failed: + misc_deregister(&mpu->dev); + out_misc_register_failed: + inv_mpu_close(&mpu->mldl_cfg, client->adapter, + accel_adapter, compass_adapter, pressure_adapter); + out_whoami_failed: + kfree(mpu); + mpu_private_data = NULL; + out_alloc_data_failed: + out_check_functionality_failed: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, res); + return res; + +} + +static int mpu_remove(struct i2c_client *client) +{ + struct mpu_private_data *mpu = i2c_get_clientdata(client); + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct mpu_platform_data *pdata = mldl_cfg->pdata; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_close(mldl_cfg, client->adapter, + accel_adapter, compass_adapter, pressure_adapter); + + if (client->irq) + mpuirq_exit(); + + if (pdata && pdata->pressure.get_slave_descr && pdata->pressure.irq) { + slaveirq_exit(&pdata->pressure); + pdata->pressure.get_slave_descr = NULL; + } + + if (pdata && pdata->compass.get_slave_descr && pdata->compass.irq) { + slaveirq_exit(&pdata->compass); + pdata->compass.get_slave_descr = NULL; + } + + if (pdata && pdata->accel.get_slave_descr && pdata->accel.irq) { + slaveirq_exit(&pdata->accel); + pdata->accel.get_slave_descr = NULL; + } + + misc_deregister(&mpu->dev); + kfree(mpu); + + return 0; +} + +static struct i2c_driver mpu_driver = { + .class = I2C_CLASS_HWMON, + .probe = mpu_probe, + .remove = mpu_remove, + .id_table = mpu_id, + .driver = { + .owner = THIS_MODULE, + .name = MPU_NAME, + }, + .address_list = normal_i2c, + .shutdown = mpu_shutdown, /* optional */ + .suspend = mpu_dev_suspend, /* optional */ + .resume = mpu_dev_resume, /* optional */ + +}; + +static int __init mpu_init(void) +{ + int res = i2c_add_driver(&mpu_driver); + pr_info("%s: Probe name %s\n", __func__, MPU_NAME); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit mpu_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&mpu_driver); +} + +module_init(mpu_init); +module_exit(mpu_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("User space character device interface for MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS(MPU_NAME); diff --git a/drivers/misc/inv_mpu/mpu-dev.h b/drivers/misc/inv_mpu/mpu-dev.h new file mode 100644 index 0000000..38b3f6f --- /dev/null +++ b/drivers/misc/inv_mpu/mpu-dev.h @@ -0,0 +1,36 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + + +#ifndef __MPU_DEV_H__ +#define __MPU_DEV_H__ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mpu.h> + +int inv_mpu_register_slave(struct module *slave_module, + struct i2c_client *client, + struct ext_slave_platform_data *pdata, + struct ext_slave_descr *(*slave_descr)(void)); + +void inv_mpu_unregister_slave(struct i2c_client *client, + struct ext_slave_platform_data *pdata, + struct ext_slave_descr *(*slave_descr)(void)); +#endif diff --git a/drivers/misc/inv_mpu/mpu3050.h b/drivers/misc/inv_mpu/mpu3050.h new file mode 100644 index 0000000..335f527 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu3050.h @@ -0,0 +1,251 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +#ifndef __MPU_H_ +#error Do not include this file directly. Include mpu.h instead. +#endif + +#ifndef __MPU3050_H_ +#define __MPU3050_H_ + +#include <linux/types.h> + + +#define MPU_NAME "mpu3050" +#define DEFAULT_MPU_SLAVEADDR 0x68 + +/*==== MPU REGISTER SET ====*/ +enum mpu_register { + MPUREG_WHO_AM_I = 0, /* 00 0x00 */ + MPUREG_PRODUCT_ID, /* 01 0x01 */ + MPUREG_02_RSVD, /* 02 0x02 */ + MPUREG_03_RSVD, /* 03 0x03 */ + MPUREG_04_RSVD, /* 04 0x04 */ + MPUREG_XG_OFFS_TC, /* 05 0x05 */ + MPUREG_06_RSVD, /* 06 0x06 */ + MPUREG_07_RSVD, /* 07 0x07 */ + MPUREG_YG_OFFS_TC, /* 08 0x08 */ + MPUREG_09_RSVD, /* 09 0x09 */ + MPUREG_0A_RSVD, /* 10 0x0a */ + MPUREG_ZG_OFFS_TC, /* 11 0x0b */ + MPUREG_X_OFFS_USRH, /* 12 0x0c */ + MPUREG_X_OFFS_USRL, /* 13 0x0d */ + MPUREG_Y_OFFS_USRH, /* 14 0x0e */ + MPUREG_Y_OFFS_USRL, /* 15 0x0f */ + MPUREG_Z_OFFS_USRH, /* 16 0x10 */ + MPUREG_Z_OFFS_USRL, /* 17 0x11 */ + MPUREG_FIFO_EN1, /* 18 0x12 */ + MPUREG_FIFO_EN2, /* 19 0x13 */ + MPUREG_AUX_SLV_ADDR, /* 20 0x14 */ + MPUREG_SMPLRT_DIV, /* 21 0x15 */ + MPUREG_DLPF_FS_SYNC, /* 22 0x16 */ + MPUREG_INT_CFG, /* 23 0x17 */ + MPUREG_ACCEL_BURST_ADDR,/* 24 0x18 */ + MPUREG_19_RSVD, /* 25 0x19 */ + MPUREG_INT_STATUS, /* 26 0x1a */ + MPUREG_TEMP_OUT_H, /* 27 0x1b */ + MPUREG_TEMP_OUT_L, /* 28 0x1c */ + MPUREG_GYRO_XOUT_H, /* 29 0x1d */ + MPUREG_GYRO_XOUT_L, /* 30 0x1e */ + MPUREG_GYRO_YOUT_H, /* 31 0x1f */ + MPUREG_GYRO_YOUT_L, /* 32 0x20 */ + MPUREG_GYRO_ZOUT_H, /* 33 0x21 */ + MPUREG_GYRO_ZOUT_L, /* 34 0x22 */ + MPUREG_23_RSVD, /* 35 0x23 */ + MPUREG_24_RSVD, /* 36 0x24 */ + MPUREG_25_RSVD, /* 37 0x25 */ + MPUREG_26_RSVD, /* 38 0x26 */ + MPUREG_27_RSVD, /* 39 0x27 */ + MPUREG_28_RSVD, /* 40 0x28 */ + MPUREG_29_RSVD, /* 41 0x29 */ + MPUREG_2A_RSVD, /* 42 0x2a */ + MPUREG_2B_RSVD, /* 43 0x2b */ + MPUREG_2C_RSVD, /* 44 0x2c */ + MPUREG_2D_RSVD, /* 45 0x2d */ + MPUREG_2E_RSVD, /* 46 0x2e */ + MPUREG_2F_RSVD, /* 47 0x2f */ + MPUREG_30_RSVD, /* 48 0x30 */ + MPUREG_31_RSVD, /* 49 0x31 */ + MPUREG_32_RSVD, /* 50 0x32 */ + MPUREG_33_RSVD, /* 51 0x33 */ + MPUREG_34_RSVD, /* 52 0x34 */ + MPUREG_DMP_CFG_1, /* 53 0x35 */ + MPUREG_DMP_CFG_2, /* 54 0x36 */ + MPUREG_BANK_SEL, /* 55 0x37 */ + MPUREG_MEM_START_ADDR, /* 56 0x38 */ + MPUREG_MEM_R_W, /* 57 0x39 */ + MPUREG_FIFO_COUNTH, /* 58 0x3a */ + MPUREG_FIFO_COUNTL, /* 59 0x3b */ + MPUREG_FIFO_R_W, /* 60 0x3c */ + MPUREG_USER_CTRL, /* 61 0x3d */ + MPUREG_PWR_MGM, /* 62 0x3e */ + MPUREG_3F_RSVD, /* 63 0x3f */ + NUM_OF_MPU_REGISTERS /* 64 0x40 */ +}; + +/*==== BITS FOR MPU ====*/ + +/*---- MPU 'FIFO_EN1' register (12) ----*/ +#define BIT_TEMP_OUT 0x80 +#define BIT_GYRO_XOUT 0x40 +#define BIT_GYRO_YOUT 0x20 +#define BIT_GYRO_ZOUT 0x10 +#define BIT_ACCEL_XOUT 0x08 +#define BIT_ACCEL_YOUT 0x04 +#define BIT_ACCEL_ZOUT 0x02 +#define BIT_AUX_1OUT 0x01 +/*---- MPU 'FIFO_EN2' register (13) ----*/ +#define BIT_AUX_2OUT 0x02 +#define BIT_AUX_3OUT 0x01 +/*---- MPU 'DLPF_FS_SYNC' register (16) ----*/ +#define BITS_EXT_SYNC_NONE 0x00 +#define BITS_EXT_SYNC_TEMP 0x20 +#define BITS_EXT_SYNC_GYROX 0x40 +#define BITS_EXT_SYNC_GYROY 0x60 +#define BITS_EXT_SYNC_GYROZ 0x80 +#define BITS_EXT_SYNC_ACCELX 0xA0 +#define BITS_EXT_SYNC_ACCELY 0xC0 +#define BITS_EXT_SYNC_ACCELZ 0xE0 +#define BITS_EXT_SYNC_MASK 0xE0 +#define BITS_FS_250DPS 0x00 +#define BITS_FS_500DPS 0x08 +#define BITS_FS_1000DPS 0x10 +#define BITS_FS_2000DPS 0x18 +#define BITS_FS_MASK 0x18 +#define BITS_DLPF_CFG_256HZ_NOLPF2 0x00 +#define BITS_DLPF_CFG_188HZ 0x01 +#define BITS_DLPF_CFG_98HZ 0x02 +#define BITS_DLPF_CFG_42HZ 0x03 +#define BITS_DLPF_CFG_20HZ 0x04 +#define BITS_DLPF_CFG_10HZ 0x05 +#define BITS_DLPF_CFG_5HZ 0x06 +#define BITS_DLPF_CFG_2100HZ_NOLPF 0x07 +#define BITS_DLPF_CFG_MASK 0x07 +/*---- MPU 'INT_CFG' register (17) ----*/ +#define BIT_ACTL 0x80 +#define BIT_ACTL_LOW 0x80 +#define BIT_ACTL_HIGH 0x00 +#define BIT_OPEN 0x40 +#define BIT_OPEN_DRAIN 0x40 +#define BIT_PUSH_PULL 0x00 +#define BIT_LATCH_INT_EN 0x20 +#define BIT_INT_PULSE_WIDTH_50US 0x00 +#define BIT_INT_ANYRD_2CLEAR 0x10 +#define BIT_INT_STAT_READ_2CLEAR 0x00 +#define BIT_MPU_RDY_EN 0x04 +#define BIT_DMP_INT_EN 0x02 +#define BIT_RAW_RDY_EN 0x01 +/*---- MPU 'INT_STATUS' register (1A) ----*/ +#define BIT_INT_STATUS_FIFO_OVERLOW 0x80 +#define BIT_MPU_RDY 0x04 +#define BIT_DMP_INT 0x02 +#define BIT_RAW_RDY 0x01 +/*---- MPU 'BANK_SEL' register (37) ----*/ +#define BIT_PRFTCH_EN 0x20 +#define BIT_CFG_USER_BANK 0x10 +#define BITS_MEM_SEL 0x0f +/*---- MPU 'USER_CTRL' register (3D) ----*/ +#define BIT_DMP_EN 0x80 +#define BIT_FIFO_EN 0x40 +#define BIT_AUX_IF_EN 0x20 +#define BIT_AUX_RD_LENG 0x10 +#define BIT_AUX_IF_RST 0x08 +#define BIT_DMP_RST 0x04 +#define BIT_FIFO_RST 0x02 +#define BIT_GYRO_RST 0x01 +/*---- MPU 'PWR_MGM' register (3E) ----*/ +#define BIT_H_RESET 0x80 +#define BIT_SLEEP 0x40 +#define BIT_STBY_XG 0x20 +#define BIT_STBY_YG 0x10 +#define BIT_STBY_ZG 0x08 +#define BITS_CLKSEL 0x07 + +/*---- MPU Silicon Revision ----*/ +#define MPU_SILICON_REV_A4 1 /* MPU A4 Device */ +#define MPU_SILICON_REV_B1 2 /* MPU B1 Device */ +#define MPU_SILICON_REV_B4 3 /* MPU B4 Device */ +#define MPU_SILICON_REV_B6 4 /* MPU B6 Device */ + +/*---- MPU Memory ----*/ +#define MPU_MEM_BANK_SIZE (256) +#define FIFO_HW_SIZE (512) + +enum MPU_MEMORY_BANKS { + MPU_MEM_RAM_BANK_0 = 0, + MPU_MEM_RAM_BANK_1, + MPU_MEM_RAM_BANK_2, + MPU_MEM_RAM_BANK_3, + MPU_MEM_NUM_RAM_BANKS, + MPU_MEM_OTP_BANK_0 = MPU_MEM_NUM_RAM_BANKS, + /* This one is always last */ + MPU_MEM_NUM_BANKS +}; + +/*---- structure containing control variables used by MLDL ----*/ +/*---- MPU clock source settings ----*/ +/*---- MPU filter selections ----*/ +enum mpu_filter { + MPU_FILTER_256HZ_NOLPF2 = 0, + MPU_FILTER_188HZ, + MPU_FILTER_98HZ, + MPU_FILTER_42HZ, + MPU_FILTER_20HZ, + MPU_FILTER_10HZ, + MPU_FILTER_5HZ, + MPU_FILTER_2100HZ_NOLPF, + NUM_MPU_FILTER +}; + +enum mpu_fullscale { + MPU_FS_250DPS = 0, + MPU_FS_500DPS, + MPU_FS_1000DPS, + MPU_FS_2000DPS, + NUM_MPU_FS +}; + +enum mpu_clock_sel { + MPU_CLK_SEL_INTERNAL = 0, + MPU_CLK_SEL_PLLGYROX, + MPU_CLK_SEL_PLLGYROY, + MPU_CLK_SEL_PLLGYROZ, + MPU_CLK_SEL_PLLEXT32K, + MPU_CLK_SEL_PLLEXT19M, + MPU_CLK_SEL_RESERVED, + MPU_CLK_SEL_STOP, + NUM_CLK_SEL +}; + +enum mpu_ext_sync { + MPU_EXT_SYNC_NONE = 0, + MPU_EXT_SYNC_TEMP, + MPU_EXT_SYNC_GYROX, + MPU_EXT_SYNC_GYROY, + MPU_EXT_SYNC_GYROZ, + MPU_EXT_SYNC_ACCELX, + MPU_EXT_SYNC_ACCELY, + MPU_EXT_SYNC_ACCELZ, + NUM_MPU_EXT_SYNC +}; + +#define DLPF_FS_SYNC_VALUE(ext_sync, full_scale, lpf) \ + ((ext_sync << 5) | (full_scale << 3) | lpf) + +#endif /* __MPU3050_H_ */ diff --git a/drivers/misc/inv_mpu/mpu6050a2.h b/drivers/misc/inv_mpu/mpu6050a2.h new file mode 100644 index 0000000..62f3dc8 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu6050a2.h @@ -0,0 +1,420 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @defgroup + * @brief + * + * @{ + * @file mpu6050.h + * @brief + */ + +#ifndef __MPU_H_ +#error Do not include this file directly. Include mpu.h instead. +#endif + +#ifndef __MPU6050A2_H_ +#define __MPU6050A2_H_ + +#error Invalid or undefined CONFIG_MPU_SENSORS_MPUxxxx + +#define MPU_NAME "mpu6000a2" +#define DEFAULT_MPU_SLAVEADDR (0x68) + +/*==== MPU6050A2 REGISTER SET ====*/ +enum { + MPUREG_XG_OFFS_TC = 0, /* 0x00, 0 */ + MPUREG_YG_OFFS_TC, /* 0x01, 1 */ + MPUREG_ZG_OFFS_TC, /* 0x02, 2 */ + MPUREG_X_FINE_GAIN, /* 0x03, 3 */ + MPUREG_Y_FINE_GAIN, /* 0x04, 4 */ + MPUREG_Z_FINE_GAIN, /* 0x05, 5 */ + MPUREG_XA_OFFS_H, /* 0x06, 6 */ + MPUREG_XA_OFFS_L_TC, /* 0x07, 7 */ + MPUREG_YA_OFFS_H, /* 0x08, 8 */ + MPUREG_YA_OFFS_L_TC, /* 0x09, 9 */ + MPUREG_ZA_OFFS_H, /* 0x0a, 10 */ + MPUREG_ZA_OFFS_L_TC, /* 0x0B, 11 */ + MPUREG_0C_RSVD, /* 0x0c, 12 */ + MPUREG_0D_RSVD, /* 0x0d, 13 */ + MPUREG_0E_RSVD, /* 0x0e, 14 */ + MPUREG_0F_RSVD, /* 0x0f, 15 */ + MPUREG_10_RSVD, /* 0x00, 16 */ + MPUREG_11_RSVD, /* 0x11, 17 */ + MPUREG_12_RSVD, /* 0x12, 18 */ + MPUREG_XG_OFFS_USRH, /* 0x13, 19 */ + MPUREG_XG_OFFS_USRL, /* 0x14, 20 */ + MPUREG_YG_OFFS_USRH, /* 0x15, 21 */ + MPUREG_YG_OFFS_USRL, /* 0x16, 22 */ + MPUREG_ZG_OFFS_USRH, /* 0x17, 23 */ + MPUREG_ZG_OFFS_USRL, /* 0x18, 24 */ + MPUREG_SMPLRT_DIV, /* 0x19, 25 */ + MPUREG_CONFIG, /* 0x1A, 26 */ + MPUREG_GYRO_CONFIG, /* 0x1b, 27 */ + MPUREG_ACCEL_CONFIG, /* 0x1c, 28 */ + MPUREG_ACCEL_FF_THR, /* 0x1d, 29 */ + MPUREG_ACCEL_FF_DUR, /* 0x1e, 30 */ + MPUREG_ACCEL_MOT_THR, /* 0x1f, 31 */ + MPUREG_ACCEL_MOT_DUR, /* 0x20, 32 */ + MPUREG_ACCEL_ZRMOT_THR, /* 0x21, 33 */ + MPUREG_ACCEL_ZRMOT_DUR, /* 0x22, 34 */ + MPUREG_FIFO_EN, /* 0x23, 35 */ + MPUREG_I2C_MST_CTRL, /* 0x24, 36 */ + MPUREG_I2C_SLV0_ADDR, /* 0x25, 37 */ + MPUREG_I2C_SLV0_REG, /* 0x26, 38 */ + MPUREG_I2C_SLV0_CTRL, /* 0x27, 39 */ + MPUREG_I2C_SLV1_ADDR, /* 0x28, 40 */ + MPUREG_I2C_SLV1_REG_PASSWORD, /* 0x29, 41 */ + MPUREG_I2C_SLV1_CTRL, /* 0x2a, 42 */ + MPUREG_I2C_SLV2_ADDR, /* 0x2B, 43 */ + MPUREG_I2C_SLV2_REG, /* 0x2c, 44 */ + MPUREG_I2C_SLV2_CTRL, /* 0x2d, 45 */ + MPUREG_I2C_SLV3_ADDR, /* 0x2E, 46 */ + MPUREG_I2C_SLV3_REG, /* 0x2f, 47 */ + MPUREG_I2C_SLV3_CTRL, /* 0x30, 48 */ + MPUREG_I2C_SLV4_ADDR, /* 0x31, 49 */ + MPUREG_I2C_SLV4_REG, /* 0x32, 50 */ + MPUREG_I2C_SLV4_DO, /* 0x33, 51 */ + MPUREG_I2C_SLV4_CTRL, /* 0x34, 52 */ + MPUREG_I2C_SLV4_DI, /* 0x35, 53 */ + MPUREG_I2C_MST_STATUS, /* 0x36, 54 */ + MPUREG_INT_PIN_CFG, /* 0x37, 55 */ + MPUREG_INT_ENABLE, /* 0x38, 56 */ + MPUREG_DMP_INT_STATUS, /* 0x39, 57 */ + MPUREG_INT_STATUS, /* 0x3A, 58 */ + MPUREG_ACCEL_XOUT_H, /* 0x3B, 59 */ + MPUREG_ACCEL_XOUT_L, /* 0x3c, 60 */ + MPUREG_ACCEL_YOUT_H, /* 0x3d, 61 */ + MPUREG_ACCEL_YOUT_L, /* 0x3e, 62 */ + MPUREG_ACCEL_ZOUT_H, /* 0x3f, 63 */ + MPUREG_ACCEL_ZOUT_L, /* 0x40, 64 */ + MPUREG_TEMP_OUT_H, /* 0x41, 65 */ + MPUREG_TEMP_OUT_L, /* 0x42, 66 */ + MPUREG_GYRO_XOUT_H, /* 0x43, 67 */ + MPUREG_GYRO_XOUT_L, /* 0x44, 68 */ + MPUREG_GYRO_YOUT_H, /* 0x45, 69 */ + MPUREG_GYRO_YOUT_L, /* 0x46, 70 */ + MPUREG_GYRO_ZOUT_H, /* 0x47, 71 */ + MPUREG_GYRO_ZOUT_L, /* 0x48, 72 */ + MPUREG_EXT_SLV_SENS_DATA_00, /* 0x49, 73 */ + MPUREG_EXT_SLV_SENS_DATA_01, /* 0x4a, 74 */ + MPUREG_EXT_SLV_SENS_DATA_02, /* 0x4b, 75 */ + MPUREG_EXT_SLV_SENS_DATA_03, /* 0x4c, 76 */ + MPUREG_EXT_SLV_SENS_DATA_04, /* 0x4d, 77 */ + MPUREG_EXT_SLV_SENS_DATA_05, /* 0x4e, 78 */ + MPUREG_EXT_SLV_SENS_DATA_06, /* 0x4F, 79 */ + MPUREG_EXT_SLV_SENS_DATA_07, /* 0x50, 80 */ + MPUREG_EXT_SLV_SENS_DATA_08, /* 0x51, 81 */ + MPUREG_EXT_SLV_SENS_DATA_09, /* 0x52, 82 */ + MPUREG_EXT_SLV_SENS_DATA_10, /* 0x53, 83 */ + MPUREG_EXT_SLV_SENS_DATA_11, /* 0x54, 84 */ + MPUREG_EXT_SLV_SENS_DATA_12, /* 0x55, 85 */ + MPUREG_EXT_SLV_SENS_DATA_13, /* 0x56, 86 */ + MPUREG_EXT_SLV_SENS_DATA_14, /* 0x57, 87 */ + MPUREG_EXT_SLV_SENS_DATA_15, /* 0x58, 88 */ + MPUREG_EXT_SLV_SENS_DATA_16, /* 0x59, 89 */ + MPUREG_EXT_SLV_SENS_DATA_17, /* 0x5a, 90 */ + MPUREG_EXT_SLV_SENS_DATA_18, /* 0x5B, 91 */ + MPUREG_EXT_SLV_SENS_DATA_19, /* 0x5c, 92 */ + MPUREG_EXT_SLV_SENS_DATA_20, /* 0x5d, 93 */ + MPUREG_EXT_SLV_SENS_DATA_21, /* 0x5e, 94 */ + MPUREG_EXT_SLV_SENS_DATA_22, /* 0x5f, 95 */ + MPUREG_EXT_SLV_SENS_DATA_23, /* 0x60, 96 */ + MPUREG_ACCEL_INTEL_STATUS, /* 0x61, 97 */ + MPUREG_62_RSVD, /* 0x62, 98 */ + MPUREG_63_RSVD, /* 0x63, 99 */ + MPUREG_64_RSVD, /* 0x64, 100 */ + MPUREG_65_RSVD, /* 0x65, 101 */ + MPUREG_66_RSVD, /* 0x66, 102 */ + MPUREG_67_RSVD, /* 0x67, 103 */ + MPUREG_SIGNAL_PATH_RESET, /* 0x68, 104 */ + MPUREG_ACCEL_INTEL_CTRL, /* 0x69, 105 */ + MPUREG_USER_CTRL, /* 0x6A, 106 */ + MPUREG_PWR_MGMT_1, /* 0x6B, 107 */ + MPUREG_PWR_MGMT_2, /* 0x6C, 108 */ + MPUREG_BANK_SEL, /* 0x6D, 109 */ + MPUREG_MEM_START_ADDR, /* 0x6E, 100 */ + MPUREG_MEM_R_W, /* 0x6F, 111 */ + MPUREG_DMP_CFG_1, /* 0x70, 112 */ + MPUREG_DMP_CFG_2, /* 0x71, 113 */ + MPUREG_FIFO_COUNTH, /* 0x72, 114 */ + MPUREG_FIFO_COUNTL, /* 0x73, 115 */ + MPUREG_FIFO_R_W, /* 0x74, 116 */ + MPUREG_WHOAMI, /* 0x75, 117 */ + + NUM_OF_MPU_REGISTERS /* = 0x76, 118 */ +}; + +/*==== MPU6050A2 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 +}; + + +/*==== MPU6050A2 parameters ====*/ + +#define NUM_REGS (NUM_OF_MPU_REGISTERS) +#define START_SENS_REGS (0x3B) +#define NUM_SENS_REGS (0x60 - START_SENS_REGS + 1) + +/*---- MPU Memory ----*/ +#define NUM_BANKS (MPU_MEM_NUM_RAM_BANKS) +#define BANK_SIZE (256) +#define MEM_SIZE (NUM_BANKS * BANK_SIZE) +#define MPU_MEM_BANK_SIZE (BANK_SIZE) /*alternative name */ + +#define FIFO_HW_SIZE (1024) + +#define NUM_EXT_SLAVES (4) + + +/*==== BITS FOR MPU6050A2 ====*/ + +/*---- MPU6050A2 'XG_OFFS_TC' register (0, 1, 2) ----*/ +#define BIT_PWR_MODE 0x80 +#define BITS_XG_OFFS_TC 0x7E +#define BIT_OTP_BNK_VLD 0x01 + +#define BITS_YG_OFFS_TC 0x7E +#define BITS_ZG_OFFS_TC 0x7E +/*---- MPU6050A2 'FIFO_EN' register (23) ----*/ +#define BIT_TEMP_OUT 0x80 +#define BIT_GYRO_XOUT 0x40 +#define BIT_GYRO_YOUT 0x20 +#define BIT_GYRO_ZOUT 0x10 +#define BIT_ACCEL 0x08 +#define BIT_SLV_2 0x04 +#define BIT_SLV_1 0x02 +#define BIT_SLV_0 0x01 +/*---- MPU6050A2 'CONFIG' register (1A) ----*/ +/*NONE 0xC0 */ +#define BITS_EXT_SYNC_SET 0x38 +#define BITS_DLPF_CFG 0x07 +/*---- MPU6050A2 'GYRO_CONFIG' register (1B) ----*/ +/* voluntarily modified label from BITS_FS_SEL to + * BITS_GYRO_FS_SEL to avoid confusion with MPU + */ +#define BITS_GYRO_FS_SEL 0x18 +/*NONE 0x07 */ +/*---- MPU6050A2 'ACCEL_CONFIG' register (1C) ----*/ +#define BITS_ACCEL_FS_SEL 0x18 +#define BITS_ACCEL_HPF 0x07 +/*---- MPU6050A2 'I2C_MST_CTRL' register (24) ----*/ +#define BIT_MULT_MST_DIS 0x80 +#define BIT_WAIT_FOR_ES 0x40 +#define BIT_I2C_MST_VDDIO 0x20 +/*NONE 0x10 */ +#define BITS_I2C_MST_CLK 0x0F +/*---- MPU6050A2 'I2C_SLV?_CTRL' register (27,2A,2D,30) ----*/ +#define BIT_SLV_ENABLE 0x80 +#define BIT_SLV_BYTE_SW 0x40 +/*NONE 0x20 */ +#define BIT_SLV_GRP 0x10 +#define BITS_SLV_LENG 0x0F +/*---- MPU6050A2 'I2C_SLV4_ADDR' register (31) ----*/ +#define BIT_I2C_SLV4_RNW 0x80 +/*---- MPU6050A2 'I2C_SLV4_CTRL' register (34) ----*/ +#define BIT_I2C_SLV4_EN 0x80 +#define BIT_SLV4_DONE_INT_EN 0x40 +/*NONE 0x3F */ +/*---- MPU6050A2 'I2C_MST_STATUS' register (36) ----*/ +#define BIT_PASSTHROUGH 0x80 +#define BIT_I2C_SLV4_DONE 0x40 +#define BIT_I2C_LOST_ARB 0x20 +#define BIT_I2C_SLV4_NACK 0x10 +#define BIT_I2C_SLV3_NACK 0x08 +#define BIT_I2C_SLV2_NACK 0x04 +#define BIT_I2C_SLV1_NACK 0x02 +#define BIT_I2C_SLV0_NACK 0x01 +/*---- MPU6050A2 'INT_PIN_CFG' register (37) ----*/ +#define BIT_ACTL 0x80 +#define BIT_ACTL_LOW 0x80 +#define BIT_ACTL_HIGH 0x00 +#define BIT_OPEN 0x40 +#define BIT_LATCH_INT_EN 0x20 +#define BIT_INT_ANYRD_2CLEAR 0x10 +#define BIT_ACTL_FSYNC 0x08 +#define BIT_FSYNC_INT_EN 0x04 +#define BIT_BYPASS_EN 0x02 +#define BIT_CLKOUT_EN 0x01 +/*---- MPU6050A2 'INT_ENABLE' register (38) ----*/ +#define BIT_FF_EN 0x80 +#define BIT_MOT_EN 0x40 +#define BIT_ZMOT_EN 0x20 +#define BIT_FIFO_OVERFLOW_EN 0x10 +#define BIT_I2C_MST_INT_EN 0x08 +#define BIT_PLL_RDY_EN 0x04 +#define BIT_DMP_INT_EN 0x02 +#define BIT_RAW_RDY_EN 0x01 +/*---- MPU6050A2 'DMP_INT_STATUS' register (39) ----*/ +/*NONE 0x80 */ +/*NONE 0x40 */ +#define BIT_DMP_INT_5 0x20 +#define BIT_DMP_INT_4 0x10 +#define BIT_DMP_INT_3 0x08 +#define BIT_DMP_INT_2 0x04 +#define BIT_DMP_INT_1 0x02 +#define BIT_DMP_INT_0 0x01 +/*---- MPU6050A2 'INT_STATUS' register (3A) ----*/ +#define BIT_FF_INT 0x80 +#define BIT_MOT_INT 0x40 +#define BIT_ZMOT_INT 0x20 +#define BIT_FIFO_OVERFLOW_INT 0x10 +#define BIT_I2C_MST_INT 0x08 +#define BIT_PLL_RDY_INT 0x04 +#define BIT_DMP_INT 0x02 +#define BIT_RAW_DATA_RDY_INT 0x01 +/*---- MPU6050A2 'BANK_SEL' register (6D) ----*/ +#define BIT_PRFTCH_EN 0x40 +#define BIT_CFG_USER_BANK 0x20 +#define BITS_MEM_SEL 0x1f +/*---- MPU6050A2 'USER_CTRL' register (6A) ----*/ +#define BIT_DMP_EN 0x80 +#define BIT_FIFO_EN 0x40 +#define BIT_I2C_MST_EN 0x20 +#define BIT_I2C_IF_DIS 0x10 +#define BIT_DMP_RST 0x08 +#define BIT_FIFO_RST 0x04 +#define BIT_I2C_MST_RST 0x02 +#define BIT_SIG_COND_RST 0x01 +/*---- MPU6050A2 'PWR_MGMT_1' register (6B) ----*/ +#define BIT_H_RESET 0x80 +#define BITS_PWRSEL 0x70 +#define BIT_WKUP_INT 0x08 +#define BITS_CLKSEL 0x07 +/*---- MPU6050A2 'PWR_MGMT_2' register (6C) ----*/ +#define BITS_LPA_WAKE_CTRL 0xC0 +#define BIT_STBY_XA 0x20 +#define BIT_STBY_YA 0x10 +#define BIT_STBY_ZA 0x08 +#define BIT_STBY_XG 0x04 +#define BIT_STBY_YG 0x02 +#define BIT_STBY_ZG 0x01 + +#define ACCEL_MOT_THR_LSB (32) /* mg */ +#define ACCEL_MOT_DUR_LSB (1) +#define ACCEL_ZRMOT_THR_LSB_CONVERSION(mg) ((mg * 1000) / 255) +#define ACCEL_ZRMOT_DUR_LSB (64) + +/*----------------------------------------------------------------------------*/ +/*---- Alternative names to take care of conflicts with current mpu3050.h ----*/ +/*----------------------------------------------------------------------------*/ + +/*-- registers --*/ +#define MPUREG_DLPF_FS_SYNC MPUREG_CONFIG /* 0x1A */ + +#define MPUREG_PRODUCT_ID MPUREG_WHOAMI /* 0x75 HACK!*/ +#define MPUREG_PWR_MGM MPUREG_PWR_MGMT_1 /* 0x6B */ +#define MPUREG_FIFO_EN1 MPUREG_FIFO_EN /* 0x23 */ +#define MPUREG_INT_CFG MPUREG_INT_ENABLE /* 0x38 */ +#define MPUREG_X_OFFS_USRH MPUREG_XG_OFFS_USRH /* 0x13 */ +#define MPUREG_WHO_AM_I MPUREG_WHOAMI /* 0x75 */ +#define MPUREG_23_RSVD MPUREG_EXT_SLV_SENS_DATA_00 /* 0x49 */ +#define MPUREG_AUX_SLV_ADDR MPUREG_I2C_SLV0_ADDR /* 0x25 */ +#define MPUREG_ACCEL_BURST_ADDR MPUREG_I2C_SLV0_REG /* 0x26 */ + +/*-- bits --*/ +/* 'USER_CTRL' register */ +#define BIT_AUX_IF_EN BIT_I2C_MST_EN +#define BIT_AUX_RD_LENG BIT_I2C_MST_EN +#define BIT_IME_IF_RST BIT_I2C_MST_RST +#define BIT_GYRO_RST BIT_SIG_COND_RST +/* 'INT_ENABLE' register */ +#define BIT_RAW_RDY BIT_RAW_DATA_RDY_INT +#define BIT_MPU_RDY_EN BIT_PLL_RDY_EN +/* 'INT_STATUS' register */ +#define BIT_INT_STATUS_FIFO_OVERLOW BIT_FIFO_OVERFLOW_INT + + + +/*---- MPU6050A2 Silicon Revisions ----*/ +#define MPU_SILICON_REV_A2 1 /* MPU6050A2 Device */ +#define MPU_SILICON_REV_B1 2 /* MPU6050A2 Device */ + +/*---- structure containing control variables used by MLDL ----*/ +/*---- MPU clock source settings ----*/ +/*---- MPU filter selections ----*/ +enum mpu_filter { + MPU_FILTER_256HZ_NOLPF2 = 0, + MPU_FILTER_188HZ, + MPU_FILTER_98HZ, + MPU_FILTER_42HZ, + MPU_FILTER_20HZ, + MPU_FILTER_10HZ, + MPU_FILTER_5HZ, + MPU_FILTER_2100HZ_NOLPF, + NUM_MPU_FILTER +}; + +enum mpu_fullscale { + MPU_FS_250DPS = 0, + MPU_FS_500DPS, + MPU_FS_1000DPS, + MPU_FS_2000DPS, + NUM_MPU_FS +}; + +enum mpu_clock_sel { + MPU_CLK_SEL_INTERNAL = 0, + MPU_CLK_SEL_PLLGYROX, + MPU_CLK_SEL_PLLGYROY, + MPU_CLK_SEL_PLLGYROZ, + MPU_CLK_SEL_PLLEXT32K, + MPU_CLK_SEL_PLLEXT19M, + MPU_CLK_SEL_RESERVED, + MPU_CLK_SEL_STOP, + NUM_CLK_SEL +}; + +enum mpu_ext_sync { + MPU_EXT_SYNC_NONE = 0, + MPU_EXT_SYNC_TEMP, + MPU_EXT_SYNC_GYROX, + MPU_EXT_SYNC_GYROY, + MPU_EXT_SYNC_GYROZ, + MPU_EXT_SYNC_ACCELX, + MPU_EXT_SYNC_ACCELY, + MPU_EXT_SYNC_ACCELZ, + NUM_MPU_EXT_SYNC +}; + +#define MPUREG_CONFIG_VALUE(ext_sync, lpf) \ + ((ext_sync << 3) | lpf) + +#define MPUREG_GYRO_CONFIG_VALUE(x_st, y_st, z_st, full_scale) \ + ((x_st ? 0x80 : 0) | \ + (y_st ? 0x70 : 0) | \ + (z_st ? 0x60 : 0) | \ + (full_scale << 3)) + +#endif /* __MPU6050_H_ */ diff --git a/drivers/misc/inv_mpu/mpu6050b1.h b/drivers/misc/inv_mpu/mpu6050b1.h new file mode 100644 index 0000000..4acc3be --- /dev/null +++ b/drivers/misc/inv_mpu/mpu6050b1.h @@ -0,0 +1,432 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @defgroup + * @brief + * + * @{ + * @file mpu6050.h + * @brief + */ + +#ifndef __MPU_H_ +#error Do not include this file directly. Include mpu.h instead. +#endif + +#ifndef __MPU6050B1_H_ +#define __MPU6050B1_H_ + +#error Invalid or undefined CONFIG_MPU_SENSORS_MPUxxxx + +#define MPU_NAME "mpu6050B1" +#define DEFAULT_MPU_SLAVEADDR 0x68 + +/*==== MPU6050B1 REGISTER SET ====*/ +enum { + MPUREG_XG_OFFS_TC = 0, /* 0x00, 0 */ + MPUREG_YG_OFFS_TC, /* 0x01, 1 */ + MPUREG_ZG_OFFS_TC, /* 0x02, 2 */ + MPUREG_X_FINE_GAIN, /* 0x03, 3 */ + MPUREG_Y_FINE_GAIN, /* 0x04, 4 */ + MPUREG_Z_FINE_GAIN, /* 0x05, 5 */ + MPUREG_XA_OFFS_H, /* 0x06, 6 */ + MPUREG_XA_OFFS_L, /* 0x07, 7 */ + MPUREG_YA_OFFS_H, /* 0x08, 8 */ + MPUREG_YA_OFFS_L, /* 0x09, 9 */ + MPUREG_ZA_OFFS_H, /* 0x0a, 10 */ + MPUREG_ZA_OFFS_L, /* 0x0B, 11 */ + MPUREG_PRODUCT_ID, /* 0x0c, 12 */ + MPUREG_0D_RSVD, /* 0x0d, 13 */ + MPUREG_0E_RSVD, /* 0x0e, 14 */ + MPUREG_0F_RSVD, /* 0x0f, 15 */ + MPUREG_10_RSVD, /* 0x00, 16 */ + MPUREG_11_RSVD, /* 0x11, 17 */ + MPUREG_12_RSVD, /* 0x12, 18 */ + MPUREG_XG_OFFS_USRH, /* 0x13, 19 */ + MPUREG_XG_OFFS_USRL, /* 0x14, 20 */ + MPUREG_YG_OFFS_USRH, /* 0x15, 21 */ + MPUREG_YG_OFFS_USRL, /* 0x16, 22 */ + MPUREG_ZG_OFFS_USRH, /* 0x17, 23 */ + MPUREG_ZG_OFFS_USRL, /* 0x18, 24 */ + MPUREG_SMPLRT_DIV, /* 0x19, 25 */ + MPUREG_CONFIG, /* 0x1A, 26 */ + MPUREG_GYRO_CONFIG, /* 0x1b, 27 */ + MPUREG_ACCEL_CONFIG, /* 0x1c, 28 */ + MPUREG_ACCEL_FF_THR, /* 0x1d, 29 */ + MPUREG_ACCEL_FF_DUR, /* 0x1e, 30 */ + MPUREG_ACCEL_MOT_THR, /* 0x1f, 31 */ + MPUREG_ACCEL_MOT_DUR, /* 0x20, 32 */ + MPUREG_ACCEL_ZRMOT_THR, /* 0x21, 33 */ + MPUREG_ACCEL_ZRMOT_DUR, /* 0x22, 34 */ + MPUREG_FIFO_EN, /* 0x23, 35 */ + MPUREG_I2C_MST_CTRL, /* 0x24, 36 */ + MPUREG_I2C_SLV0_ADDR, /* 0x25, 37 */ + MPUREG_I2C_SLV0_REG, /* 0x26, 38 */ + MPUREG_I2C_SLV0_CTRL, /* 0x27, 39 */ + MPUREG_I2C_SLV1_ADDR, /* 0x28, 40 */ + MPUREG_I2C_SLV1_REG, /* 0x29, 41 */ + MPUREG_I2C_SLV1_CTRL, /* 0x2a, 42 */ + MPUREG_I2C_SLV2_ADDR, /* 0x2B, 43 */ + MPUREG_I2C_SLV2_REG, /* 0x2c, 44 */ + MPUREG_I2C_SLV2_CTRL, /* 0x2d, 45 */ + MPUREG_I2C_SLV3_ADDR, /* 0x2E, 46 */ + MPUREG_I2C_SLV3_REG, /* 0x2f, 47 */ + MPUREG_I2C_SLV3_CTRL, /* 0x30, 48 */ + MPUREG_I2C_SLV4_ADDR, /* 0x31, 49 */ + MPUREG_I2C_SLV4_REG, /* 0x32, 50 */ + MPUREG_I2C_SLV4_DO, /* 0x33, 51 */ + MPUREG_I2C_SLV4_CTRL, /* 0x34, 52 */ + MPUREG_I2C_SLV4_DI, /* 0x35, 53 */ + MPUREG_I2C_MST_STATUS, /* 0x36, 54 */ + MPUREG_INT_PIN_CFG, /* 0x37, 55 */ + MPUREG_INT_ENABLE, /* 0x38, 56 */ + MPUREG_DMP_INT_STATUS, /* 0x39, 57 */ + MPUREG_INT_STATUS, /* 0x3A, 58 */ + MPUREG_ACCEL_XOUT_H, /* 0x3B, 59 */ + MPUREG_ACCEL_XOUT_L, /* 0x3c, 60 */ + MPUREG_ACCEL_YOUT_H, /* 0x3d, 61 */ + MPUREG_ACCEL_YOUT_L, /* 0x3e, 62 */ + MPUREG_ACCEL_ZOUT_H, /* 0x3f, 63 */ + MPUREG_ACCEL_ZOUT_L, /* 0x40, 64 */ + MPUREG_TEMP_OUT_H, /* 0x41, 65 */ + MPUREG_TEMP_OUT_L, /* 0x42, 66 */ + MPUREG_GYRO_XOUT_H, /* 0x43, 67 */ + MPUREG_GYRO_XOUT_L, /* 0x44, 68 */ + MPUREG_GYRO_YOUT_H, /* 0x45, 69 */ + MPUREG_GYRO_YOUT_L, /* 0x46, 70 */ + MPUREG_GYRO_ZOUT_H, /* 0x47, 71 */ + MPUREG_GYRO_ZOUT_L, /* 0x48, 72 */ + MPUREG_EXT_SLV_SENS_DATA_00, /* 0x49, 73 */ + MPUREG_EXT_SLV_SENS_DATA_01, /* 0x4a, 74 */ + MPUREG_EXT_SLV_SENS_DATA_02, /* 0x4b, 75 */ + MPUREG_EXT_SLV_SENS_DATA_03, /* 0x4c, 76 */ + MPUREG_EXT_SLV_SENS_DATA_04, /* 0x4d, 77 */ + MPUREG_EXT_SLV_SENS_DATA_05, /* 0x4e, 78 */ + MPUREG_EXT_SLV_SENS_DATA_06, /* 0x4F, 79 */ + MPUREG_EXT_SLV_SENS_DATA_07, /* 0x50, 80 */ + MPUREG_EXT_SLV_SENS_DATA_08, /* 0x51, 81 */ + MPUREG_EXT_SLV_SENS_DATA_09, /* 0x52, 82 */ + MPUREG_EXT_SLV_SENS_DATA_10, /* 0x53, 83 */ + MPUREG_EXT_SLV_SENS_DATA_11, /* 0x54, 84 */ + MPUREG_EXT_SLV_SENS_DATA_12, /* 0x55, 85 */ + MPUREG_EXT_SLV_SENS_DATA_13, /* 0x56, 86 */ + MPUREG_EXT_SLV_SENS_DATA_14, /* 0x57, 87 */ + MPUREG_EXT_SLV_SENS_DATA_15, /* 0x58, 88 */ + MPUREG_EXT_SLV_SENS_DATA_16, /* 0x59, 89 */ + MPUREG_EXT_SLV_SENS_DATA_17, /* 0x5a, 90 */ + MPUREG_EXT_SLV_SENS_DATA_18, /* 0x5B, 91 */ + MPUREG_EXT_SLV_SENS_DATA_19, /* 0x5c, 92 */ + MPUREG_EXT_SLV_SENS_DATA_20, /* 0x5d, 93 */ + MPUREG_EXT_SLV_SENS_DATA_21, /* 0x5e, 94 */ + MPUREG_EXT_SLV_SENS_DATA_22, /* 0x5f, 95 */ + MPUREG_EXT_SLV_SENS_DATA_23, /* 0x60, 96 */ + MPUREG_ACCEL_INTEL_STATUS, /* 0x61, 97 */ + MPUREG_62_RSVD, /* 0x62, 98 */ + MPUREG_I2C_SLV0_DO, /* 0x63, 99 */ + MPUREG_I2C_SLV1_DO, /* 0x64, 100 */ + MPUREG_I2C_SLV2_DO, /* 0x65, 101 */ + MPUREG_I2C_SLV3_DO, /* 0x66, 102 */ + MPUREG_I2C_MST_DELAY_CTRL, /* 0x67, 103 */ + MPUREG_SIGNAL_PATH_RESET, /* 0x68, 104 */ + MPUREG_ACCEL_INTEL_CTRL, /* 0x69, 105 */ + MPUREG_USER_CTRL, /* 0x6A, 106 */ + MPUREG_PWR_MGMT_1, /* 0x6B, 107 */ + MPUREG_PWR_MGMT_2, /* 0x6C, 108 */ + MPUREG_BANK_SEL, /* 0x6D, 109 */ + MPUREG_MEM_START_ADDR, /* 0x6E, 100 */ + MPUREG_MEM_R_W, /* 0x6F, 111 */ + MPUREG_DMP_CFG_1, /* 0x70, 112 */ + MPUREG_DMP_CFG_2, /* 0x71, 113 */ + MPUREG_FIFO_COUNTH, /* 0x72, 114 */ + MPUREG_FIFO_COUNTL, /* 0x73, 115 */ + MPUREG_FIFO_R_W, /* 0x74, 116 */ + MPUREG_WHOAMI, /* 0x75, 117 */ + + NUM_OF_MPU_REGISTERS /* = 0x76, 118 */ +}; + +/*==== 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 +}; + + +/*==== MPU6050B1 parameters ====*/ + +#define NUM_REGS (NUM_OF_MPU_REGISTERS) +#define START_SENS_REGS (0x3B) +#define NUM_SENS_REGS (0x60 - START_SENS_REGS + 1) + +/*---- MPU Memory ----*/ +#define NUM_BANKS (MPU_MEM_NUM_RAM_BANKS) +#define BANK_SIZE (256) +#define MEM_SIZE (NUM_BANKS * BANK_SIZE) +#define MPU_MEM_BANK_SIZE (BANK_SIZE) /*alternative name */ + +#define FIFO_HW_SIZE (1024) + +#define NUM_EXT_SLAVES (4) + + +/*==== BITS FOR MPU6050B1 ====*/ +/*---- MPU6050B1 'XG_OFFS_TC' register (0, 1, 2) ----*/ +#define BIT_PU_SLEEP_MODE 0x80 +#define BITS_XG_OFFS_TC 0x7E +#define BIT_OTP_BNK_VLD 0x01 + +#define BIT_I2C_MST_VDDIO 0x80 +#define BITS_YG_OFFS_TC 0x7E +#define BITS_ZG_OFFS_TC 0x7E +/*---- MPU6050B1 'FIFO_EN' register (23) ----*/ +#define BIT_TEMP_OUT 0x80 +#define BIT_GYRO_XOUT 0x40 +#define BIT_GYRO_YOUT 0x20 +#define BIT_GYRO_ZOUT 0x10 +#define BIT_ACCEL 0x08 +#define BIT_SLV_2 0x04 +#define BIT_SLV_1 0x02 +#define BIT_SLV_0 0x01 +/*---- MPU6050B1 'CONFIG' register (1A) ----*/ +/*NONE 0xC0 */ +#define BITS_EXT_SYNC_SET 0x38 +#define BITS_DLPF_CFG 0x07 +/*---- MPU6050B1 'GYRO_CONFIG' register (1B) ----*/ +/* voluntarily modified label from BITS_FS_SEL to + * BITS_GYRO_FS_SEL to avoid confusion with MPU + */ +#define BITS_GYRO_FS_SEL 0x18 +/*NONE 0x07 */ +/*---- MPU6050B1 'ACCEL_CONFIG' register (1C) ----*/ +#define BITS_ACCEL_FS_SEL 0x18 +#define BITS_ACCEL_HPF 0x07 +/*---- MPU6050B1 'I2C_MST_CTRL' register (24) ----*/ +#define BIT_MULT_MST_EN 0x80 +#define BIT_WAIT_FOR_ES 0x40 +#define BIT_SLV_3_FIFO_EN 0x20 +#define BIT_I2C_MST_PSR 0x10 +#define BITS_I2C_MST_CLK 0x0F +/*---- MPU6050B1 'I2C_SLV?_ADDR' register (27,2A,2D,30) ----*/ +#define BIT_I2C_READ 0x80 +#define BIT_I2C_WRITE 0x00 +#define BITS_I2C_ADDR 0x7F +/*---- MPU6050B1 'I2C_SLV?_CTRL' register (27,2A,2D,30) ----*/ +#define BIT_SLV_ENABLE 0x80 +#define BIT_SLV_BYTE_SW 0x40 +#define BIT_SLV_REG_DIS 0x20 +#define BIT_SLV_GRP 0x10 +#define BITS_SLV_LENG 0x0F +/*---- MPU6050B1 'I2C_SLV4_ADDR' register (31) ----*/ +#define BIT_I2C_SLV4_RNW 0x80 +/*---- MPU6050B1 'I2C_SLV4_CTRL' register (34) ----*/ +#define BIT_I2C_SLV4_EN 0x80 +#define BIT_SLV4_DONE_INT_EN 0x40 +#define BIT_SLV4_REG_DIS 0x20 +#define MASK_I2C_MST_DLY 0x1F +/*---- MPU6050B1 'I2C_MST_STATUS' register (36) ----*/ +#define BIT_PASS_THROUGH 0x80 +#define BIT_I2C_SLV4_DONE 0x40 +#define BIT_I2C_LOST_ARB 0x20 +#define BIT_I2C_SLV4_NACK 0x10 +#define BIT_I2C_SLV3_NACK 0x08 +#define BIT_I2C_SLV2_NACK 0x04 +#define BIT_I2C_SLV1_NACK 0x02 +#define BIT_I2C_SLV0_NACK 0x01 +/*---- MPU6050B1 'INT_PIN_CFG' register (37) ----*/ +#define BIT_ACTL 0x80 +#define BIT_ACTL_LOW 0x80 +#define BIT_ACTL_HIGH 0x00 +#define BIT_OPEN 0x40 +#define BIT_LATCH_INT_EN 0x20 +#define BIT_INT_ANYRD_2CLEAR 0x10 +#define BIT_ACTL_FSYNC 0x08 +#define BIT_FSYNC_INT_EN 0x04 +#define BIT_BYPASS_EN 0x02 +#define BIT_CLKOUT_EN 0x01 +/*---- MPU6050B1 'INT_ENABLE' register (38) ----*/ +#define BIT_FF_EN 0x80 +#define BIT_MOT_EN 0x40 +#define BIT_ZMOT_EN 0x20 +#define BIT_FIFO_OVERFLOW_EN 0x10 +#define BIT_I2C_MST_INT_EN 0x08 +#define BIT_PLL_RDY_EN 0x04 +#define BIT_DMP_INT_EN 0x02 +#define BIT_RAW_RDY_EN 0x01 +/*---- MPU6050B1 'DMP_INT_STATUS' register (39) ----*/ +/*NONE 0x80 */ +/*NONE 0x40 */ +#define BIT_DMP_INT_5 0x20 +#define BIT_DMP_INT_4 0x10 +#define BIT_DMP_INT_3 0x08 +#define BIT_DMP_INT_2 0x04 +#define BIT_DMP_INT_1 0x02 +#define BIT_DMP_INT_0 0x01 +/*---- MPU6050B1 'INT_STATUS' register (3A) ----*/ +#define BIT_FF_INT 0x80 +#define BIT_MOT_INT 0x40 +#define BIT_ZMOT_INT 0x20 +#define BIT_FIFO_OVERFLOW_INT 0x10 +#define BIT_I2C_MST_INT 0x08 +#define BIT_PLL_RDY_INT 0x04 +#define BIT_DMP_INT 0x02 +#define BIT_RAW_DATA_RDY_INT 0x01 +/*---- MPU6050B1 'MPUREG_I2C_MST_DELAY_CTRL' register (0x67) ----*/ +#define BIT_DELAY_ES_SHADOW 0x80 +#define BIT_SLV4_DLY_EN 0x10 +#define BIT_SLV3_DLY_EN 0x08 +#define BIT_SLV2_DLY_EN 0x04 +#define BIT_SLV1_DLY_EN 0x02 +#define BIT_SLV0_DLY_EN 0x01 +/*---- MPU6050B1 'BANK_SEL' register (6D) ----*/ +#define BIT_PRFTCH_EN 0x40 +#define BIT_CFG_USER_BANK 0x20 +#define BITS_MEM_SEL 0x1f +/*---- MPU6050B1 'USER_CTRL' register (6A) ----*/ +#define BIT_DMP_EN 0x80 +#define BIT_FIFO_EN 0x40 +#define BIT_I2C_MST_EN 0x20 +#define BIT_I2C_IF_DIS 0x10 +#define BIT_DMP_RST 0x08 +#define BIT_FIFO_RST 0x04 +#define BIT_I2C_MST_RST 0x02 +#define BIT_SIG_COND_RST 0x01 +/*---- MPU6050B1 'PWR_MGMT_1' register (6B) ----*/ +#define BIT_H_RESET 0x80 +#define BIT_SLEEP 0x40 +#define BIT_CYCLE 0x20 +#define BIT_PD_PTAT 0x08 +#define BITS_CLKSEL 0x07 +/*---- MPU6050B1 'PWR_MGMT_2' register (6C) ----*/ +#define BITS_LPA_WAKE_CTRL 0xC0 +#define BIT_STBY_XA 0x20 +#define BIT_STBY_YA 0x10 +#define BIT_STBY_ZA 0x08 +#define BIT_STBY_XG 0x04 +#define BIT_STBY_YG 0x02 +#define BIT_STBY_ZG 0x01 + +#define ACCEL_MOT_THR_LSB (32) /* mg */ +#define ACCEL_MOT_DUR_LSB (1) +#define ACCEL_ZRMOT_THR_LSB_CONVERSION(mg) ((mg * 1000) / 255) +#define ACCEL_ZRMOT_DUR_LSB (64) + +/*----------------------------------------------------------------------------*/ +/*---- Alternative names to take care of conflicts with current mpu3050.h ----*/ +/*----------------------------------------------------------------------------*/ + +/*-- registers --*/ +#define MPUREG_DLPF_FS_SYNC MPUREG_CONFIG /* 0x1A */ + +#define MPUREG_PWR_MGM MPUREG_PWR_MGMT_1 /* 0x6B */ +#define MPUREG_FIFO_EN1 MPUREG_FIFO_EN /* 0x23 */ +#define MPUREG_INT_CFG MPUREG_INT_ENABLE /* 0x38 */ +#define MPUREG_X_OFFS_USRH MPUREG_XG_OFFS_USRH /* 0x13 */ +#define MPUREG_WHO_AM_I MPUREG_WHOAMI /* 0x75 */ +#define MPUREG_23_RSVD MPUREG_EXT_SLV_SENS_DATA_00 /* 0x49 */ + +/*-- bits --*/ +/* 'USER_CTRL' register */ +#define BIT_AUX_IF_EN BIT_I2C_MST_EN +#define BIT_AUX_RD_LENG BIT_I2C_MST_EN +#define BIT_IME_IF_RST BIT_I2C_MST_RST +#define BIT_GYRO_RST BIT_SIG_COND_RST +/* 'INT_ENABLE' register */ +#define BIT_RAW_RDY BIT_RAW_DATA_RDY_INT +#define BIT_MPU_RDY_EN BIT_PLL_RDY_EN +/* 'INT_STATUS' register */ +#define BIT_INT_STATUS_FIFO_OVERLOW BIT_FIFO_OVERFLOW_INT + +/*---- MPU6050 Silicon Revisions ----*/ +#define MPU_SILICON_REV_A2 1 /* MPU6050A2 Device */ +#define MPU_SILICON_REV_B1 2 /* MPU6050B1 Device */ + +/*---- MPU6050 notable product revisions ----*/ +#define MPU_PRODUCT_KEY_B1_E1_5 105 +#define MPU_PRODUCT_KEY_B2_F1 431 + +/*---- structure containing control variables used by MLDL ----*/ +/*---- MPU clock source settings ----*/ +/*---- MPU filter selections ----*/ +enum mpu_filter { + MPU_FILTER_256HZ_NOLPF2 = 0, + MPU_FILTER_188HZ, + MPU_FILTER_98HZ, + MPU_FILTER_42HZ, + MPU_FILTER_20HZ, + MPU_FILTER_10HZ, + MPU_FILTER_5HZ, + MPU_FILTER_2100HZ_NOLPF, + NUM_MPU_FILTER +}; + +enum mpu_fullscale { + MPU_FS_250DPS = 0, + MPU_FS_500DPS, + MPU_FS_1000DPS, + MPU_FS_2000DPS, + NUM_MPU_FS +}; + +enum mpu_clock_sel { + MPU_CLK_SEL_INTERNAL = 0, + MPU_CLK_SEL_PLLGYROX, + MPU_CLK_SEL_PLLGYROY, + MPU_CLK_SEL_PLLGYROZ, + MPU_CLK_SEL_PLLEXT32K, + MPU_CLK_SEL_PLLEXT19M, + MPU_CLK_SEL_RESERVED, + MPU_CLK_SEL_STOP, + NUM_CLK_SEL +}; + +enum mpu_ext_sync { + MPU_EXT_SYNC_NONE = 0, + MPU_EXT_SYNC_TEMP, + MPU_EXT_SYNC_GYROX, + MPU_EXT_SYNC_GYROY, + MPU_EXT_SYNC_GYROZ, + MPU_EXT_SYNC_ACCELX, + MPU_EXT_SYNC_ACCELY, + MPU_EXT_SYNC_ACCELZ, + NUM_MPU_EXT_SYNC +}; + +#define MPUREG_CONFIG_VALUE(ext_sync, lpf) \ + ((ext_sync << 3) | lpf) + +#define MPUREG_GYRO_CONFIG_VALUE(x_st, y_st, z_st, full_scale) \ + ((x_st ? 0x80 : 0) | \ + (y_st ? 0x70 : 0) | \ + (z_st ? 0x60 : 0) | \ + (full_scale << 3)) + +#endif /* __MPU6050_H_ */ diff --git a/drivers/misc/inv_mpu/mpuirq.c b/drivers/misc/inv_mpu/mpuirq.c new file mode 100644 index 0000000..094712c --- /dev/null +++ b/drivers/misc/inv_mpu/mpuirq.c @@ -0,0 +1,253 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/irq.h> +#include <linux/signal.h> +#include <linux/miscdevice.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/poll.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include <linux/mpu.h> +#include "mpuirq.h" +#include "mldl_cfg.h" + +#define MPUIRQ_NAME "mpuirq" + +/* function which gets accel data and sends it to MPU */ + +DECLARE_WAIT_QUEUE_HEAD(mpuirq_wait); + +struct mpuirq_dev_data { + struct i2c_client *mpu_client; + struct miscdevice *dev; + int irq; + int pid; + int accel_divider; + int data_ready; + int timeout; +}; + +static struct mpuirq_dev_data mpuirq_dev_data; +static struct mpuirq_data mpuirq_data; +static char *interface = MPUIRQ_NAME; + +static int mpuirq_open(struct inode *inode, struct file *file) +{ + dev_dbg(mpuirq_dev_data.dev->this_device, + "%s current->pid %d\n", __func__, current->pid); + mpuirq_dev_data.pid = current->pid; + file->private_data = &mpuirq_dev_data; + return 0; +} + +/* close function - called when the "file" /dev/mpuirq is closed in userspace */ +static int mpuirq_release(struct inode *inode, struct file *file) +{ + dev_dbg(mpuirq_dev_data.dev->this_device, "mpuirq_release\n"); + return 0; +} + +/* read function called when from /dev/mpuirq is read */ +static ssize_t mpuirq_read(struct file *file, + char *buf, size_t count, loff_t *ppos) +{ + int len, err; + struct mpuirq_dev_data *p_mpuirq_dev_data = file->private_data; + + if (!mpuirq_dev_data.data_ready && + mpuirq_dev_data.timeout && (!(file->f_flags & O_NONBLOCK))) { + wait_event_interruptible_timeout(mpuirq_wait, + mpuirq_dev_data.data_ready, + mpuirq_dev_data.timeout); + } + + if (mpuirq_dev_data.data_ready && NULL != buf + && count >= sizeof(mpuirq_data)) { + err = copy_to_user(buf, &mpuirq_data, sizeof(mpuirq_data)); + mpuirq_data.data_type = 0; + } else { + return 0; + } + if (err != 0) { + dev_err(p_mpuirq_dev_data->dev->this_device, + "Copy to user returned %d\n", err); + return -EFAULT; + } + mpuirq_dev_data.data_ready = 0; + len = sizeof(mpuirq_data); + return len; +} + +unsigned int mpuirq_poll(struct file *file, struct poll_table_struct *poll) +{ + int mask = 0; + + poll_wait(file, &mpuirq_wait, poll); + if (mpuirq_dev_data.data_ready) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +/* ioctl - I/O control */ +static long mpuirq_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int data; + + switch (cmd) { + case MPUIRQ_SET_TIMEOUT: + mpuirq_dev_data.timeout = arg; + break; + + case MPUIRQ_GET_INTERRUPT_CNT: + data = mpuirq_data.interruptcount - 1; + if (mpuirq_data.interruptcount > 1) + mpuirq_data.interruptcount = 1; + + if (copy_to_user((int *)arg, &data, sizeof(int))) + return -EFAULT; + break; + case MPUIRQ_GET_IRQ_TIME: + if (copy_to_user((int *)arg, &mpuirq_data.irqtime, + sizeof(mpuirq_data.irqtime))) + return -EFAULT; + mpuirq_data.irqtime = 0; + break; + case MPUIRQ_SET_FREQUENCY_DIVIDER: + mpuirq_dev_data.accel_divider = arg; + break; + default: + retval = -EINVAL; + } + return retval; +} + +static irqreturn_t mpuirq_handler(int irq, void *dev_id) +{ + static int mycount; + mycount++; + + mpuirq_data.interruptcount++; + + /* wake up (unblock) for reading data from userspace */ + /* and ignore first interrupt generated in module init */ + mpuirq_dev_data.data_ready = 1; + + mpuirq_data.irqtime = ktime_to_ns(ktime_get()); + mpuirq_data.data_type = MPUIRQ_DATA_TYPE_MPU_IRQ; + mpuirq_data.data = 0; + + wake_up_interruptible(&mpuirq_wait); + + return IRQ_HANDLED; + +} + +/* define which file operations are supported */ +const struct file_operations mpuirq_fops = { + .owner = THIS_MODULE, + .read = mpuirq_read, + .poll = mpuirq_poll, + + .unlocked_ioctl = mpuirq_ioctl, + .open = mpuirq_open, + .release = mpuirq_release, +}; + +static struct miscdevice mpuirq_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = MPUIRQ_NAME, + .fops = &mpuirq_fops, +}; + +int mpuirq_init(struct i2c_client *mpu_client, struct mldl_cfg *mldl_cfg) +{ + + int res; + + mpuirq_dev_data.mpu_client = mpu_client; + + dev_info(&mpu_client->adapter->dev, + "Module Param interface = %s\n", interface); + + mpuirq_dev_data.irq = mpu_client->irq; + mpuirq_dev_data.pid = 0; + mpuirq_dev_data.accel_divider = -1; + mpuirq_dev_data.data_ready = 0; + mpuirq_dev_data.timeout = 0; + mpuirq_dev_data.dev = &mpuirq_device; + + if (mpuirq_dev_data.irq) { + unsigned long flags; + if (BIT_ACTL_LOW == ((mldl_cfg->pdata->int_config) & BIT_ACTL)) + flags = IRQF_TRIGGER_FALLING; + else + flags = IRQF_TRIGGER_RISING; + + res = + request_irq(mpuirq_dev_data.irq, mpuirq_handler, flags, + interface, &mpuirq_dev_data.irq); + if (res) { + dev_err(&mpu_client->adapter->dev, + "myirqtest: cannot register IRQ %d\n", + mpuirq_dev_data.irq); + } else { + res = misc_register(&mpuirq_device); + if (res < 0) { + dev_err(&mpu_client->adapter->dev, + "misc_register returned %d\n", res); + free_irq(mpuirq_dev_data.irq, + &mpuirq_dev_data.irq); + } + } + + } else { + res = 0; + } + + return res; +} + +void mpuirq_exit(void) +{ + if (mpuirq_dev_data.irq > 0) + free_irq(mpuirq_dev_data.irq, &mpuirq_dev_data.irq); + + dev_info(mpuirq_device.this_device, "Unregistering %s\n", MPUIRQ_NAME); + misc_deregister(&mpuirq_device); + + return; +} + +module_param(interface, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(interface, "The Interface name"); diff --git a/drivers/misc/inv_mpu/mpuirq.h b/drivers/misc/inv_mpu/mpuirq.h new file mode 100644 index 0000000..36b2faf --- /dev/null +++ b/drivers/misc/inv_mpu/mpuirq.h @@ -0,0 +1,36 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +#ifndef __MPUIRQ__ +#define __MPUIRQ__ + +#include <linux/i2c-dev.h> +#include <linux/time.h> +#include <linux/ioctl.h> +#include "mldl_cfg.h" + +#define MPUIRQ_SET_TIMEOUT _IOW(MPU_IOCTL, 0x40, unsigned long) +#define MPUIRQ_GET_INTERRUPT_CNT _IOR(MPU_IOCTL, 0x41, unsigned long) +#define MPUIRQ_GET_IRQ_TIME _IOR(MPU_IOCTL, 0x42, struct timeval) +#define MPUIRQ_SET_FREQUENCY_DIVIDER _IOW(MPU_IOCTL, 0x43, unsigned long) + +void mpuirq_exit(void); +int mpuirq_init(struct i2c_client *mpu_client, struct mldl_cfg *mldl_cfg); + +#endif diff --git a/drivers/misc/inv_mpu/pressure/Kconfig b/drivers/misc/inv_mpu/pressure/Kconfig new file mode 100644 index 0000000..f1c021e --- /dev/null +++ b/drivers/misc/inv_mpu/pressure/Kconfig @@ -0,0 +1,20 @@ +menuconfig: INV_SENSORS_PRESSURE + bool "Pressure Sensor Slaves" + depends on INV_SENSORS + default y + help + Select y to see a list of supported pressure sensors that can be + integrated with the MPUxxxx set of motion processors. + +if INV_SENSORS_PRESSURE + +config MPU_SENSORS_BMA085 + tristate "Bosch BMA085" + help + This enables support for the Bosch bma085 pressure sensor + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one accelerometer can be registered at a time. + Specifying more that one accelerometer in the board file will result + in runtime errors. + +endif diff --git a/drivers/misc/inv_mpu/pressure/Makefile b/drivers/misc/inv_mpu/pressure/Makefile new file mode 100644 index 0000000..dd669d2 --- /dev/null +++ b/drivers/misc/inv_mpu/pressure/Makefile @@ -0,0 +1,7 @@ +# +# Pressure Slaves to MPUxxxx +# +obj-$(CONFIG_MPU_SENSORS_BMA085) += inv_mpu_bma085.o +inv_mpu_bma085-objs += bma085.o + +EXTRA_CFLAGS += -Idrivers/misc/inv_mpu diff --git a/drivers/misc/inv_mpu/pressure/bma085.c b/drivers/misc/inv_mpu/pressure/bma085.c new file mode 100644 index 0000000..397adbb --- /dev/null +++ b/drivers/misc/inv_mpu/pressure/bma085.c @@ -0,0 +1,370 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @defgroup ACCELDL (Motion Library - Pressure Driver Layer) + * @brief Provides the interface to setup and handle a pressure + * connected to the secondary I2C interface of the gyroscope. + * + * @{ + * @file bma085.c + * @brief Pressure setup and handling methods. + */ + +/* ------------------ */ +/* - Include Files. - */ +/* ------------------ */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <linux/mpu.h> +#include "mlsl.h" +#include "log.h" + +/* --------------------- */ +/* - Variables. - */ +/* --------------------- */ + +/** this structure holds all device specific calibration parameters +*/ +struct bmp085_calibration_param_t { + short ac1; + short ac2; + short ac3; + unsigned short ac4; + unsigned short ac5; + unsigned short ac6; + short b1; + short b2; + short mb; + short mc; + short md; + long param_b5; +}; + +struct bmp085_calibration_param_t cal_param; + +#define PRESSURE_BMA085_PARAM_MG 3038 /* calibration parameter */ +#define PRESSURE_BMA085_PARAM_MH -7357 /* calibration parameter */ +#define PRESSURE_BMA085_PARAM_MI 3791 /* calibration parameter */ + +/********************************************* + Pressure Initialization Functions +**********************************************/ + +static int bma085_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + return result; +} + +#define PRESSURE_BMA085_PROM_START_ADDR (0xAA) +#define PRESSURE_BMA085_PROM_DATA_LEN (22) +#define PRESSURE_BMP085_CTRL_MEAS_REG (0xF4) +/* temperature measurent */ +#define PRESSURE_BMP085_T_MEAS (0x2E) +/* pressure measurement; oversampling_setting */ +#define PRESSURE_BMP085_P_MEAS_OSS_0 (0x34) +#define PRESSURE_BMP085_P_MEAS_OSS_1 (0x74) +#define PRESSURE_BMP085_P_MEAS_OSS_2 (0xB4) +#define PRESSURE_BMP085_P_MEAS_OSS_3 (0xF4) +#define PRESSURE_BMP085_ADC_OUT_MSB_REG (0xF6) +#define PRESSURE_BMP085_ADC_OUT_LSB_REG (0xF7) + +static int bma085_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char data[PRESSURE_BMA085_PROM_DATA_LEN]; + + result = + inv_serial_read(mlsl_handle, pdata->address, + PRESSURE_BMA085_PROM_START_ADDR, + PRESSURE_BMA085_PROM_DATA_LEN, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* parameters AC1-AC6 */ + cal_param.ac1 = (data[0] << 8) | data[1]; + cal_param.ac2 = (data[2] << 8) | data[3]; + cal_param.ac3 = (data[4] << 8) | data[5]; + cal_param.ac4 = (data[6] << 8) | data[7]; + cal_param.ac5 = (data[8] << 8) | data[9]; + cal_param.ac6 = (data[10] << 8) | data[11]; + + /* parameters B1,B2 */ + cal_param.b1 = (data[12] << 8) | data[13]; + cal_param.b2 = (data[14] << 8) | data[15]; + + /* parameters MB,MC,MD */ + cal_param.mb = (data[16] << 8) | data[17]; + cal_param.mc = (data[18] << 8) | data[19]; + cal_param.md = (data[20] << 8) | data[21]; + + return result; +} + +static int bma085_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result; + long pressure, x1, x2, x3, b3, b6; + unsigned long b4, b7; + unsigned long up; + unsigned short ut; + short oversampling_setting = 0; + short temperature; + long divisor; + + /* get temprature */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + PRESSURE_BMP085_CTRL_MEAS_REG, + PRESSURE_BMP085_T_MEAS); + msleep(5); + result = + inv_serial_read(mlsl_handle, pdata->address, + PRESSURE_BMP085_ADC_OUT_MSB_REG, 2, + (unsigned char *)data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + ut = (data[0] << 8) | data[1]; + + x1 = (((long) ut - (long)cal_param.ac6) * (long)cal_param.ac5) >> 15; + divisor = x1 + cal_param.md; + if (!divisor) + return INV_ERROR_DIVIDE_BY_ZERO; + + x2 = ((long)cal_param.mc << 11) / (x1 + cal_param.md); + cal_param.param_b5 = x1 + x2; + /* temperature in 0.1 degree C */ + temperature = (short)((cal_param.param_b5 + 8) >> 4); + + /* get pressure */ + result = inv_serial_single_write(mlsl_handle, pdata->address, + PRESSURE_BMP085_CTRL_MEAS_REG, + PRESSURE_BMP085_P_MEAS_OSS_0); + msleep(5); + result = + inv_serial_read(mlsl_handle, pdata->address, + PRESSURE_BMP085_ADC_OUT_MSB_REG, 2, + (unsigned char *)data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + up = (((unsigned long) data[0] << 8) | ((unsigned long) data[1])); + + b6 = cal_param.param_b5 - 4000; + /* calculate B3 */ + x1 = (b6*b6) >> 12; + x1 *= cal_param.b2; + x1 >>= 11; + + x2 = (cal_param.ac2*b6); + x2 >>= 11; + + x3 = x1 + x2; + + b3 = (((((long)cal_param.ac1) * 4 + x3) + << oversampling_setting) + 2) >> 2; + + /* calculate B4 */ + x1 = (cal_param.ac3 * b6) >> 13; + x2 = (cal_param.b1 * ((b6*b6) >> 12)) >> 16; + x3 = ((x1 + x2) + 2) >> 2; + b4 = (cal_param.ac4 * (unsigned long) (x3 + 32768)) >> 15; + if (!b4) + return INV_ERROR; + + b7 = ((unsigned long)(up - b3) * (50000>>oversampling_setting)); + if (b7 < 0x80000000) + pressure = (b7 << 1) / b4; + else + pressure = (b7 / b4) << 1; + + x1 = pressure >> 8; + x1 *= x1; + x1 = (x1 * PRESSURE_BMA085_PARAM_MG) >> 16; + x2 = (pressure * PRESSURE_BMA085_PARAM_MH) >> 16; + /* pressure in Pa */ + pressure += (x1 + x2 + PRESSURE_BMA085_PARAM_MI) >> 4; + + data[0] = (unsigned char)(pressure >> 16); + data[1] = (unsigned char)(pressure >> 8); + data[2] = (unsigned char)(pressure & 0xFF); + + return result; +} + +static struct ext_slave_descr bma085_descr = { + .init = NULL, + .exit = NULL, + .suspend = bma085_suspend, + .resume = bma085_resume, + .read = bma085_read, + .config = NULL, + .get_config = NULL, + .name = "bma085", + .type = EXT_SLAVE_TYPE_PRESSURE, + .id = PRESSURE_ID_BMA085, + .read_reg = 0xF6, + .read_len = 3, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {0, 0}, +}; + +static +struct ext_slave_descr *bma085_get_slave_descr(void) +{ + return &bma085_descr; +} + +/* Platform data for the MPU */ +struct bma085_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int bma085_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct bma085_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + bma085_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int bma085_mod_remove(struct i2c_client *client) +{ + struct bma085_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_unregister_slave(client, private_data->pdata, + bma085_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id bma085_mod_id[] = { + { "bma085", PRESSURE_ID_BMA085 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, bma085_mod_id); + +static struct i2c_driver bma085_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = bma085_mod_probe, + .remove = bma085_mod_remove, + .id_table = bma085_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "bma085_mod", + }, + .address_list = normal_i2c, +}; + +static int __init bma085_mod_init(void) +{ + int res = i2c_add_driver(&bma085_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "bma085_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit bma085_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&bma085_mod_driver); +} + +module_init(bma085_mod_init); +module_exit(bma085_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate BMA085 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("bma085_mod"); +/** + * @} +**/ diff --git a/drivers/misc/inv_mpu/slaveirq.c b/drivers/misc/inv_mpu/slaveirq.c new file mode 100644 index 0000000..efbaeb8 --- /dev/null +++ b/drivers/misc/inv_mpu/slaveirq.c @@ -0,0 +1,262 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/irq.h> +#include <linux/signal.h> +#include <linux/miscdevice.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/poll.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/wait.h> +#include <linux/slab.h> + +#include <linux/mpu.h> +#include "slaveirq.h" +#include "mldl_cfg.h" + +/* function which gets slave data and sends it to SLAVE */ + +struct slaveirq_dev_data { + struct miscdevice dev; + struct i2c_client *slave_client; + struct mpuirq_data data; + wait_queue_head_t slaveirq_wait; + int irq; + int pid; + int data_ready; + int timeout; +}; + +/* The following depends on patch fa1f68db6ca7ebb6fc4487ac215bffba06c01c28 + * drivers: misc: pass miscdevice pointer via file private data + */ +static int slaveirq_open(struct inode *inode, struct file *file) +{ + /* Device node is availabe in the file->private_data, this is + * exactly what we want so we leave it there */ + struct slaveirq_dev_data *data = + container_of(file->private_data, struct slaveirq_dev_data, dev); + + dev_dbg(data->dev.this_device, + "%s current->pid %d\n", __func__, current->pid); + data->pid = current->pid; + return 0; +} + +static int slaveirq_release(struct inode *inode, struct file *file) +{ + struct slaveirq_dev_data *data = + container_of(file->private_data, struct slaveirq_dev_data, dev); + dev_dbg(data->dev.this_device, "slaveirq_release\n"); + return 0; +} + +/* read function called when from /dev/slaveirq is read */ +static ssize_t slaveirq_read(struct file *file, + char *buf, size_t count, loff_t *ppos) +{ + int len, err; + struct slaveirq_dev_data *data = + container_of(file->private_data, struct slaveirq_dev_data, dev); + + if (!data->data_ready && data->timeout && + !(file->f_flags & O_NONBLOCK)) { + wait_event_interruptible_timeout(data->slaveirq_wait, + data->data_ready, + data->timeout); + } + + if (data->data_ready && NULL != buf && count >= sizeof(data->data)) { + err = copy_to_user(buf, &data->data, sizeof(data->data)); + data->data.data_type = 0; + } else { + return 0; + } + if (err != 0) { + dev_err(data->dev.this_device, + "Copy to user returned %d\n", err); + return -EFAULT; + } + data->data_ready = 0; + len = sizeof(data->data); + return len; +} + +static unsigned int slaveirq_poll(struct file *file, + struct poll_table_struct *poll) +{ + int mask = 0; + struct slaveirq_dev_data *data = + container_of(file->private_data, struct slaveirq_dev_data, dev); + + poll_wait(file, &data->slaveirq_wait, poll); + if (data->data_ready) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +/* ioctl - I/O control */ +static long slaveirq_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int tmp; + struct slaveirq_dev_data *data = + container_of(file->private_data, struct slaveirq_dev_data, dev); + + switch (cmd) { + case SLAVEIRQ_SET_TIMEOUT: + data->timeout = arg; + break; + + case SLAVEIRQ_GET_INTERRUPT_CNT: + tmp = data->data.interruptcount - 1; + if (data->data.interruptcount > 1) + data->data.interruptcount = 1; + + if (copy_to_user((int *)arg, &tmp, sizeof(int))) + return -EFAULT; + break; + case SLAVEIRQ_GET_IRQ_TIME: + if (copy_to_user((int *)arg, &data->data.irqtime, + sizeof(data->data.irqtime))) + return -EFAULT; + data->data.irqtime = 0; + break; + default: + retval = -EINVAL; + } + return retval; +} + +static irqreturn_t slaveirq_handler(int irq, void *dev_id) +{ + struct slaveirq_dev_data *data = (struct slaveirq_dev_data *)dev_id; + static int mycount; + mycount++; + + data->data.interruptcount++; + + /* wake up (unblock) for reading data from userspace */ + data->data_ready = 1; + + data->data.irqtime = ktime_to_ns(ktime_get()); + data->data.data_type |= 1; + + wake_up_interruptible(&data->slaveirq_wait); + + return IRQ_HANDLED; + +} + +/* define which file operations are supported */ +static const struct file_operations slaveirq_fops = { + .owner = THIS_MODULE, + .read = slaveirq_read, + .poll = slaveirq_poll, + +#if HAVE_COMPAT_IOCTL + .compat_ioctl = slaveirq_ioctl, +#endif +#if HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = slaveirq_ioctl, +#endif + .open = slaveirq_open, + .release = slaveirq_release, +}; + +int slaveirq_init(struct i2c_adapter *slave_adapter, + struct ext_slave_platform_data *pdata, char *name) +{ + + int res; + struct slaveirq_dev_data *data; + + if (!pdata->irq) + return -EINVAL; + + pdata->irq_data = kzalloc(sizeof(*data), GFP_KERNEL); + data = (struct slaveirq_dev_data *)pdata->irq_data; + if (!data) + return -ENOMEM; + + data->dev.minor = MISC_DYNAMIC_MINOR; + data->dev.name = name; + data->dev.fops = &slaveirq_fops; + data->irq = pdata->irq; + data->pid = 0; + data->data_ready = 0; + data->timeout = 0; + + init_waitqueue_head(&data->slaveirq_wait); + + res = request_irq(data->irq, slaveirq_handler, IRQF_TRIGGER_RISING, + data->dev.name, data); + + if (res) { + dev_err(&slave_adapter->dev, + "myirqtest: cannot register IRQ %d\n", data->irq); + goto out_request_irq; + } + + res = misc_register(&data->dev); + if (res < 0) { + dev_err(&slave_adapter->dev, + "misc_register returned %d\n", res); + goto out_misc_register; + } + + return res; + + out_misc_register: + free_irq(data->irq, data); + out_request_irq: + kfree(pdata->irq_data); + pdata->irq_data = NULL; + + return res; +} + +void slaveirq_exit(struct ext_slave_platform_data *pdata) +{ + struct slaveirq_dev_data *data = pdata->irq_data; + + if (!pdata->irq_data || data->irq <= 0) + return; + + dev_info(data->dev.this_device, "Unregistering %s\n", data->dev.name); + + free_irq(data->irq, data); + misc_deregister(&data->dev); + kfree(pdata->irq_data); + pdata->irq_data = NULL; +} diff --git a/drivers/misc/inv_mpu/slaveirq.h b/drivers/misc/inv_mpu/slaveirq.h new file mode 100644 index 0000000..ee8ad85 --- /dev/null +++ b/drivers/misc/inv_mpu/slaveirq.h @@ -0,0 +1,38 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +#ifndef __SLAVEIRQ__ +#define __SLAVEIRQ__ + +#include <linux/i2c-dev.h> + +#include <linux/mpu.h> +#include "mpuirq.h" + +#define SLAVEIRQ_SET_TIMEOUT _IOW(MPU_IOCTL, 0x50, unsigned long) +#define SLAVEIRQ_GET_INTERRUPT_CNT _IOR(MPU_IOCTL, 0x51, unsigned long) +#define SLAVEIRQ_GET_IRQ_TIME _IOR(MPU_IOCTL, 0x52, unsigned long) + + +void slaveirq_exit(struct ext_slave_platform_data *pdata); +int slaveirq_init(struct i2c_adapter *slave_adapter, + struct ext_slave_platform_data *pdata, char *name); + + +#endif diff --git a/drivers/misc/inv_mpu/timerirq.c b/drivers/misc/inv_mpu/timerirq.c new file mode 100644 index 0000000..495a808 --- /dev/null +++ b/drivers/misc/inv_mpu/timerirq.c @@ -0,0 +1,293 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/signal.h> +#include <linux/miscdevice.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/poll.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/timer.h> +#include <linux/slab.h> + +#include <linux/mpu.h> +#include "mltypes.h" +#include "timerirq.h" + +/* function which gets timer data and sends it to TIMER */ +struct timerirq_data { + int pid; + int data_ready; + int run; + int timeout; + unsigned long period; + struct mpuirq_data data; + struct completion timer_done; + wait_queue_head_t timerirq_wait; + struct timer_list timer; + struct miscdevice *dev; +}; + +static struct miscdevice *timerirq_dev_data; + +static void timerirq_handler(unsigned long arg) +{ + struct timerirq_data *data = (struct timerirq_data *)arg; + + data->data.interruptcount++; + + data->data_ready = 1; + + data->data.irqtime = ktime_to_ns(ktime_get()); + data->data.data_type |= 1; + + dev_dbg(data->dev->this_device, + "%s, %lld, %ld\n", __func__, data->data.irqtime, + (unsigned long)data); + + wake_up_interruptible(&data->timerirq_wait); + + if (data->run) + mod_timer(&data->timer, + jiffies + msecs_to_jiffies(data->period)); + else + complete(&data->timer_done); +} + +static int start_timerirq(struct timerirq_data *data) +{ + dev_dbg(data->dev->this_device, + "%s current->pid %d\n", __func__, current->pid); + + /* Timer already running... success */ + if (data->run) + return 0; + + /* Don't allow a period of 0 since this would fire constantly */ + if (!data->period) + return -EINVAL; + + data->run = TRUE; + data->data_ready = FALSE; + + init_completion(&data->timer_done); + setup_timer(&data->timer, timerirq_handler, (unsigned long)data); + + return mod_timer(&data->timer, + jiffies + msecs_to_jiffies(data->period)); +} + +static int stop_timerirq(struct timerirq_data *data) +{ + dev_dbg(data->dev->this_device, + "%s current->pid %lx\n", __func__, (unsigned long)data); + + if (data->run) { + data->run = FALSE; + mod_timer(&data->timer, jiffies + 1); + wait_for_completion(&data->timer_done); + } + return 0; +} + +/* The following depends on patch fa1f68db6ca7ebb6fc4487ac215bffba06c01c28 + * drivers: misc: pass miscdevice pointer via file private data + */ +static int timerirq_open(struct inode *inode, struct file *file) +{ + /* Device node is availabe in the file->private_data, this is + * exactly what we want so we leave it there */ + struct miscdevice *dev_data = file->private_data; + struct timerirq_data *data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev_data; + file->private_data = data; + data->pid = current->pid; + init_waitqueue_head(&data->timerirq_wait); + + dev_dbg(data->dev->this_device, + "%s current->pid %d\n", __func__, current->pid); + return 0; +} + +static int timerirq_release(struct inode *inode, struct file *file) +{ + struct timerirq_data *data = file->private_data; + dev_dbg(data->dev->this_device, "timerirq_release\n"); + if (data->run) + stop_timerirq(data); + kfree(data); + return 0; +} + +/* read function called when from /dev/timerirq is read */ +static ssize_t timerirq_read(struct file *file, + char *buf, size_t count, loff_t *ppos) +{ + int len, err; + struct timerirq_data *data = file->private_data; + + if (!data->data_ready && data->timeout && + !(file->f_flags & O_NONBLOCK)) { + wait_event_interruptible_timeout(data->timerirq_wait, + data->data_ready, + data->timeout); + } + + if (data->data_ready && NULL != buf && count >= sizeof(data->data)) { + err = copy_to_user(buf, &data->data, sizeof(data->data)); + data->data.data_type = 0; + } else { + return 0; + } + if (err != 0) { + dev_err(data->dev->this_device, + "Copy to user returned %d\n", err); + return -EFAULT; + } + data->data_ready = 0; + len = sizeof(data->data); + return len; +} + +static unsigned int timerirq_poll(struct file *file, + struct poll_table_struct *poll) +{ + int mask = 0; + struct timerirq_data *data = file->private_data; + + poll_wait(file, &data->timerirq_wait, poll); + if (data->data_ready) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +/* ioctl - I/O control */ +static long timerirq_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int tmp; + struct timerirq_data *data = file->private_data; + + dev_dbg(data->dev->this_device, + "%s current->pid %d, %d, %ld\n", + __func__, current->pid, cmd, arg); + + if (!data) + return -EFAULT; + + switch (cmd) { + case TIMERIRQ_SET_TIMEOUT: + data->timeout = arg; + break; + case TIMERIRQ_GET_INTERRUPT_CNT: + tmp = data->data.interruptcount - 1; + if (data->data.interruptcount > 1) + data->data.interruptcount = 1; + + if (copy_to_user((int *)arg, &tmp, sizeof(int))) + return -EFAULT; + break; + case TIMERIRQ_START: + data->period = arg; + retval = start_timerirq(data); + break; + case TIMERIRQ_STOP: + retval = stop_timerirq(data); + break; + default: + retval = -EINVAL; + } + return retval; +} + +/* define which file operations are supported */ +static const struct file_operations timerirq_fops = { + .owner = THIS_MODULE, + .read = timerirq_read, + .poll = timerirq_poll, + +#if HAVE_COMPAT_IOCTL + .compat_ioctl = timerirq_ioctl, +#endif +#if HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = timerirq_ioctl, +#endif + .open = timerirq_open, + .release = timerirq_release, +}; + +static int __init timerirq_init(void) +{ + + int res; + static struct miscdevice *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + timerirq_dev_data = data; + data->minor = MISC_DYNAMIC_MINOR; + data->name = "timerirq"; + data->fops = &timerirq_fops; + + res = misc_register(data); + if (res < 0) { + dev_err(data->this_device, "misc_register returned %d\n", res); + return res; + } + + return res; +} + +module_init(timerirq_init); + +static void __exit timerirq_exit(void) +{ + struct miscdevice *data = timerirq_dev_data; + + dev_info(data->this_device, "Unregistering %s\n", data->name); + + misc_deregister(data); + kfree(data); + + timerirq_dev_data = NULL; +} + +module_exit(timerirq_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Timer IRQ device driver."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("timerirq"); diff --git a/drivers/misc/inv_mpu/timerirq.h b/drivers/misc/inv_mpu/timerirq.h new file mode 100644 index 0000000..4455cac --- /dev/null +++ b/drivers/misc/inv_mpu/timerirq.h @@ -0,0 +1,30 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +#ifndef __TIMERIRQ__ +#define __TIMERIRQ__ + +#include <linux/mpu.h> + +#define TIMERIRQ_SET_TIMEOUT _IOW(MPU_IOCTL, 0x60, unsigned long) +#define TIMERIRQ_GET_INTERRUPT_CNT _IOW(MPU_IOCTL, 0x61, unsigned long) +#define TIMERIRQ_START _IOW(MPU_IOCTL, 0x62, unsigned long) +#define TIMERIRQ_STOP _IO(MPU_IOCTL, 0x63) + +#endif diff --git a/drivers/misc/leds-an30259a.c b/drivers/misc/leds-an30259a.c new file mode 100644 index 0000000..4f113b6 --- /dev/null +++ b/drivers/misc/leds-an30259a.c @@ -0,0 +1,495 @@ +/* + * leds_an30259a.c - driver for panasonic AN30259A led control chip + * + * Copyright (C) 2011, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * Contact: Yufi Li <tai-yun.li@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; version 2 of the License. + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/miscdevice.h> +#include <linux/leds-an30259a.h> + +/* AN30259A register map */ +#define AN30259A_REG_SRESET 0x00 +#define AN30259A_REG_LEDON 0x01 +#define AN30259A_REG_SEL 0x02 + +#define AN30259A_REG_LED1CC 0x03 +#define AN30259A_REG_LED2CC 0x04 +#define AN30259A_REG_LED3CC 0x05 + +#define AN30259A_REG_LED1SLP 0x06 +#define AN30259A_REG_LED2SLP 0x07 +#define AN30259A_REG_LED3SLP 0x08 + +#define AN30259A_REG_LED1CNT1 0x09 +#define AN30259A_REG_LED1CNT2 0x0a +#define AN30259A_REG_LED1CNT3 0x0b +#define AN30259A_REG_LED1CNT4 0x0c + +#define AN30259A_REG_LED2CNT1 0x0d +#define AN30259A_REG_LED2CNT2 0x0e +#define AN30259A_REG_LED2CNT3 0x0f +#define AN30259A_REG_LED2CNT4 0x10 + +#define AN30259A_REG_LED3CNT1 0x11 +#define AN30259A_REG_LED3CNT2 0x12 +#define AN30259A_REG_LED3CNT3 0x13 +#define AN30259A_REG_LED3CNT4 0x14 +#define AN30259A_REG_MAX 0x15 +/* MASK */ +#define AN30259A_MASK_IMAX 0xc0 +#define AN30259A_MASK_DELAY 0xf0 +#define AN30259A_SRESET 0x01 +#define LED_SLOPE_MODE 0x10 +#define LED_ON 0x01 + +#define DUTYMAX_MAX_VALUE 0x7f +#define DUTYMIN_MIN_VALUE 0x00 +#define SLPTT_MAX_VALUE 0x0f + +#define DETENTION_MAX_VALUE 60 +#define DELAY_MAX_VALUE 7500 +#define AN30259A_TIME_UNIT 500 +#define AN30259A_DT_TIME_UNIT 4 + +#define LED_R_MASK 0x00ff0000 +#define LED_G_MASK 0x0000ff00 +#define LED_B_MASK 0x000000ff +#define LED_R_SHIFT 16 +#define LED_G_SHIFT 8 +#define LED_IMAX_SHIFT 6 +#define AN30259A_CTN_RW_FLG 0x80 + +enum an30259a_led { + LED_R, + LED_G, + LED_B, +}; + +struct an30259a_data { + struct i2c_client *client; + struct miscdevice dev; + struct mutex mutex; + u8 shadow_reg[AN30259A_REG_MAX]; +}; + +static int leds_i2c_write_all(struct i2c_client *client) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + int ret; + + /*we need to set all the configs setting first, then LEDON later*/ + ret = i2c_smbus_write_i2c_block_data(client, + AN30259A_REG_SEL | AN30259A_CTN_RW_FLG, + AN30259A_REG_MAX - AN30259A_REG_SEL, + &data->shadow_reg[AN30259A_REG_SEL]); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c block write\n", + __func__); + return ret; + } + ret = i2c_smbus_write_byte_data(client, AN30259A_REG_LEDON, + data->shadow_reg[AN30259A_REG_LEDON]); + + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c byte write\n", + __func__); + return ret; + } + + return 0; +} + +/* + * leds_set_slope_mode() sets correct values to corresponding shadow registers. + * led: stands for LED_R or LED_G or LED_B. + * delay: represents for starting delay time in multiple of .5 second. + * dutymax: led at slope lighting maximum PWM Duty setting. + * dutymid: led at slope lighting middle PWM Duty setting. + * dutymin: led at slope lighting minimum PWM Duty Setting. + * slptt1: total time of slope operation 1 and 2, in multiple of .5 second. + * slptt2: total time of slope operation 3 and 4, in multiple of .5 second. + * dt1: detention time at each step in slope operation 1, in multiple of 4ms. + * dt2: detention time at each step in slope operation 2, in multiple of 4ms. + * dt3: detention time at each step in slope operation 3, in multiple of 4ms. + * dt4: detention time at each step in slope operation 4, in multiple of 4ms. + */ +static void leds_set_slope_mode(struct i2c_client *client, + enum an30259a_led led, u8 delay, + u8 dutymax, u8 dutymid, u8 dutymin, + u8 slptt1, u8 slptt2, + u8 dt1, u8 dt2, u8 dt3, u8 dt4) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + + data->shadow_reg[AN30259A_REG_LED1CNT1 + led * 4] = + dutymax << 4 | dutymid; + data->shadow_reg[AN30259A_REG_LED1CNT2 + led * 4] = + delay << 4 | dutymin; + data->shadow_reg[AN30259A_REG_LED1CNT3 + led * 4] = dt2 << 4 | dt1; + data->shadow_reg[AN30259A_REG_LED1CNT4 + led * 4] = dt4 << 4 | dt3; + data->shadow_reg[AN30259A_REG_LED1SLP + led] = slptt2 << 4 | slptt1; +} + +static void leds_on(struct i2c_client *client, enum an30259a_led led, + bool on, bool slopemode, + u8 ledcc) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + + if (on) + data->shadow_reg[AN30259A_REG_LEDON] |= LED_ON << led; + else { + data->shadow_reg[AN30259A_REG_LEDON] &= ~(LED_ON << led); + data->shadow_reg[AN30259A_REG_LED1CNT2 + led * 4] &= + ~AN30259A_MASK_DELAY; + } + if (slopemode) + data->shadow_reg[AN30259A_REG_LEDON] |= LED_SLOPE_MODE << led; + else + data->shadow_reg[AN30259A_REG_LEDON] &= + ~(LED_SLOPE_MODE << led); + + data->shadow_reg[AN30259A_REG_LED1CC + led] = ledcc; +} + +/* calculate the detention time for each step, return ms */ +static u8 calculate_dt(u8 min, u8 max, u16 time) +{ + u16 step_time; + u16 detention_time; + + if (min >= max) + return 0; + + step_time = time / (u16)(max - min); + detention_time = (step_time + 0x03) & 0xfffc; + /* the detention time at each step can be set as 4ms, 8ms, ...60ms */ + detention_time = (detention_time > DETENTION_MAX_VALUE) ? + DETENTION_MAX_VALUE : detention_time; + return detention_time; +} + +/* calculate the constant current output */ +static u8 calculate_cc(u32 brightness, enum an30259a_led led) +{ + u8 value = 0; + switch (led) { + case LED_R: + value = (brightness & LED_R_MASK) >> LED_R_SHIFT; + break; + case LED_G: + value = (brightness & LED_G_MASK) >> LED_G_SHIFT; + break; + case LED_B: + value = brightness & LED_B_MASK; + break; + default: + break; + } + return value; +} + +static u8 calculate_slope_tt(u8 mid, u8 dt1, u8 dt2, u16 time) +{ + u8 slptt; + u16 tt = (dt1 * (mid - DUTYMIN_MIN_VALUE) + + dt2 * (DUTYMAX_MAX_VALUE - mid)) * AN30259A_DT_TIME_UNIT + time; + /* round up to the nearest .5 seconds */ + slptt = (tt + AN30259A_TIME_UNIT - 1) / AN30259A_TIME_UNIT; + slptt = slptt > SLPTT_MAX_VALUE ? SLPTT_MAX_VALUE : slptt; + return slptt; +} + +static int leds_handle_cmds(struct i2c_client *client, + enum an30259a_led color, + struct an30259a_pr_control *leds) +{ + u8 cc, delay, dutymid, dt1, dt2, dt3, dt4, tt1, tt2; + + cc = calculate_cc(leds->color, color); + switch (leds->state) { + case LED_LIGHT_OFF: + leds_on(client, color, false, false, 0); + break; + case LED_LIGHT_ON: + leds_on(client, color, true, false, cc); + break; + case LED_LIGHT_PULSE: + /* + * PULSE is a special case of slope with delay=0, + * dutymid = dutymax ,dt1,dt2,dt3,dt4 are all zero + */ + leds_on(client, color, true, true, cc); + leds_set_slope_mode(client, color, 0, + DUTYMAX_MAX_VALUE >> 3, DUTYMAX_MAX_VALUE >> 3, + DUTYMIN_MIN_VALUE >> 3, + (leds->time_on + AN30259A_TIME_UNIT - 1) / + AN30259A_TIME_UNIT, + (leds->time_off + AN30259A_TIME_UNIT - 1) / + AN30259A_TIME_UNIT, + 0, 0, 0, 0); + + break; + case LED_LIGHT_SLOPE: + if (leds->mid_brightness > DUTYMAX_MAX_VALUE) + return -EINVAL; + + delay = ((leds->start_delay > DELAY_MAX_VALUE) ? + DELAY_MAX_VALUE : leds->start_delay) / + AN30259A_TIME_UNIT; + + dutymid = leds->mid_brightness >> 3; + dt1 = calculate_dt(DUTYMIN_MIN_VALUE, leds->mid_brightness, + leds->time_slope_up_1) / AN30259A_DT_TIME_UNIT; + dt2 = calculate_dt(leds->mid_brightness, DUTYMAX_MAX_VALUE, + leds->time_slope_up_2) / AN30259A_DT_TIME_UNIT; + dt3 = calculate_dt(leds->mid_brightness, DUTYMAX_MAX_VALUE, + leds->time_slope_down_1) / + AN30259A_DT_TIME_UNIT; + dt4 = calculate_dt(DUTYMIN_MIN_VALUE, leds->mid_brightness, + leds->time_slope_down_2) / + AN30259A_DT_TIME_UNIT; + tt1 = calculate_slope_tt(leds->mid_brightness, + dt1, dt2, leds->time_on); + tt2 = calculate_slope_tt(leds->mid_brightness, + dt4, dt3, leds->time_off); + + leds_on(client, color, true, true, cc); + leds_set_slope_mode(client, color, delay, + DUTYMAX_MAX_VALUE >> 3, dutymid, DUTYMIN_MIN_VALUE >> 3, + tt1, tt2, dt1, dt2, dt3, dt4); + break; + } + + return 0; +} + +static int leds_set_imax(struct i2c_client *client, u8 imax) +{ + int ret; + struct an30259a_data *data = i2c_get_clientdata(client); + + data->shadow_reg[AN30259A_REG_SEL] &= ~AN30259A_MASK_IMAX; + data->shadow_reg[AN30259A_REG_SEL] |= imax << LED_IMAX_SHIFT; + + ret = i2c_smbus_write_byte_data(client, AN30259A_REG_SEL, + data->shadow_reg[AN30259A_REG_SEL]); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c write\n", + __func__); + } + return 0; +} + +static long an30250a_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct an30259a_data *leds_data = container_of(file->private_data, + struct an30259a_data, dev); + struct i2c_client *client = leds_data->client; + int retval, i; + u8 imax; + struct an30259a_pr_control leds[3]; + + mutex_lock(&leds_data->mutex); + + switch (cmd) { + case AN30259A_PR_SET_LED: + retval = copy_from_user(leds, (unsigned char __user *)arg, + sizeof(struct an30259a_pr_control)); + if (retval) + break; + + for (i = LED_R; i <= LED_B; i++) { + retval = leds_handle_cmds(client, i, leds); + if (retval < 0) + goto an30259a_ioctl_failed; + } + retval = leds_i2c_write_all(client); + break; + case AN30259A_PR_SET_LEDS: + retval = copy_from_user(leds, (unsigned char __user *)arg, + 3 * sizeof(struct an30259a_pr_control)); + + if (retval) + break; + + for (i = LED_R; i <= LED_B; i++) { + retval = leds_handle_cmds(client, i, &leds[i]); + if (retval < 0) + goto an30259a_ioctl_failed; + } + retval = leds_i2c_write_all(client); + break; + + case AN30259A_PR_SET_IMAX: + retval = copy_from_user(&imax, (unsigned char __user *)arg, + sizeof(u8)); + if (retval) + break; + + retval = leds_set_imax(client, imax); + break; + + default: + dev_err(&client->adapter->dev, + "%s: Unknown cmd %x, arg %lu\n", + __func__, cmd, arg); + retval = -EINVAL; + break; + } + +an30259a_ioctl_failed: + mutex_unlock(&leds_data->mutex); + return retval; +} + +static int an30250a_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations an30259a_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = an30250a_ioctl, + .open = an30250a_open, +}; + +static int __devinit an30259a_initialize(struct i2c_client *client) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + int ret; + + /* reset an30259a*/ + ret = i2c_smbus_write_byte_data(client, AN30259A_REG_SRESET, + AN30259A_SRESET); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c write (reg = 0x%2x)\n", + __func__, AN30259A_REG_SRESET); + return ret; + } + ret = i2c_smbus_read_i2c_block_data(client, + AN30259A_REG_SRESET | AN30259A_CTN_RW_FLG, + AN30259A_REG_MAX, data->shadow_reg); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c read block(ledxcc)\n", + __func__); + return ret; + } + + return 0; +} + +static int __devinit an30259a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct an30259a_data *data; + int ret; + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "need I2C_FUNC_I2C.\n"); + return -ENODEV; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&client->adapter->dev, + "failed to allocate driver data.\n"); + return -ENOMEM; + } + + data->client = client; + + i2c_set_clientdata(client, data); + + mutex_init(&data->mutex); + + /* initialize LED */ + ret = an30259a_initialize(client); + if (ret < 0) { + dev_err(&client->adapter->dev, "failure on initialization\n"); + goto exit; + } + + data->dev.minor = MISC_DYNAMIC_MINOR; + data->dev.name = "an30259a_leds"; + data->dev.fops = &an30259a_fops; + ret = misc_register(&data->dev); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: ERROR: misc_register returned %d\n", + __func__, ret); + goto exit; + } + + return 0; +exit: + mutex_destroy(&data->mutex); + kfree(data); + return ret; +} + +static int __devexit an30259a_remove(struct i2c_client *client) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + misc_deregister(&data->dev); + mutex_destroy(&data->mutex); + kfree(data); + return 0; +} + +static struct i2c_device_id an30259a_id[] = { + {"an30259a", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, an30259a_id); + +static struct i2c_driver an30259a_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "an30259a", + }, + .id_table = an30259a_id, + .probe = an30259a_probe, + .remove = __devexit_p(an30259a_remove), +}; + +static int __init an30259a_init(void) +{ + return i2c_add_driver(&an30259a_i2c_driver); +} + +static void __exit an30259a_exit(void) +{ + i2c_del_driver(&an30259a_i2c_driver); +} + +module_init(an30259a_init); +module_exit(an30259a_exit); + +MODULE_DESCRIPTION("AN30259A LED driver"); +MODULE_AUTHOR("Yufi Li <tai-yun.li@samsung.com"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/modem_if/Kconfig b/drivers/misc/modem_if/Kconfig new file mode 100755 index 0000000..223a275 --- /dev/null +++ b/drivers/misc/modem_if/Kconfig @@ -0,0 +1,40 @@ +menuconfig SEC_MODEM + bool "Modem Interface Driver" + default n + ---help--- + Samsung Modem Interface Driver. + +config UMTS_LINK_MIPI + bool "modem driver link device MIPI-HSI" + depends on SEC_MODEM + default n + +config UMTS_LINK_HSIC + bool "modem driver link device HSIC" + depends on SEC_MODEM + default n + +config UMTS_MODEM_XMM6260 + bool "modem chip : IMC xmm6260" + depends on SEC_MODEM + default n + +config CDMA_LINK_DPRAM + bool "modem driver link device DPRAM" + depends on SEC_MODEM + default n + +config CDMA_MODEM_CBP71 + bool "modem chip : cbp71" + depends on SEC_MODEM + default n + +config LTE_LINK_USB + bool "modem driver link device US" + depends on SEC_MODEM + default n + +config LTE_MODEM_CMC221 + bool "modem chip : cmc221" + depends on SEC_MODEM + default n diff --git a/drivers/misc/modem_if/Makefile b/drivers/misc/modem_if/Makefile new file mode 100755 index 0000000..e76c8b2 --- /dev/null +++ b/drivers/misc/modem_if/Makefile @@ -0,0 +1,8 @@ +obj-y += modem.o modem_io_device.o modem_net_flowcontrol_device.o +obj-$(CONFIG_UMTS_MODEM_XMM6260) += modem_modemctl_device_xmm6260.o +obj-$(CONFIG_UMTS_LINK_MIPI) += modem_link_device_mipi.o +obj-$(CONFIG_UMTS_LINK_HSIC) += modem_link_device_hsic.o +obj-$(CONFIG_CDMA_MODEM_CBP71) += modem_modemctl_device_cbp71.o +obj-$(CONFIG_CDMA_LINK_DPRAM) += modem_link_device_dpram.o +obj-$(CONFIG_LTE_MODEM_CMC221) += modem_modemctl_device_cmc221.o lte_modem_bootloader.o +obj-$(CONFIG_LTE_LINK_USB) += modem_link_device_usb.o diff --git a/drivers/misc/modem_if/lte_modem_bootloader.c b/drivers/misc/modem_if/lte_modem_bootloader.c new file mode 100755 index 0000000..38591a5 --- /dev/null +++ b/drivers/misc/modem_if/lte_modem_bootloader.c @@ -0,0 +1,320 @@ +/* Lte modem bootloader support for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2011 Samsung Electronics. + * + * 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/interrupt.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> + +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> + +#include <linux/platform_data/lte_modem_bootloader.h> + +#define LEN_XMIT_DELEY 10 +#define MAX_XMIT_SIZE 16 + +#ifdef AIRPLAIN_MODE_TEST +int lte_airplain_mode; +#endif + +enum xmit_bootloader_status { + XMIT_BOOT_READY, + XMIT_LOADER_READY, +}; + +struct lte_modem_bootloader { + struct spi_device *spi_dev; + struct miscdevice dev; + + struct mutex lock; + + unsigned int gpio_lte2ap_status; + enum xmit_bootloader_status xmit_status; +}; +#define to_loader(misc) container_of(misc, struct lte_modem_bootloader, dev); + +static inline +int spi_xmit(struct lte_modem_bootloader *loader, + const char *buf, int size_per_xmit) +{ + int i; + int ret; + unsigned char xmit_buf[MAX_XMIT_SIZE]; + struct spi_message msg; + struct spi_transfer xfers[MAX_XMIT_SIZE]; + + memcpy(xmit_buf, buf, sizeof(xmit_buf)); + spi_message_init(&msg); + memset(xfers, 0, sizeof(xfers)); + for (i = 0; i < size_per_xmit ; i++) { + xfers[i].cs_change = 1; + xfers[i].len = 1; + xfers[i].tx_buf = xmit_buf + i; + spi_message_add_tail(&xfers[i], &msg); + } + ret = spi_sync(loader->spi_dev, &msg); + + if (ret < 0) + dev_err(&loader->spi_dev->dev, + "%s - error %d\n", __func__, ret); + + return ret; +} + + +static +int bootloader_write(struct lte_modem_bootloader *loader, + const char *addr, const int len) +{ + int i; + int ret = 0; + unsigned char lenbuf[4]; + + if (loader->xmit_status == XMIT_LOADER_READY) { + memcpy(lenbuf, &len, ARRAY_SIZE(lenbuf)); + ret = spi_xmit(loader, lenbuf, + ARRAY_SIZE(lenbuf)); + if (ret < 0) + return ret; + msleep(LEN_XMIT_DELEY); + } + + for (i = 0 ; i < len / MAX_XMIT_SIZE ; i++) { + ret = spi_xmit(loader, + addr + i * MAX_XMIT_SIZE, + MAX_XMIT_SIZE); + if (ret < 0) + return ret; + } + ret = spi_xmit(loader, addr + i * MAX_XMIT_SIZE , len % MAX_XMIT_SIZE); + + return 0; +} + + +static +int bootloader_open(struct inode *inode, struct file *flip) +{ + struct lte_modem_bootloader *loader = to_loader(flip->private_data); + flip->private_data = loader; + + return 0; +} + +static +long bootloader_ioctl(struct file *flip, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + int status; + struct lte_modem_bootloader_param param; + struct lte_modem_bootloader *loader = flip->private_data; + + mutex_lock(&loader->lock); + switch (cmd) { + case IOCTL_LTE_MODEM_XMIT_BOOT: + + ret = copy_from_user(¶m, (const void __user *)arg, + sizeof(param)); + if (ret) { + dev_err(&loader->spi_dev->dev, "%s - can not copy userdata\n", + __func__); + ret = -EFAULT; + goto exit_err; + } + + dev_info(&loader->spi_dev->dev, + "IOCTL_LTE_MODEM_XMIT_BOOT - bin size: %d\n", + param.len); + + ret = bootloader_write(loader, param.buf, param.len); + if (ret < 0) { + dev_err(&loader->spi_dev->dev, "failed to xmit boot bin\n"); + } else { + if (loader->xmit_status == XMIT_BOOT_READY) + loader->xmit_status = XMIT_LOADER_READY; + else + loader->xmit_status = XMIT_BOOT_READY; + } + + break; + case IOCTL_LTE_MODEM_LTE2AP_STATUS: + status = gpio_get_value(loader->gpio_lte2ap_status); + pr_debug("LTE2AP status :%d\n", status); + ret = copy_to_user((unsigned int *)arg, &status, + sizeof(status)); + + break; +#ifdef AIRPLAIN_MODE_TEST + case IOCTL_LTE_MODEM_AIRPLAIN_ON: + lte_airplain_mode = 1; + pr_info("usb %s, IOCTL_LTE_MODEM LPM_ON\n", __func__); + break; + case IOCTL_LTE_MODEM_AIRPLAIN_OFF: + pr_info("usb %s, IOCTL_LTE_MODEM LPM_OFF\n", __func__); + lte_airplain_mode = 0; + break; +#endif + default: + dev_err(&loader->spi_dev->dev, + "%s - ioctl cmd error\n", + __func__); + ret = -ENOIOCTLCMD; + + break; + } + mutex_unlock(&loader->lock); + +exit_err: + return ret; +} + +static const struct file_operations lte_modem_bootloader_fops = { + .owner = THIS_MODULE, + .open = bootloader_open, + .unlocked_ioctl = bootloader_ioctl, +}; + +static +int bootloader_gpio_setup(struct lte_modem_bootloader *loader) +{ + if (!loader->gpio_lte2ap_status) + return -EINVAL; + + gpio_request(loader->gpio_lte2ap_status, "GPIO_LTE2AP_STATUS"); + gpio_direction_input(loader->gpio_lte2ap_status); + + return 0; +} + +static +int __devinit lte_modem_bootloader_probe(struct spi_device *spi) +{ + int ret; + + struct lte_modem_bootloader *loader; + struct lte_modem_bootloader_platform_data *pdata; + + loader = kzalloc(sizeof(*loader), GFP_KERNEL); + if (!loader) { + pr_err("failed to allocate for lte_modem_bootloader\n"); + ret = -ENOMEM; + goto err_alloc; + } + mutex_init(&loader->lock); + + spi->bits_per_word = 8; + + if (spi_setup(spi)) { + pr_err("failed to setup spi for lte_modem_bootloader\n"); + ret = -EINVAL; + goto err_setup; + } + + loader->spi_dev = spi; + + if (!spi->dev.platform_data) { + pr_err("failed to get platform data for lte_modem_bootloader\n"); + ret = -EINVAL; + goto err_setup; + } + pdata = (struct lte_modem_bootloader_platform_data *)spi->dev.platform_data; + loader->gpio_lte2ap_status = pdata->gpio_lte2ap_status; + + ret = bootloader_gpio_setup(loader); + if (ret) { + pr_err("failed to set gpio for lte_modem_boot_loader\n"); + goto err_setup; + } + + loader->gpio_lte2ap_status = pdata->gpio_lte2ap_status; + loader->xmit_status = XMIT_BOOT_READY; + + spi_set_drvdata(spi, loader); + + loader->dev.minor = MISC_DYNAMIC_MINOR; + loader->dev.name = "lte_spi"; + loader->dev.fops = <e_modem_bootloader_fops; + ret = misc_register(&loader->dev); + if (ret) { + pr_err("failed to register misc dev for lte_modem_bootloader\n"); + goto err_setup; + } + pr_info("lte_modem_bootloader successfully probed\n"); +#ifdef AIRPLAIN_MODE_TEST + lte_airplain_mode = 0; +#endif + return 0; + +err_setup: + mutex_destroy(&loader->lock); + kfree(loader); + +err_alloc: + + return ret; +} + +static +int __devexit lte_modem_bootloader_remove(struct spi_device *spi) +{ + struct lte_modem_bootloader *loader = spi_get_drvdata(spi); + + misc_deregister(&loader->dev); + mutex_destroy(&loader->lock); + kfree(loader); + + return 0; +} + +static +struct spi_driver lte_modem_bootloader_driver = { + .driver = { + .name = "lte_modem_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = lte_modem_bootloader_probe, + .remove = __devexit_p(lte_modem_bootloader_remove), +}; + +static +int __init lte_modem_bootloader_init(void) +{ + return spi_register_driver(<e_modem_bootloader_driver); +} + +static +void __exit lte_modem_bootloader_exit(void) +{ + spi_unregister_driver(<e_modem_bootloader_driver); +} + +module_init(lte_modem_bootloader_init); +module_exit(lte_modem_bootloader_exit); + +MODULE_DESCRIPTION("LTE Modem Bootloader driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/modem_if/modem.c b/drivers/misc/modem_if/modem.c new file mode 100644 index 0000000..b96d96e --- /dev/null +++ b/drivers/misc/modem_if/modem.c @@ -0,0 +1,218 @@ +/* linux/drivers/modem/modem.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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/interrupt.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> + +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/wakelock.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_variation.h" + + +static struct modem_ctl *create_modemctl_device(struct platform_device *pdev) +{ + int ret = 0; + struct modem_data *pdata; + struct modem_ctl *modemctl; + struct device *dev = &pdev->dev; + + /* create modem control device */ + modemctl = kzalloc(sizeof(struct modem_ctl), GFP_KERNEL); + if (!modemctl) + return NULL; + + modemctl->dev = dev; + modemctl->phone_state = STATE_OFFLINE; + + pdata = pdev->dev.platform_data; + modemctl->name = pdata->name; + + /* init modemctl device for getting modemctl operations */ + ret = call_modem_init_func(modemctl, pdata); + if (ret) { + kfree(modemctl); + return NULL; + } + + pr_debug("[MODEM_IF] %s:create_modemctl_device DONE\n", modemctl->name); + return modemctl; +} + +static struct io_device *create_io_device(struct modem_io_t *io_t, + struct modem_ctl *modemctl, enum modem_network modem_net) +{ + int ret = 0; + struct io_device *iod = NULL; + + iod = kzalloc(sizeof(struct io_device), GFP_KERNEL); + if (!iod) { + pr_err("[MODEM_IF] io device memory alloc fail\n"); + return NULL; + } + + iod->name = io_t->name; + iod->id = io_t->id; + iod->format = io_t->format; + iod->io_typ = io_t->io_type; + iod->net_typ = modem_net; + + /* link between io device and modem control */ + iod->mc = modemctl; + if (iod->format == IPC_FMT) + modemctl->iod = iod; + + /* register misc device or net device */ + ret = init_io_device(iod); + if (ret) { + kfree(iod); + return NULL; + } + + pr_debug("[MODEM_IF] %s : create_io_device DONE\n", io_t->name); + return iod; +} + +static int __devinit modem_probe(struct platform_device *pdev) +{ + int i; + struct modem_data *pdata; + struct modem_ctl *modemctl; + struct io_device *iod[MAX_NUM_IO_DEV]; + struct link_device *ld; + struct io_raw_devices *io_raw_devs = NULL; + + pdata = pdev->dev.platform_data; + memset(iod, 0, sizeof(iod)); + + modemctl = create_modemctl_device(pdev); + if (!modemctl) + return -ENOMEM; + + /* create link device */ + ld = call_link_init_func(pdev, pdata->link_type); + if (!ld) + goto err_free_modemctl; + + io_raw_devs = kzalloc(sizeof(struct io_raw_devices), GFP_KERNEL); + if (!io_raw_devs) + return -ENOMEM; + + /* create io deivces and connect to modemctl device */ + for (i = 0; i < pdata->num_iodevs; i++) { + iod[i] = create_io_device(&pdata->iodevs[i], modemctl, + pdata->modem_net); + if (!iod[i]) + goto err_free_modemctl; + + if (iod[i]->format == IPC_RAW) { + int ch = iod[i]->id & 0x1F; + io_raw_devs->raw_devices[ch] = iod[i]; + io_raw_devs->num_of_raw_devs++; + iod[i]->link = ld; + } else { + /* connect io devices to one link device */ + ld->attach(ld, iod[i]); + } + + if (iod[i]->format == IPC_MULTI_RAW) + iod[i]->private_data = (void *)io_raw_devs; + } + + platform_set_drvdata(pdev, modemctl); + + pr_debug("[MODEM_IF] modem_probe DONE\n"); + return 0; + +err_free_modemctl: + for (i = 0; i < pdata->num_iodevs; i++) + if (iod[i] != NULL) + kfree(iod[i]); + + if (io_raw_devs != NULL) + kfree(io_raw_devs); + + if (modemctl != NULL) + kfree(modemctl); + + return -ENOMEM; +} + +static void modem_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct modem_ctl *mc = dev_get_drvdata(dev); + + if (!mc) + return; + + free_irq(mc->irq_phone_active, mc); + + if (mc->ops.modem_off) + mc->ops.modem_off(mc); +} + +static int modem_suspend(struct device *pdev) +{ + struct modem_ctl *mc = dev_get_drvdata(pdev); + gpio_set_value(mc->gpio_pda_active, 0); + return 0; +} + +static int modem_resume(struct device *pdev) +{ + struct modem_ctl *mc = dev_get_drvdata(pdev); + gpio_set_value(mc->gpio_pda_active, 1); + return 0; +} + +static const struct dev_pm_ops modem_pm_ops = { + .suspend = modem_suspend, + .resume = modem_resume, +}; + +static struct platform_driver modem_driver = { + .probe = modem_probe, + .shutdown = modem_shutdown, + .driver = { + .name = "modem_if", + .pm = &modem_pm_ops, + }, +}; + +static int __init modem_init(void) +{ + return platform_driver_register(&modem_driver); +} + +module_init(modem_init); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung Modem Interface Driver"); diff --git a/drivers/misc/modem_if/modem_io_device.c b/drivers/misc/modem_if/modem_io_device.c new file mode 100755 index 0000000..2909bc5 --- /dev/null +++ b/drivers/misc/modem_if/modem_io_device.c @@ -0,0 +1,924 @@ +/* /linux/drivers/misc/modem_if/modem_io_device.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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/sched.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/ip.h> +#include <linux/if_ether.h> +#include <linux/etherdevice.h> +#include <linux/ratelimit.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" + + +#define HDLC_START 0x7F +#define HDLC_END 0x7E +#define SIZE_OF_HDLC_START 1 +#define SIZE_OF_HDLC_END 1 +#define MAX_RXDATA_SIZE (4096 - 512) + +static const char hdlc_start[1] = { HDLC_START }; +static const char hdlc_end[1] = { HDLC_END }; + +struct fmt_hdr { + u16 len; + u8 control; +} __attribute__ ((packed)); + +struct raw_hdr { + u32 len; + u8 channel; + u8 control; +} __attribute__ ((packed)); + +struct rfs_hdr { + u32 len; + u8 cmd; + u8 id; +} __attribute__ ((packed)); + +static const char const *modem_state_name[] = { + [STATE_OFFLINE] = "OFFLINE", + [STATE_CRASH_EXIT] = "CRASH_EXIT", + [STATE_BOOTING] = "BOOTING", + [STATE_ONLINE] = "ONLINE", + [STATE_LOADER_DONE] = "LOADER_DONE", +}; + +static int rx_iodev_skb(struct io_device *iod); + +static int get_header_size(struct io_device *iod) +{ + switch (iod->format) { + case IPC_FMT: + return sizeof(struct fmt_hdr); + + case IPC_RAW: + case IPC_MULTI_RAW: + return sizeof(struct raw_hdr); + + case IPC_RFS: + return sizeof(struct rfs_hdr); + + case IPC_BOOT: + /* minimum size for transaction align */ + return 4; + + case IPC_RAMDUMP: + default: + return 0; + } +} + +static int get_hdlc_size(struct io_device *iod, char *buf) +{ + struct fmt_hdr *fmt_header; + struct raw_hdr *raw_header; + struct rfs_hdr *rfs_header; + + pr_debug("[MODEM_IF] buf : %02x %02x %02x (%d)\n", *buf, *(buf + 1), + *(buf + 2), __LINE__); + + switch (iod->format) { + case IPC_FMT: + fmt_header = (struct fmt_hdr *)buf; + return fmt_header->len; + case IPC_RAW: + case IPC_MULTI_RAW: + raw_header = (struct raw_hdr *)buf; + return raw_header->len; + case IPC_RFS: + rfs_header = (struct rfs_hdr *)buf; + return rfs_header->len; + default: + break; + } + return 0; +} + +static void *get_header(struct io_device *iod, size_t count, + char *frame_header_buf) +{ + struct fmt_hdr *fmt_h; + struct raw_hdr *raw_h; + struct rfs_hdr *rfs_h; + + switch (iod->format) { + case IPC_FMT: + fmt_h = (struct fmt_hdr *)frame_header_buf; + + fmt_h->len = count + sizeof(struct fmt_hdr); + fmt_h->control = 0; + + return (void *)frame_header_buf; + + case IPC_RAW: + case IPC_MULTI_RAW: + raw_h = (struct raw_hdr *)frame_header_buf; + + raw_h->len = count + sizeof(struct raw_hdr); + raw_h->channel = iod->id & 0x1F; + raw_h->control = 0; + + return (void *)frame_header_buf; + + case IPC_RFS: + rfs_h = (struct rfs_hdr *)frame_header_buf; + + rfs_h->len = count + sizeof(struct raw_hdr); + rfs_h->id = iod->id; + + return (void *)frame_header_buf; + + default: + return 0; + } +} + +static inline int rx_hdlc_head_start_check(char *buf) +{ + /* check hdlc head and return size of start byte */ + return (buf[0] == HDLC_START) ? SIZE_OF_HDLC_START : -EBADMSG; +} + +static inline int rx_hdlc_tail_check(char *buf) +{ + /* check hdlc tail and return size of tail byte */ + return (buf[0] == HDLC_END) ? SIZE_OF_HDLC_END : -EBADMSG; +} + +/* remove hdlc header and store IPC header */ +static int rx_hdlc_head_check(struct io_device *iod, char *buf, unsigned rest) +{ + struct header_data *hdr = &iod->h_data; + int head_size = get_header_size(iod); + int done_len = 0; + int len = 0; + + /* first frame, remove start header 7F */ + if (!hdr->start) { + len = rx_hdlc_head_start_check(buf); + if (len < 0) { + pr_err("[MODEM_IF] Wrong HDLC start: 0x%x(%s)\n", + *buf, iod->name); + return len; /*Wrong hdlc start*/ + } + + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, + rest, __LINE__); + + /* set the start flag of current packet */ + hdr->start = HDLC_START; + hdr->len = 0; + + buf += len; + done_len += len; + rest -= len; /* rest, call by value */ + } + + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + /* store the IPC header to iod priv */ + if (hdr->len < head_size) { + len = min(rest, head_size - hdr->len); + memcpy(hdr->hdr + hdr->len, buf, len); + hdr->len += len; + done_len += len; + } + + pr_debug("[MODEM_IF] check done_len : %d, rest : %d (%d)\n", done_len, + rest, __LINE__); + return done_len; +} + +/* alloc skb and copy dat to skb */ +static int rx_hdlc_data_check(struct io_device *iod, char *buf, unsigned rest) +{ + struct header_data *hdr = &iod->h_data; + struct sk_buff *skb = iod->skb_recv; + int head_size = get_header_size(iod); + int data_size = get_hdlc_size(iod, hdr->hdr) - head_size; + int alloc_size = min(data_size, MAX_RXDATA_SIZE); + int len; + int done_len = 0; + int rest_len = data_size - hdr->flag_len; + + /* first payload data - alloc skb */ + if (!skb) { + switch (iod->format) { + case IPC_RFS: + alloc_size = min(data_size + head_size, MAX_RXDATA_SIZE); + skb = alloc_skb(alloc_size, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + /* copy the RFS haeder to skb->data */ + memcpy(skb_put(skb, head_size), hdr->hdr, head_size); + break; + + case IPC_MULTI_RAW: + if (data_size > MAX_RXDATA_SIZE) { + pr_err("%s: %s: packet size too large (%d)\n", + __func__, iod->name, data_size); + return -EINVAL; + } + + if (iod->net_typ == UMTS_NETWORK) + skb = alloc_skb(alloc_size, GFP_ATOMIC); + else + skb = alloc_skb(alloc_size + + sizeof(struct ethhdr), GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + + if (iod->net_typ != UMTS_NETWORK) + skb_reserve(skb, sizeof(struct ethhdr)); + break; + + default: + skb = alloc_skb(alloc_size, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + break; + } + iod->skb_recv = skb; + } + + while (rest > 0) { + len = min(rest, alloc_size - skb->len); + len = min(len, rest_len); + memcpy(skb_put(skb, len), buf, len); + buf += len; + done_len += len; + hdr->flag_len += len; + rest -= len; + rest_len -= len; + + if (!rest_len || !rest) + break; + + rx_iodev_skb(iod); + iod->skb_recv = NULL; + + alloc_size = min(rest_len, MAX_RXDATA_SIZE); + skb = alloc_skb(alloc_size, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + iod->skb_recv = skb; + } + + return done_len; +} + +static int rx_iodev_skb_raw(struct io_device *iod) +{ + int err; + struct sk_buff *skb = iod->skb_recv; + struct net_device *ndev; + struct iphdr *ip_header; + struct ethhdr *ehdr; + const char source[ETH_ALEN] = SOURCE_MAC_ADDR; + + switch (iod->io_typ) { + case IODEV_MISC: + skb_queue_tail(&iod->sk_rx_q, iod->skb_recv); + wake_up(&iod->wq); + return 0; + + case IODEV_NET: + ndev = iod->ndev; + if (!ndev) + return NET_RX_DROP; + + skb->dev = ndev; + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += skb->len; + + /* check the version of IP */ + ip_header = (struct iphdr *)skb->data; + if (ip_header->version == IP6VERSION) + skb->protocol = htons(ETH_P_IPV6); + else + skb->protocol = htons(ETH_P_IP); + + if (iod->net_typ == UMTS_NETWORK) { + skb_reset_mac_header(skb); + } else { + ehdr = (void *)skb_push(skb, sizeof(struct ethhdr)); + memcpy(ehdr->h_dest, ndev->dev_addr, ETH_ALEN); + memcpy(ehdr->h_source, source, ETH_ALEN); + ehdr->h_proto = skb->protocol; + skb->ip_summed = CHECKSUM_NONE; + skb_reset_mac_header(skb); + + skb_pull(skb, sizeof(struct ethhdr)); + } + + err = netif_rx_ni(skb); + if (err != NET_RX_SUCCESS) + dev_err(&ndev->dev, "rx error: %d\n", err); + return err; + + default: + pr_err("[MODEM_IF] wrong io_type : %d\n", iod->io_typ); + return -EINVAL; + } +} + +static void rx_iodev_work(struct work_struct *work) +{ + int ret; + struct sk_buff *skb; + struct io_device *real_iod; + struct io_device *iod = container_of(work, struct io_device, + rx_work.work); + + skb = skb_dequeue(&iod->sk_rx_q); + while (skb) { + real_iod = *((struct io_device **)skb->cb); + real_iod->skb_recv = skb; + + ret = rx_iodev_skb_raw(real_iod); + if (ret == NET_RX_DROP) { + pr_err("[MODEM_IF] %s: queue delayed work!\n", + __func__); + skb_queue_head(&iod->sk_rx_q, skb); + schedule_delayed_work(&iod->rx_work, + msecs_to_jiffies(20)); + break; + } else if (ret < 0) + dev_kfree_skb_any(skb); + + skb = skb_dequeue(&iod->sk_rx_q); + } +} + +static int rx_multipdp(struct io_device *iod) +{ + u8 ch; + struct raw_hdr *raw_header = (struct raw_hdr *)&iod->h_data.hdr; + struct io_raw_devices *io_raw_devs = + (struct io_raw_devices *)iod->private_data; + struct io_device *real_iod; + + ch = raw_header->channel; + real_iod = io_raw_devs->raw_devices[ch]; + if (!real_iod) { + pr_err("[MODEM_IF] %s: wrong channel %d\n", __func__, ch); + return -1; + } + + *((struct io_device **)iod->skb_recv->cb) = real_iod; + skb_queue_tail(&iod->sk_rx_q, iod->skb_recv); + pr_debug("sk_rx_qlen:%d\n", iod->sk_rx_q.qlen); + + schedule_delayed_work(&iod->rx_work, 0); + return 0; +} + +/* de-mux function draft */ +static int rx_iodev_skb(struct io_device *iod) +{ + switch (iod->format) { + case IPC_MULTI_RAW: + return rx_multipdp(iod); + + case IPC_FMT: + case IPC_RFS: + default: + skb_queue_tail(&iod->sk_rx_q, iod->skb_recv); + + pr_debug("[MODEM_IF] wake up fmt,rfs skb\n"); + wake_up(&iod->wq); + return 0; + } +} + +static int rx_hdlc_packet(struct io_device *iod, const char *data, + unsigned recv_size) +{ + unsigned rest = recv_size; + char *buf = (char *)data; + int err = 0; + int len; + + if (rest <= 0) + goto exit; + + pr_debug("[MODEM_IF] RX_SIZE=%d\n", rest); + + if (iod->h_data.flag_len) + goto data_check; + +next_frame: + err = len = rx_hdlc_head_check(iod, buf, rest); + if (err < 0) + goto exit; /* buf++; rest--; goto next_frame; */ + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + buf += len; + rest -= len; + if (rest <= 0) + goto exit; + +data_check: + err = len = rx_hdlc_data_check(iod, buf, rest); + if (err < 0) + goto exit; + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + buf += len; + rest -= len; + + if (!rest && iod->h_data.flag_len) + return 0; + else if (rest <= 0) + goto exit; + + err = len = rx_hdlc_tail_check(buf); + if (err < 0) { + pr_err("[MODEM_IF] Wrong HDLC end: 0x%x(%s)\n", + *buf, iod->name); + goto exit; + } + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + buf += len; + rest -= len; + if (rest < 0) + goto exit; + + err = rx_iodev_skb(iod); + if (err < 0) + goto exit; + + /* initialize header & skb */ + iod->skb_recv = NULL; + memset(&iod->h_data, 0x00, sizeof(struct header_data)); + + if (rest) + goto next_frame; + +exit: + /* free buffers. mipi-hsi re-use recv buf */ + if (rest < 0) + err = -ERANGE; + + if (err < 0) { + /* clear headers */ + memset(&iod->h_data, 0x00, sizeof(struct header_data)); + + if (iod->skb_recv) { + dev_kfree_skb_any(iod->skb_recv); + iod->skb_recv = NULL; + } + } + + return err; +} + +/* called from link device when a packet arrives for this io device */ +static int io_dev_recv_data_from_link_dev(struct io_device *iod, + const char *data, unsigned int len) +{ + struct sk_buff *skb; + int err; + + switch (iod->format) { + case IPC_FMT: + case IPC_RAW: + case IPC_RFS: + case IPC_MULTI_RAW: + err = rx_hdlc_packet(iod, data, len); + if (err < 0) + pr_err("[MODEM_IF] fail process hdlc fram\n"); + return err; + + case IPC_CMD: + /* TODO- handle flow control command from CP */ + return 0; + + case IPC_BOOT: + case IPC_RAMDUMP: + /* save packet to sk_buff */ + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + pr_err("[MODEM_IF] fail alloc skb (%d)\n", __LINE__); + return -ENOMEM; + } + + pr_debug("[MODEM_IF] boot len : %d\n", len); + + memcpy(skb_put(skb, len), data, len); + skb_queue_tail(&iod->sk_rx_q, skb); + pr_debug("[MODEM_IF] skb len : %d\n", skb->len); + + wake_up(&iod->wq); + return len; + + default: + return -EINVAL; + } +} + +/* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ +static void io_dev_modem_state_changed(struct io_device *iod, + enum modem_state state) +{ + iod->mc->phone_state = state; + pr_info("[MODEM_IF] %s state changed: %s\n", iod->name, modem_state_name[state]); + + if (state == STATE_CRASH_EXIT) + wake_up(&iod->wq); +} + +static int misc_open(struct inode *inode, struct file *filp) +{ + struct io_device *iod = to_io_device(filp->private_data); + filp->private_data = (void *)iod; + + pr_info("[MODEM_IF] misc_open : %s\n", iod->name); + + if (iod->link->init_comm) + return iod->link->init_comm(iod->link, iod); + return 0; +} + +static int misc_release(struct inode *inode, struct file *filp) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + + pr_info("[MODEM_IF] misc_release : %s\n", iod->name); + + if (iod->link->terminate_comm) + iod->link->terminate_comm(iod->link, iod); + + skb_queue_purge(&iod->sk_rx_q); + return 0; +} + +static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + + poll_wait(filp, &iod->wq, wait); + + if ((!skb_queue_empty(&iod->sk_rx_q)) + && (iod->mc->phone_state != STATE_OFFLINE)) + return POLLIN | POLLRDNORM; + else if (iod->mc->phone_state == STATE_CRASH_EXIT) + return POLLHUP; + else + return 0; +} + +static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long _arg) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + + pr_debug("[MODEM_IF] misc_ioctl : 0x%x\n", cmd); + + switch (cmd) { + case IOCTL_MODEM_ON: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_ON\n"); + return iod->mc->ops.modem_on(iod->mc); + + case IOCTL_MODEM_OFF: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_OFF\n"); + return iod->mc->ops.modem_off(iod->mc); + + case IOCTL_MODEM_RESET: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_RESET\n"); + return iod->mc->ops.modem_reset(iod->mc); + + case IOCTL_MODEM_FORCE_CRASH_EXIT: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_FORCE_CRASH_EXIT\n"); + return iod->mc->ops.modem_force_crash_exit(iod->mc); + + case IOCTL_MODEM_DUMP_RESET: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_FORCE_CRASH_EXIT\n"); + return iod->mc->ops.modem_dump_reset(iod->mc); + + case IOCTL_MODEM_BOOT_ON: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_ON\n"); + return iod->mc->ops.modem_boot_on(iod->mc); + + case IOCTL_MODEM_BOOT_OFF: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_OFF\n"); + return iod->mc->ops.modem_boot_off(iod->mc); + + /* TODO - will remove this command after ril updated */ + case IOCTL_MODEM_START: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_START\n"); + return 0; + + case IOCTL_MODEM_STATUS: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_STATUS\n"); + return iod->mc->phone_state; + + case IOCTL_MODEM_DUMP_START: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_DUMP_START\n"); + return iod->link->dump_start(iod->link, iod); + + case IOCTL_MODEM_DUMP_UPDATE: + pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_DUMP_UPDATE\n"); + return iod->link->dump_update(iod->link, iod, _arg); + + case IOCTL_MODEM_GOTA_START: + pr_debug("[GOTA] misc_ioctl : IOCTL_MODEM_GOTA_START\n"); + return iod->link->gota_start(iod->link, iod); + + case IOCTL_MODEM_FW_UPDATE: + pr_debug("[GOTA] misc_ioctl : IOCTL_MODEM_FW_UPDATE\n"); + return iod->link->modem_update(iod->link, iod, _arg); + + default: + return -EINVAL; + } +} + +static ssize_t misc_write(struct file *filp, const char __user * buf, + size_t count, loff_t *ppos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + int frame_len = 0; + char frame_header_buf[sizeof(struct raw_hdr)]; + struct sk_buff *skb; + + /* TODO - check here flow control for only raw data */ + + if (iod->format == IPC_BOOT || iod->format == IPC_RAMDUMP) + frame_len = count + get_header_size(iod); + else + frame_len = count + SIZE_OF_HDLC_START + get_header_size(iod) + + SIZE_OF_HDLC_END; + + skb = alloc_skb(frame_len, GFP_KERNEL); + if (!skb) { + pr_err("[MODEM_IF] fail alloc skb (%d)\n", __LINE__); + return -ENOMEM; + } + + switch (iod->format) { + case IPC_BOOT: + case IPC_RAMDUMP: + if (copy_from_user(skb_put(skb, count), buf, count) != 0) { + dev_kfree_skb_any(skb); + return -EFAULT; + } + break; + + case IPC_RFS: + memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start, + SIZE_OF_HDLC_START); + if (copy_from_user(skb_put(skb, count), buf, count) != 0) { + dev_kfree_skb_any(skb); + return -EFAULT; + } + memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end, + SIZE_OF_HDLC_END); + break; + + default: + memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start, + SIZE_OF_HDLC_START); + memcpy(skb_put(skb, get_header_size(iod)), + get_header(iod, count, frame_header_buf), + get_header_size(iod)); + if (copy_from_user(skb_put(skb, count), buf, count) != 0) { + dev_kfree_skb_any(skb); + return -EFAULT; + } + memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end, + SIZE_OF_HDLC_END); + break; + } + + /* send data with sk_buff, link device will put sk_buff + * into the specific sk_buff_q and run work-q to send data + */ + return iod->link->send(iod->link, iod, skb); +} + +static ssize_t misc_read(struct file *filp, char *buf, size_t count, + loff_t *f_pos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct sk_buff *skb; + int pktsize = 0; + + skb = skb_dequeue(&iod->sk_rx_q); + if (!skb) { + printk_ratelimited(KERN_ERR "[MODEM_IF] no data from sk_rx_q, " + "modem_state : %s(%s)\n", + modem_state_name[iod->mc->phone_state], iod->name); + return 0; + } + + if (skb->len > count) { + pr_err("[MODEM_IF] skb len is too big = %d,%d!(%d)\n", + count, skb->len, __LINE__); + dev_kfree_skb_any(skb); + return -EIO; + } + pr_debug("[MODEM_IF] skb len : %d\n", skb->len); + + pktsize = skb->len; + if (copy_to_user(buf, skb->data, pktsize) != 0) + return -EIO; + dev_kfree_skb_any(skb); + + pr_debug("[MODEM_IF] copy to user : %d\n", pktsize); + + return pktsize; +} + +static const struct file_operations misc_io_fops = { + .owner = THIS_MODULE, + .open = misc_open, + .release = misc_release, + .poll = misc_poll, + .unlocked_ioctl = misc_ioctl, + .write = misc_write, + .read = misc_read, +}; + +static int vnet_open(struct net_device *ndev) +{ + netif_start_queue(ndev); + return 0; +} + +static int vnet_stop(struct net_device *ndev) +{ + netif_stop_queue(ndev); + return 0; +} + +static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + int ret; + struct raw_hdr hd; + struct sk_buff *skb_new; + struct vnet *vnet = netdev_priv(ndev); + struct io_device *iod = vnet->iod; + + /* umts doesn't need to discard ethernet header */ + if (iod->net_typ != UMTS_NETWORK) { + if (iod->id >= PSD_DATA_CHID_BEGIN && + iod->id <= PSD_DATA_CHID_END) + skb_pull(skb, sizeof(struct ethhdr)); + } + + hd.len = skb->len + sizeof(hd); + hd.control = 0; + hd.channel = iod->id & 0x1F; + + skb_new = skb_copy_expand(skb, sizeof(hd) + sizeof(hdlc_start), + sizeof(hdlc_end), GFP_ATOMIC); + if (!skb_new) { + dev_kfree_skb_any(skb); + return -ENOMEM; + } + + memcpy(skb_push(skb_new, sizeof(hd)), &hd, sizeof(hd)); + memcpy(skb_push(skb_new, sizeof(hdlc_start)), hdlc_start, + sizeof(hdlc_start)); + memcpy(skb_put(skb_new, sizeof(hdlc_end)), hdlc_end, sizeof(hdlc_end)); + + ret = iod->link->send(iod->link, iod, skb_new); + if (ret < 0) { + dev_kfree_skb_any(skb); + return NETDEV_TX_BUSY; + } + + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += skb->len; + dev_kfree_skb_any(skb); + + return NETDEV_TX_OK; +} + +static struct net_device_ops vnet_ops = { + .ndo_open = vnet_open, + .ndo_stop = vnet_stop, + .ndo_start_xmit = vnet_xmit, +}; + +static void vnet_setup(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_PPP; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + ndev->addr_len = 0; + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +static void vnet_setup_ether(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_ETHER; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST | IFF_SLAVE; + ndev->addr_len = ETH_ALEN; + random_ether_addr(ndev->dev_addr); + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +int init_io_device(struct io_device *iod) +{ + int ret = 0; + struct vnet *vnet; + + /* get modem state from modem control device */ + iod->modem_state_changed = io_dev_modem_state_changed; + /* get data from link device */ + iod->recv = io_dev_recv_data_from_link_dev; + + INIT_LIST_HEAD(&iod->list); + + /* register misc or net drv */ + switch (iod->io_typ) { + case IODEV_MISC: + init_waitqueue_head(&iod->wq); + skb_queue_head_init(&iod->sk_rx_q); + INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + ret = misc_register(&iod->miscdev); + if (ret) + pr_err("failed to register misc io device : %s\n", + iod->name); + + break; + + case IODEV_NET: + if (iod->net_typ == UMTS_NETWORK) + iod->ndev = alloc_netdev(0, iod->name, vnet_setup); + else + iod->ndev = alloc_netdev(0, iod->name, + vnet_setup_ether); + if (!iod->ndev) { + pr_err("failed to alloc netdev\n"); + return -ENOMEM; + } + + ret = register_netdev(iod->ndev); + if (ret) + free_netdev(iod->ndev); + + pr_debug("%s: %d(iod:0x%p)\n", __func__, __LINE__, iod); + vnet = netdev_priv(iod->ndev); + pr_debug("%s: %d(vnet:0x%p)\n", __func__, __LINE__, vnet); + vnet->iod = iod; + + break; + + case IODEV_DUMMY: + skb_queue_head_init(&iod->sk_rx_q); + INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work); + + break; + + default: + pr_err("wrong io_type : %d\n", iod->io_typ); + return -EINVAL; + } + + pr_debug("[MODEM_IF] %s(%d) : init_io_device() done : %d\n", + iod->name, iod->io_typ, ret); + return ret; +} + diff --git a/drivers/misc/modem_if/modem_link_device_dpram.c b/drivers/misc/modem_if/modem_link_device_dpram.c new file mode 100755 index 0000000..60c5c7f --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_dpram.c @@ -0,0 +1,1006 @@ +/* + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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/irq.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/if_arp.h> +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_dpram.h" + +/* interrupt masks.*/ +#define INT_MASK_VALID 0x0080 +#define INT_MASK_CMD 0x0040 +#define INT_MASK_REQ_ACK_F 0x0020 +#define INT_MASK_REQ_ACK_R 0x0010 +#define INT_MASK_RES_ACK_F 0x0008 +#define INT_MASK_RES_ACK_R 0x0004 +#define INT_MASK_SEND_F 0x0002 +#define INT_MASK_SEND_R 0x0001 +#define INT_VALID(x) ((x) & INT_MASK_VALID) +#define INT_CMD_VALID(x) ((x) & INT_MASK_CMD) +#define INT_NON_CMD(x) (INT_MASK_VALID | (x)) +#define INT_CMD(x) (INT_MASK_VALID | INT_MASK_CMD | (x)) + +#define INT_CMD_MASK(x) ((x) & 0xF) +#define INT_CMD_INIT_START 0x1 +#define INT_CMD_INIT_END 0x2 +#define INT_CMD_REQ_ACTIVE 0x3 +#define INT_CMD_RES_ACTIVE 0x4 +#define INT_CMD_REQ_TIME_SYNC 0x5 +#define INT_CMD_PHONE_START 0x8 +#define INT_CMD_ERR_DISPLAY 0x9 +#define INT_CMD_PHONE_DEEP_SLEEP 0xA +#define INT_CMD_NV_REBUILDING 0xB +#define INT_CMD_EMER_DOWN 0xC +#define INT_CMD_PIF_INIT_DONE 0xD +#define INT_CMD_SILENT_NV_REBUILDING 0xE +#define INT_CMD_NORMAL_POWER_OFF 0xF + +/* special interrupt cmd indicating modem boot failure. */ +#define INT_POWERSAFE_FAIL 0xDEAD + +#define GOTA_CMD_VALID(x) (((x) & 0xA000) == 0xA000) +#define GOTA_RESULT_FAIL 0x2 +#define GOTA_RESULT_SUCCESS 0x1 +#define GOTA_CMD_MASK(x) (((x) >> 8) & 0xF) +#define GOTA_CMD_RECEIVE_READY 0x1 +#define GOTA_CMD_DOWNLOAD_START_REQ 0x2 +#define GOTA_CMD_DOWNLOAD_START_RESP 0x3 +#define GOTA_CMD_IMAGE_SEND_REQ 0x4 +#define GOTA_CMD_IMAGE_SEND_RESP 0x5 +#define GOTA_CMD_SEND_DONE_REQ 0x6 +#define GOTA_CMD_SEND_DONE_RESP 0x7 +#define GOTA_CMD_STATUS_UPDATE 0x8 +#define GOTA_CMD_UPDATE_DONE 0x9 +#define GOTA_CMD_EFS_CLEAR_RESP 0xB +#define GOTA_CMD_ALARM_BOOT_OK 0xC +#define GOTA_CMD_ALARM_BOOT_FAIL 0xD + +#define CMD_DL_START_REQ 0x9200 +#define CMD_IMG_SEND_REQ 0x9400 +#define CMD_DL_SEND_DONE_REQ 0x9600 +#define CMD_UL_RECEIVE_RESP 0x9601 +#define CMD_UL_RECEIVE_DONE_RESP 0x9801 + +#define START_INDEX 0x7F +#define END_INDEX 0x7E + +#define DP_MAGIC_CODE 0xAA +#define DP_MAGIC_DMDL 0x4445444C +#define DP_MAGIC_UMDL 0x4445444D +#define DP_DPRAM_SIZE 0x4000 +#define DP_DEFAULT_WRITE_LEN 8168 +#define DP_DEFAULT_DUMP_LEN 16366 +#define DP_DUMP_HEADER_SIZE 7 + +#define GOTA_TIMEOUT (50 * HZ) +#define GOTA_SEND_TIMEOUT (200 * HZ) +#define DUMP_TIMEOUT (30 * HZ) +#define DUMP_START_TIMEOUT (100 * HZ) + +static int +dpram_download(struct dpram_link_device *dpld, const char *buf, int len); +static int +dpram_upload(struct dpram_link_device *dpld, struct dpram_firmware *uploaddata); + +static inline int dpram_readh(void __iomem *p_dest) +{ + unsigned long dest = (unsigned long)p_dest; + return ioread16(dest); +} + +static inline void dpram_writew(u32 value, void __iomem *p_dest) +{ + unsigned long dest = (unsigned long)p_dest; + iowrite32(value, dest); +} + +static inline void dpram_writeh(u16 value, void __iomem *p_dest) +{ + unsigned long dest = (unsigned long)p_dest; + iowrite16(value, dest); +} + +static inline void dpram_writeb(u8 value, void __iomem *p_dest) +{ + unsigned long dest = (unsigned long)p_dest; + iowrite8(value, dest); +} + + +static void dpram_write_command(struct dpram_link_device *dpld, u16 cmd) +{ + dpram_writeh(cmd, &dpld->dpram->mbx_ap2cp); +} + +static void dpram_clear_interrupt(struct dpram_link_device *dpld) +{ + dpram_writeh(0, &dpld->dpram->mbx_cp2ap); +} + +static void dpram_drop_data(struct dpram_device *device, u16 head) +{ + dpram_writeh(head, &device->in->tail); +} + +static void dpram_zero_circ(struct dpram_circ *circ) +{ + dpram_writeh(0, &circ->head); + dpram_writeh(0, &circ->tail); +} + +static void dpram_clear(struct dpram_link_device *dpld) +{ + dpram_zero_circ(&dpld->dpram->fmt_out); + dpram_zero_circ(&dpld->dpram->raw_out); + dpram_zero_circ(&dpld->dpram->fmt_in); + dpram_zero_circ(&dpld->dpram->raw_in); +} + +static bool dpram_circ_valid(int size, u16 head, u16 tail) +{ + if (head >= size) { + pr_err("[DPRAM] head(%d) >= size(%d)\n", head, size); + return false; + } + if (tail >= size) { + pr_err("[DPRAM] tail(%d) >= size(%d)\n", tail, size); + return false; + } + return true; +} + +static int dpram_init_and_report(struct dpram_link_device *dpld) +{ + const u16 init_end = INT_CMD(INT_CMD_INIT_END); + u16 magic; + u16 enable; + + dpram_writeh(0, &dpld->dpram->enable); + dpram_clear(dpld); + dpram_writeh(DP_MAGIC_CODE, &dpld->dpram->magic); + dpram_writeh(1, &dpld->dpram->enable); + + /* Send init end code to modem */ + dpram_write_command(dpld, init_end); + + magic = dpram_readh(&dpld->dpram->magic); + if (magic != DP_MAGIC_CODE) { + pr_err("[DPRAM]: %s: Failed to check magic\n", __func__); + return -1; + } + + enable = dpram_readh(&dpld->dpram->enable); + if (!enable) { + pr_err("[DPRAM]: %s: DPRAM enable failed\n", __func__); + return -1; + } + + return 0; +} + +static struct io_device *dpram_find_iod(struct dpram_link_device *dpld, int id) +{ + struct io_device *iod; + + list_for_each_entry(iod, &dpld->list_of_io_devices, list) { + if ((id == FMT_IDX && iod->format == IPC_FMT) || + (id == RAW_IDX && iod->format == IPC_MULTI_RAW)) + return iod; + } + + return NULL; +} + +static void cmd_req_active_handler(struct dpram_link_device *dpld) +{ + dpram_write_command(dpld, INT_CMD(INT_CMD_RES_ACTIVE)); +} + +static void cmd_error_display_handler(struct dpram_link_device *dpld) +{ + struct io_device *iod = dpram_find_iod(dpld, FMT_IDX); + + pr_info("[DPRAM] Received 0xc9 from modem (CP Crash)\n"); + pr_info("[DPRAM] %s\n", dpld->dpram->fmt_in_buff); + + if (iod && iod->modem_state_changed) + iod->modem_state_changed(iod, STATE_CRASH_EXIT); +} + +static void cmd_phone_start_handler(struct dpram_link_device *dpld) +{ + pr_debug("[DPRAM] Received 0xc8 from modem (Boot OK)\n"); + complete_all(&dpld->dpram_init_cmd); + dpram_init_and_report(dpld); +} + +static void command_handler(struct dpram_link_device *dpld, u16 cmd) +{ + pr_debug("[DPRAM] %s: %x\n", __func__, cmd); + + switch (INT_CMD_MASK(cmd)) { + case INT_CMD_REQ_ACTIVE: + cmd_req_active_handler(dpld); + break; + + case INT_CMD_ERR_DISPLAY: + cmd_error_display_handler(dpld); + break; + + case INT_CMD_PHONE_START: + cmd_phone_start_handler(dpld); + break; + + case INT_CMD_NV_REBUILDING: + pr_err("[MODEM_IF] NV_REBUILDING\n"); + break; + + case INT_CMD_PIF_INIT_DONE: + complete_all(&dpld->modem_pif_init_done); + break; + + case INT_CMD_SILENT_NV_REBUILDING: + pr_err("[MODEM_IF] SILENT_NV_REBUILDING\n"); + break; + + case INT_CMD_NORMAL_POWER_OFF: + /*ToDo:*/ + /*kernel_sec_set_cp_ack()*/; + break; + + case INT_CMD_REQ_TIME_SYNC: + case INT_CMD_PHONE_DEEP_SLEEP: + case INT_CMD_EMER_DOWN: + break; + + default: + pr_err("Unknown command.. %x\n", cmd); + } +} + + +static int dpram_process_modem_update(struct dpram_link_device *dpld, + struct dpram_firmware *pfw) +{ + int ret = 0; + char *buff = vmalloc(pfw->size); + + pr_debug("[GOTA] modem size =[%d]\n", pfw->size); + + if (!buff) + return -ENOMEM; + + ret = copy_from_user(buff, pfw->firmware, pfw->size); + if (ret < 0) { + pr_err("[%s:%d] Copy from user failed\n", __func__, __LINE__); + goto out; + } + + ret = dpram_download(dpld, buff, pfw->size); + if (ret < 0) + pr_err("firmware write failed\n"); + +out: + vfree(buff); + return ret; +} + + +static int dpram_modem_update(struct link_device *ld, struct io_device *iod, + unsigned long _arg) +{ + int ret; + struct dpram_link_device *dpld = to_dpram_link_device(ld); + struct dpram_firmware fw; + + pr_debug("[GOTA] dpram_modem_update\n"); + + ret = copy_from_user(&fw, (void __user *)_arg, sizeof(fw)); + if (ret < 0) { + pr_err("copy from user failed!"); + return ret; + } + + return dpram_process_modem_update(dpld, &fw); +} + +static int dpram_dump_update(struct link_device *ld, struct io_device *iod, + unsigned long _arg) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + struct dpram_firmware *fw = (struct dpram_firmware *)_arg ; + + pr_debug("[DPRAM] dpram_dump_update()\n"); + + return dpram_upload(dpld, fw); +} + +static int dpram_read(struct dpram_link_device *dpld, + struct dpram_device *device, int dev_idx) +{ + struct io_device *iod; + int size; + int tmp_size; + u16 head, tail; + char *buff; + + head = dpram_readh(&device->in->head); + tail = dpram_readh(&device->in->tail); + pr_debug("=====> %s, head: %d, tail: %d\n", __func__, head, tail); + + if (head == tail) { + pr_err("[DPRAM] %s: head == tail\n", __func__); + goto err_dpram_read; + } + + if (!dpram_circ_valid(device->in_buff_size, head, tail)) { + pr_err("[DPRAM] %s: invalid circular buffer\n", __func__); + dpram_zero_circ(device->in); + goto err_dpram_read; + } + + iod = dpram_find_iod(dpld, dev_idx); + if (!iod) { + pr_err("[DPRAM] iod == NULL\n"); + goto err_dpram_read; + } + + /* Get data size in DPRAM*/ + size = (head > tail) ? (head - tail) : + (device->in_buff_size - tail + head); + + /* ----- (tail) 7f 00 00 7e (head) ----- */ + if (head > tail) { + buff = device->in_buff_addr + tail; + if (iod->recv(iod, buff, size) < 0) { + pr_err("[DPRAM] %s: recv error, dropping data\n", + __func__); + dpram_drop_data(device, head); + goto err_dpram_read; + } + } else { /* 00 7e (head) ----------- (tail) 7f 00 */ + /* 1. tail -> buffer end.*/ + tmp_size = device->in_buff_size - tail; + buff = device->in_buff_addr + tail; + if (iod->recv(iod, buff, tmp_size) < 0) { + pr_err("[DPRAM] %s: recv error, dropping data\n", + __func__); + dpram_drop_data(device, head); + goto err_dpram_read; + } + + /* 2. buffer start -> head.*/ + if (size > tmp_size) { + buff = (char *)device->in_buff_addr; + if (iod->recv(iod, buff, (size - tmp_size)) < 0) { + pr_err("[DPRAM] %s: recv error, dropping data\n", + __func__); + dpram_drop_data(device, head); + goto err_dpram_read; + } + } + } + + /* new tail */ + tail = (u16)((tail + size) % device->in_buff_size); + dpram_writeh(tail, &device->in->tail); + + return size; + +err_dpram_read: + return -EINVAL; +} + +static void non_command_handler(struct dpram_link_device *dpld, + u16 non_cmd) +{ + struct dpram_device *device = NULL; + u16 head, tail; + u16 magic, access; + int ret = 0; + + pr_debug("[DPRAM] Entering non_command_handler(0x%04X)\n", non_cmd); + + magic = dpram_readh(&dpld->dpram->magic); + access = dpram_readh(&dpld->dpram->enable); + + if (!access || magic != DP_MAGIC_CODE) { + pr_err("fmr recevie error!!!! access = 0x%x, magic =0x%x", + access, magic); + return; + } + + /* Check formatted data region */ + device = &dpld->dev_map[FMT_IDX]; + head = dpram_readh(&device->in->head); + tail = dpram_readh(&device->in->tail); + + if (!dpram_circ_valid(device->in_buff_size, head, tail)) { + pr_err("[DPRAM] %s: invalid circular buffer\n", __func__); + dpram_zero_circ(device->in); + return; + } + + if (head != tail) { + if (non_cmd & INT_MASK_REQ_ACK_F) + atomic_inc(&dpld->fmt_txq_req_ack_rcvd); + + ret = dpram_read(dpld, device, FMT_IDX); + if (ret < 0) + pr_err("%s, dpram_read failed\n", __func__); + + if (atomic_read(&dpld->fmt_txq_req_ack_rcvd) > 0) { + dpram_write_command(dpld, + INT_NON_CMD(INT_MASK_RES_ACK_F)); + atomic_set(&dpld->fmt_txq_req_ack_rcvd, 0); + } + } else { + if (non_cmd & INT_MASK_REQ_ACK_F) { + dpram_write_command(dpld, + INT_NON_CMD(INT_MASK_RES_ACK_F)); + atomic_set(&dpld->fmt_txq_req_ack_rcvd, 0); + } + } + + /* Check raw data region */ + device = &dpld->dev_map[RAW_IDX]; + head = dpram_readh(&device->in->head); + tail = dpram_readh(&device->in->tail); + + if (!dpram_circ_valid(device->in_buff_size, head, tail)) { + pr_err("[DPRAM] %s: invalid circular buffer\n", __func__); + dpram_zero_circ(device->in); + return; + } + + if (head != tail) { + if (non_cmd & INT_MASK_REQ_ACK_R) + atomic_inc(&dpld->raw_txq_req_ack_rcvd); + + ret = dpram_read(dpld, device, RAW_IDX); + if (ret < 0) + pr_err("%s, dpram_read failed\n", __func__); + + if (atomic_read(&dpld->raw_txq_req_ack_rcvd) > 0) { + dpram_write_command(dpld, + INT_NON_CMD(INT_MASK_RES_ACK_R)); + atomic_set(&dpld->raw_txq_req_ack_rcvd, 0); + } + } else { + if (non_cmd & INT_MASK_REQ_ACK_R) { + dpram_write_command(dpld, + INT_NON_CMD(INT_MASK_RES_ACK_R)); + atomic_set(&dpld->raw_txq_req_ack_rcvd, 0); + } + } +} + +static void gota_cmd_handler(struct dpram_link_device *dpld, u16 cmd) +{ + if (cmd & GOTA_RESULT_FAIL) { + pr_err("[GOTA] Command failed: %04x\n", cmd); + return; + } + + switch (GOTA_CMD_MASK(cmd)) { + case GOTA_CMD_RECEIVE_READY: + pr_debug("[GOTA] Send CP-->AP RECEIVE_READY\n"); + dpram_write_command(dpld, CMD_DL_START_REQ); + break; + + case GOTA_CMD_DOWNLOAD_START_RESP: + pr_debug("[GOTA] Send CP-->AP DOWNLOAD_START_RESP\n"); + complete_all(&dpld->gota_download_start_complete); + break; + + case GOTA_CMD_SEND_DONE_RESP: + pr_debug("[GOTA] Send CP-->AP SEND_DONE_RESP\n"); + complete_all(&dpld->gota_send_done); + break; + + case GOTA_CMD_UPDATE_DONE: + pr_debug("[GOTA] Send CP-->AP UPDATE_DONE\n"); + complete_all(&dpld->gota_update_done); + break; + + case GOTA_CMD_IMAGE_SEND_RESP: + pr_debug("[DPRAM] Send CP-->AP IMAGE_SEND_RESP\n"); + complete_all(&dpld->dump_receive_done); + break; + + default: + pr_err("[GOTA] Unknown command.. %x\n", cmd); + } +} + +static irqreturn_t dpram_irq_handler(int irq, void *p_ld) +{ + u16 cp2ap; + + struct link_device *ld = (struct link_device *)p_ld; + struct dpram_link_device *dpld = to_dpram_link_device(ld); + + cp2ap = dpram_readh(&dpld->dpram->mbx_cp2ap); + + pr_debug("[DPRAM] received CP2AP = 0x%x\n", cp2ap); + + if (cp2ap == INT_POWERSAFE_FAIL) { + pr_err("[DPRAM] Received POWERSAFE_FAIL\n"); + goto exit_irq; + } + + if (GOTA_CMD_VALID(cp2ap)) + gota_cmd_handler(dpld, cp2ap); + else if (INT_CMD_VALID(cp2ap)) + command_handler(dpld, cp2ap); + else if (INT_VALID(cp2ap)) + non_command_handler(dpld, cp2ap); + else + pr_err("[DPRAM] Invalid command %04x\n", cp2ap); + +exit_irq: + return IRQ_HANDLED; +} + +static int dpram_attach_io_dev(struct link_device *ld, struct io_device *iod) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + + iod->link = ld; + /* list up io devices */ + list_add(&iod->list, &dpld->list_of_io_devices); + + return 0; +} + +static int dpram_write(struct dpram_link_device *dpld, + struct dpram_device *device, + const unsigned char *buf, + int len) +{ + u16 head; + u16 tail; + u16 irq_mask; + int free_space; + int last_size; + + head = dpram_readh(&device->out->head); + tail = dpram_readh(&device->out->tail); + + if (!dpram_circ_valid(device->out_buff_size, head, tail)) { + pr_err("[DPRAM] %s: invalid circular buffer\n", __func__); + dpram_zero_circ(device->out); + return -EINVAL; + } + + free_space = (head < tail) ? tail - head - 1 : + device->out_buff_size + tail - head - 1; + if (len > free_space) { + pr_debug("WRITE: No space in Q\n" + "len[%d] free_space[%d] head[%u] tail[%u] out_buff_size =%d\n", + len, free_space, head, tail, device->out_buff_size); + return -EINVAL; + } + + pr_debug("WRITE: len[%d] free_space[%d] head[%u] tail[%u] out_buff_size =%d\n", + len, free_space, head, tail, device->out_buff_size); + + if (head < tail) { + /* +++++++++ head ---------- tail ++++++++++ */ + memcpy((device->out_buff_addr + head), buf, len); + } else { + /* ------ tail +++++++++++ head ------------ */ + last_size = device->out_buff_size - head; + memcpy((device->out_buff_addr + head), buf, + len > last_size ? last_size : len); + if (len > last_size) { + memcpy(device->out_buff_addr, (buf + last_size), + (len - last_size)); + } + } + + /* Update new head */ + head = (u16)((head + len) % device->out_buff_size); + dpram_writeh(head, &device->out->head); + + irq_mask = INT_MASK_VALID; + + if (len > 0) + irq_mask |= device->mask_send; + + dpram_write_command(dpld, irq_mask); + + return len; +} + +static void dpram_write_work(struct work_struct *work) +{ + struct link_device *ld = + container_of(work, struct link_device, tx_delayed_work.work); + struct dpram_link_device *dpld = to_dpram_link_device(ld); + struct dpram_device *device; + struct sk_buff *skb; + bool reschedule = false; + int ret; + + device = &dpld->dev_map[FMT_IDX]; + while ((skb = skb_dequeue(&ld->sk_fmt_tx_q))) { + ret = dpram_write(dpld, device, skb->data, skb->len); + if (ret < 0) { + skb_queue_head(&ld->sk_fmt_tx_q, skb); + reschedule = true; + break; + } + dev_kfree_skb_any(skb); + } + + device = &dpld->dev_map[RAW_IDX]; + while ((skb = skb_dequeue(&ld->sk_raw_tx_q))) { + ret = dpram_write(dpld, device, skb->data, skb->len); + if (ret < 0) { + skb_queue_head(&ld->sk_raw_tx_q, skb); + reschedule = true; + break; + } + dev_kfree_skb_any(skb); + } + + if (reschedule) + schedule_delayed_work(&ld->tx_delayed_work, + msecs_to_jiffies(10)); +} + +static int dpram_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + int len = skb->len; + pr_debug("%s: iod->format = %d\n", __func__, iod->format); + + switch (iod->format) { + case IPC_FMT: + skb_queue_tail(&ld->sk_fmt_tx_q, skb); + break; + + case IPC_RAW: + skb_queue_tail(&ld->sk_raw_tx_q, skb); + break; + + case IPC_BOOT: + case IPC_RFS: + default: + dev_kfree_skb_any(skb); + return 0; + } + + schedule_delayed_work(&ld->tx_delayed_work, 0); + return len; +} + +static void dpram_table_init(struct dpram_link_device *dpld) +{ + struct dpram_device *dev; + struct dpram_map __iomem *dpram = dpld->dpram; + + dev = &dpld->dev_map[FMT_IDX]; + dev->in = &dpram->fmt_in; + dev->in_buff_addr = dpram->fmt_in_buff; + dev->in_buff_size = DP_FMT_IN_BUFF_SIZE; + dev->out = &dpram->fmt_out; + dev->out_buff_addr = dpram->fmt_out_buff; + dev->out_buff_size = DP_FMT_OUT_BUFF_SIZE; + dev->mask_req_ack = INT_MASK_REQ_ACK_F; + dev->mask_res_ack = INT_MASK_RES_ACK_F; + dev->mask_send = INT_MASK_SEND_F; + + dev = &dpld->dev_map[RAW_IDX]; + dev->in = &dpram->raw_in; + dev->in_buff_addr = dpram->raw_in_buff; + dev->in_buff_size = DP_RAW_IN_BUFF_SIZE; + dev->out = &dpram->raw_out; + dev->out_buff_addr = dpram->raw_out_buff; + dev->out_buff_size = DP_RAW_OUT_BUFF_SIZE; + dev->mask_req_ack = INT_MASK_REQ_ACK_R; + dev->mask_res_ack = INT_MASK_RES_ACK_R; + dev->mask_send = INT_MASK_SEND_R; +} + +static int dpram_set_dlmagic(struct link_device *ld, struct io_device *iod) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + dpram_writew(DP_MAGIC_DMDL, &dpld->dpram->magic); + return 0; +} + +static int dpram_set_ulmagic(struct link_device *ld, struct io_device *iod) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + struct dpram_map *dpram = (void *)dpld->dpram; + u8 *dest; + dest = (u8 *)(&dpram->fmt_out); + + dpram_writew(DP_MAGIC_UMDL, &dpld->dpram->magic); + + dpram_writeb((u8)START_INDEX, dest + 0); + dpram_writeb((u8)0x1, dest + 1); + dpram_writeb((u8)0x1, dest + 2); + dpram_writeb((u8)0x0, dest + 3); + dpram_writeb((u8)END_INDEX, dest + 4); + + init_completion(&dpld->gota_download_start_complete); + dpram_write_command(dpld, CMD_DL_START_REQ); + + return 0; +} + +static int +dpram_download(struct dpram_link_device *dpld, const char *buf, int len) +{ + struct dpram_map *dpram = (void *)dpld->dpram; + struct dpram_ota_header header; + u16 nframes; + u16 curframe = 1; + u16 plen; + u8 *dest; + int ret; + + nframes = DIV_ROUND_UP(len, DP_DEFAULT_WRITE_LEN); + + pr_debug("[GOTA] download len = %d\n", len); + + header.start_index = START_INDEX; + header.nframes = nframes; + + while (len > 0) { + plen = min(len, DP_DEFAULT_WRITE_LEN); + dest = (u8 *)&dpram->fmt_out; + + pr_debug("[GOTA] Start write frame %d/%d\n", curframe, nframes); + + header.curframe = curframe; + header.len = plen; + + memcpy(dest, &header, sizeof(header)); + dest += sizeof(header); + + memcpy(dest, buf, plen); + dest += plen; + buf += plen; + len -= plen; + + dpram_writeb(END_INDEX, dest+3); + + init_completion(&dpld->gota_send_done); + + if (curframe == 1) { + ret = wait_for_completion_interruptible_timeout( + &dpld->gota_download_start_complete, + GOTA_TIMEOUT); + if (!ret) { + pr_err("[GOTA] CP didn't send DOWNLOAD_START\n"); + return -ENXIO; + } + } + + dpram_write_command(dpld, CMD_IMG_SEND_REQ); + ret = wait_for_completion_interruptible_timeout( + &dpld->gota_send_done, GOTA_SEND_TIMEOUT); + if (!ret) { + pr_err("[GOTA] CP didn't send SEND_DONE_RESP\n"); + return -ENXIO; + } + + curframe++; + } + + dpram_write_command(dpld, CMD_DL_SEND_DONE_REQ); + ret = wait_for_completion_interruptible_timeout( + &dpld->gota_update_done, GOTA_TIMEOUT); + if (!ret) { + pr_err("[GOTA] CP didn't send UPDATE_DONE_NOTIFICATION\n"); + return -ENXIO; + } + + return 0; +} + +static int +dpram_upload(struct dpram_link_device *dpld, struct dpram_firmware *uploaddata) +{ + struct dpram_map *dpram = (void *)dpld->dpram; + struct ul_header header; + u8 *dest; + u8 *buff = vmalloc(DP_DEFAULT_DUMP_LEN); + u16 plen = 0; + u32 tlen = 0; + int ret; + int region = 0; + + pr_debug("[DPRAM] dpram_upload()\n"); + + ret = wait_for_completion_interruptible_timeout( + &dpld->gota_download_start_complete, + DUMP_START_TIMEOUT); + if (!ret) { + pr_err("[GOTA] CP didn't send DOWNLOAD_START\n"); + goto err_out; + } + + wake_lock(&dpld->dpram_wake_lock); + + memset(buff, 0, DP_DEFAULT_DUMP_LEN); + + dpram_write_command(dpld, CMD_IMG_SEND_REQ); + pr_debug("[DPRAM] write CMD_IMG_SEND_REQ(0x9400)\n"); + + while (1) { + init_completion(&dpld->dump_receive_done); + ret = wait_for_completion_interruptible_timeout( + &dpld->dump_receive_done, DUMP_TIMEOUT); + if (!ret) { + pr_err("[DPRAM] CP didn't send DATA_SEND_DONE_RESP\n"); + goto err_out; + } + + dest = (u8 *)(&dpram->fmt_out); + + header.bop = *(u8 *)(dest); + header.total_frame = *(u16 *)(dest + 1); + header.curr_frame = *(u16 *)(dest + 3); + header.len = *(u16 *)(dest + 5); + + pr_debug("total frame:%d, current frame:%d, data len:%d\n", + header.total_frame, header.curr_frame, + header.len); + + dest += DP_DUMP_HEADER_SIZE; + plen = min(header.len, (u16)DP_DEFAULT_DUMP_LEN); + + memcpy(buff, dest, plen); + dest += plen; + + ret = copy_to_user(uploaddata->firmware + tlen, buff, plen); + if (ret < 0) { + pr_err("[DPRAM] Copy to user failed\n"); + goto err_out; + } + + tlen += plen; + + if (header.total_frame == header.curr_frame) { + if (region) { + uploaddata->is_delta = tlen - uploaddata->size; + dpram_write_command(dpld, CMD_UL_RECEIVE_RESP); + break; + } else { + uploaddata->size = tlen; + region = 1; + } + } + dpram_write_command(dpld, CMD_UL_RECEIVE_RESP); + } + + pr_debug("1st dump region data size=%d\n", uploaddata->size); + pr_debug("2st dump region data size=%d\n", uploaddata->is_delta); + + init_completion(&dpld->gota_send_done); + ret = wait_for_completion_interruptible_timeout( + &dpld->gota_send_done, DUMP_TIMEOUT); + if (!ret) { + pr_err("[GOTA] CP didn't send SEND_DONE_RESP\n"); + goto err_out; + } + + dpram_write_command(dpld, CMD_UL_RECEIVE_DONE_RESP); + pr_debug("[DPRAM] write CMD_UL_RECEIVE_DONE_RESP(0x9801)\n"); + + dpram_writew(0, &dpld->dpram->magic); /*clear magic code */ + + wake_unlock(&dpld->dpram_wake_lock); + + vfree(buff); + return 0; + +err_out: + vfree(buff); + dpram_writew(0, &dpld->dpram->magic); + pr_err("CDMA dump error out\n"); + wake_unlock(&dpld->dpram_wake_lock); + return -EIO; +} + +struct link_device *dpram_create_link_device(struct platform_device *pdev) +{ + int ret; + struct dpram_link_device *dpld; + struct link_device *ld; + struct resource *res; + + BUILD_BUG_ON(sizeof(struct dpram_map) != DP_DPRAM_SIZE); + + dpld = kzalloc(sizeof(struct dpram_link_device), GFP_KERNEL); + if (!dpld) + return NULL; + + ld = &dpld->ld; + + INIT_LIST_HEAD(&dpld->list_of_io_devices); + skb_queue_head_init(&ld->sk_fmt_tx_q); + skb_queue_head_init(&ld->sk_raw_tx_q); + INIT_DELAYED_WORK(&ld->tx_delayed_work, dpram_write_work); + + wake_lock_init(&dpld->dpram_wake_lock, WAKE_LOCK_SUSPEND, "DPRAM"); + + init_completion(&dpld->modem_pif_init_done); + init_completion(&dpld->dpram_init_cmd); + init_completion(&dpld->gota_send_done); + init_completion(&dpld->gota_update_done); + init_completion(&dpld->gota_download_start_complete); + init_completion(&dpld->dump_receive_done); + + ld->name = "dpram"; + ld->attach = dpram_attach_io_dev; + ld->send = dpram_send; + ld->gota_start = dpram_set_dlmagic; + ld->modem_update = dpram_modem_update; + ld->dump_start = dpram_set_ulmagic; + ld->dump_update = dpram_dump_update; + + dpld->clear_interrupt = dpram_clear_interrupt; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + pr_err("[DPRAM] Failed to get mem region\n"); + goto err; + } + dpld->dpram = ioremap(res->start, resource_size(res)); + + dpld->irq = platform_get_irq(pdev, 1); + if (!dpld->irq) { + pr_err("[MODEM_IF] %s: Failed to get IRQ\n", __func__); + goto err; + } + + dpram_table_init(dpld); + + atomic_set(&dpld->raw_txq_req_ack_rcvd, 0); + atomic_set(&dpld->fmt_txq_req_ack_rcvd, 0); + + dpram_clear_interrupt(dpld); + dpram_writeh(0, &dpld->dpram->magic); + + ret = request_irq(dpld->irq, dpram_irq_handler, IRQ_TYPE_LEVEL_LOW, + "dpram irq", ld); + if (ret) { + pr_err("DPRAM interrupt handler failed\n"); + goto err; + } + enable_irq_wake(dpld->irq); + + return ld; + +err: + iounmap(dpld->dpram); + kfree(dpld); + return NULL; +} diff --git a/drivers/misc/modem_if/modem_link_device_dpram.h b/drivers/misc/modem_if/modem_link_device_dpram.h new file mode 100755 index 0000000..3af6d90 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_dpram.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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/wakelock.h> + +#ifndef __MODEM_LINK_DEVICE_DPRAM_H__ +#define __MODEM_LINK_DEVICE_DPRAM_H__ + +#define FMT_IDX 0 +#define RAW_IDX 1 +#define MAX_IDX 2 + +#define DP_FMT_OUT_BUFF_SIZE 2044 +#define DP_RAW_OUT_BUFF_SIZE 6128 +#define DP_FMT_IN_BUFF_SIZE 2044 +#define DP_RAW_IN_BUFF_SIZE 6128 + +struct dpram_circ { + u16 head; + u16 tail; +}; + +struct dpram_ota_header { + u8 start_index; + u16 nframes; + u16 curframe; + u16 len; + +} __packed; + +struct dpram_map { + u16 magic; + u16 enable; + + struct dpram_circ fmt_out; + u8 fmt_out_buff[DP_FMT_OUT_BUFF_SIZE]; + + struct dpram_circ raw_out; + u8 raw_out_buff[DP_RAW_OUT_BUFF_SIZE]; + + struct dpram_circ fmt_in; + u8 fmt_in_buff[DP_FMT_IN_BUFF_SIZE]; + + struct dpram_circ raw_in; + u8 raw_in_buff[DP_RAW_IN_BUFF_SIZE]; + + u8 padding[16]; + u16 mbx_cp2ap; + u16 mbx_ap2cp; + +} __packed; + +struct dpram_device { + struct dpram_circ __iomem *in; + u8 __iomem *in_buff_addr; + int in_buff_size; + + struct dpram_circ __iomem *out; + u8 __iomem *out_buff_addr; + int out_buff_size; + + u16 mask_req_ack; + u16 mask_res_ack; + u16 mask_send; +}; + +struct ul_header { + u8 bop; + u16 total_frame; + u16 curr_frame; + u16 len; +}; + +struct dpram_link_device { + struct link_device ld; + + /* maybe -list of io devices for the link device to use + * to find where to send incoming packets to */ + struct list_head list_of_io_devices; + + atomic_t raw_txq_req_ack_rcvd; + atomic_t fmt_txq_req_ack_rcvd; + + struct dpram_map __iomem *dpram; + struct dpram_device dev_map[MAX_IDX]; + + struct wake_lock dpram_wake_lock; + + struct completion dpram_init_cmd; + struct completion modem_pif_init_done; + struct completion gota_download_start_complete; + struct completion gota_send_done; + struct completion gota_update_done; + struct completion dump_receive_done; + + int irq; + void (*clear_interrupt)(struct dpram_link_device *); +}; + +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_dpram_link_device(linkdev) \ + container_of(linkdev, struct dpram_link_device, ld) + +#endif diff --git a/drivers/misc/modem_if/modem_link_device_mipi.c b/drivers/misc/modem_if/modem_link_device_mipi.c new file mode 100755 index 0000000..3150cd2 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_mipi.c @@ -0,0 +1,1694 @@ +/* /linux/drivers/new_modem_if/link_dev_mipi.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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/device.h> +#include <linux/sched.h> +#include <linux/irq.h> +#include <linux/poll.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/wakelock.h> + +#include <linux/hsi_driver_if.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_mipi.h" + + +static int mipi_hsi_attach_io_dev(struct link_device *ld, + struct io_device *iod) +{ + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + iod->link = ld; + + /* list up io devices */ + list_add_tail(&iod->list, &mipi_ld->list_of_io_devices); + return 0; +} + +static int mipi_hsi_init_communication(struct link_device *ld, + struct io_device *iod) +{ + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + switch (iod->format) { + case IPC_FMT: + return hsi_init_handshake(mipi_ld, HSI_INIT_MODE_NORMAL); + + case IPC_BOOT: + if (iod->id == 0x0) + return hsi_init_handshake(mipi_ld, + HSI_INIT_MODE_FLASHLESS_BOOT); + + return hsi_init_handshake(mipi_ld, + HSI_INIT_MODE_FLASHLESS_BOOT_EBL); + + case IPC_RAMDUMP: + return hsi_init_handshake(mipi_ld, + HSI_INIT_MODE_CP_RAMDUMP); + + case IPC_RFS: + case IPC_RAW: + default: + return 0; + } +} + +static void mipi_hsi_terminate_communication( + struct link_device *ld, struct io_device *iod) +{ + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + switch (iod->format) { + case IPC_BOOT: + if (&mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) + if_hsi_close_channel(&mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL]); + if (wake_lock_active(&mipi_ld->wlock)) + wake_unlock(&mipi_ld->wlock); + break; + + case IPC_RAMDUMP: + if (&mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].opened) + if_hsi_close_channel(&mipi_ld->hsi_channles[ + HSI_CP_RAMDUMP_CHANNEL]); + if (wake_lock_active(&mipi_ld->wlock)) + wake_unlock(&mipi_ld->wlock); + break; + + case IPC_FMT: + case IPC_RFS: + case IPC_RAW: + default: + break; + } +} + +static int mipi_hsi_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + int ret; + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + struct sk_buff_head *txq; + + switch (iod->format) { + case IPC_RAW: + txq = &ld->sk_raw_tx_q; + break; + + case IPC_RAMDUMP: + ret = if_hsi_write(&mipi_ld->hsi_channles[ + HSI_CP_RAMDUMP_CHANNEL], + (u32 *)skb->data, skb->len); + if (ret < 0) { + pr_err("[MIPI-HSI] write fail : %d\n", ret); + dev_kfree_skb_any(skb); + return ret; + } else + pr_debug("[MIPI-HSI] write Done\n"); + dev_kfree_skb_any(skb); + return ret; + + case IPC_BOOT: + ret = if_hsi_write(&mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL], + (u32 *)skb->data, skb->len); + if (ret < 0) { + pr_err("[MIPI-HSI] write fail : %d\n", ret); + dev_kfree_skb_any(skb); + return ret; + } else + pr_debug("[MIPI-HSI] write Done\n"); + dev_kfree_skb_any(skb); + return ret; + + case IPC_FMT: + case IPC_RFS: + default: + txq = &ld->sk_fmt_tx_q; + break; + } + + /* save io device into cb area */ + *((struct io_device **)skb->cb) = iod; + /* en queue skb data */ + skb_queue_tail(txq, skb); + + if (iod->format == IPC_RAW) + queue_delayed_work(ld->tx_raw_wq, &ld->tx_delayed_work, 0); + else + queue_work(ld->tx_wq, &ld->tx_work); + return skb->len; +} + +static void mipi_hsi_tx_work(struct work_struct *work) +{ + int ret; + struct link_device *ld = container_of(work, struct link_device, + tx_work); + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + struct io_device *iod; + struct sk_buff *fmt_skb; + int send_channel = 0; + + while (ld->sk_fmt_tx_q.qlen) { + pr_debug("[MIPI-HSI] fmt qlen : %d\n", ld->sk_fmt_tx_q.qlen); + + if (ld->com_state != COM_ONLINE) { + pr_debug("[MIPI-HSI] fmt CP not ready\n"); + return; + } + + fmt_skb = skb_dequeue(&ld->sk_fmt_tx_q); + if (fmt_skb) { + iod = *((struct io_device **)fmt_skb->cb); + + pr_debug("[MIPI-HSI] dequeue. fmt qlen : %d\n", + ld->sk_fmt_tx_q.qlen); + + switch (iod->format) { + case IPC_FMT: + send_channel = HSI_FMT_CHANNEL; + break; + + case IPC_RFS: + send_channel = HSI_RFS_CHANNEL; + break; + + case IPC_BOOT: + send_channel = HSI_FLASHLESS_CHANNEL; + break; + + case IPC_RAMDUMP: + send_channel = HSI_CP_RAMDUMP_CHANNEL; + break; + + default: + break; + } + ret = if_hsi_protocol_send(mipi_ld, send_channel, + (u32 *)fmt_skb->data, fmt_skb->len); + if (ret < 0) { + /* TODO: Re Enqueue */ + pr_err("[MIPI-HSI] write fail : %d\n", ret); + } else + pr_debug("[MIPI-HSI] write Done\n"); + + dev_kfree_skb_any(fmt_skb); + } + } +} + +static void mipi_hsi_tx_raw_work(struct work_struct *work) +{ + int ret; + struct link_device *ld = container_of(work, struct link_device, + tx_delayed_work.work); + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + struct sk_buff *raw_skb; + unsigned bulk_size; + + while (ld->sk_raw_tx_q.qlen) { + pr_debug("[MIPI-HSI] raw qlen:%d\n", ld->sk_raw_tx_q.qlen); + + if (ld->com_state != COM_ONLINE) { + pr_debug("[MIPI-HSI] raw CP not ready\n"); + return; + } + + bulk_size = 0; + raw_skb = skb_dequeue(&ld->sk_raw_tx_q); + while (raw_skb) { + if (bulk_size + raw_skb->len < MIPI_BULK_TX_SIZE) { + memcpy(mipi_ld->bulk_tx_buf + bulk_size, + raw_skb->data, raw_skb->len); + bulk_size += raw_skb->len; + skb_queue_head(&mipi_ld->bulk_txq, raw_skb); + } else { + skb_queue_head(&ld->sk_raw_tx_q, raw_skb); + break; + } + raw_skb = skb_dequeue(&ld->sk_raw_tx_q); + } + + ret = if_hsi_protocol_send(mipi_ld, HSI_RAW_CHANNEL, + (u32 *)mipi_ld->bulk_tx_buf, bulk_size); + if (ret < 0) { + raw_skb = skb_dequeue(&mipi_ld->bulk_txq); + while (raw_skb) { + skb_queue_head(&ld->sk_raw_tx_q, raw_skb); + raw_skb = skb_dequeue(&mipi_ld->bulk_txq); + } + } else + skb_queue_purge(&mipi_ld->bulk_txq); + } +} + +static int __devinit if_hsi_probe(struct hsi_device *dev); +static struct hsi_device_driver if_hsi_driver = { + .ctrl_mask = ANY_HSI_CONTROLLER, + .probe = if_hsi_probe, + .driver = { + .name = "if_hsi_driver" + }, +}; + +static int if_hsi_set_wakeline(struct if_hsi_channel *channel, + unsigned int state) +{ + int ret; + + spin_lock_bh(&channel->acwake_lock); + if (channel->acwake == state) { + spin_unlock_bh(&channel->acwake_lock); + return 0; + } + + ret = hsi_ioctl(channel->dev, state ? + HSI_IOCTL_ACWAKE_UP : HSI_IOCTL_ACWAKE_DOWN, NULL); + if (ret) { + if (ret != -EPERM) + pr_err("[MIPI-HSI] ACWAKE(%d) setting fail : %d\n", + state, ret); + /* duplicate operation */ + if (ret == -EPERM) + channel->acwake = state; + spin_unlock_bh(&channel->acwake_lock); + return ret; + } + + channel->acwake = state; + spin_unlock_bh(&channel->acwake_lock); + + pr_debug("[MIPI-HSI] ACWAKE_%d(%d)\n", channel->channel_id, state); + return 0; +} + +static void if_hsi_acwake_down_func(unsigned long data) +{ + int i; + struct if_hsi_channel *channel; + struct mipi_link_device *mipi_ld = (struct mipi_link_device *)data; + + pr_debug("[MIPI-HSI] %s\n", __func__); + + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) { + channel = &mipi_ld->hsi_channles[i]; + + if ((channel->send_step == STEP_IDLE) && + (channel->recv_step == STEP_IDLE)) { + if_hsi_set_wakeline(channel, 0); + } else { + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + pr_debug("[MIPI-HSI] mod_timer done(%d)\n", + HSI_ACWAKE_DOWN_TIMEOUT); + return; + } + } +} + +static int if_hsi_open_channel(struct if_hsi_channel *channel) +{ + int ret; + + if (channel->opened) { + pr_debug("[MIPI-HSI] channel %d is already opened\n", + channel->channel_id); + return 0; + } + + ret = hsi_open(channel->dev); + if (ret) { + pr_err("[MIPI-HSI] hsi_open fail : %d\n", ret); + if (ret == -EBUSY) + pr_err("[MIPI-HSI] ch %d already opened\n", + channel->channel_id); + else + return ret; + } + channel->opened = 1; + + pr_debug("[MIPI-HSI] hsi_open Done : %d\n", channel->channel_id); + return 0; +} + +static int if_hsi_close_channel(struct if_hsi_channel *channel) +{ + unsigned long int flags; + + if (!channel->opened) { + pr_debug("[MIPI-HSI] channel %d is already closed\n", + channel->channel_id); + return 0; + } + + if_hsi_set_wakeline(channel, 0); + hsi_write_cancel(channel->dev); + hsi_read_cancel(channel->dev); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + hsi_close(channel->dev); + channel->opened = 0; + + channel->send_step = STEP_CLOSED; + channel->recv_step = STEP_CLOSED; + + pr_debug("[MIPI-HSI] hsi_close Done : %d\n", channel->channel_id); + return 0; +} + +static void mipi_hsi_start_work(struct work_struct *work) +{ + int ret; + u32 start_cmd = 0xC2; + struct mipi_link_device *mipi_ld = + container_of(work, struct mipi_link_device, + start_work.work); + + mipi_ld->ld.com_state = COM_HANDSHAKE; + ret = if_hsi_protocol_send(mipi_ld, HSI_CMD_CHANNEL, &start_cmd, 1); + if (ret < 0) { + /* TODO: Re Enqueue */ + pr_err("[MIPI-HSI] First write fail : %d\n", ret); + } else { + pr_debug("[MIPI-HSI] First write Done : %d\n", ret); + mipi_ld->ld.com_state = COM_ONLINE; + } +} + +static int hsi_init_handshake(struct mipi_link_device *mipi_ld, int mode) +{ + int ret; + int i; + struct hst_ctx tx_config; + struct hsr_ctx rx_config; + + switch (mode) { + case HSI_INIT_MODE_NORMAL: + if (timer_pending(&mipi_ld->hsi_acwake_down_timer)) + del_timer(&mipi_ld->hsi_acwake_down_timer); + + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) { + if (mipi_ld->hsi_channles[i].opened) { + hsi_write_cancel(mipi_ld->hsi_channles[i].dev); + hsi_read_cancel(mipi_ld->hsi_channles[i].dev); + } else { + ret = if_hsi_open_channel( + &mipi_ld->hsi_channles[i]); + if (ret) + return ret; + } + mipi_ld->hsi_channles[i].send_step = STEP_IDLE; + mipi_ld->hsi_channles[i].recv_step = STEP_IDLE; + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 0; /* Speed : 96MHz */ + tx_config.channels = HSI_MAX_CHANNELS; + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 0; /* Speed : 96MHz */ + rx_config.channels = HSI_MAX_CHANNELS; + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_RX, &rx_config); + pr_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + } + + hsi_ioctl(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL); + pr_debug("[MIPI-HSI] Set 4 WIRE MODE\n"); + + if (mipi_ld->ld.com_state != COM_ONLINE) + mipi_ld->ld.com_state = COM_HANDSHAKE; + + ret = hsi_read(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data, + 1); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + if (mipi_ld->ld.com_state != COM_ONLINE) + schedule_delayed_work(&mipi_ld->start_work, 3 * HZ); + + pr_debug("[MIPI-HSI] hsi_init_handshake Done : MODE_NORMAL\n"); + return 0; + + case HSI_INIT_MODE_FLASHLESS_BOOT: + mipi_ld->ld.com_state = COM_BOOT; + + if (mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) { + hsi_ioctl(mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].dev, HSI_IOCTL_SW_RESET, + NULL); + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) + mipi_ld->hsi_channles[i].opened = 0; + } + + if (!mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) + if_hsi_open_channel( + &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL]); + mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].send_step + = STEP_IDLE; + mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].recv_step + = STEP_IDLE; + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 3; /* Speed : 24MHz */ + tx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 3; /* Speed : 24MHz */ + rx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_RX, &rx_config); + pr_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_WAKE_RX_3WIRES_MODE, NULL); + pr_debug("[MIPI-HSI] Set 3 WIRE MODE\n"); + + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + pr_debug("[MIPI-HSI] wake_lock\n"); + } + + ret = hsi_read(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].rx_data, 1); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + pr_debug("[MIPI-HSI] hsi_init_handshake Done : FLASHLESS_BOOT\n"); + return 0; + + case HSI_INIT_MODE_FLASHLESS_BOOT_EBL: + mipi_ld->ld.com_state = COM_BOOT_EBL; + + if (mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) { + hsi_ioctl(mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].dev, HSI_IOCTL_SW_RESET, + NULL); + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) + mipi_ld->hsi_channles[i].opened = 0; + } + + if (!mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) + if_hsi_open_channel( + &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL]); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 0; /* Speed : 96MHz */ + tx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 0; /* Speed : 96MHz */ + rx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_RX, &rx_config); + pr_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL); + pr_debug("[MIPI-HSI] Set 4 WIRE MODE\n"); + + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + pr_debug("[MIPI-HSI] wake_lock\n"); + } + + if_hsi_set_wakeline( + &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL], 1); + + ret = hsi_read(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].rx_data, 1); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + pr_debug("[MIPI-HSI] hsi_init_handshake Done : FLASHLESS_BOOT_EBL\n"); + return 0; + + case HSI_INIT_MODE_CP_RAMDUMP: + mipi_ld->ld.com_state = COM_CRASH; + + if (mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].opened) { + hsi_ioctl(mipi_ld->hsi_channles[ + HSI_CP_RAMDUMP_CHANNEL].dev, HSI_IOCTL_SW_RESET, + NULL); + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) + mipi_ld->hsi_channles[i].opened = 0; + } + + if (!mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].opened) + if_hsi_open_channel( + &mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL]); + mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].send_step + = STEP_IDLE; + mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].recv_step + = STEP_IDLE; + + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 0; /* Speed : 96MHz */ + tx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 0; /* Speed : 96MHz */ + rx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_SET_RX, &rx_config); + pr_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL); + pr_debug("[MIPI-HSI] Set 4 WIRE MODE\n"); + + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + pr_debug("[MIPI-HSI] wake_lock\n"); + } + + if_hsi_set_wakeline( + &mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL], 1); + + ret = hsi_read( + mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].rx_data, + DUMP_ERR_INFO_SIZE); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + pr_debug("[MIPI-HSI] hsi_init_handshake Done : RAMDUMP\n"); + return 0; + + default: + return -EINVAL; + } +} + +static void hsi_conn_err_recovery(struct mipi_link_device *mipi_ld) +{ + int i; + int ret; + struct hst_ctx tx_config; + struct hsr_ctx rx_config; + + if (mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].opened) { + hsi_ioctl(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + HSI_IOCTL_SW_RESET, NULL); + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) + mipi_ld->hsi_channles[i].opened = 0; + } + + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) { + if (!mipi_ld->hsi_channles[i].opened) + if_hsi_open_channel(&mipi_ld->hsi_channles[i]); + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 0; /* Speed : 96MHz */ + tx_config.channels = HSI_MAX_CHANNELS; + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 0; /* Speed : 96MHz */ + rx_config.channels = HSI_MAX_CHANNELS; + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_RX, &rx_config); + pr_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + } + + hsi_ioctl(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL); + pr_debug("[MIPI-HSI] Set 4 WIRE MODE\n"); + + ret = hsi_read(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data, 1); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + for (i = 1; i < HSI_NUM_OF_USE_CHANNELS; i++) { + if ((mipi_ld->hsi_channles[i].recv_step == STEP_RX) && + (mipi_ld->hsi_channles[i].rx_count)) { + pr_err("[MIPI-HSI] there was rx pending. ch:%d, len:%d", + i, mipi_ld->hsi_channles[i].rx_count); + ret = hsi_read(mipi_ld->hsi_channles[i].dev, + mipi_ld->hsi_channles[i].rx_data, + mipi_ld->hsi_channles[i].rx_count / 4); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + } + } + + pr_debug("[MIPI-HSI] hsi_conn_err_recovery Done\n"); +} + +static u32 if_hsi_create_cmd(u32 cmd_type, int ch, void *arg) +{ + u32 cmd = 0; + unsigned int size = 0; + + switch (cmd_type) { + case HSI_LL_MSG_BREAK: + return 0; + + case HSI_LL_MSG_CONN_CLOSED: + cmd = ((HSI_LL_MSG_CONN_CLOSED & 0x0000000F) << 28) + |((ch & 0x000000FF) << 24); + return cmd; + + case HSI_LL_MSG_ACK: + size = *(unsigned int *)arg; + + cmd = ((HSI_LL_MSG_ACK & 0x0000000F) << 28) + |((ch & 0x000000FF) << 24) | ((size & 0x00FFFFFF)); + return cmd; + + case HSI_LL_MSG_NAK: + cmd = ((HSI_LL_MSG_NAK & 0x0000000F) << 28) + |((ch & 0x000000FF) << 24); + return cmd; + + case HSI_LL_MSG_OPEN_CONN_OCTET: + size = *(unsigned int *)arg; + + cmd = ((HSI_LL_MSG_OPEN_CONN_OCTET & 0x0000000F) + << 28) | ((ch & 0x000000FF) << 24) + | ((size & 0x00FFFFFF)); + return cmd; + + case HSI_LL_MSG_OPEN_CONN: + case HSI_LL_MSG_CONF_RATE: + case HSI_LL_MSG_CANCEL_CONN: + case HSI_LL_MSG_CONN_READY: + case HSI_LL_MSG_ECHO: + case HSI_LL_MSG_INFO_REQ: + case HSI_LL_MSG_INFO: + case HSI_LL_MSG_CONFIGURE: + case HSI_LL_MSG_ALLOCATE_CH: + case HSI_LL_MSG_RELEASE_CH: + case HSI_LL_MSG_INVALID: + default: + pr_err("[MIPI-HSI] ERROR... CMD Not supported : %08x\n", + cmd_type); + return -EINVAL; + } +} + +static void if_hsi_cmd_work(struct work_struct *work) +{ + int ret; + int retry_count = 0; + unsigned long int flags; + struct mipi_link_device *mipi_ld = + container_of(work, struct mipi_link_device, cmd_work.work); + struct if_hsi_channel *channel = + &mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL]; + struct if_hsi_command *hsi_cmd; + + pr_debug("[MIPI-HSI] cmd_work\n"); + + do { + spin_lock_irqsave(&mipi_ld->list_cmd_lock, flags); + if (!list_empty(&mipi_ld->list_of_hsi_cmd)) { + hsi_cmd = list_entry(mipi_ld->list_of_hsi_cmd.next, + struct if_hsi_command, list); + list_del(&hsi_cmd->list); + spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags); + + channel->send_step = STEP_TX; + if_hsi_set_wakeline(channel, 1); + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + } else { + spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags); + channel->send_step = STEP_IDLE; + break; + } + pr_debug("[MIPI-HSI] take command : %08x\n", hsi_cmd->command); + + ret = if_hsi_write(channel, &hsi_cmd->command, 4); + if (ret < 0) { + pr_err("[MIPI-HSI] write command fail : %d\n", ret); + + retry_count++; + if (retry_count > 5) { + channel->send_step = STEP_IDLE; + kfree(hsi_cmd); + return; + } + + hsi_conn_err_recovery(mipi_ld); + channel->send_step = STEP_IDLE; + + spin_lock_irqsave(&mipi_ld->list_cmd_lock, flags); + list_add(&hsi_cmd->list, &mipi_ld->list_of_hsi_cmd); + spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags); + + pr_err("[MIPI-HSI] retry write command : %d\n", + retry_count); + continue; + } + pr_debug("[MIPI-HSI] SEND CMD : %08x\n", hsi_cmd->command); + + kfree(hsi_cmd); + } while (true); +} + +static int if_hsi_send_command(struct mipi_link_device *mipi_ld, + u32 cmd_type, int ch, u32 param) +{ + unsigned long int flags; + struct if_hsi_command *hsi_cmd; + + hsi_cmd = kmalloc(sizeof(struct if_hsi_command), GFP_ATOMIC); + if (!hsi_cmd) { + pr_err("[MIPI-HSI] hsi_cmd kmalloc fail\n"); + return -ENOMEM; + } + INIT_LIST_HEAD(&hsi_cmd->list); + + hsi_cmd->command = if_hsi_create_cmd(cmd_type, ch, ¶m); + pr_debug("[MIPI-HSI] made command : %08x\n", hsi_cmd->command); + + spin_lock_irqsave(&mipi_ld->list_cmd_lock, flags); + list_add_tail(&hsi_cmd->list, &mipi_ld->list_of_hsi_cmd); + spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags); + + pr_debug("[MIPI-HSI] queue_work : cmd_work\n"); + queue_delayed_work(mipi_ld->mipi_wq, &mipi_ld->cmd_work, 0); + + return 0; +} + +static int if_hsi_decode_cmd(u32 *cmd_data, u32 *cmd, u32 *ch, + u32 *param) +{ + u32 data = *cmd_data; + u8 lrc_cal, lrc_act; + u8 val1, val2, val3; + + *cmd = ((data & 0xF0000000) >> 28); + switch (*cmd) { + case HSI_LL_MSG_BREAK: + pr_err("[MIPI-HSI] Command MSG_BREAK Received\n"); + return -1; + + case HSI_LL_MSG_OPEN_CONN: + *ch = ((data & 0x0F000000) >> 24); + *param = ((data & 0x00FFFF00) >> 8); + val1 = ((data & 0xFF000000) >> 24); + val2 = ((data & 0x00FF0000) >> 16); + val3 = ((data & 0x0000FF00) >> 8); + lrc_act = (data & 0x000000FF); + lrc_cal = val1 ^ val2 ^ val3; + + if (lrc_cal != lrc_act) { + pr_err("[MIPI-HSI] CAL is broken\n"); + return -1; + } + return 0; + + case HSI_LL_MSG_CONN_READY: + case HSI_LL_MSG_CONN_CLOSED: + case HSI_LL_MSG_CANCEL_CONN: + case HSI_LL_MSG_NAK: + *ch = ((data & 0x0F000000) >> 24); + return 0; + + case HSI_LL_MSG_ACK: + *ch = ((data & 0x0F000000) >> 24); + *param = (data & 0x00FFFFFF); + return 0; + + case HSI_LL_MSG_CONF_RATE: + *ch = ((data & 0x0F000000) >> 24); + *param = ((data & 0x0F000000) >> 24); + return 0; + + case HSI_LL_MSG_OPEN_CONN_OCTET: + *ch = ((data & 0x0F000000) >> 24); + *param = (data & 0x00FFFFFF); + return 0; + + case HSI_LL_MSG_ECHO: + case HSI_LL_MSG_INFO_REQ: + case HSI_LL_MSG_INFO: + case HSI_LL_MSG_CONFIGURE: + case HSI_LL_MSG_ALLOCATE_CH: + case HSI_LL_MSG_RELEASE_CH: + case HSI_LL_MSG_INVALID: + default: + pr_err("[MIPI-HSI] Invalid command received : %08x\n", *cmd); + *cmd = HSI_LL_MSG_INVALID; + *ch = HSI_LL_INVALID_CHANNEL; + return -1; + } + return 0; +} + +static int if_hsi_rx_cmd_handle(struct mipi_link_device *mipi_ld, u32 cmd, + u32 ch, u32 param) +{ + int ret; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[ch]; + + pr_debug("[MIPI-HSI] if_hsi_rx_cmd_handle cmd=0x%x, ch=%d, param=%d\n", + cmd, ch, param); + + switch (cmd) { + case HSI_LL_MSG_OPEN_CONN_OCTET: + switch (channel->recv_step) { + case STEP_SEND_TO_CONN_CLOSED: + case STEP_IDLE: + channel->recv_step = STEP_TO_ACK; + + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + pr_debug("[MIPI-HSI] wake_lock\n"); + } + + if_hsi_set_wakeline(channel, 1); + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + pr_debug("[MIPI-HSI] mod_timer done(%d)\n", + HSI_ACWAKE_DOWN_TIMEOUT); + + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_ACK, ch, + param); + if (ret) { + pr_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", + ret); + return ret; + } + + channel->packet_size = param; + channel->recv_step = STEP_RX; + if (param % 4) + param += (4 - (param % 4)); + channel->rx_count = param; + ret = hsi_read(channel->dev, channel->rx_data, + channel->rx_count / 4); + if (ret) { + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + return ret; + } + return 0; + + case STEP_NOT_READY: + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_NAK, ch, + param); + if (ret) { + pr_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", + ret); + return ret; + } + return 0; + + default: + pr_err("[MIPI-HSI] wrong state : %08x, recv_step : %d\n", + cmd, channel->recv_step); + + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_ACK, ch, + param); + if (ret) { + pr_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", + ret); + return ret; + } + pr_err("[MIPI-HSI] Send ACK AGAIN\n"); + return -1; + } + + case HSI_LL_MSG_ACK: + case HSI_LL_MSG_NAK: + switch (channel->send_step) { + case STEP_WAIT_FOR_ACK: + case STEP_SEND_OPEN_CONN: + if (cmd == HSI_LL_MSG_ACK) { + channel->send_step = STEP_TX; + channel->got_nack = 0; + pr_debug("[MIPI-HSI] got ack\n"); + } else { + channel->send_step = STEP_WAIT_FOR_ACK; + channel->got_nack = 1; + pr_debug("[MIPI-HSI] got nack\n"); + } + + up(&channel->ack_done_sem); + return 0; + + default: + pr_err("[MIPI-HSI] wrong state : %d, %08x(%d)\n", + channel->send_step, cmd, channel->channel_id); + return -1; + } + + case HSI_LL_MSG_CONN_CLOSED: + switch (channel->send_step) { + case STEP_TX: + case STEP_WAIT_FOR_CONN_CLOSED: + pr_debug("[MIPI-HSI] got close\n"); + + channel->send_step = STEP_IDLE; + up(&channel->close_conn_done_sem); + return 0; + + default: + pr_err("[MIPI-HSI] wrong state : %d, %08x(%d)\n", + channel->send_step, cmd, channel->channel_id); + return -1; + } + + case HSI_LL_MSG_OPEN_CONN: + case HSI_LL_MSG_ECHO: + case HSI_LL_MSG_CANCEL_CONN: + case HSI_LL_MSG_CONF_RATE: + default: + pr_err("[MIPI-HSI] ERROR... CMD Not supported : %08x\n", cmd); + return -EINVAL; + } +} + +static int if_hsi_protocol_send(struct mipi_link_device *mipi_ld, int ch, + u32 *data, unsigned int len) +{ + int ret; + int retry_count = 0; + int ack_timeout_cnt = 0; + struct io_device *iod; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[ch]; + + if (channel->send_step != STEP_IDLE) { + pr_err("[MIPI-HSI] send step is not IDLE : %d\n", + channel->send_step); + return -EBUSY; + } + channel->send_step = STEP_SEND_OPEN_CONN; + + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + pr_debug("[MIPI-HSI] wake_lock\n"); + } + + if_hsi_set_wakeline(channel, 1); + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + pr_debug("[MIPI-HSI] mod_timer done(%d)\n", + HSI_ACWAKE_DOWN_TIMEOUT); + +retry_send: + + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_OPEN_CONN_OCTET, ch, + len); + if (ret) { + pr_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", ret); + if_hsi_set_wakeline(channel, 0); + channel->send_step = STEP_IDLE; + return -1; + } + + channel->send_step = STEP_WAIT_FOR_ACK; + + if (down_timeout(&channel->ack_done_sem, HSI_ACK_DONE_TIMEOUT) < 0) { + pr_err("[MIPI-HSI] ch=%d, ack_done timeout\n", + channel->channel_id); + + list_for_each_entry(iod, &mipi_ld->list_of_io_devices, list) + if (iod->format == IPC_FMT) + break; + + if (((mipi_ld->ld.com_state == COM_ONLINE) || + (mipi_ld->ld.com_state == COM_HANDSHAKE)) && + (iod->mc->phone_state == STATE_ONLINE)) { + channel->send_step = STEP_SEND_OPEN_CONN; + hsi_conn_err_recovery(mipi_ld); + + ack_timeout_cnt++; + if (ack_timeout_cnt < 5) { + pr_err("[MIPI-HSI] check ack again. cnt:%d\n", + ack_timeout_cnt); + msleep(10); + if (down_trylock(&channel->ack_done_sem)) { + pr_err("[MIPI-HSI] retry send open\n"); + if_hsi_set_wakeline(channel, 0); + if_hsi_set_wakeline(channel, 1); + sema_init(&channel->ack_done_sem, 0); + goto retry_send; + } else { + pr_err("[MIPI-HSI] got ack after sw-reset\n"); + goto check_nack; + } + } + + /* try to recover cp */ + list_for_each_entry(iod, &mipi_ld->list_of_io_devices, + list) { + if (iod->format == IPC_FMT) { + iod->modem_state_changed(iod, + STATE_CRASH_EXIT); + break; + } + } + } + + channel->send_step = STEP_IDLE; + return -ETIMEDOUT; + } + +check_nack: + + pr_debug("[MIPI-HSI] ch=%d, got ack_done=%d\n", channel->channel_id, + channel->got_nack); + + if (channel->got_nack && (retry_count < 10)) { + pr_debug("[MIPI-HSI] ch=%d, got nack=%d retry=%d\n", + channel->channel_id, channel->got_nack, + retry_count); + retry_count++; + msleep(1); + goto retry_send; + } + retry_count = 0; + + channel->send_step = STEP_TX; + + ret = if_hsi_write(channel, data, len); + if (ret < 0) { + pr_err("[MIPI-HSI] if_hsi_write fail : %d\n", ret); + if_hsi_set_wakeline(channel, 0); + channel->send_step = STEP_IDLE; + return ret; + } + pr_debug("[MIPI-HSI] SEND DATA : %08x(%d)\n", *data, len); + + pr_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n", + *channel->tx_data, *(channel->tx_data + 1), + *(channel->tx_data + 2), *(channel->tx_data + 3), + *(channel->tx_data + 4), *(channel->tx_data + 5), + *(channel->tx_data + 6), *(channel->tx_data + 7)); + + channel->send_step = STEP_WAIT_FOR_CONN_CLOSED; + if (down_timeout(&channel->close_conn_done_sem, + HSI_CLOSE_CONN_DONE_TIMEOUT) < 0) { + pr_err("[MIPI-HSI] ch=%d, close conn timeout\n", + channel->channel_id); + + channel->send_step = STEP_IDLE; + hsi_conn_err_recovery(mipi_ld); + } + pr_debug("[MIPI-HSI] ch=%d, got close_conn_done\n", + channel->channel_id); + + channel->send_step = STEP_IDLE; + + pr_debug("[MIPI-HSI] write protocol Done : %d\n", channel->tx_count); + return channel->tx_count; +} + +static int if_hsi_write(struct if_hsi_channel *channel, u32 *data, + unsigned int size) +{ + int ret; + unsigned long int flags; + + spin_lock_irqsave(&channel->tx_state_lock, flags); + if (channel->tx_state & HSI_CHANNEL_TX_STATE_WRITING) { + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + return -EBUSY; + } + channel->tx_state |= HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + channel->tx_data = data; + if (size % 4) + size += (4 - (size % 4)); + channel->tx_count = size; + + pr_debug("[MIPI-HSI] submit write data : 0x%x(%d)\n", + *(u32 *)channel->tx_data, channel->tx_count); + ret = hsi_write(channel->dev, channel->tx_data, channel->tx_count / 4); + if (ret) { + pr_err("[MIPI-HSI] ch=%d, hsi_write fail : %d\n", + channel->channel_id, ret); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + return ret; + } + + if (down_timeout(&channel->write_done_sem, + HSI_WRITE_DONE_TIMEOUT) < 0) { + pr_err("[MIPI-HSI] ch=%d, hsi_write_done timeout : %d\n", + channel->channel_id, size); + + print_hex_dump_bytes("[HSI]", DUMP_PREFIX_OFFSET, + channel->tx_data, size); + + hsi_write_cancel(channel->dev); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + return -ETIMEDOUT; + } + + if (channel->tx_count != size) + pr_err("[MIPI-HSI] ch:%d,write_done fail,write_size:%d,origin_size:%d\n", + channel->channel_id, channel->tx_count, size); + +#ifdef DEBUG + print_hex_dump_bytes("[HSI]", DUMP_PREFIX_OFFSET, + channel->tx_data, size); +#endif + + return channel->tx_count; +} + +static void if_hsi_write_done(struct hsi_device *dev, unsigned int size) +{ + unsigned long int flags; + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[dev->n_ch]; + + if ((channel->channel_id == HSI_CONTROL_CHANNEL) && + (((*channel->tx_data & 0xF0000000) >> 28) == + HSI_LL_MSG_CONN_CLOSED) && + mipi_ld->ld.com_state == COM_ONLINE) { + mipi_ld->hsi_channles[ + (*channel->tx_data & 0x0F000000) >> 24].recv_step = STEP_IDLE; + } + + pr_debug("[MIPI-HSI] got write data : 0x%x(%d)\n", + *(u32 *)channel->tx_data, size); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + pr_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n", + *channel->tx_data, *(channel->tx_data + 1), + *(channel->tx_data + 2), *(channel->tx_data + 3), + *(channel->tx_data + 4), *(channel->tx_data + 5), + *(channel->tx_data + 6), *(channel->tx_data + 7)); + + channel->tx_count = 4 * size; + up(&channel->write_done_sem); +} + +static void if_hsi_read_done(struct hsi_device *dev, unsigned int size) +{ + int ret; + unsigned long int flags; + u32 cmd = 0, ch = 0, param = 0; + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[dev->n_ch]; + struct io_device *iod; + enum dev_format format_type = 0; + + pr_debug("[MIPI-HSI] got read data : 0x%x(%d)\n", + *(u32 *)channel->rx_data, size); + + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + channel->rx_count = 4 * size; + + switch (channel->channel_id) { + case HSI_CONTROL_CHANNEL: + switch (mipi_ld->ld.com_state) { + case COM_HANDSHAKE: + case COM_ONLINE: + pr_debug("[MIPI-HSI] RECV CMD : %08x\n", + *channel->rx_data); + + if (channel->rx_count != 4) { + pr_err("[MIPI-HSI] wrong command len : %d\n", + channel->rx_count); + return; + } + + ret = if_hsi_decode_cmd(channel->rx_data, &cmd, &ch, + ¶m); + if (ret) + pr_err("[MIPI-HSI] decode_cmd fail=%d, cmd=%x\n", + ret, cmd); + else { + pr_debug("[MIPI-HSI] decode_cmd : %08x\n", cmd); + ret = if_hsi_rx_cmd_handle(mipi_ld, cmd, ch, + param); + if (ret) + pr_debug("[MIPI-HSI] handle cmd cmd=%x\n", + cmd); + } + + ret = hsi_read(channel->dev, channel->rx_data, 1); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + return; + + case COM_BOOT: + pr_debug("[MIPI-HSI] receive data : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + + list_for_each_entry(iod, &mipi_ld->list_of_io_devices, + list) { + if ((iod->format == IPC_BOOT) && + (iod->id == 0x0)) { + ret = iod->recv(iod, + (char *)channel->rx_data, + channel->rx_count); + if (ret < 0) + pr_err("[MIPI-HSI] recv call " + "fail : %d\n", ret); + + break; + } + } + + ret = hsi_read(channel->dev, channel->rx_data, 1); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + return; + + case COM_BOOT_EBL: + pr_debug("[MIPI-HSI] receive data : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + + list_for_each_entry(iod, &mipi_ld->list_of_io_devices, + list) { + if ((iod->format == IPC_BOOT) && + (iod->id == 0x1)) { + ret = iod->recv(iod, + (char *)channel->rx_data, + channel->rx_count); + if (ret < 0) + pr_err("[MIPI-HSI] recv call " + "fail : %d\n", ret); + + break; + } + } + + ret = hsi_read(channel->dev, channel->rx_data, 1); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + return; + + case COM_CRASH: + pr_debug("[MIPI-HSI] receive data : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + + list_for_each_entry(iod, &mipi_ld->list_of_io_devices, + list) { + if (iod->format == IPC_RAMDUMP) { + channel->packet_size = + *channel->rx_data; + pr_debug("[MIPI-HSI] ramdump packet size : " + "%d\n", channel->packet_size); + + ret = iod->recv(iod, + (char *)channel->rx_data + 4, + channel->packet_size); + if (ret < 0) + pr_err("[MIPI-HSI] recv call " + "fail : %d\n", ret); + + break; + } + } + + ret = hsi_read(channel->dev, channel->rx_data, + DUMP_PACKET_SIZE); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + return; + + case COM_NONE: + default: + pr_err("[MIPI-HSI] receive data in wrong state : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + return; + } + break; + + case HSI_FMT_CHANNEL: + pr_debug("[MIPI-HSI] iodevice format : IPC_FMT\n"); + format_type = IPC_FMT; + break; + case HSI_RAW_CHANNEL: + pr_debug("[MIPI-HSI] iodevice format : IPC_MULTI_RAW\n"); + format_type = IPC_MULTI_RAW; + break; + case HSI_RFS_CHANNEL: + pr_debug("[MIPI-HSI] iodevice format : IPC_RFS\n"); + format_type = IPC_RFS; + break; + + case HSI_CMD_CHANNEL: + pr_debug("[MIPI-HSI] receive command data : 0x%x\n", + *channel->rx_data); + + channel->recv_step = STEP_SEND_TO_CONN_CLOSED; + + ch = channel->channel_id; + param = 0; + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_CONN_CLOSED, + ch, param); + if (ret) + pr_err("[MIPI-HSI] send_cmd fail=%d\n", ret); + return; + + default: + return; + } + + list_for_each_entry(iod, &mipi_ld->list_of_io_devices, list) { + pr_debug("[MIPI-HSI] iodevice format : %d\n", iod->format); + + if (iod->format == format_type) { + channel->recv_step = STEP_NOT_READY; + + pr_debug("[MIPI-HSI] RECV DATA : %08x(%d)-%d\n", + *channel->rx_data, channel->packet_size, + iod->format); + + pr_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n", + *channel->rx_data, *(channel->rx_data + 1), + *(channel->rx_data + 2), *(channel->rx_data + 3), + *(channel->rx_data + 4), *(channel->rx_data + 5), + *(channel->rx_data + 6), *(channel->rx_data + 7)); + + ret = iod->recv(iod, (char *)channel->rx_data, + channel->packet_size); + if (ret < 0) { + pr_err("[MIPI-HSI] recv call fail : %d\n", ret); + print_hex_dump_bytes("[HSI]", + DUMP_PREFIX_OFFSET, + channel->rx_data, channel->packet_size); + hsi_conn_err_recovery(mipi_ld); + } + channel->packet_size = 0; + + channel->recv_step = STEP_SEND_TO_CONN_CLOSED; + + ch = channel->channel_id; + param = 0; + ret = if_hsi_send_command(mipi_ld, + HSI_LL_MSG_CONN_CLOSED, ch, param); + if (ret) + pr_err("[MIPI-HSI] send_cmd fail=%d\n", ret); + return; + } + } +} + +static void if_hsi_port_event(struct hsi_device *dev, unsigned int event, + void *arg) +{ + int acwake_level = 1; + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + + switch (event) { + case HSI_EVENT_BREAK_DETECTED: + pr_err("[MIPI-HSI] HSI_EVENT_BREAK_DETECTED\n"); + return; + + case HSI_EVENT_HSR_DATAAVAILABLE: + pr_err("[MIPI-HSI] HSI_EVENT_HSR_DATAAVAILABLE\n"); + return; + + case HSI_EVENT_CAWAKE_UP: + if (dev->n_ch == HSI_CONTROL_CHANNEL) { + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + pr_debug("[MIPI-HSI] wake_lock\n"); + } + pr_debug("[MIPI-HSI] CAWAKE_%d(1)\n", dev->n_ch); + } + return; + + case HSI_EVENT_CAWAKE_DOWN: + if (dev->n_ch == HSI_CONTROL_CHANNEL) + pr_debug("[MIPI-HSI] CAWAKE_%d(0)\n", dev->n_ch); + + if ((dev->n_ch == HSI_CONTROL_CHANNEL) && + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].opened) { + hsi_ioctl( + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + HSI_IOCTL_GET_ACWAKE, &acwake_level); + + pr_debug("[MIPI-HSI] GET_ACWAKE. Ch : %d, level : %d\n", + dev->n_ch, acwake_level); + + if (!acwake_level) { + wake_unlock(&mipi_ld->wlock); + pr_debug("[MIPI-HSI] wake_unlock\n"); + } + } + return; + + case HSI_EVENT_ERROR: + pr_err("[MIPI-HSI] HSI_EVENT_ERROR\n"); + return; + + default: + pr_err("[MIPI-HSI] Unknown Event : %d\n", event); + return; + } +} + +static int __devinit if_hsi_probe(struct hsi_device *dev) +{ + int port = 0; + unsigned long *address; + + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + + for (port = 0; port < HSI_MAX_PORTS; port++) { + if (if_hsi_driver.ch_mask[port]) + break; + } + address = (unsigned long *)&if_hsi_driver.ch_mask[port]; + + if (test_bit(dev->n_ch, address) && (dev->n_p == port)) { + /* Register callback func */ + hsi_set_write_cb(dev, if_hsi_write_done); + hsi_set_read_cb(dev, if_hsi_read_done); + hsi_set_port_event_cb(dev, if_hsi_port_event); + + /* Init device data */ + mipi_ld->hsi_channles[dev->n_ch].dev = dev; + mipi_ld->hsi_channles[dev->n_ch].tx_count = 0; + mipi_ld->hsi_channles[dev->n_ch].rx_count = 0; + mipi_ld->hsi_channles[dev->n_ch].tx_state = 0; + mipi_ld->hsi_channles[dev->n_ch].rx_state = 0; + mipi_ld->hsi_channles[dev->n_ch].packet_size = 0; + mipi_ld->hsi_channles[dev->n_ch].acwake = 0; + mipi_ld->hsi_channles[dev->n_ch].send_step = STEP_UNDEF; + mipi_ld->hsi_channles[dev->n_ch].recv_step = STEP_UNDEF; + spin_lock_init(&mipi_ld->hsi_channles[dev->n_ch].tx_state_lock); + spin_lock_init(&mipi_ld->hsi_channles[dev->n_ch].rx_state_lock); + spin_lock_init(&mipi_ld->hsi_channles[dev->n_ch].acwake_lock); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].write_done_sem, 0); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].ack_done_sem, 0); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].close_conn_done_sem, + 0); + } + + pr_debug("[MIPI-HSI] if_hsi_probe() done. ch : %d\n", dev->n_ch); + return 0; +} + +static int if_hsi_init(struct link_device *ld) +{ + int ret; + int i = 0; + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + for (i = 0; i < HSI_MAX_PORTS; i++) + if_hsi_driver.ch_mask[i] = 0; + + for (i = 0; i < HSI_MAX_CHANNELS; i++) { + mipi_ld->hsi_channles[i].dev = NULL; + mipi_ld->hsi_channles[i].opened = 0; + mipi_ld->hsi_channles[i].channel_id = i; + } + if_hsi_driver.ch_mask[0] = CHANNEL_MASK; + + /* TODO - need to get priv data (request to TI) */ + if_hsi_driver.priv_data = (void *)mipi_ld; + ret = hsi_register_driver(&if_hsi_driver); + if (ret) { + pr_err("[MIPI-HSI] hsi_register_driver() fail : %d\n", ret); + return ret; + } + + mipi_ld->mipi_wq = alloc_workqueue("mipi_cmd_wq", + WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1); + if (!mipi_ld->mipi_wq) { + pr_err("[MIPI-HSI] fail to create work Q.\n"); + return -ENOMEM; + } + INIT_DELAYED_WORK(&mipi_ld->cmd_work, if_hsi_cmd_work); + INIT_DELAYED_WORK(&mipi_ld->start_work, mipi_hsi_start_work); + + setup_timer(&mipi_ld->hsi_acwake_down_timer, if_hsi_acwake_down_func, + (unsigned long)mipi_ld); + + /* TODO - allocate rx buff */ + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data = + kmalloc(64 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_CONTROL_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_FMT_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_FMT_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_FMT_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_RAW_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_RAW_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_RAW_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_RFS_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_RFS_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_RFS_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_CMD_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_CMD_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_CMD_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + + mipi_ld->bulk_tx_buf = kmalloc(MIPI_BULK_TX_SIZE, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->bulk_tx_buf) { + pr_err("[MIPI-HSI] alloc bulk tx buffer fail\n"); + return -ENOMEM; + } + + skb_queue_head_init(&mipi_ld->bulk_txq); + + return 0; +} + +struct link_device *mipi_create_link_device(struct platform_device *pdev) +{ + int ret; + struct mipi_link_device *mipi_ld; + struct link_device *ld; + + /* for dpram int */ + /* struct modem_data *pdata = pdev->dev.platform_data; */ + + mipi_ld = kzalloc(sizeof(struct mipi_link_device), GFP_KERNEL); + if (!mipi_ld) + return NULL; + + INIT_LIST_HEAD(&mipi_ld->list_of_io_devices); + INIT_LIST_HEAD(&mipi_ld->list_of_hsi_cmd); + spin_lock_init(&mipi_ld->list_cmd_lock); + skb_queue_head_init(&mipi_ld->ld.sk_fmt_tx_q); + skb_queue_head_init(&mipi_ld->ld.sk_raw_tx_q); + + wake_lock_init(&mipi_ld->wlock, WAKE_LOCK_SUSPEND, "mipi_link"); + + ld = &mipi_ld->ld; + + ld->name = "mipi_hsi"; + ld->attach = mipi_hsi_attach_io_dev; + ld->init_comm = mipi_hsi_init_communication; + ld->terminate_comm = mipi_hsi_terminate_communication; + ld->send = mipi_hsi_send; + ld->com_state = COM_NONE; + + /* for dpram int */ + /* ld->irq = gpio_to_irq(pdata->gpio); s*/ + + ld->tx_wq = create_singlethread_workqueue("mipi_tx_wq"); + if (!ld->tx_wq) { + pr_err("[MIPI-HSI] fail to create work Q.\n"); + return NULL; + } + INIT_WORK(&ld->tx_work, mipi_hsi_tx_work); + + ld->tx_raw_wq = alloc_workqueue("mipi_tx_raw_wq", + WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1); + if (!ld->tx_raw_wq) { + pr_err("[MIPI-HSI] fail to create raw work Q.\n"); + return NULL; + } + INIT_DELAYED_WORK(&ld->tx_delayed_work, mipi_hsi_tx_raw_work); + + ret = if_hsi_init(ld); + if (ret) + return NULL; + + return ld; +} + diff --git a/drivers/misc/modem_if/modem_link_device_mipi.h b/drivers/misc/modem_if/modem_link_device_mipi.h new file mode 100755 index 0000000..fb628a3 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_mipi.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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 __MODEM_LINK_DEVICE_MIPI_H__ +#define __MODEM_LINK_DEVICE_MIPI_H__ + + +#define HSI_MAX_CHANNELS 16 +#define CHANNEL_MASK 0xFF + +#define HSI_CHANNEL_TX_STATE_UNAVAIL (1 << 0) +#define HSI_CHANNEL_TX_STATE_WRITING (1 << 1) +#define HSI_CHANNEL_RX_STATE_UNAVAIL (1 << 0) +#define HSI_CHANNEL_RX_STATE_READING (1 << 1) + +#define HSI_WRITE_DONE_TIMEOUT (HZ) +#define HSI_READ_DONE_TIMEOUT (HZ) +#define HSI_ACK_DONE_TIMEOUT (HZ / 2) +#define HSI_CLOSE_CONN_DONE_TIMEOUT (HZ / 10) +#define HSI_ACWAKE_DOWN_TIMEOUT (HZ / 2) + +#define HSI_CONTROL_CHANNEL 0 +#define HSI_FLASHLESS_CHANNEL 0 +#define HSI_CP_RAMDUMP_CHANNEL 0 +#define HSI_FMT_CHANNEL 1 +#define HSI_RAW_CHANNEL 2 +#define HSI_RFS_CHANNEL 3 +#define HSI_CMD_CHANNEL 4 +#define HSI_NUM_OF_USE_CHANNELS 5 + +#define HSI_LL_INVALID_CHANNEL 0xFF + +#define DUMP_PACKET_SIZE 12289 /* 48K + 4 length, word unit */ +#define DUMP_ERR_INFO_SIZE 39 /* 150 bytes + 4 length , word unit */ + +#define MIPI_BULK_TX_SIZE (8 * 1024) + +enum { + HSI_LL_MSG_BREAK, /* 0x0 */ + HSI_LL_MSG_ECHO, + HSI_LL_MSG_INFO_REQ, + HSI_LL_MSG_INFO, + HSI_LL_MSG_CONFIGURE, + HSI_LL_MSG_ALLOCATE_CH, + HSI_LL_MSG_RELEASE_CH, + HSI_LL_MSG_OPEN_CONN, + HSI_LL_MSG_CONN_READY, + HSI_LL_MSG_CONN_CLOSED, /* 0x9 */ + HSI_LL_MSG_CANCEL_CONN, + HSI_LL_MSG_ACK, /* 0xB */ + HSI_LL_MSG_NAK, /* 0xC */ + HSI_LL_MSG_CONF_RATE, + HSI_LL_MSG_OPEN_CONN_OCTET, /* 0xE */ + HSI_LL_MSG_INVALID = 0xFF, +}; + +enum { + STEP_UNDEF, + STEP_CLOSED, + STEP_NOT_READY, + STEP_IDLE, + STEP_ERROR, + STEP_SEND_OPEN_CONN, + STEP_SEND_ACK, + STEP_WAIT_FOR_ACK, + STEP_TO_ACK, + STEP_SEND_NACK, + STEP_GET_NACK, + STEP_SEND_CONN_READY, + STEP_WAIT_FOR_CONN_READY, + STEP_SEND_CONF_RATE, + STEP_WAIT_FOR_CONF_ACK, + STEP_TX, + STEP_RX, + STEP_SEND_CONN_CLOSED, + STEP_WAIT_FOR_CONN_CLOSED, + STEP_SEND_BREAK, + STEP_SEND_TO_CONN_CLOSED, +}; + + +struct if_hsi_channel { + struct hsi_device *dev; + unsigned int channel_id; + + u32 *tx_data; + unsigned int tx_count; + u32 *rx_data; + unsigned int rx_count; + unsigned int packet_size; + + unsigned int tx_state; + unsigned int rx_state; + spinlock_t tx_state_lock; + spinlock_t rx_state_lock; + + unsigned int send_step; + unsigned int recv_step; + + unsigned int got_nack; + unsigned int acwake; + spinlock_t acwake_lock; + + struct semaphore write_done_sem; + struct semaphore ack_done_sem; + struct semaphore close_conn_done_sem; + + unsigned int opened; +}; + +struct if_hsi_command { + u32 command; + struct list_head list; +}; + +struct mipi_link_device { + struct link_device ld; + + /* mipi specific link data */ + struct if_hsi_channel hsi_channles[HSI_MAX_CHANNELS]; + struct list_head list_of_hsi_cmd; + spinlock_t list_cmd_lock; + + struct workqueue_struct *mipi_wq; + struct delayed_work cmd_work; + struct delayed_work start_work; + + struct wake_lock wlock; + struct timer_list hsi_acwake_down_timer; + + /* maybe -list of io devices for the link device to use + * to find where to send incoming packets to */ + struct list_head list_of_io_devices; + + void *bulk_tx_buf; + struct sk_buff_head bulk_txq; +}; +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_mipi_link_device(linkdev) \ + container_of(linkdev, struct mipi_link_device, ld) + + +enum { + HSI_INIT_MODE_NORMAL, + HSI_INIT_MODE_FLASHLESS_BOOT, + HSI_INIT_MODE_CP_RAMDUMP, + HSI_INIT_MODE_FLASHLESS_BOOT_EBL, +}; +static int hsi_init_handshake(struct mipi_link_device *mipi_ld, int mode); +static int if_hsi_write(struct if_hsi_channel *channel, u32 *data, + unsigned int size); +static int if_hsi_protocol_send(struct mipi_link_device *mipi_ld, int ch, + u32 *data, unsigned int len); +static int if_hsi_close_channel(struct if_hsi_channel *channel); + +#endif diff --git a/drivers/misc/modem_if/modem_link_device_usb.c b/drivers/misc/modem_if/modem_link_device_usb.c new file mode 100755 index 0000000..c1280ac --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_usb.c @@ -0,0 +1,873 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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/device.h> +#include <linux/sched.h> +#include <linux/irq.h> +#include <linux/poll.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_usb.h" + +#define URB_COUNT 4 + +static irqreturn_t usb_resume_irq(int irq, void *data); + +static int usb_attach_io_dev(struct link_device *ld, + struct io_device *iod) +{ + struct usb_link_device *usb_ld = to_usb_link_device(ld); + + iod->link = ld; + + /* list up io devices */ + list_add(&iod->list, &usb_ld->list_of_io_devices); + + return 0; +} + +static void +usb_free_urbs(struct usb_link_device *usb_ld, struct if_usb_devdata *pipe) +{ + struct usb_device *usbdev = usb_ld->usbdev; + struct urb *urb; + + while ((urb = usb_get_from_anchor(&pipe->urbs))) { + usb_poison_urb(urb); + usb_free_coherent(usbdev, pipe->rx_buf_size, + urb->transfer_buffer, urb->transfer_dma); + urb->transfer_buffer = NULL; + usb_put_urb(urb); + usb_free_urb(urb); + } +} + +static int usb_init_communication(struct link_device *ld, + struct io_device *iod) +{ + switch (iod->format) { + case IPC_BOOT: + ld->com_state = COM_BOOT; + skb_queue_purge(&ld->sk_fmt_tx_q); + break; + + case IPC_RAMDUMP: + ld->com_state = COM_CRASH; + break; + + case IPC_FMT: + case IPC_RFS: + case IPC_RAW: + default: + ld->com_state = COM_ONLINE; + break; + } + + pr_debug("%s: iod = %s, com_state = %d\n", __func__, iod->name, + ld->com_state); + return 0; +} + +static void usb_terminate_communication( + struct link_device *ld, struct io_device *iod) +{ + pr_debug("%s: iod = %s, com_state = %d\n", __func__, iod->name, + ld->com_state); +} + + +static int usb_rx_submit(struct if_usb_devdata *pipe, struct urb *urb, gfp_t gfp_flags) +{ + int ret; + + usb_anchor_urb(urb, &pipe->reading); + ret = usb_submit_urb(urb, gfp_flags); + if (ret) { + usb_unanchor_urb(urb); + usb_anchor_urb(urb, &pipe->urbs); + pr_err("%s: submit urb fail with ret (%d)\n", __func__, ret); + } + + usb_mark_last_busy(urb->dev); + return ret; +} + +static void usb_rx_complete(struct urb *urb) +{ + struct if_usb_devdata *pipe_data = urb->context; + struct usb_link_device *usb_ld = usb_get_intfdata(pipe_data->data_intf); + struct io_device *iod; + int iod_format = IPC_FMT; + int ret; + + switch (urb->status) { + case 0: + case -ENOENT: + if (!urb->actual_length) + goto re_submit; + /* call iod recv */ + /* how we can distinguish boot ch with fmt ch ?? */ + switch (pipe_data->format) { + case IF_USB_FMT_EP: + iod_format = IPC_FMT; + break; + case IF_USB_RAW_EP: + iod_format = IPC_MULTI_RAW; + break; + case IF_USB_RFS_EP: + iod_format = IPC_RFS; + break; + default: + break; + } + + list_for_each_entry(iod, &usb_ld->list_of_io_devices, list) { + /* during boot stage fmt end point */ + /* shared with boot io device */ + /* when we use fmt device only, at boot and ipc exchange + it can be reduced to 1 device */ + if (iod_format == IPC_FMT && + usb_ld->ld.com_state == COM_BOOT) + iod_format = IPC_BOOT; + if (iod_format == IPC_FMT && + usb_ld->ld.com_state == COM_CRASH) + iod_format = IPC_RAMDUMP; + + if (iod->format == iod_format) { + ret = iod->recv(iod, + (char *)urb->transfer_buffer, + urb->actual_length); + if (ret < 0) + pr_err("%s: io device recv error :%d\n", + __func__, ret); + break; + } + } +re_submit: + if (urb->status || atomic_read(&usb_ld->suspend_count)) + break; + usb_rx_submit(pipe_data, urb, GFP_ATOMIC); + return; + case -ESHUTDOWN: + case -EPROTO: + break; + case -EOVERFLOW: + pr_err("%s: RX overflow\n", __func__); + break; + default: + pr_err("%s: RX complete Status (%d)\n", __func__, urb->status); + break; + } + + usb_anchor_urb(urb, &pipe_data->urbs); +} + +static int usb_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + struct sk_buff_head *txq; + + if (iod->format == IPC_RAW) + txq = &ld->sk_raw_tx_q; + else + txq = &ld->sk_fmt_tx_q; + + /* save io device into cb area */ + *((struct io_device **)skb->cb) = iod; + /* en queue skb data */ + skb_queue_tail(txq, skb); + + queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0); + + return skb->len; +} + +static void usb_tx_complete(struct urb *urb) +{ + int ret = 0; + struct sk_buff *skb = urb->context; + + switch (urb->status) { + case 0: + break; + default: + pr_err("%s:TX error (%d)\n", __func__, urb->status); + } + + usb_mark_last_busy(urb->dev); + ret = pm_runtime_put_autosuspend(&urb->dev->dev); + if (ret < 0) + pr_debug("%s pm_runtime_put_autosuspend failed : ret(%d)\n", __func__, ret); + usb_free_urb(urb); + dev_kfree_skb_any(skb); +} + +static void if_usb_force_disconnect(struct work_struct *work) +{ + struct usb_link_device *usb_ld = + container_of(work, struct usb_link_device, disconnect_work); + struct usb_device *udev = usb_ld->usbdev; + + pm_runtime_get_sync(&udev->dev); + if (udev->state != USB_STATE_NOTATTACHED) { + usb_force_disconnect(udev); + pr_info("force disconnect by modem not responding!!\n"); + } + pm_runtime_put_autosuspend(&udev->dev); +} + +static void +usb_change_modem_state(struct usb_link_device *usb_ld, enum modem_state state) +{ + struct io_device *iod; + + list_for_each_entry(iod, &usb_ld->list_of_io_devices, list) { + if (iod->format == IPC_FMT) { + iod->modem_state_changed(iod, state); + return; + } + } +} + +static int usb_tx_urb_with_skb(struct usb_link_device *usb_ld, + struct sk_buff *skb, struct if_usb_devdata *pipe_data) +{ + int ret, cnt = 0; + struct urb *urb; + struct usb_device *usbdev = usb_ld->usbdev; + unsigned long flags; + + if (!usbdev || (usbdev->state == USB_STATE_NOTATTACHED) || + usb_ld->host_wake_timeout_flag) + return -ENODEV; + + pm_runtime_get_noresume(&usbdev->dev); + + if (usbdev->dev.power.runtime_status == RPM_SUSPENDED || + usbdev->dev.power.runtime_status == RPM_SUSPENDING) { + usb_ld->resume_status = AP_INITIATED_RESUME; + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + + while (!wait_event_interruptible_timeout(usb_ld->l2_wait, + usbdev->dev.power.runtime_status == RPM_ACTIVE || + pipe_data->disconnected, + HOST_WAKEUP_TIMEOUT_JIFFIES)) { + + if (cnt == MAX_RETRY) { + pr_err("host wakeup timeout !!\n"); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + pm_runtime_put_autosuspend(&usbdev->dev); + schedule_work(&usb_ld->disconnect_work); + usb_ld->host_wake_timeout_flag = 1; + return -1; + } + pr_err("host wakeup timeout ! retry..\n"); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + udelay(100); + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + cnt++; + } + + if (pipe_data->disconnected) { + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + pm_runtime_put_autosuspend(&usbdev->dev); + return -ENODEV; + } + + pr_debug("wait_q done (runtime_status=%d)\n", + usbdev->dev.power.runtime_status); + } + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + pr_err("%s alloc urb error\n", __func__); + if (pm_runtime_put_autosuspend(&usbdev->dev) < 0) + pr_debug("pm_runtime_put_autosuspend fail\n"); + return -ENOMEM; + } + + urb->transfer_flags = URB_ZERO_PACKET; + usb_fill_bulk_urb(urb, usbdev, pipe_data->tx_pipe, skb->data, + skb->len, usb_tx_complete, (void *)skb); + + spin_lock_irqsave(&usb_ld->lock, flags); + if (atomic_read(&usb_ld->suspend_count)) { + /* transmission will be done in resume */ + usb_anchor_urb(urb, &usb_ld->deferred); + usb_put_urb(urb); + pr_debug("%s: anchor urb (0x%p)\n", __func__, urb); + spin_unlock_irqrestore(&usb_ld->lock, flags); + return 0; + } + spin_unlock_irqrestore(&usb_ld->lock, flags); + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + pr_err("%s usb_submit_urb with ret(%d)\n", __func__, ret); + if (pm_runtime_put_autosuspend(&usbdev->dev) < 0) + pr_debug("pm_runtime_put_autosuspend fail\n"); + } + return ret; +} + +static void usb_tx_work(struct work_struct *work) +{ + int ret = 0; + struct link_device *ld = + container_of(work, struct link_device, tx_delayed_work.work); + struct usb_link_device *usb_ld = to_usb_link_device(ld); + struct io_device *iod; + struct sk_buff *skb; + struct if_usb_devdata *pipe_data; + + while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) { + /* send skb from fmt_txq and raw_txq, + * one by one for fair flow control */ + skb = skb_dequeue(&ld->sk_fmt_tx_q); + if (skb) { + iod = *((struct io_device **)skb->cb); + switch (iod->format) { + case IPC_BOOT: + case IPC_RAMDUMP: + case IPC_FMT: + /* boot device uses same intf with fmt*/ + pipe_data = &usb_ld->devdata[IF_USB_FMT_EP]; + break; + case IPC_RFS: + pipe_data = &usb_ld->devdata[IF_USB_RFS_EP]; + break; + default: + /* wrong packet for fmt tx q , drop it */ + dev_kfree_skb_any(skb); + continue; + } + + ret = usb_tx_urb_with_skb(usb_ld, skb, pipe_data); + if (ret < 0) { + pr_err("%s usb_tx_urb_with_skb for iod(%d), ret(%d)\n", + __func__, iod->format, ret); + skb_queue_head(&ld->sk_fmt_tx_q, skb); + return; + } + } + + skb = skb_dequeue(&ld->sk_raw_tx_q); + if (skb) { + pipe_data = &usb_ld->devdata[IF_USB_RAW_EP]; + ret = usb_tx_urb_with_skb(usb_ld, skb, pipe_data); + if (ret < 0) { + pr_err("%s usb_tx_urb_with_skb for raw, ret(%d)\n", + __func__, ret); + skb_queue_head(&ld->sk_raw_tx_q, skb); + return; + } + } + } +} + +static int if_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_link_device *usb_ld = usb_get_intfdata(intf); + int i; + + if (atomic_inc_return(&usb_ld->suspend_count) == IF_USB_DEVNUM_MAX) { + pr_debug("%s\n", __func__); + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) + usb_kill_anchored_urbs(&usb_ld->devdata[i].reading); + + wake_unlock(&usb_ld->susplock); + } + + return 0; +} + +static void runtime_pm_work(struct work_struct *work) +{ + struct usb_link_device *usb_ld = + container_of(work, struct usb_link_device, runtime_pm_work.work); + int ret; + + ret = pm_request_autosuspend(&usb_ld->usbdev->dev); + + if (ret == -EAGAIN || ret == 1) + queue_delayed_work(system_nrt_wq, &usb_ld->runtime_pm_work, + msecs_to_jiffies(50)); +} + +static void wait_enumeration_work(struct work_struct *work) +{ + struct usb_link_device *usb_ld = + container_of(work, struct usb_link_device, wait_enumeration.work); + if (usb_ld->if_usb_connected == 0) { + pr_err("USB disconnected and not enumerated for long time\n"); + usb_change_modem_state(usb_ld, STATE_CRASH_EXIT); + } +} + +static int if_usb_resume(struct usb_interface *intf) +{ + int i, ret; + struct sk_buff *skb; + struct usb_link_device *usb_ld = usb_get_intfdata(intf); + struct if_usb_devdata *pipe; + struct urb *urb; + + spin_lock_irq(&usb_ld->lock); + if (!atomic_dec_return(&usb_ld->suspend_count)) { + spin_unlock_irq(&usb_ld->lock); + + pr_debug("%s\n", __func__); + wake_lock(&usb_ld->susplock); + + /* HACK: Runtime pm does not allow requesting autosuspend from + * resume callback, delayed it after resume */ + queue_delayed_work(system_nrt_wq, &usb_ld->runtime_pm_work, + msecs_to_jiffies(50)); + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + pipe = &usb_ld->devdata[i]; + while ((urb = usb_get_from_anchor(&pipe->urbs))) { + ret = usb_rx_submit(pipe, urb, GFP_KERNEL); + if (ret < 0) { + usb_put_urb(urb); + pr_err("%s: usb_rx_submit error with (%d)\n", + __func__, ret); + return ret; + } + usb_put_urb(urb); + } + } + + while ((urb = usb_get_from_anchor(&usb_ld->deferred))) { + pr_debug("%s: got urb (0x%p) from anchor & resubmit\n", + __func__, urb); + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + pr_err("%s: resubmit failed\n", __func__); + skb = urb->context; + dev_kfree_skb_any(skb); + usb_free_urb(urb); + if (pm_runtime_put_autosuspend(&usb_ld->usbdev->dev) < 0) + pr_debug("pm_runtime_put_autosuspend fail\n"); + } + } + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + udelay(100); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + return 0; + } + + spin_unlock_irq(&usb_ld->lock); + return 0; +} + +static int if_usb_reset_resume(struct usb_interface *intf) +{ + int ret; + + pr_debug("%s\n", __func__); + ret = if_usb_resume(intf); + return ret; +} + +static struct usb_device_id if_usb_ids[] = { + { USB_DEVICE(0x04e8, 0x6999), /* CMC221 LTE Modem */ + /*.driver_info = 0,*/ + }, + { } /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, if_usb_ids); + +static struct usb_driver if_usb_driver; +static void if_usb_disconnect(struct usb_interface *intf) +{ + struct usb_link_device *usb_ld = usb_get_intfdata(intf); + struct usb_device *usbdev = usb_ld->usbdev; + int dev_id = intf->altsetting->desc.bInterfaceNumber; + struct if_usb_devdata *pipe_data = &usb_ld->devdata[dev_id]; + + usb_set_intfdata(intf, NULL); + + pipe_data->disconnected = 1; + smp_wmb(); + + wake_up(&usb_ld->l2_wait); + + if (usb_ld->if_usb_connected) { + disable_irq_wake(usb_ld->pdata->irq_host_wakeup); + free_irq(usb_ld->pdata->irq_host_wakeup, usb_ld); + } + usb_ld->if_usb_connected = 0; + usb_ld->flow_suspend = 1; + + dev_dbg(&usbdev->dev, "%s\n", __func__); + usb_ld->dev_count--; + usb_driver_release_interface(&if_usb_driver, pipe_data->data_intf); + + usb_kill_anchored_urbs(&pipe_data->reading); + usb_free_urbs(usb_ld, pipe_data); + + if (usb_ld->dev_count == 0) { + cancel_delayed_work_sync(&usb_ld->runtime_pm_work); + cancel_delayed_work_sync(&usb_ld->ld.tx_delayed_work); + usb_put_dev(usbdev); + usb_ld->usbdev = NULL; + schedule_delayed_work(&usb_ld->wait_enumeration, + WAIT_ENUMURATION_TIMEOUT_JIFFIES); + } +} + +static int __devinit if_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_host_interface *data_desc; + struct usb_link_device *usb_ld = + (struct usb_link_device *)id->driver_info; + struct link_device *ld = &usb_ld->ld; + struct usb_interface *data_intf; + struct usb_device *usbdev = interface_to_usbdev(intf); + struct device *dev; + struct if_usb_devdata *pipe; + struct urb *urb; + int i; + int j; + int dev_id; + int err; + + /* To detect usb device order probed */ + dev_id = intf->cur_altsetting->desc.bInterfaceNumber; + + if (dev_id >= IF_USB_DEVNUM_MAX) { + dev_err(&intf->dev, "Device id %d cannot support\n", + dev_id); + return -EINVAL; + } + + if (!usb_ld) { + dev_err(&intf->dev, + "if_usb device doesn't be allocated\n"); + err = ENOMEM; + goto out; + } + + pr_info("%s: probe dev_id=%d usb_device_id(0x%p), usb_ld (0x%p)\n", + __func__, dev_id, id, usb_ld); + + usb_ld->usbdev = usbdev; + + usb_get_dev(usbdev); + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + data_intf = usb_ifnum_to_if(usbdev, i); + + /* remap endpoint of RAW to no.1 for LTE modem */ + if (i == 0) + pipe = &usb_ld->devdata[1]; + else if (i == 1) + pipe = &usb_ld->devdata[0]; + else + pipe = &usb_ld->devdata[i]; + + pipe->disconnected = 0; + pipe->data_intf = data_intf; + data_desc = data_intf->cur_altsetting; + + /* Endpoints */ + if (usb_pipein(data_desc->endpoint[0].desc.bEndpointAddress)) { + pipe->rx_pipe = usb_rcvbulkpipe(usbdev, + data_desc->endpoint[0].desc.bEndpointAddress); + pipe->tx_pipe = usb_sndbulkpipe(usbdev, + data_desc->endpoint[1].desc.bEndpointAddress); + pipe->rx_buf_size = 1024*4; + } else { + pipe->rx_pipe = usb_rcvbulkpipe(usbdev, + data_desc->endpoint[1].desc.bEndpointAddress); + pipe->tx_pipe = usb_sndbulkpipe(usbdev, + data_desc->endpoint[0].desc.bEndpointAddress); + pipe->rx_buf_size = 1024*4; + } + + if (i == 0) { + dev_info(&usbdev->dev, "USB IF USB device found\n"); + } else { + err = usb_driver_claim_interface(&if_usb_driver, + data_intf, usb_ld); + if (err < 0) { + pr_err("%s - failed to cliam usb interface\n", + __func__); + goto out; + } + } + + usb_set_intfdata(data_intf, usb_ld); + usb_ld->dev_count++; + pm_suspend_ignore_children(&data_intf->dev, true); + + for (j = 0; j < URB_COUNT; j++) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + pr_err("%s: alloc urb fail\n", __func__); + err = -ENOMEM; + goto out2; + } + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = usb_alloc_coherent(usbdev, + pipe->rx_buf_size, GFP_KERNEL, &urb->transfer_dma); + if (!urb->transfer_buffer) { + pr_err("%s: Failed to allocate transfer buffer\n", __func__); + usb_free_urb(urb); + err = -ENOMEM; + goto out2; + } + + usb_fill_bulk_urb(urb, usbdev, pipe->rx_pipe, + urb->transfer_buffer, pipe->rx_buf_size, + usb_rx_complete, pipe); + usb_anchor_urb(urb, &pipe->urbs); + } + } + + /* temporary call reset_resume */ + atomic_set(&usb_ld->suspend_count, 1); + if_usb_reset_resume(data_intf); + atomic_set(&usb_ld->suspend_count, 0); + + SET_HOST_ACTIVE(usb_ld->pdata, 1); + usb_ld->host_wake_timeout_flag = 0; + + if (gpio_get_value(usb_ld->pdata->gpio_phone_active)) { + pm_runtime_set_autosuspend_delay( + &usbdev->dev, AUTOSUSPEND_DELAY_MS); + dev = &usb_ld->usbdev->dev; + if (dev->parent) { + dev_dbg(&usbdev->dev, "if_usb Runtime PM Start!!\n"); + usb_enable_autosuspend(usb_ld->usbdev); + } + usb_ld->if_usb_connected = 1; + usb_ld->flow_suspend = 0; + err = request_threaded_irq(usb_ld->pdata->irq_host_wakeup, + NULL, + usb_resume_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "modem_usb_wake", + usb_ld); + if (err) + pr_err("Failed to allocate an interrupt(%d)\n", + usb_ld->pdata->irq_host_wakeup); + + enable_irq_wake(usb_ld->pdata->irq_host_wakeup); + + /* Queue work if skbs were pending before a disconnect/probe */ + if (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) + queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0); + + usb_change_modem_state(usb_ld, STATE_ONLINE); + } else { + usb_change_modem_state(usb_ld, STATE_LOADER_DONE); + } + + return 0; + +out2: + usb_ld->dev_count--; + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) + usb_free_urbs(usb_ld, &usb_ld->devdata[i]); +out: + usb_set_intfdata(intf, NULL); + return err; +} + +static irqreturn_t usb_resume_irq(int irq, void *data) +{ + int ret; + struct usb_link_device *usb_ld = data; + int val; + struct device *dev; + + val = gpio_get_value(usb_ld->pdata->gpio_host_wakeup); + irq_set_irq_type(irq, val ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); + dev = &usb_ld->usbdev->dev; + wake_lock_timeout(&usb_ld->gpiolock, 100); + + pr_debug("< H-WUP %d\n", val); + + if (val == usb_ld->wake_status) { + pr_err("%s: Received spurious wake irq: %d", __func__, val); + return IRQ_HANDLED; + } + + usb_ld->wake_status = val; + + if (val) { + device_lock(dev); + if (dev->power.is_prepared || dev->power.is_suspended) { + pm_runtime_get_noresume(dev); + ret = 0; + } else { + ret = pm_runtime_get_sync(dev); + } + device_unlock(dev); + if (ret < 0) { + pr_err("%s pm_runtime_get fail (%d)\n", __func__, ret); + return IRQ_HANDLED; + } + } else { + if (usb_ld->resume_status == AP_INITIATED_RESUME) + wake_up(&usb_ld->l2_wait); + usb_ld->resume_status = CP_INITIATED_RESUME; + pm_runtime_mark_last_busy(&usb_ld->usbdev->dev); + pm_runtime_put_autosuspend(&usb_ld->usbdev->dev); + } + + return IRQ_HANDLED; +} + +static int if_usb_init(struct usb_link_device *usb_ld) +{ + int ret; + int i; + struct if_usb_devdata *pipe; + + /* give it to probe, or global variable needed */ + if_usb_ids[0].driver_info = (unsigned long)usb_ld; + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + pipe = &usb_ld->devdata[i]; + pipe->format = i; + pipe->disconnected = 1; + init_usb_anchor(&pipe->urbs); + init_usb_anchor(&pipe->reading); + } + + init_waitqueue_head(&usb_ld->l2_wait); + init_usb_anchor(&usb_ld->deferred); + + ret = usb_register(&if_usb_driver); + if (ret) { + pr_err("usb_register_driver() fail : %d\n", ret); + return ret; + } + + return 0; +} + +struct link_device *usb_create_link_device(void *data) +{ + int ret; + struct modem_data *pdata; + struct platform_device *pdev = (struct platform_device *)data; + struct usb_link_device *usb_ld; + struct link_device *ld; + + pdata = pdev->dev.platform_data; + + usb_ld = kzalloc(sizeof(struct usb_link_device), GFP_KERNEL); + if (!usb_ld) + return NULL; + + INIT_LIST_HEAD(&usb_ld->list_of_io_devices); + skb_queue_head_init(&usb_ld->ld.sk_fmt_tx_q); + skb_queue_head_init(&usb_ld->ld.sk_raw_tx_q); + spin_lock_init(&usb_ld->lock); + + ld = &usb_ld->ld; + usb_ld->pdata = pdata; + + ld->name = "usb"; + ld->attach = usb_attach_io_dev; + ld->init_comm = usb_init_communication; + ld->terminate_comm = usb_terminate_communication; + ld->send = usb_send; + ld->com_state = COM_NONE; + + /*ld->tx_wq = create_singlethread_workqueue("usb_tx_wq");*/ + ld->tx_wq = alloc_workqueue("usb_tx_wq", + WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1); + + if (!ld->tx_wq) { + pr_err("fail to create work Q.\n"); + return NULL; + } + + usb_ld->pdata->irq_host_wakeup = platform_get_irq(pdev, 1); + wake_lock_init(&usb_ld->gpiolock, WAKE_LOCK_SUSPEND, "modem_usb_gpio_wake"); + wake_lock_init(&usb_ld->susplock, WAKE_LOCK_SUSPEND, "modem_usb_suspend_block"); + + INIT_DELAYED_WORK(&ld->tx_delayed_work, usb_tx_work); + INIT_DELAYED_WORK(&usb_ld->runtime_pm_work, runtime_pm_work); + INIT_DELAYED_WORK(&usb_ld->wait_enumeration, wait_enumeration_work); + INIT_WORK(&usb_ld->disconnect_work, if_usb_force_disconnect); + + ret = if_usb_init(usb_ld); + if (ret) + return NULL; + + return ld; +} + +static struct usb_driver if_usb_driver = { + .name = "if_usb_driver", + .probe = if_usb_probe, + .disconnect = if_usb_disconnect, + .id_table = if_usb_ids, + .suspend = if_usb_suspend, + .resume = if_usb_resume, + .reset_resume = if_usb_reset_resume, + .supports_autosuspend = 1, +}; + +static void __exit if_usb_exit(void) +{ + usb_deregister(&if_usb_driver); +} + + +static int lte_wake_resume(struct device *pdev) +{ + struct modem_data *pdata = pdev->platform_data; + int val; + + val = gpio_get_value(pdata->gpio_host_wakeup); + if (!val) { + pr_debug("%s: > S-WUP 1\n", __func__); + gpio_set_value(pdata->gpio_slave_wakeup, 1); + } + + return 0; +} + +static const struct dev_pm_ops lte_wake_pm_ops = { + .resume = lte_wake_resume, +}; + +static struct platform_driver lte_wake_driver = { + .driver = { + .name = "modem_lte_wake", + .pm = <e_wake_pm_ops, + }, +}; + +static int __init lte_wake_init(void) +{ + return platform_driver_register(<e_wake_driver); +} +module_init(lte_wake_init); diff --git a/drivers/misc/modem_if/modem_link_device_usb.h b/drivers/misc/modem_if/modem_link_device_usb.h new file mode 100644 index 0000000..1a5bdd9 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_usb.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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 __MODEM_LINK_DEVICE_USB_H__ +#define __MODEM_LINK_DEVICE_USB_H__ + +#include <linux/usb.h> +#include <linux/wakelock.h> + +#define IF_USB_DEVNUM_MAX 3 + +#define IF_USB_FMT_EP 0 +#define IF_USB_RAW_EP 1 +#define IF_USB_RFS_EP 2 + +#define AUTOSUSPEND_DELAY_MS 500 +#define HOST_WAKEUP_TIMEOUT_JIFFIES msecs_to_jiffies(500) +#define WAIT_ENUMURATION_TIMEOUT_JIFFIES msecs_to_jiffies(15000) +#define MAX_RETRY 3 + +enum RESUME_STATUS { + CP_INITIATED_RESUME, + AP_INITIATED_RESUME, +}; + +struct if_usb_devdata { + struct usb_interface *data_intf; + unsigned int tx_pipe; + unsigned int rx_pipe; + u8 disconnected; + + int format; + struct usb_anchor urbs; + struct usb_anchor reading; + unsigned int rx_buf_size; +}; + +struct usb_link_device { + /*COMMON LINK DEVICE*/ + struct link_device ld; + + struct modem_data *pdata; + + /*USB SPECIFIC LINK DEVICE*/ + struct usb_device *usbdev; + struct if_usb_devdata devdata[IF_USB_DEVNUM_MAX]; + struct delayed_work runtime_pm_work; + struct delayed_work wait_enumeration; + struct work_struct disconnect_work; + + struct wake_lock gpiolock; + struct wake_lock susplock; + + unsigned int dev_count; + unsigned int suspended; + atomic_t suspend_count; + enum RESUME_STATUS resume_status; + int if_usb_connected; + int flow_suspend; + int host_wake_timeout_flag; + int wake_status; + + unsigned gpio_slave_wakeup; + unsigned gpio_host_wakeup; + unsigned gpio_host_active; + int irq_host_wakeup; + struct delayed_work dwork; + struct work_struct resume_work; + int cpcrash_flag; + wait_queue_head_t l2_wait; + + spinlock_t lock; + struct usb_anchor deferred; + + /*COMMON LINK DEVICE*/ + /* maybe -list of io devices for the link device to use */ + /* to find where to send incoming packets to */ + struct list_head list_of_io_devices; +}; +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_usb_link_device(linkdev) \ + container_of(linkdev, struct usb_link_device, ld) +#endif + +#define SET_SLAVE_WAKEUP(_pdata, _value) \ +do { \ + gpio_set_value(_pdata->gpio_slave_wakeup, _value); \ + pr_debug("> S-WUP %s\n", _value ? "1" : "0"); \ +} while (0) + +#define SET_HOST_ACTIVE(_pdata, _value) \ +do { \ + gpio_set_value(_pdata->gpio_host_active, _value); \ + pr_debug("> H-ACT %s\n", _value ? "1" : "0"); \ +} while (0) + diff --git a/drivers/misc/modem_if/modem_modemctl_device_cbp71.c b/drivers/misc/modem_if/modem_modemctl_device_cbp71.c new file mode 100755 index 0000000..fcb1c12 --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_cbp71.c @@ -0,0 +1,234 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_cbp7.1.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_dpram.h" + +#define PIF_TIMEOUT (180 * HZ) +#define DPRAM_INIT_TIMEOUT (15 * HZ) + +static int cbp71_on(struct modem_ctl *mc) +{ + int ret; + struct dpram_link_device *dpram_ld = + to_dpram_link_device(mc->iod->link); + + pr_info("[MODEM_IF] cbp71_on()\n"); + + if (!mc->gpio_cp_off || !mc->gpio_cp_reset) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_cp_off, 1); + msleep(600); + gpio_set_value(mc->gpio_cp_reset, 1); + gpio_set_value(mc->gpio_cp_off, 0); + + msleep(300); + gpio_set_value(mc->gpio_pda_active, 1); + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + + /* Wait here until the PHONE is up. + * Waiting as the this called from IOCTL->UM thread */ + pr_debug("[MODEM_IF] power control waiting for INT_MASK_CMD_PIF_INIT_DONE\n"); + + dpram_ld->clear_interrupt(dpram_ld); + + ret = wait_for_completion_interruptible_timeout( + &dpram_ld->dpram_init_cmd, DPRAM_INIT_TIMEOUT); + if (!ret) { + /* ret will be 0 on timeout, < zero if interrupted */ + pr_warn("[MODEM_IF] INIT_START cmd was not arrived.\n"); + pr_warn("init_cmd_wait_condition is 0 and wait timeout happend\n"); + return -ENXIO; + } + + ret = wait_for_completion_interruptible_timeout( + &dpram_ld->modem_pif_init_done, PIF_TIMEOUT); + if (!ret) { + pr_warn("[MODEM_IF] PIF init failed\n"); + pr_warn("pif_init_wait_condition is 0 and wait timeout happend\n"); + return -ENXIO; + } + + pr_debug("[MODEM_IF] complete cbp71_on\n"); + + mc->iod->modem_state_changed(mc->iod, STATE_ONLINE); + + return 0; +} + +static int cbp71_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] cbp71_off()\n"); + + if (!mc->gpio_cp_off || !mc->gpio_cp_reset) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_off, 1); + gpio_set_value(mc->gpio_cp_reset, 0); + + mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE); + + return 0; +} + +static int cbp71_reset(struct modem_ctl *mc) +{ + int ret = 0; + + pr_debug("[MODEM_IF] cbp71_reset()\n"); + + ret = cbp71_off(mc); + if (ret) + return -ENXIO; + + msleep(100); + + ret = cbp71_on(mc); + if (ret) + return -ENXIO; + + return 0; +} + +static int cbp71_boot_on(struct modem_ctl *mc) +{ + pr_debug("[MODEM_IF] cbp71_boot_on()\n"); + + if (!mc->gpio_cp_reset) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + gpio_set_value(mc->gpio_cp_reset, 0); + msleep(600); + gpio_set_value(mc->gpio_cp_reset, 1); + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + + return 0; +} + +static int cbp71_boot_off(struct modem_ctl *mc) +{ + pr_debug("[MODEM_IF] cbp71_boot_off()\n"); + return 0; +} + +static irqreturn_t phone_active_irq_handler(int irq, void *_mc) +{ + int phone_reset = 0; + int phone_active_value = 0; + int phone_state = 0; + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + if (!mc->gpio_cp_reset || !mc->gpio_phone_active) { + pr_err("[MODEM_IF] no gpio data\n"); + return IRQ_HANDLED; + } + + phone_reset = gpio_get_value(mc->gpio_cp_reset); + phone_active_value = gpio_get_value(mc->gpio_phone_active); + + if (phone_reset && phone_active_value) { + phone_state = STATE_ONLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } else if (phone_reset && !phone_active_value) { + if (mc->phone_state == STATE_ONLINE) { + phone_state = STATE_CRASH_EXIT; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, + phone_state); + } + } else { + phone_state = STATE_OFFLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } + + if (phone_active_value) + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_LOW); + else + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_HIGH); + + pr_info("phone_active_irq_handler : phone_state=%d\n", phone_state); + + return IRQ_HANDLED; +} + +static void cbp71_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = cbp71_on; + mc->ops.modem_off = cbp71_off; + mc->ops.modem_reset = cbp71_reset; + mc->ops.modem_boot_on = cbp71_boot_on; + mc->ops.modem_boot_off = cbp71_boot_off; +} + +int cbp71_init_modemctl_device(struct modem_ctl *mc, + struct modem_data *pdata) +{ + int ret = 0; + struct platform_device *pdev; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_reset_req_n = pdata->gpio_reset_req_n; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + mc->gpio_cp_off = pdata->gpio_cp_off; + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = platform_get_irq(pdev, 0); + + cbp71_get_ops(mc); + + ret = request_irq(mc->irq_phone_active, phone_active_irq_handler, + IRQF_TRIGGER_HIGH, "phone_active", mc); + if (ret) { + pr_err("[MODEM_IF]failed to irq_phone_active request_irq: %d\n" + , ret); + return ret; + } + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) { + pr_err("[MODEM_IF] %s: failed to enable_irq_wake:%d\n", + __func__, ret); + free_irq(mc->irq_phone_active, mc); + return ret; + } + + return ret; +} diff --git a/drivers/misc/modem_if/modem_modemctl_device_cmc221.c b/drivers/misc/modem_if/modem_modemctl_device_cmc221.c new file mode 100644 index 0000000..97428e9 --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_cmc221.c @@ -0,0 +1,254 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_cmc221.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_usb.h" + +static int cmc221_on(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] %s()\n", __func__); + + if (!mc->gpio_cp_off || !mc->gpio_cp_on || !mc->gpio_cp_reset) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_on, 1); + msleep(100); + gpio_set_value(mc->gpio_cp_reset, 1); + msleep(100); + gpio_set_value(mc->gpio_cp_off, 0); + msleep(300); + mc->phone_state = STATE_BOOTING; + return 0; +} + +static int cmc221_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] %s()\n", __func__); + + if (!mc->gpio_cp_off || !mc->gpio_cp_on || !mc->gpio_cp_reset) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_on, 0); + msleep(100); + gpio_set_value(mc->gpio_cp_off, 1); + msleep(100); + gpio_set_value(mc->gpio_cp_reset, 0); + + mc->phone_state = STATE_OFFLINE; + + return 0; +} + +static int cmc221_force_crash_exit(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] %s: # %d\n", __func__, ++(mc->crash_cnt)); + + mc->phone_state = STATE_CRASH_EXIT;/* DUMP START */ + + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, mc->phone_state); + + return 0; +} + +static int cmc221_dump_reset(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] %s()\n", __func__); + + if (!mc->gpio_cp_reset) + return -ENXIO; + + gpio_set_value(mc->gpio_host_active, 0); + mc->cpcrash_flag = 1; + + gpio_set_value(mc->gpio_cp_reset, 0); + msleep(100); + gpio_set_value(mc->gpio_cp_reset, 1); + msleep(300); + + mc->phone_state = STATE_BOOTING; + + return 0; +} + +static int cmc221_reset(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] %s()\n", __func__); + + if (!mc->gpio_cp_reset) + return -ENXIO; + + if (cmc221_off(mc)) + return -ENXIO; + msleep(100); + if (cmc221_on(mc)) + return -ENXIO; + + mc->phone_state = STATE_BOOTING; + + return 0; +} + +static int cmc221_boot_on(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] %s()\n", __func__); + return 0; +} + +static int cmc221_boot_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] %s()\n", __func__); + return 0; +} + +static int cmc221_get_active(struct modem_ctl *mc) +{ + if (!mc->gpio_phone_active || !mc->gpio_cp_reset) + return -ENXIO; + + pr_debug("cp %d phone %d\n", + gpio_get_value(mc->gpio_cp_reset), + gpio_get_value(mc->gpio_phone_active)); + + if (gpio_get_value(mc->gpio_cp_reset)) + return gpio_get_value(mc->gpio_phone_active); + + return 0; +} + + +static void mc_work(struct work_struct *work_arg) +{ + + struct modem_ctl *mc = container_of(work_arg, struct modem_ctl, + dwork.work); + + int phone_active; + + phone_active = cmc221_get_active(mc); + if (phone_active < 0) { + pr_err("[MODEM_IF] gpio not initialized\n"); + return; + } + + switch (mc->phone_state) { + case STATE_CRASH_EXIT: + case STATE_BOOTING: + case STATE_LOADER_DONE: + if (phone_active) { + if (mc->cpcrash_flag) { + pr_info("[MODEM_IF] LTE DUMP END!!\n"); + mc->cpcrash_flag = 0; + } + } + break; + case STATE_ONLINE: + if (!phone_active) { + pr_info("[MODEM_IF] LTE CRASHED!! LTE DUMP START!!\n"); + mc->phone_state = STATE_CRASH_EXIT; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, + mc->phone_state); + } + break; + default: + mc->phone_state = STATE_OFFLINE; + pr_err("[MODEM_IF], phone_status changed to invalid!!\n"); + break; + } +} + + + +static irqreturn_t phone_active_irq_handler(int irq, void *_mc) +{ + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + schedule_delayed_work(&mc->dwork, 20); + + return IRQ_HANDLED; +} + +static void cmc221_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = cmc221_on; + mc->ops.modem_off = cmc221_off; + mc->ops.modem_reset = cmc221_reset; + mc->ops.modem_boot_on = cmc221_boot_on; + mc->ops.modem_boot_off = cmc221_boot_off; + mc->ops.modem_force_crash_exit = cmc221_force_crash_exit; + mc->ops.modem_dump_reset = cmc221_dump_reset; +} + +int cmc221_init_modemctl_device(struct modem_ctl *mc, + struct modem_data *pdata) +{ + int ret = 0; + struct platform_device *pdev; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_reset_req_n = pdata->gpio_reset_req_n; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + mc->gpio_cp_off = pdata->gpio_cp_off; + mc->gpio_slave_wakeup = pdata->gpio_slave_wakeup; + mc->gpio_host_active = pdata->gpio_host_active; + mc->gpio_host_wakeup = pdata->gpio_host_wakeup; + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = platform_get_irq(pdev, 0); + mc->irq_host_wakeup = platform_get_irq(pdev, 1); + + cmc221_get_ops(mc); + + dev_set_drvdata(mc->dev, mc); + + INIT_DELAYED_WORK(&mc->dwork, mc_work); + + ret = request_irq(mc->irq_phone_active, phone_active_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "phone_active", mc); + if (ret) { + pr_err("[MODEM_IF] Failed to allocate an interrupt(%d)\n", + mc->irq_phone_active); + goto irq_fail; + } + mc->irq[0] = mc->irq_phone_active; + enable_irq_wake(mc->irq_phone_active); + /*disable_irq(mc->irq_phone_active);*/ + + return ret; + +irq_fail: + kfree(mc); + return ret; +} diff --git a/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c b/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c new file mode 100755 index 0000000..c5e6cb7 --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c @@ -0,0 +1,213 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" + +static int xmm6260_on(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] xmm6260_on()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on || !mc->gpio_reset_req_n) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + udelay(160); + gpio_set_value(mc->gpio_pda_active, 0); + msleep(100); + gpio_set_value(mc->gpio_cp_reset, 1); + udelay(160); + gpio_set_value(mc->gpio_reset_req_n, 1); + udelay(160); + gpio_set_value(mc->gpio_cp_on, 1); + udelay(60); + gpio_set_value(mc->gpio_cp_on, 0); + msleep(20); + gpio_set_value(mc->gpio_pda_active, 1); + + mc->phone_state = STATE_BOOTING; + + return 0; +} + +static int xmm6260_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] xmm6260_off()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + + mc->phone_state = STATE_OFFLINE; + + return 0; +} + + +static int xmm6260_reset(struct modem_ctl *mc) +{ + int ret; + + pr_info("[MODEM_IF] xmm6260_reset()\n"); + + ret = xmm6260_off(mc); + if (ret) + return ret; + + msleep(100); + + ret = xmm6260_on(mc); + if (ret) + return ret; + + return 0; +} + +static int xmm6260_boot_on(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] xmm6260_boot_on()\n"); + + if (!mc->gpio_flm_uart_sel) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_flm_uart_sel, 0); + + return 0; +} + +static int xmm6260_boot_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] xmm6260_boot_off()\n"); + + if (!mc->gpio_flm_uart_sel) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_flm_uart_sel, 1); + + return 0; +} + +static irqreturn_t phone_active_irq_handler(int irq, void *_mc) +{ + int phone_reset = 0; + int phone_active_value = 0; + int phone_state = 0; + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + disable_irq_nosync(mc->irq_phone_active); + + if (!mc->gpio_cp_reset || !mc->gpio_phone_active || + !mc->gpio_cp_dump_int) { + pr_err("[MODEM_IF] no gpio data\n"); + return IRQ_HANDLED; + } + + phone_reset = gpio_get_value(mc->gpio_cp_reset); + phone_active_value = gpio_get_value(mc->gpio_phone_active); + + pr_info("[MODEM_IF] PA EVENT : reset =%d, pa=%d\n", + phone_reset, phone_active_value); + + if (phone_reset && phone_active_value) { + phone_state = STATE_ONLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } else if (phone_reset && !phone_active_value) { + if ((mc->phone_state == STATE_ONLINE) && + (mc->iod->link->com_state == COM_ONLINE)) { + phone_state = STATE_CRASH_EXIT; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, + phone_state); + } + } else { + phone_state = STATE_OFFLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } + + if (phone_active_value) + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_LOW); + else + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_HIGH); + enable_irq(mc->irq_phone_active); + + return IRQ_HANDLED; +} + +static void xmm6260_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = xmm6260_on; + mc->ops.modem_off = xmm6260_off; + mc->ops.modem_reset = xmm6260_reset; + mc->ops.modem_boot_on = xmm6260_boot_on; + mc->ops.modem_boot_off = xmm6260_boot_off; +} + +int xmm6260_init_modemctl_device(struct modem_ctl *mc, + struct modem_data *pdata) +{ + int ret; + struct platform_device *pdev; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_reset_req_n = pdata->gpio_reset_req_n; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = platform_get_irq(pdev, 0); + + xmm6260_get_ops(mc); + + ret = request_irq(mc->irq_phone_active, phone_active_irq_handler, + IRQF_NO_SUSPEND | IRQF_TRIGGER_HIGH, + "phone_active", mc); + if (ret) { + pr_err("[MODEM_IF] %s: failed to request_irq:%d\n", + __func__, ret); + return ret; + } + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) + pr_err("[MODEM_IF] %s: failed to enable_irq_wake:%d\n", + __func__, ret); + + return ret; +} diff --git a/drivers/misc/modem_if/modem_net_flowcontrol_device.c b/drivers/misc/modem_if/modem_net_flowcontrol_device.c new file mode 100755 index 0000000..9397edd --- /dev/null +++ b/drivers/misc/modem_if/modem_net_flowcontrol_device.c @@ -0,0 +1,115 @@ +/* /linux/drivers/misc/modem_if/modem_net_flowcontrol_device.c + * + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2011 Samsung Electronics. + * + * 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/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/platform_data/modem.h> + +#include "modem_prj.h" + + +#define NET_FLOWCONTROL_DEV_NAME_LEN 8 + +static int modem_net_flowcontrol_device_open( + struct inode *inode, struct file *filp) +{ + return 0; +} + +static int modem_net_flowcontrol_device_release( + struct inode *inode, struct file *filp) +{ + return 0; +} + +static long modem_net_flowcontrol_device_ioctl( + struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct net *this_net; + struct net_device *ndev; + char dev_name[NET_FLOWCONTROL_DEV_NAME_LEN]; + u8 chan; + + if (copy_from_user(&chan, (void __user *)arg, sizeof(char))) + return -EFAULT; + + if (chan > 15) + return -ENODEV; + + snprintf(dev_name, NET_FLOWCONTROL_DEV_NAME_LEN, "rmnet%d", (int)chan); + this_net = get_net_ns_by_pid(current->pid); + ndev = __dev_get_by_name(this_net, dev_name); + if (ndev == NULL) { + pr_err("[MODEM_IF] %s: device = %s not exist\n", __func__, + dev_name); + return -ENODEV; + } + + switch (cmd) { + case IOCTL_MODEM_NET_SUSPEND: + netif_stop_queue(ndev); + pr_info("[MODEM_IF] NET SUSPEND(%s)\n", dev_name); + break; + case IOCTL_MODEM_NET_RESUME: + netif_wake_queue(ndev); + pr_info("[MODEM_IF] NET RESUME(%s)\n", dev_name); + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct file_operations modem_net_flowcontrol_device_fops = { + .owner = THIS_MODULE, + .open = modem_net_flowcontrol_device_open, + .release = modem_net_flowcontrol_device_release, + .unlocked_ioctl = modem_net_flowcontrol_device_ioctl, +}; + +static int __init modem_net_flowcontrol_device_init(void) +{ + int ret = 0; + struct io_device *net_flowcontrol_dev; + + net_flowcontrol_dev = kzalloc(sizeof(struct io_device), GFP_KERNEL); + if (!net_flowcontrol_dev) { + pr_err("[MODEM_IF] net_flowcontrol_dev io device memory alloc fail\n"); + return -ENOMEM; + } + + net_flowcontrol_dev->miscdev.minor = MISC_DYNAMIC_MINOR; + net_flowcontrol_dev->miscdev.name = "modem_br"; + net_flowcontrol_dev->miscdev.fops = &modem_net_flowcontrol_device_fops; + + ret = misc_register(&net_flowcontrol_dev->miscdev); + if (ret) + pr_err("[MODEM_IF] failed to register misc br device : %s\n", + net_flowcontrol_dev->miscdev.name); + + return ret; +} + +module_init(modem_net_flowcontrol_device_init); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung Modem IF Net Flowcontrol Driver"); diff --git a/drivers/misc/modem_if/modem_prj.h b/drivers/misc/modem_if/modem_prj.h new file mode 100755 index 0000000..e728bdb --- /dev/null +++ b/drivers/misc/modem_if/modem_prj.h @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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 __MODEM_PRJ_H__ +#define __MODEM_PRJ_H__ + +#include <linux/wait.h> +#include <linux/miscdevice.h> +#include <linux/skbuff.h> + + +#define MAX_LINK_DEVTYPE 3 +#define MAX_RAW_DEVS 32 +#define MAX_NUM_IO_DEV (MAX_RAW_DEVS + 4) + +#define IOCTL_MODEM_ON _IO('o', 0x19) +#define IOCTL_MODEM_OFF _IO('o', 0x20) +#define IOCTL_MODEM_RESET _IO('o', 0x21) +#define IOCTL_MODEM_BOOT_ON _IO('o', 0x22) +#define IOCTL_MODEM_BOOT_OFF _IO('o', 0x23) +#define IOCTL_MODEM_START _IO('o', 0x24) + +#define IOCTL_MODEM_SEND _IO('o', 0x25) +#define IOCTL_MODEM_RECV _IO('o', 0x26) + +#define IOCTL_MODEM_STATUS _IO('o', 0x27) +#define IOCTL_MODEM_GOTA_START _IO('o', 0x28) +#define IOCTL_MODEM_FW_UPDATE _IO('o', 0x29) + +#define IOCTL_MODEM_NET_SUSPEND _IO('o', 0x30) +#define IOCTL_MODEM_NET_RESUME _IO('o', 0x31) +#define IOCTL_MODEM_DUMP_START _IO('o', 0x32) +#define IOCTL_MODEM_DUMP_UPDATE _IO('o', 0x33) +#define IOCTL_MODEM_FORCE_CRASH_EXIT _IO('o', 0x34) +#define IOCTL_MODEM_DUMP_RESET _IO('o', 0x35) + +#define IPC_HEADER_MAX_SIZE 6 /* fmt 3, raw 6, rfs 6 */ + +#define PSD_DATA_CHID_BEGIN 0x2A +#define PSD_DATA_CHID_END 0x38 + +#define IP6VERSION 6 + +#define SOURCE_MAC_ADDR {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC} + +/* Does modem ctl structure will use state ? or status defined below ?*/ +/* Be careful!! below sequence shouldn't be changed*/ +enum modem_state { + STATE_OFFLINE, + __UNUSED__, + STATE_CRASH_EXIT, + STATE_BOOTING, + STATE_ONLINE, + STATE_NV_REBUILDING, + STATE_LOADER_DONE, +}; + +enum { + COM_NONE, + COM_ONLINE, + COM_HANDSHAKE, + COM_BOOT, + COM_CRASH, + COM_BOOT_EBL, +}; + +struct header_data { + char hdr[IPC_HEADER_MAX_SIZE]; + unsigned len; + unsigned flag_len; + char start; /*hdlc start header 0x7F*/ +}; + +/* buffer type for modem image */ +struct dpram_firmware { + char *firmware; + int size; + int is_delta; +}; + + +struct vnet { + struct io_device *iod; +}; + +struct io_device { + struct list_head list; + char *name; + + wait_queue_head_t wq; + + struct miscdevice miscdev; + struct net_device *ndev; + + /* ID and Format for channel on the link */ + unsigned id; + enum dev_format format; + enum modem_io io_typ; + enum modem_network net_typ; + + struct sk_buff_head sk_rx_q; + + /* work for each io device, when delayed work needed + * use this for private io device rx action + */ + struct delayed_work rx_work; + + /* for fragmentation data from link device */ + struct sk_buff *skb_recv; + struct header_data h_data; + + /* called from linkdevice when a packet arrives for this iodevice */ + int (*recv)(struct io_device *iod, const char *data, unsigned int len); + + /* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ + void (*modem_state_changed)(struct io_device *iod, enum modem_state); + + struct link_device *link; + struct modem_ctl *mc; + + void *private_data; +}; +#define to_io_device(misc) container_of(misc, struct io_device, miscdev) + +struct io_raw_devices { + struct io_device *raw_devices[MAX_RAW_DEVS]; + int num_of_raw_devs; +}; + +struct link_device { + char *name; + + struct sk_buff_head sk_fmt_tx_q; + struct sk_buff_head sk_raw_tx_q; + + struct workqueue_struct *tx_wq; + struct workqueue_struct *tx_raw_wq; + struct work_struct tx_work; + struct delayed_work tx_delayed_work; + + unsigned com_state; + + /* called during init to associate an io device with this link */ + int (*attach)(struct link_device *ld, struct io_device *iod); + + /* init communication - setting link driver */ + int (*init_comm)(struct link_device *ld, struct io_device *iod); + /* terminate communication */ + void (*terminate_comm)(struct link_device *ld, struct io_device *iod); + + /* called by an io_device when it has a packet to send over link + * - the io device is passed so the link device can look at id and + * format fields to determine how to route/format the packet + */ + int (*send)(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb); + + int (*gota_start)(struct link_device *ld, struct io_device *iod); + int (*dump_start)(struct link_device *ld, struct io_device *iod); + + int (*modem_update)( + struct link_device *ld, + struct io_device *iod, + unsigned long arg); + int (*dump_update)( + struct link_device *ld, + struct io_device *iod, + unsigned long arg); +}; + +struct modemctl_ops { + int (*modem_on) (struct modem_ctl *); + int (*modem_off) (struct modem_ctl *); + int (*modem_reset) (struct modem_ctl *); + int (*modem_boot_on) (struct modem_ctl *); + int (*modem_boot_off) (struct modem_ctl *); + int (*modem_force_crash_exit) (struct modem_ctl *); + int (*modem_dump_reset) (struct modem_ctl *); +}; + +struct modem_ctl { + struct device *dev; + char *name; + + int phone_state; + + unsigned gpio_cp_on; + unsigned gpio_reset_req_n; + unsigned gpio_cp_reset; + unsigned gpio_pda_active; + unsigned gpio_phone_active; + unsigned gpio_cp_dump_int; + unsigned gpio_flm_uart_sel; + unsigned gpio_cp_warm_reset; + unsigned gpio_cp_off; + + int irq_phone_active; + + struct work_struct work; + +#ifdef CONFIG_LTE_MODEM_CMC221 + const struct attribute_group *group; + unsigned gpio_slave_wakeup; + unsigned gpio_host_wakeup; + unsigned gpio_host_active; + int irq_host_wakeup; + struct delayed_work dwork; + struct work_struct resume_work; + int wakeup_flag; /*flag for CP boot GPIO sync flag*/ + int cpcrash_flag; + int crash_cnt; + struct completion *l2_done; + int irq[3]; +#endif /*CONFIG_LTE_MODEM_CMC221*/ + + struct modemctl_ops ops; + struct io_device *iod; +}; + +int init_io_device(struct io_device *iod); + +#endif diff --git a/drivers/misc/modem_if/modem_variation.h b/drivers/misc/modem_if/modem_variation.h new file mode 100755 index 0000000..79b9240 --- /dev/null +++ b/drivers/misc/modem_if/modem_variation.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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 __MODEM_VARIATION_H__ +#define __MODEM_VARIATION_H__ + +#define DECLARE_MODEM_INIT(type) \ + int type ## _init_modemctl_device(struct modem_ctl *mc, \ + struct modem_data *pdata) +#define DECLARE_MODEM_INIT_DUMMY(type) \ + DECLARE_MODEM_INIT(type) { return 0; } + +#define DECLARE_LINK_INIT(type) \ + struct link_device *type ## _create_link_device( \ + struct platform_device *pdev) +#define DECLARE_LINK_INIT_DUMMY(type) \ + DECLARE_LINK_INIT(type) { return NULL; } + +#define MODEM_INIT_CALL(type) type ## _init_modemctl_device +#define LINK_INIT_CALL(type) type ## _create_link_device + +/* add declaration of modem & link type */ +/* modem device support */ +#ifdef CONFIG_UMTS_MODEM_XMM6260 +DECLARE_MODEM_INIT(xmm6260); +#else +DECLARE_MODEM_INIT_DUMMY(xmm6260) +#endif + +#ifdef CONFIG_LTE_MODEM_CMC221 +DECLARE_MODEM_INIT(cmc221); +#else +DECLARE_MODEM_INIT_DUMMY(cmc221) +#endif + +#ifdef CONFIG_CDMA_MODEM_CBP71 +DECLARE_MODEM_INIT(cbp71); +#else +DECLARE_MODEM_INIT_DUMMY(cbp71) +#endif + +/* link device support */ +#ifdef CONFIG_UMTS_LINK_MIPI +DECLARE_LINK_INIT(mipi); +#else +DECLARE_LINK_INIT_DUMMY(mipi) +#endif + +#ifdef CONFIG_CDMA_LINK_DPRAM +DECLARE_LINK_INIT(dpram); +#else +DECLARE_LINK_INIT_DUMMY(dpram) +#endif + +#ifdef CONFIG_LTE_LINK_USB +DECLARE_LINK_INIT(usb); +#else +DECLARE_LINK_INIT_DUMMY(usb) +#endif + +#ifdef CONFIG_UMTS_LINK_HSIC +DECLARE_LINK_INIT(hsic); +#else +DECLARE_LINK_INIT_DUMMY(hsic) +#endif + +#ifdef CONFIG_UMTS_LINK_SPI +DECLARE_LINK_INIT(spi); +#else +DECLARE_LINK_INIT_DUMMY(spi) +#endif + +typedef int (*modem_init_call)(struct modem_ctl *, struct modem_data *); +modem_init_call modem_init_func[] = { + MODEM_INIT_CALL(xmm6260), + MODEM_INIT_CALL(cbp71), + MODEM_INIT_CALL(cmc221), +}; + +typedef struct link_device *(*link_init_call)(struct platform_device *); +link_init_call link_init_func[] = { + LINK_INIT_CALL(mipi), + LINK_INIT_CALL(dpram), + LINK_INIT_CALL(spi), + LINK_INIT_CALL(usb), + LINK_INIT_CALL(hsic), +}; + +static int call_modem_init_func(struct modem_ctl *mc, struct modem_data *pdata) +{ + if (modem_init_func[pdata->modem_type]) + return modem_init_func[pdata->modem_type](mc, pdata); + else + return -ENOTSUPP; +} + +static struct link_device *call_link_init_func(struct platform_device *pdev, + enum modem_link link_type) +{ + if (link_init_func[link_type]) + return link_init_func[link_type](pdev); + else + return NULL; +} + +#endif + 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/omap_hsi/hsi-char.c b/drivers/omap_hsi/hsi-char.c index 871de30..8b59df8 100644 --- a/drivers/omap_hsi/hsi-char.c +++ b/drivers/omap_hsi/hsi-char.c @@ -375,6 +375,12 @@ static long hsi_char_ioctl(struct file *file, if (copy_to_user((void __user *)arg, &state, sizeof(state))) ret = -EFAULT; break; + case CS_SET_WAKE_RX_3WIRES_MODE: + if (copy_from_user(&state, (void __user *)arg, sizeof(state))) + ret = -EFAULT; + else + if_hsi_set_wake_rx_3wires_mode(ch, state); + break; case CS_GET_CAWAKELINE: if_hsi_get_cawakeline(ch, &state); if (copy_to_user((void __user *)arg, &state, sizeof(state))) diff --git a/drivers/omap_hsi/hsi-if.c b/drivers/omap_hsi/hsi-if.c index 5228b6a..f360e5a 100644 --- a/drivers/omap_hsi/hsi-if.c +++ b/drivers/omap_hsi/hsi-if.c @@ -262,6 +262,15 @@ void if_hsi_get_cawakeline(int ch, unsigned int *state) hsi_ioctl(channel->dev, HSI_IOCTL_GET_CAWAKE, state); } +void if_hsi_set_wake_rx_3wires_mode(int ch, unsigned int state) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + hsi_ioctl(channel->dev, + state ? HSI_IOCTL_SET_WAKE_RX_3WIRES_MODE : + HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL); +} + int if_hsi_set_rx(int ch, struct hsi_rx_config *cfg) { int ret; diff --git a/drivers/omap_hsi/hsi-if.h b/drivers/omap_hsi/hsi-if.h index 96afdd4..fe83aa8a 100644 --- a/drivers/omap_hsi/hsi-if.h +++ b/drivers/omap_hsi/hsi-if.h @@ -52,6 +52,7 @@ void if_hsi_bootstrap(int ch); void if_hsi_set_acwakeline(int ch, unsigned int state); void if_hsi_get_acwakeline(int ch, unsigned int *state); void if_hsi_get_cawakeline(int ch, unsigned int *state); +void if_hsi_set_wake_rx_3wires_mode(int ch, unsigned int state); int if_hsi_set_rx(int ch, struct hsi_rx_config *cfg); void if_hsi_get_rx(int ch, struct hsi_rx_config *cfg); int if_hsi_set_tx(int ch, struct hsi_tx_config *cfg); diff --git a/drivers/omap_hsi/hsi_driver.c b/drivers/omap_hsi/hsi_driver.c index bb07c10..2aad1ef 100644..100755 --- a/drivers/omap_hsi/hsi_driver.c +++ b/drivers/omap_hsi/hsi_driver.c @@ -41,7 +41,7 @@ static struct pm_qos_request_list *pm_qos_handle; #endif #define HSI_MODULENAME "omap_hsi" -#define HSI_DRIVER_VERSION "0.4.1" +#define HSI_DRIVER_VERSION "0.4.2" #define HSI_RESETDONE_MAX_RETRIES 5 /* Max 5*L4 Read cycles waiting for */ /* reset to complete */ #define HSI_RESETDONE_NORMAL_RETRIES 1 /* Reset should complete in 1 R/W */ @@ -51,7 +51,7 @@ void hsi_save_ctx(struct hsi_dev *hsi_ctrl) struct hsi_platform_data *pdata = hsi_ctrl->dev->platform_data; struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); void __iomem *base = hsi_ctrl->base; - struct port_ctx *p; + struct hsi_port_ctx *p; int port; pdata->ctx->sysconfig = hsi_inl(base, HSI_SYS_SYSCONFIG_REG); @@ -92,7 +92,7 @@ void hsi_restore_ctx(struct hsi_dev *hsi_ctrl) struct hsi_platform_data *pdata = hsi_ctrl->dev->platform_data; struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); void __iomem *base = hsi_ctrl->base; - struct port_ctx *p; + struct hsi_port_ctx *p; int port; hsi_outl(pdata->ctx->sysconfig, base, HSI_SYS_SYSCONFIG_REG); @@ -128,7 +128,7 @@ void hsi_restore_ctx(struct hsi_dev *hsi_ctrl) if (hsi_driver_device_is_hsi(pdev)) { /* SW strategy for HSI fifo management can be changed here */ - hsi_fifo_mapping(hsi_ctrl, HSI_FIFO_MAPPING_DEFAULT); + hsi_fifo_mapping(hsi_ctrl, hsi_ctrl->fifo_mapping_strategy); } /* As a last step move HSR from MODE_VAL.SLEEP to the relevant mode. */ @@ -275,12 +275,31 @@ void hsi_set_pm_force_hsi_on(struct hsi_dev *hsi_ctrl) /* HSI_TODO : use the HWMOD API : omap_hwmod_set_slave_idlemode() */ } +/** +* hsi_softreset - Force a SW RESET of HSI (core + DMA) +* +* @hsi_ctrl - reference to the hsi controller to be reset. +* +*/ int hsi_softreset(struct hsi_dev *hsi_ctrl) { unsigned int ind = 0; + unsigned int port; void __iomem *base = hsi_ctrl->base; u32 status; + /* HSI-C1BUG00088: i696 : HSI: Issue with SW reset + * No recovery from SW reset under specific circumstances + * If a SW RESET is done while some HSI errors are still not + * acknowledged, the HSR FSM is stucked. */ + if (is_hsi_errata(hsi_ctrl, HSI_ERRATUM_i696_SW_RESET_FSM_STUCK)) { + for (port = 1; port <= hsi_ctrl->max_p; port++) { + hsi_outl_and(HSI_HSR_MODE_MODE_VAL_SLEEP, base, + HSI_HSR_MODE_REG(port)); + hsi_outl(HSI_HSR_ERROR_ALL, base, + HSI_HSR_ERRORACK_REG(port)); + } + } /* Reseting HSI Block */ hsi_outl_or(HSI_SOFTRESET, base, HSI_SYS_SYSCONFIG_REG); do { @@ -324,7 +343,7 @@ int hsi_softreset(struct hsi_dev *hsi_ctrl) static void hsi_set_ports_default(struct hsi_dev *hsi_ctrl, struct platform_device *pd) { - struct port_ctx *cfg; + struct hsi_port_ctx *cfg; struct hsi_platform_data *pdata = pd->dev.platform_data; unsigned int port = 0; void __iomem *base = hsi_ctrl->base; @@ -358,7 +377,7 @@ static void hsi_set_ports_default(struct hsi_dev *hsi_ctrl, if (hsi_driver_device_is_hsi(pdev)) { /* SW strategy for HSI fifo management can be changed here */ - hsi_fifo_mapping(hsi_ctrl, HSI_FIFO_MAPPING_DEFAULT); + hsi_fifo_mapping(hsi_ctrl, hsi_ctrl->fifo_mapping_strategy); hsi_outl(pdata->ctx->dll, base, HSI_HSR_DLL_REG); } } @@ -408,6 +427,13 @@ static int hsi_port_channels_reset(struct hsi_port *port) return 0; } +/** +* hsi_softreset_driver - Must be called following HSI SW RESET, to re-align +* variable states with new HW state. +* +* @hsi_ctrl - reference to the hsi controller to be re-aligned. +* +*/ void hsi_softreset_driver(struct hsi_dev *hsi_ctrl) { struct platform_device *pd = to_platform_device(hsi_ctrl->dev); @@ -424,7 +450,7 @@ void hsi_softreset_driver(struct hsi_dev *hsi_ctrl) hsi_port_channels_reset(&hsi_ctrl->hsi_port[port]); } - hsi_set_pm_default(hsi_ctrl); + hsi_set_pm_force_hsi_on(hsi_ctrl); /* Re-Configure HSI ports */ hsi_set_ports_default(hsi_ctrl, pd); @@ -516,11 +542,13 @@ static int __init hsi_ports_init(struct hsi_dev *hsi_ctrl) for (port = 0; port < hsi_ctrl->max_p; port++) { hsi_p = &hsi_ctrl->hsi_port[port]; - hsi_p->port_number = port + 1; + hsi_p->flags = 0; + hsi_p->port_number = pdata->ctx->pctx[port].port_number; hsi_p->hsi_controller = hsi_ctrl; hsi_p->max_ch = hsi_driver_device_is_hsi(pd) ? HSI_CHANNELS_MAX : HSI_SSI_CHANNELS_MAX; hsi_p->irq = 0; + hsi_p->wake_rx_3_wires_mode = 0; /* 4 wires */ hsi_p->cawake_status = -1; /* Unknown */ hsi_p->cawake_off_event = false; hsi_p->acwake_status = 0; @@ -529,22 +557,21 @@ static int __init hsi_ports_init(struct hsi_dev *hsi_ctrl) hsi_p->counters_on = 1; hsi_p->reg_counters = pdata->ctx->pctx[port].hsr.counters; spin_lock_init(&hsi_p->lock); - err = hsi_port_channels_init(&hsi_ctrl->hsi_port[port]); + err = hsi_port_channels_init(hsi_p); if (err < 0) - goto rback1; + goto rback; err = hsi_request_mpu_irq(hsi_p); if (err < 0) - goto rback2; + goto rback; err = hsi_request_cawake_irq(hsi_p); if (err < 0) - goto rback3; + goto rback; + dev_info(hsi_ctrl->dev, "HSI port %d initialized\n", + hsi_p->port_number); } return 0; -rback3: - hsi_mpu_exit(hsi_p); -rback2: +rback: hsi_ports_exit(hsi_ctrl, port + 1); -rback1: return err; } @@ -636,7 +663,7 @@ void hsi_clocks_disable_channel(struct device *dev, u8 channel_number, } if (hsi_is_hst_controller_busy(hsi_ctrl)) - dev_dbg(dev, "Disabling clocks with HST FSM not IDLE !\n"); + dev_warn(dev, "Disabling clocks with HST FSM not IDLE !\n"); #ifdef K3_0_PORTING_HSI_MISSING_FEATURE /* Allow Fclk to change */ @@ -654,6 +681,9 @@ void hsi_clocks_disable_channel(struct device *dev, u8 channel_number, * @channel_number - channel number which requests clock to be enabled * 0xFF means no particular channel * +* Returns: -EEXIST if clocks were already active +* 0 if clocks were previously inactive +* * Note : there is no real HW clock management per HSI channel, this is only * virtual to keep track of active channels and ease debug * @@ -728,8 +758,10 @@ static int __init hsi_controller_init(struct hsi_dev *hsi_ctrl, return -ENXIO; } hsi_ctrl->max_p = pdata->num_ports; + hsi_ctrl->clock_enabled = false; + hsi_ctrl->clock_rate = 0; hsi_ctrl->in_dma_tasklet = false; - hsi_ctrl->fifo_mapping_strategy = HSI_FIFO_MAPPING_UNDEF; + hsi_ctrl->fifo_mapping_strategy = pdata->fifo_mapping_strategy; hsi_ctrl->dev = &pd->dev; spin_lock_init(&hsi_ctrl->lock); err = hsi_init_gdd_chan_count(hsi_ctrl); @@ -804,7 +836,7 @@ static int __init hsi_platform_device_probe(struct platform_device *pd) if (err < 0) goto rollback2; - hsi_set_pm_default(hsi_ctrl); + hsi_set_pm_force_hsi_on(hsi_ctrl); /* Configure HSI ports */ hsi_set_ports_default(hsi_ctrl, pd); @@ -835,19 +867,31 @@ static int __init hsi_platform_device_probe(struct platform_device *pd) /* Allow HSI to wake up the platform */ device_init_wakeup(hsi_ctrl->dev, true); -#ifdef K3_0_PORTING_HSI_MISSING_FEATURE /* Set the HSI FCLK to default. */ - err = omap_device_set_rate(hsi_ctrl->dev, hsi_ctrl->dev, - pdata->default_hsi_fclk); - if (err) - dev_err(&pd->dev, "Cannot set HSI FClk to default value: %ld\n", + if (!pdata->device_scale) { + dev_err(&pd->dev, "%s: No platform device_scale function\n", + __func__); + err = -ENXIO; + goto rollback3; + } + err = pdata->device_scale(hsi_ctrl->dev, hsi_ctrl->dev, + pdata->default_hsi_fclk); + if (err == -EBUSY) { + dev_warn(&pd->dev, "Cannot set HSI FClk to default value: %ld. " + "Will retry on next open\n", pdata->default_hsi_fclk); -#endif + } else if (err) { + dev_err(&pd->dev, "%s: Error %d setting HSI FClk to %ld.\n", + __func__, err, pdata->default_hsi_fclk); + goto rollback3; + } else { + hsi_ctrl->clock_rate = pdata->default_hsi_fclk; + } /* From here no need for HSI HW access */ hsi_clocks_disable(hsi_ctrl->dev, __func__); - return err; + return 0; rollback3: hsi_debug_remove_ctrl(hsi_ctrl); @@ -885,11 +929,28 @@ static int __exit hsi_platform_device_remove(struct platform_device *pd) } #ifdef CONFIG_SUSPEND -static int hsi_suspend_noirq(struct device *dev) +static int hsi_pm_prepare(struct device *dev) +{ + struct platform_device *pd = to_platform_device(dev); + struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd); + + dev_dbg(dev, "%s\n", __func__); + + /* If HSI is busy, refuse the suspend */ + if (hsi_ctrl->clock_enabled) { + dev_info(dev, "Platform prepare while HSI active\n"); + return -EBUSY; + } + + return 0; +} + +static int hsi_pm_suspend(struct device *dev) { struct hsi_platform_data *pdata = dev->platform_data; struct platform_device *pd = to_platform_device(dev); struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd); + unsigned int i; dev_dbg(dev, "%s\n", __func__); @@ -897,20 +958,43 @@ static int hsi_suspend_noirq(struct device *dev) /* we don't want to re-enable it here. HSI interrupt shall be */ /* generated normally because HSI HW is ON. */ if (hsi_ctrl->clock_enabled) { - dev_info(dev, "Platform Suspend while HSI active\n"); - return 0; + dev_info(dev, "Platform suspend while HSI active\n"); + return -EBUSY; } /* Perform HSI board specific action before platform suspend */ if (pdata->board_suspend) - pdata->board_suspend(0, device_may_wakeup(dev)); + for (i = 0; i < hsi_ctrl->max_p; i++) + pdata->board_suspend(hsi_ctrl->hsi_port[i].port_number, + device_may_wakeup(dev)); + + return 0; +} + +/* This callback can be useful in case an HSI interrupt occured between */ +/* ->suspend() phase and ->suspend_noirq() phase */ +static int hsi_pm_suspend_noirq(struct device *dev) +{ + struct platform_device *pd = to_platform_device(dev); + struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd); + + dev_dbg(dev, "%s\n", __func__); + + /* If HSI is busy, refuse the suspend */ + if (hsi_ctrl->clock_enabled) { + dev_info(dev, "Platform suspend_noirq while HSI active\n"); + return -EBUSY; + } return 0; } -static int hsi_resume_noirq(struct device *dev) +static int hsi_pm_resume(struct device *dev) { struct hsi_platform_data *pdata = dev->platform_data; + struct platform_device *pd = to_platform_device(dev); + struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd); + unsigned int i; dev_dbg(dev, "%s\n", __func__); @@ -919,7 +1003,7 @@ static int hsi_resume_noirq(struct device *dev) /* HSI IO checking in PRCM int handler is done when waking up from : */ /* - Device OFF mode (wake up from suspend) */ /* - L3INIT in RET (Idle mode) */ - /* hsi_resume_noirq is called only when system wakes up from suspend. */ + /* hsi_resume is called only when system wakes up from suspend. */ /* So HSI IO checking in PRCM int handler and hsi_resume_noirq are */ /* redundant. We need to choose which one will schedule the tasklet */ /* Since HSI IO checking in PRCM int handler covers more cases, it is */ @@ -927,7 +1011,8 @@ static int hsi_resume_noirq(struct device *dev) /* Perform (optional) HSI board specific action after platform wakeup */ if (pdata->board_resume) - pdata->board_resume(0); + for (i = 0; i < hsi_ctrl->max_p; i++) + pdata->board_resume(hsi_ctrl->hsi_port[i].port_number); return 0; } @@ -940,11 +1025,15 @@ static int hsi_resume_noirq(struct device *dev) * * */ +#define HSI_HSR_MODE_FRAME 0x2 +#define HSI_PORT1 0x1 int hsi_runtime_resume(struct device *dev) { struct platform_device *pd = to_platform_device(dev); struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd); struct hsi_platform_data *pdata = hsi_ctrl->dev->platform_data; + unsigned int i; + dev_dbg(dev, "%s\n", __func__); if (hsi_ctrl->clock_enabled) @@ -955,8 +1044,16 @@ int hsi_runtime_resume(struct device *dev) /* Restore context */ hsi_restore_ctx(hsi_ctrl); - /* When HSI is ON, no need for IO wakeup mechanism */ - pdata->wakeup_disable(0); + /* Restore HSR_MODE register value */ + /* WARNING: works only in this configuration: */ + /* - Flow = Synchronized */ + /* - Mode = frame */ + hsi_outl(HSI_HSR_MODE_FRAME, hsi_ctrl->base, + HSI_HSR_MODE_REG(HSI_PORT1)); + + /* When HSI is ON, no need for IO wakeup mechanism on any HSI port */ + for (i = 0; i < hsi_ctrl->max_p; i++) + pdata->wakeup_disable(hsi_ctrl->hsi_port[i].port_number); /* HSI device is now fully operational and _must_ be able to */ /* complete I/O operations */ @@ -977,28 +1074,32 @@ int hsi_runtime_suspend(struct device *dev) struct platform_device *pd = to_platform_device(dev); struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd); struct hsi_platform_data *pdata = hsi_ctrl->dev->platform_data; - int port; + int port, i; + dev_dbg(dev, "%s\n", __func__); if (!hsi_ctrl->clock_enabled) dev_warn(dev, "Warning: clock status mismatch vs runtime PM\n"); - /* Save context */ - hsi_save_ctx(hsi_ctrl); - - hsi_ctrl->clock_enabled = false; - /* Put HSR into SLEEP mode to force ACREADY to low while HSI is idle */ for (port = 1; port <= pdata->num_ports; port++) { hsi_outl_and(HSI_HSR_MODE_MODE_VAL_SLEEP, hsi_ctrl->base, HSI_HSR_MODE_REG(port)); } - /* HSI is going to INA/RET/OFF, it needs IO wakeup mechanism enabled */ + /* Save context */ + hsi_save_ctx(hsi_ctrl); + + hsi_ctrl->clock_enabled = false; + + /* HSI is going to IDLE, it needs IO wakeup mechanism enabled */ if (device_may_wakeup(dev)) - pdata->wakeup_enable(0); + for (i = 0; i < hsi_ctrl->max_p; i++) + pdata->wakeup_enable(hsi_ctrl->hsi_port[i].port_number); else - pdata->wakeup_disable(0); + for (i = 0; i < hsi_ctrl->max_p; i++) + pdata->wakeup_disable( + hsi_ctrl->hsi_port[i].port_number); /* HSI is now ready to be put in low power state */ @@ -1051,8 +1152,10 @@ MODULE_DEVICE_TABLE(platform, hsi_id_table); #ifdef CONFIG_PM static const struct dev_pm_ops hsi_driver_pm_ops = { #ifdef CONFIG_SUSPEND - .suspend_noirq = hsi_suspend_noirq, - .resume_noirq = hsi_resume_noirq, + .prepare = hsi_pm_prepare, + .suspend = hsi_pm_suspend, + .suspend_noirq = hsi_pm_suspend_noirq, + .resume = hsi_pm_resume, #endif #ifdef CONFIG_PM_RUNTIME .runtime_suspend = hsi_runtime_suspend, @@ -1086,7 +1189,7 @@ static int __init hsi_driver_init(void) { int err = 0; - pr_info(LOG_NAME "HSI DRIVER Version " HSI_DRIVER_VERSION "\n"); + pr_info(LOG_NAME "HSI driver version " HSI_DRIVER_VERSION "\n"); /* Register the (virtual) HSI bus */ err = hsi_bus_init(); diff --git a/drivers/omap_hsi/hsi_driver.h b/drivers/omap_hsi/hsi_driver.h index 0991d98..2720657 100644 --- a/drivers/omap_hsi/hsi_driver.h +++ b/drivers/omap_hsi/hsi_driver.h @@ -55,16 +55,20 @@ /* Number of DMA channels when nothing is defined for the device */ #define HSI_DMA_CHANNEL_DEFAULT 8 +/* Defines bit number for atomic operations */ +#define HSI_FLAGS_TASKLET_LOCK 0 /* prevents to disable IRQ and */ + /* schedule tasklet more than once */ + #define LOG_NAME "OMAP HSI: " /* SW strategies for HSI FIFO mapping */ enum { HSI_FIFO_MAPPING_UNDEF = 0, + HSI_FIFO_MAPPING_ALL_PORT1, /* ALL FIFOs mapped on port 1 */ + HSI_FIFO_MAPPING_ALL_PORT2, /* ALL FIFOs mapped on port 2 */ HSI_FIFO_MAPPING_SSI, /* 8 FIFOs per port (SSI compatible mode) */ - HSI_FIFO_MAPPING_ALL_PORT1, /* ALL FIFOs mapped on 1st port */ }; -#define HSI_FIFO_MAPPING_DEFAULT HSI_FIFO_MAPPING_ALL_PORT1 /* Device identifying constants */ enum { @@ -115,10 +119,12 @@ struct hsi_channel { * struct hsi_port - hsi port driver data * @hsi_channel: Array of channels in the port * @hsi_controller: Reference to the HSI controller - * @port_number: port number + * @flags: atomic flags (for atomic operations) + * @port_number: port number. Range [1,2] * @max_ch: maximum number of channels supported on the port * @n_irq: HSI irq line use to handle interrupts (0 or 1) * @irq: IRQ number + * @wake_rx_3_wires_mode: receiver 3 wires mode (1) or 4 wires mode (0) * @cawake_gpio: GPIO number for cawake line (-1 if none) * @cawake_gpio_irq: IRQ number for cawake gpio events * @cawake_status: Tracks CAWAKE line status @@ -126,6 +132,7 @@ struct hsi_channel { * @acwake_status: Bitmap to track ACWAKE line status per channel * @in_int_tasklet: True if interrupt tasklet for this port is currently running * @in_cawake_tasklet: True if CAWAKE tasklet for this port is currently running + * @tasklet_lock: prevents to disable IRQ and schedule tasklet more than once * @counters_on: indicates if the HSR counters are in use or not * @reg_counters: stores the previous counters values when deactivated * @lock: Serialize access to the port registers and internal data @@ -135,11 +142,12 @@ struct hsi_channel { struct hsi_port { struct hsi_channel hsi_channel[HSI_PORT_MAX_CH]; struct hsi_dev *hsi_controller; - u8 flags; - u8 port_number; /* Range [1,2] */ + unsigned long flags; + u8 port_number; u8 max_ch; u8 n_irq; int irq; + int wake_rx_3_wires_mode; int cawake_gpio; int cawake_gpio_irq; int cawake_status; @@ -151,8 +159,7 @@ struct hsi_port { unsigned long reg_counters; spinlock_t lock; /* access to the port registers and internal data */ struct tasklet_struct hsi_tasklet; - struct tasklet_struct cawake_tasklet; /* SSI_TODO : need to replace */ - /* by a workqueue */ + struct tasklet_struct cawake_tasklet; }; /** @@ -167,6 +174,7 @@ struct hsi_port { * @phy_base: HSI registers base physical address * @lock: Serializes access to internal data and regs * @clock_enabled: Indicates if HSI Clocks are ON + * @clock_rate: Indicates current HSI Fclock speed * @gdd_irq: GDD (DMA) irq number * @fifo_mapping_strategy: Selected strategy for fifo to ports/channels mapping * @gdd_usecount: Holds the number of ongoning DMA transfers @@ -189,6 +197,7 @@ struct hsi_dev { /* HSI_TODO: should be later renamed into hsi_controller*/ unsigned long phy_base; spinlock_t lock; /* Serializes access to internal data and regs */ bool clock_enabled; + unsigned long clock_rate; int gdd_irq; unsigned int fifo_mapping_strategy; unsigned int gdd_usecount; @@ -214,15 +223,19 @@ struct hsi_platform_data { int (*device_enable) (struct platform_device *pdev); int (*device_shutdown) (struct platform_device *pdev); int (*device_idle) (struct platform_device *pdev); + int (*device_scale) (struct device *req_dev, struct device *target_dev, + unsigned long rate); int (*wakeup_enable) (int hsi_port); int (*wakeup_disable) (int hsi_port); - int (*wakeup_is_from_hsi) (void); + bool (*wakeup_is_from_hsi) (int *hsi_port); int (*board_suspend)(int hsi_port, bool dev_may_wakeup); int (*board_resume)(int hsi_port); u8 num_ports; - struct ctrl_ctx *ctx; + struct hsi_ctrl_ctx *ctx; u8 hsi_gdd_chan_count; unsigned long default_hsi_fclk; + unsigned int fifo_mapping_strategy; + u32 errata; }; /* HSI Bus */ @@ -240,8 +253,11 @@ bool hsi_is_hsi_port_busy(struct hsi_port *pport); bool hsi_is_hsi_controller_busy(struct hsi_dev *hsi_ctrl); bool hsi_is_hst_port_busy(struct hsi_port *pport); bool hsi_is_hst_controller_busy(struct hsi_dev *hsi_ctrl); - +void hsi_driver_ack_interrupt(struct hsi_port *pport, u32 flag, bool backup); +bool hsi_driver_is_interrupt_pending(struct hsi_port *pport, u32 flag, + bool backup); int hsi_driver_enable_interrupt(struct hsi_port *pport, u32 flag); +int hsi_driver_disable_interrupt(struct hsi_port *pport, u32 flag); int hsi_driver_enable_read_interrupt(struct hsi_channel *hsi_channel, u32 *data); int hsi_driver_enable_write_interrupt(struct hsi_channel *hsi_channel, @@ -395,4 +411,17 @@ static inline int hsi_clocks_enable(struct device *dev, const char *s) return hsi_clocks_enable_channel(dev, HSI_CH_NUMBER_NONE, s); } +static inline int is_hsi_errata(struct hsi_dev *hsi_ctrl, unsigned int id) +{ + struct hsi_platform_data *pdata = dev_get_platdata(hsi_ctrl->dev); + + return IS_HSI_ERRATA(pdata->errata, id); +} + +#if defined(CONFIG_PM) && defined(CONFIG_ARCH_OMAP4) +extern void omap_pm_clear_dsp_wake_up(void); +#else +#define static inline void omap_pm_clear_dsp_wake_up(void) { } +#endif + #endif /* __HSI_DRIVER_H__ */ diff --git a/drivers/omap_hsi/hsi_driver_debugfs.c b/drivers/omap_hsi/hsi_driver_debugfs.c index d1f32dd..703079e 100644 --- a/drivers/omap_hsi/hsi_driver_debugfs.c +++ b/drivers/omap_hsi/hsi_driver_debugfs.c @@ -464,7 +464,7 @@ int __init hsi_debug_add_ctrl(struct hsi_dev *hsi_ctrl) debugfs_create_file("regs", S_IRUGO, dir, &hsi_ctrl->hsi_port[port], &hsi_port_regs_fops); - debugfs_create_file("counters", S_IRUGO | S_IWUGO, dir, + debugfs_create_file("counters", S_IRUGO | S_IWUSR, dir, &hsi_ctrl->hsi_port[port], &hsi_port_counters_fops); } diff --git a/drivers/omap_hsi/hsi_driver_dma.c b/drivers/omap_hsi/hsi_driver_dma.c index ad819f5..4cffd0c 100644 --- a/drivers/omap_hsi/hsi_driver_dma.c +++ b/drivers/omap_hsi/hsi_driver_dma.c @@ -436,24 +436,23 @@ int hsi_get_info_from_gdd_lch(struct hsi_dev *hsi_ctrl, unsigned int lch, unsigned int *port, unsigned int *channel, unsigned int *is_read_path) { - int i_ports; - int i_chans; + int i, j; int err = -1; - for (i_ports = 0; i_ports < HSI_MAX_PORTS; i_ports++) - for (i_chans = 0; i_chans < HSI_PORT_MAX_CH; i_chans++) - if (hsi_ctrl->hsi_port[i_ports]. - hsi_channel[i_chans].read_data.lch == lch) { + for (i = 0; i < hsi_ctrl->max_p; i++) + for (j = 0; j < hsi_ctrl->hsi_port[i].max_ch; j++) + if (hsi_ctrl->hsi_port[i]. + hsi_channel[j].read_data.lch == lch) { *is_read_path = 1; - *port = i_ports + 1; - *channel = i_chans; + *port = i + 1; + *channel = j; err = 0; goto get_info_bk; - } else if (hsi_ctrl->hsi_port[i_ports]. - hsi_channel[i_chans].write_data.lch == lch) { + } else if (hsi_ctrl->hsi_port[i]. + hsi_channel[j].write_data.lch == lch) { *is_read_path = 0; - *port = i_ports + 1; - *channel = i_chans; + *port = i + 1; + *channel = j; err = 0; goto get_info_bk; } diff --git a/drivers/omap_hsi/hsi_driver_fifo.c b/drivers/omap_hsi/hsi_driver_fifo.c index aa33a1a..862a12d 100644 --- a/drivers/omap_hsi/hsi_driver_fifo.c +++ b/drivers/omap_hsi/hsi_driver_fifo.c @@ -26,7 +26,7 @@ * hsi_fifo_get_id - Get fifo index corresponding to (port, channel) * @hsi_ctrl - HSI controler data * @channel - channel used - * @port - HSI port used + * @port - HSI port used. Range [1, 2] * * Returns the fifo index associated to the provided (port, channel). * Notes: 1) The fifo <=> (port, channel) correspondance depends on the selected @@ -39,6 +39,7 @@ int hsi_fifo_get_id(struct hsi_dev *hsi_ctrl, unsigned int channel, { int fifo_index = 0; int err = 0; + int fifo_port; /* Range [1, 2] */ if (unlikely((channel >= HSI_CHANNELS_MAX) || (port < 1) || (port > 2))) { @@ -46,8 +47,11 @@ int hsi_fifo_get_id(struct hsi_dev *hsi_ctrl, unsigned int channel, goto fifo_id_bk; } - if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_ALL_PORT1) { - if (unlikely(port != 1)) { + if ((hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_ALL_PORT1) || + (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_ALL_PORT2)) { + fifo_port = (hsi_ctrl->fifo_mapping_strategy == + HSI_FIFO_MAPPING_ALL_PORT1) ? 1 : 2; + if (unlikely(port != fifo_port)) { err = -EINVAL; goto fifo_id_bk; } else { @@ -102,6 +106,10 @@ int hsi_fifo_get_chan(struct hsi_dev *hsi_ctrl, unsigned int fifo, if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_ALL_PORT1) { *channel = fifo; *port = 1; + } else if (hsi_ctrl->fifo_mapping_strategy == + HSI_FIFO_MAPPING_ALL_PORT2) { + *channel = fifo; + *port = 2; } else if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_SSI) { if (fifo < 8) { *channel = fifo; @@ -128,7 +136,7 @@ fifo_id_bk: * @hsi_ctrl - HSI controler data * @mtype - mapping strategy * - * Returns 0 in case of success, and errocode (< 0) else + * Returns 0 in case of success, and error code (< 0) else * Configures the HSI FIFO mapping registers. Several mapping strategies are * proposed. * Note: The mapping is identical for Read and Write path. @@ -141,24 +149,26 @@ int hsi_fifo_mapping(struct hsi_dev *hsi_ctrl, unsigned int mtype) int i; unsigned int channel, port; - if (mtype == HSI_FIFO_MAPPING_ALL_PORT1) { + if ((mtype == HSI_FIFO_MAPPING_ALL_PORT1) || + (mtype == HSI_FIFO_MAPPING_ALL_PORT2)) { + port = (mtype == HSI_FIFO_MAPPING_ALL_PORT1) ? 0 : 1; channel = 0; for (i = 0; i < HSI_HST_FIFO_COUNT; i++) { hsi_outl(HSI_MAPPING_ENABLE | (channel << HSI_MAPPING_CH_NUMBER_OFFSET) | - (0 << HSI_MAPPING_PORT_NUMBER_OFFSET) | + (port << HSI_MAPPING_PORT_NUMBER_OFFSET) | HSI_HST_MAPPING_THRESH_VALUE, base, HSI_HST_MAPPING_FIFO_REG(i)); hsi_outl(HSI_MAPPING_ENABLE | (channel << HSI_MAPPING_CH_NUMBER_OFFSET) | - (0 << HSI_MAPPING_PORT_NUMBER_OFFSET), + (port << HSI_MAPPING_PORT_NUMBER_OFFSET), base, HSI_HSR_MAPPING_FIFO_REG(i)); channel++; } + if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_UNDEF) dev_dbg(hsi_ctrl->dev, "Fifo mapping : All FIFOs for " - "Port1\n"); - hsi_ctrl->fifo_mapping_strategy = HSI_FIFO_MAPPING_ALL_PORT1; + "Port %d\n", port + 1); } else if (mtype == HSI_FIFO_MAPPING_SSI) { channel = 0; port = 0; @@ -182,14 +192,14 @@ int hsi_fifo_mapping(struct hsi_dev *hsi_ctrl, unsigned int mtype) if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_UNDEF) dev_dbg(hsi_ctrl->dev, "Fifo mapping : 8 FIFOs per Port" " (SSI compatible mode)\n"); - hsi_ctrl->fifo_mapping_strategy = HSI_FIFO_MAPPING_SSI; } else { - hsi_ctrl->fifo_mapping_strategy = HSI_FIFO_MAPPING_UNDEF; dev_err(hsi_ctrl->dev, "Bad Fifo strategy request : %d\n", mtype); err = -EINVAL; } + hsi_ctrl->fifo_mapping_strategy = mtype; + return err; } @@ -318,6 +328,11 @@ u8 hsi_get_rx_fifo_occupancy(struct hsi_dev *hsi_ctrl, u8 fifo) void __iomem *base = hsi_ctrl->base; int hsr_mapping, mapping_words; + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, "Invalid FIFO id %d.\n", fifo); + return 0; + } + hsr_mapping = hsi_inl(base, HSI_HSR_MAPPING_FIFO_REG(fifo)); mapping_words = (hsr_mapping >> HSI_HST_MAPPING_THRESH_OFFSET) & 0xF; return mapping_words; diff --git a/drivers/omap_hsi/hsi_driver_if.c b/drivers/omap_hsi/hsi_driver_if.c index 19012e5..79e05cc 100644 --- a/drivers/omap_hsi/hsi_driver_if.c +++ b/drivers/omap_hsi/hsi_driver_if.c @@ -50,7 +50,6 @@ int hsi_set_rx_divisor(struct hsi_port *sport, struct hsr_ctx *cfg) } else if (cfg->divisor != HSI_HSR_DIVISOR_AUTO) { /* Divisor set mode: use counters */ /* Leave auto mode: use new counters values */ - cfg->counters = 0xFFFFF; sport->reg_counters = cfg->counters; sport->counters_on = 1; hsi_outl(cfg->counters, base, @@ -260,6 +259,7 @@ int hsi_open(struct hsi_device *dev) struct hsi_channel *ch; struct hsi_port *port; struct hsi_dev *hsi_ctrl; + int err; if (!dev || !dev->ch) { pr_err(LOG_NAME "Wrong HSI device %p\n", dev); @@ -274,26 +274,69 @@ int hsi_open(struct hsi_device *dev) "registered\n"); return -EINVAL; } - port = ch->hsi_port; - hsi_ctrl = port->hsi_controller; - - spin_lock_bh(&hsi_ctrl->lock); - hsi_clocks_enable_channel(dev->device.parent, ch->channel_number, - __func__); - if (ch->flags & HSI_CH_OPEN) { dev_err(dev->device.parent, "Port %d Channel %d already OPENED\n", dev->n_p, dev->n_ch); - spin_unlock_bh(&hsi_ctrl->lock); return -EBUSY; } + port = ch->hsi_port; + hsi_ctrl = port->hsi_controller; + if (!hsi_ctrl) { + dev_err(dev->device.parent, + "%s: Port %d Channel %d has no hsi controller?\n", + __func__, dev->n_p, dev->n_ch); + return -EINVAL; + } + + if (hsi_ctrl->clock_rate == 0) { + struct hsi_platform_data *pdata; + + pdata = dev_get_platdata(hsi_ctrl->dev); + if (!pdata) { + dev_err(dev->device.parent, + "%s: Port %d Channel %d has no pdata\n", + __func__, dev->n_p, dev->n_ch); + return -EINVAL; + } + if (!pdata->device_scale) { + dev_err(dev->device.parent, + "%s: Undefined platform device_scale function\n", + __func__); + return -ENXIO; + } + + /* Retry to set the HSI FCLK to default. */ + err = pdata->device_scale(hsi_ctrl->dev, hsi_ctrl->dev, + pdata->default_hsi_fclk); + if (err) { + dev_err(dev->device.parent, + "%s: Error %d setting HSI FClk to %ld. " + "Will retry on next open\n", + __func__, err, pdata->default_hsi_fclk); + return err; + } else { + dev_info(dev->device.parent, "HSI clock is now %ld\n", + pdata->default_hsi_fclk); + hsi_ctrl->clock_rate = pdata->default_hsi_fclk; + } + } + spin_lock_bh(&hsi_ctrl->lock); + hsi_clocks_enable_channel(dev->device.parent, ch->channel_number, + __func__); + /* Restart with flags cleaned up */ ch->flags = HSI_CH_OPEN; - hsi_driver_enable_interrupt(port, HSI_CAWAKEDETECTED | HSI_ERROROCCURED - | HSI_BREAKDETECTED); + if (port->wake_rx_3_wires_mode) + hsi_driver_enable_interrupt(port, HSI_ERROROCCURED + | HSI_BREAKDETECTED); + else + hsi_driver_enable_interrupt(port, HSI_CAWAKEDETECTED + | HSI_ERROROCCURED + | HSI_BREAKDETECTED); + /* NOTE: error and break are port events and do not need to be * enabled for HSI extended enable register */ @@ -312,7 +355,7 @@ EXPORT_SYMBOL(hsi_open); * @addr - pointer to a 32-bit word data to be written. * @size - number of 32-bit word to be written. * - * Return 0 on sucess, a negative value on failure. + * Return 0 on success, a negative value on failure. * A success value only indicates that the request has been accepted. * Transfer is only completed when the write_done callback is called. * @@ -342,6 +385,13 @@ int hsi_write(struct hsi_device *dev, u32 *addr, unsigned int size) } ch = dev->ch; + if (ch->write_data.addr != NULL) { + dev_err(dev->device.parent, "# Invalid request - Write " + "operation pending port %d channel %d\n", + ch->hsi_port->port_number, + ch->channel_number); + return -EINVAL; + } spin_lock_bh(&ch->hsi_port->hsi_controller->lock); if (pm_runtime_suspended(dev->device.parent) || @@ -353,18 +403,6 @@ int hsi_write(struct hsi_device *dev, u32 *addr, unsigned int size) hsi_clocks_enable_channel(dev->device.parent, ch->channel_number, __func__); - if (ch->write_data.addr != NULL) { - dev_err(dev->device.parent, "# Invalid request - Write " - "operation pending port %d channel %d\n", - ch->hsi_port->port_number, - ch->channel_number); - - hsi_clocks_disable_channel(dev->device.parent, - ch->channel_number, __func__); - spin_unlock_bh(&ch->hsi_port->hsi_controller->lock); - return -EINVAL; - } - ch->write_data.addr = addr; ch->write_data.size = size; ch->write_data.lch = -1; @@ -669,7 +707,7 @@ EXPORT_SYMBOL(hsi_unpoll); * @command - HSI I/O control command * @arg - parameter associated to the control command. NULL, if no parameter. * - * Return 0 on sucess, a negative value on failure. + * Return 0 on success, a negative value on failure. * */ int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg) @@ -707,15 +745,14 @@ int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg) switch (command) { case HSI_IOCTL_ACWAKE_UP: + /* Wake up request to Modem (typically OMAP initiated) */ + /* Symetrical disable will be done in HSI_IOCTL_ACWAKE_DOWN */ if (ch->flags & HSI_CH_ACWAKE) { dev_dbg(dev->device.parent, "Duplicate ACWAKE UP\n"); err = -EPERM; goto out; } - /* Wake up request to Modem (typically OMAP initiated) */ - /* Symetrical disable will be done in HSI_IOCTL_ACWAKE_DOWN */ - ch->flags |= HSI_CH_ACWAKE; pport->acwake_status |= BIT(channel); @@ -839,16 +876,45 @@ int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg) } *(size_t *)arg = hsi_get_rx_fifo_occupancy(hsi_ctrl, fifo); break; - case HSI_IOCTL_SET_ACREADY_SAFEMODE: - omap_writel(omap_readl(0x4A1000C8) | 0x7, 0x4A1000C8); - break; - case HSI_IOCTL_SET_ACREADY_NORMAL: - omap_writel(omap_readl(0x4A1000C8) & 0xFFFFFFF9, 0x4A1000C8); - case HSI_IOCTL_SET_3WIRE_MODE: - omap_writel(0x30000, 0x4A058C08); + case HSI_IOCTL_SET_WAKE_RX_3WIRES_MODE: + dev_info(dev->device.parent, + "Entering RX wakeup in 3 wires mode (no CAWAKE)\n"); + pport->wake_rx_3_wires_mode = 1; + + /* HSI-C1BUG00085: ixxx: HSI wakeup issue in 3 wires mode + * HSI will NOT generate the Swakeup for 2nd frame if it entered + * IDLE after 1st received frame */ + if (is_hsi_errata(hsi_ctrl, HSI_ERRATUM_ixxx_3WIRES_NO_SWAKEUP)) + if (hsi_driver_device_is_hsi(to_platform_device + (hsi_ctrl->dev))) + hsi_set_pm_force_hsi_on(hsi_ctrl); + + /* When WAKE is not available, ACREADY must be set to 1 at + * reset else remote will never have a chance to transmit. */ + hsi_outl_or(HSI_SET_WAKE_3_WIRES | HSI_SET_WAKE_READY_LVL_1, + base, HSI_SYS_SET_WAKE_REG(port)); + hsi_driver_disable_interrupt(pport, HSI_CAWAKEDETECTED); break; - case HSI_IOCTL_SET_4WIRE_MODE: - omap_writel((omap_readl(0x4A058C08) & 0xFFFF), 0x4A058C08); + case HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE: + dev_info(dev->device.parent, + "Entering RX wakeup in 4 wires mode\n"); + pport->wake_rx_3_wires_mode = 0; + + /* HSI-C1BUG00085: ixxx: HSI wakeup issue in 3 wires mode + * HSI will NOT generate the Swakeup for 2nd frame if it entered + * IDLE after 1st received frame */ + if (is_hsi_errata(hsi_ctrl, HSI_ERRATUM_ixxx_3WIRES_NO_SWAKEUP)) + if (hsi_driver_device_is_hsi(to_platform_device + (hsi_ctrl->dev))) + hsi_set_pm_default(hsi_ctrl); + + /* Clean CA_WAKE status */ + pport->cawake_status = -1; + hsi_outl(HSI_CAWAKEDETECTED, base, + HSI_SYS_MPU_STATUS_REG(port, pport->n_irq)); + hsi_driver_enable_interrupt(pport, HSI_CAWAKEDETECTED); + hsi_outl_and(HSI_SET_WAKE_3_WIRES_MASK, base, + HSI_SYS_SET_WAKE_REG(port)); break; default: err = -ENOIOCTLCMD; @@ -956,8 +1022,13 @@ void hsi_set_port_event_cb(struct hsi_device *dev, spin_lock_bh(&hsi_ctrl->lock); hsi_clocks_enable_channel(dev->device.parent, dev->ch->channel_number, __func__); - hsi_driver_enable_interrupt(port, HSI_CAWAKEDETECTED | HSI_ERROROCCURED - | HSI_BREAKDETECTED); + if (port->wake_rx_3_wires_mode) + hsi_driver_enable_interrupt(port, HSI_ERROROCCURED + | HSI_BREAKDETECTED); + else + hsi_driver_enable_interrupt(port, HSI_CAWAKEDETECTED + | HSI_ERROROCCURED + | HSI_BREAKDETECTED); hsi_clocks_disable_channel(dev->device.parent, dev->ch->channel_number, __func__); spin_unlock_bh(&hsi_ctrl->lock); diff --git a/drivers/omap_hsi/hsi_driver_int.c b/drivers/omap_hsi/hsi_driver_int.c index 52bbba1..ac71bc7 100644 --- a/drivers/omap_hsi/hsi_driver_int.c +++ b/drivers/omap_hsi/hsi_driver_int.c @@ -20,7 +20,7 @@ #include "hsi_driver.h" #include <linux/delay.h> -int shceduled_already_flag = 0; + void hsi_reset_ch_read(struct hsi_channel *ch) { ch->read_data.addr = NULL; @@ -40,6 +40,16 @@ void hsi_reset_ch_write(struct hsi_channel *ch) */ bool hsi_is_channel_busy(struct hsi_channel *ch) { + struct hsi_port *p = ch->hsi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + unsigned int fifo; + + /* Data in FIFO is lost during the transition to RET or OFF modes */ + fifo = hsi_fifo_get_id(p->hsi_controller, channel, port); + if (hsi_get_rx_fifo_occupancy(p->hsi_controller, fifo) > 0) + return true; + if (ch->write_data.addr == NULL) return false; @@ -54,6 +64,7 @@ bool hsi_is_channel_busy(struct hsi_channel *ch) /* Check if a HSI port is busy : * - data transfer (Write) is ongoing for a given HSI channel * - CAWAKE is high + * - CAWAKE is not used (receiver in 3-wires mode) * - Currently in HSI interrupt tasklet * - Currently in HSI CAWAKE tasklet (for SSI) */ @@ -73,6 +84,13 @@ bool hsi_is_hsi_port_busy(struct hsi_port *pport) return true; } + if (pport->wake_rx_3_wires_mode) { + dev_dbg(hsi_ctrl->dev, "Receiver Port %d in 3 wires mode," + "acwake_status %d\n", pport->port_number, + pport->acwake_status); + return true; + } + if (cur_cawake) { dev_dbg(hsi_ctrl->dev, "Port %d: WAKE status: acwake_status %d," "cur_cawake %d", pport->port_number, @@ -143,7 +161,16 @@ bool hsi_is_hst_controller_busy(struct hsi_dev *hsi_ctrl) } -/* Enables the CAWAKE, BREAK, or ERROR interrupt for the given port */ +/* Enables the CAWAKE, BREAK, or ERROR interrupt for the given port + * + * Since these 3 interrupts ENABLE and STATUS bits are duplicated in both + * HSI_Pp_M_IRQr_xxx(channels [0..7]) and HSI_Pp_M_IRQrU_xxx(channels [8..15]), + * for convenience we always enable the interrupts for channels [0..7]. + * + * BREAK and ERROR interrupts in HSI_Pp_M_IRQrU_xxx are not used. + * CAWAKE interrupt in HSI_Pp_M_IRQrU_xxx is used as a backup interrupt to be + * sure we don't miss a CAWAKE interrupt while clearing the previous one. + */ int hsi_driver_enable_interrupt(struct hsi_port *pport, u32 flag) { hsi_outl_or(flag, pport->hsi_controller->base, @@ -152,6 +179,45 @@ int hsi_driver_enable_interrupt(struct hsi_port *pport, u32 flag) return 0; } +/* Disables the CAWAKE, BREAK, or ERROR interrupt for the given port */ +int hsi_driver_disable_interrupt(struct hsi_port *pport, u32 flag) +{ + hsi_outl_and(~flag, pport->hsi_controller->base, + HSI_SYS_MPU_ENABLE_REG(pport->port_number, pport->n_irq)); + + return 0; +} + +/* hsi_driver_ack_interrupt - Acknowledge the CAWAKE, BREAK, or ERROR interrupt + for the given port + * @backup - indicate whether we shall look for the backup status interrupt + * register (HSI_Pp_M_IRQrU_STATUS) or the normal status interrupt + * register (HSI_Pp_M_IRQr_STATUS) + * Backup status interrupt register is used for CAWAKE only. + */ +void hsi_driver_ack_interrupt(struct hsi_port *pport, u32 flag, bool backup) +{ + hsi_outl(flag, pport->hsi_controller->base, + HSI_SYS_MPU_STATUS_CH_REG(pport->port_number, + pport->n_irq, + backup ? HSI_SSI_CHANNELS_MAX : + 0)); +} + +bool hsi_driver_is_interrupt_pending(struct hsi_port *pport, u32 flag, + bool backup) +{ + u32 val; + + val = hsi_inl(pport->hsi_controller->base, + HSI_SYS_MPU_STATUS_CH_REG(pport->port_number, + pport->n_irq, + backup ? HSI_SSI_CHANNELS_MAX : + 0)); + + return val & flag; +} + /* Enables the Data Accepted Interrupt of HST for the given channel */ int hsi_driver_enable_write_interrupt(struct hsi_channel *ch, u32 * data) { @@ -323,7 +389,6 @@ static void hsi_do_channel_rx(struct hsi_channel *ch) int rx_poll = 0; int data_read = 0; int fifo, fifo_words_avail; - unsigned int data; n_ch = ch->channel_number; n_p = ch->hsi_port->port_number; @@ -352,15 +417,18 @@ static void hsi_do_channel_rx(struct hsi_channel *ch) } } + /* Disable interrupts if not needed for polling */ + if (!(ch->flags & HSI_CH_RX_POLL)) + hsi_driver_disable_read_interrupt(ch); + /* * Check race condition: RX transmission initiated but DMA transmission * already started - acknowledge then ignore interrupt occurence */ if (ch->read_data.lch != -1) { - dev_err(hsi_ctrl->dev, - "race condition between rx txmn and DMA txmn %0x\n", - ch->read_data.lch); - hsi_driver_disable_read_interrupt(ch); + dev_warn(hsi_ctrl->dev, + "Race condition between RX Int ch %d and DMA %0x\n", + n_ch, ch->read_data.lch); goto done; } @@ -371,11 +439,10 @@ static void hsi_do_channel_rx(struct hsi_channel *ch) buff_offset = hsi_hsr_buffer_reg(hsi_ctrl, n_p, n_ch); if (buff_offset >= 0) { data_read = 1; - data = *(ch->read_data.addr) = hsi_inl(base, - buff_offset); + *(ch->read_data.addr) = hsi_inl(base, buff_offset); } } - hsi_driver_disable_read_interrupt(ch); + hsi_reset_ch_read(ch); done: @@ -389,6 +456,7 @@ done: if (data_read) { spin_unlock(&hsi_ctrl->lock); + dev_dbg(hsi_ctrl->dev, "Calling ch %d read callback.\n", n_ch); (*ch->read_done) (ch->dev, 1); spin_lock(&hsi_ctrl->lock); } @@ -412,6 +480,11 @@ int hsi_do_cawake_process(struct hsi_port *pport) struct hsi_dev *hsi_ctrl = pport->hsi_controller; bool cawake_status = hsi_get_cawake(pport); + if (pport->wake_rx_3_wires_mode) { + dev_warn(hsi_ctrl->dev, "CAWAKE edge in RX 3 wires, exiting\n"); + return 0; + } + /* Deal with init condition */ if (unlikely(pport->cawake_status < 0)) pport->cawake_status = !cawake_status; @@ -431,25 +504,32 @@ int hsi_do_cawake_process(struct hsi_port *pport) /* Check for possible mismatch (race condition) */ if (unlikely(pport->cawake_status)) { dev_warn(hsi_ctrl->dev, - "CAWAKE race is detected: %s.\n", - "HI -> LOW -> HI"); + "Missed previous CAWAKE falling edge...\n"); spin_unlock(&hsi_ctrl->lock); hsi_port_event_handler(pport, HSI_EVENT_CAWAKE_DOWN, NULL); spin_lock(&hsi_ctrl->lock); + + /* In case another CAWAKE interrupt occured and caused + * a race condition, clear CAWAKE backup interrupt to + * avoid handling twice the race condition */ + hsi_driver_ack_interrupt(pport, HSI_CAWAKEDETECTED, + true); } pport->cawake_status = 1; - /* Force HSI to ON_ACTIVE when CAWAKE is high */ - hsi_set_pm_force_hsi_on(hsi_ctrl); - /* - * TODO: Use pm_qos() to set latency constraint to prevent - * L3INIT to enter RET/OFF when CAWAKE is high. - */ - spin_unlock(&hsi_ctrl->lock); hsi_port_event_handler(pport, HSI_EVENT_CAWAKE_UP, NULL); spin_lock(&hsi_ctrl->lock); + + /* + * HSI - OMAP4430-2.2BUG00055: i702 + * HSI: DSP Swakeup generated is the same than MPU Swakeup. + * System cannot enter in off mode due to the DSP. + */ + if (is_hsi_errata(hsi_ctrl, HSI_ERRATUM_i702_PM_HSI_SWAKEUP)) + omap_pm_clear_dsp_wake_up(); + } else { dev_dbg(hsi_ctrl->dev, "CAWAKE falling edge detected\n"); @@ -462,26 +542,35 @@ int hsi_do_cawake_process(struct hsi_port *pport) } if (unlikely(!pport->cawake_status)) { dev_warn(hsi_ctrl->dev, - "CAWAKE race is detected: %s.\n", - "LOW -> HI -> LOW"); + "Missed previous CAWAKE rising edge...\n"); spin_unlock(&hsi_ctrl->lock); hsi_port_event_handler(pport, HSI_EVENT_CAWAKE_UP, NULL); spin_lock(&hsi_ctrl->lock); + + /* In case another CAWAKE interrupt occured and caused + * a race condition, clear CAWAKE backup interrupt to + * avoid handling twice the race condition */ + hsi_driver_ack_interrupt(pport, HSI_CAWAKEDETECTED, + true); } pport->cawake_status = 0; - /* Allow HSI HW to enter IDLE when CAWAKE is low */ - hsi_set_pm_default(hsi_ctrl); - /* - * TODO: Use pm_qos() to release latency constraint to allow - * L3INIT to enter RET/OFF when CAWAKE is low - */ - spin_unlock(&hsi_ctrl->lock); hsi_port_event_handler(pport, HSI_EVENT_CAWAKE_DOWN, NULL); spin_lock(&hsi_ctrl->lock); } + + /* If another CAWAKE event occured while previous is still processed */ + /* do not clear the status bit */ + cawake_status = hsi_get_cawake(pport); + if (cawake_status != pport->cawake_status) { + dev_warn(hsi_ctrl->dev, "CAWAKE line changed to %d while CAWAKE" + "event is still being processed\n", + cawake_status); + return -EAGAIN; + } + return 0; } @@ -501,7 +590,8 @@ int hsi_do_cawake_process(struct hsi_port *pport) static u32 hsi_driver_int_proc(struct hsi_port *pport, unsigned long status_offset, unsigned long enable_offset, unsigned int start, - unsigned int stop) + unsigned int stop, + bool cawake_double_int) { struct hsi_dev *hsi_ctrl = pport->hsi_controller; void __iomem *base = hsi_ctrl->base; @@ -515,6 +605,10 @@ static u32 hsi_driver_int_proc(struct hsi_port *pport, status_reg = hsi_inl(base, status_offset); status_reg &= hsi_inl(base, enable_offset); + /* Check if we need to process an additional CAWAKE interrupt */ + if (cawake_double_int) + status_reg |= HSI_CAWAKEDETECTED; + if (pport->cawake_off_event) { dev_dbg(hsi_ctrl->dev, "CAWAKE detected from OFF mode.\n"); } else if (!status_reg) { @@ -528,7 +622,6 @@ static u32 hsi_driver_int_proc(struct hsi_port *pport, if (status_reg & HSI_BREAKDETECTED) { dev_info(hsi_ctrl->dev, "Hardware BREAK on port %d\n", port); - hsi_outl(0, base, HSI_HSR_BREAK_REG(port)); spin_unlock(&hsi_ctrl->lock); hsi_port_event_handler(pport, HSI_EVENT_BREAK_DETECTED, NULL); spin_lock(&hsi_ctrl->lock); @@ -603,20 +696,36 @@ static u32 hsi_process_int_event(struct hsi_port *pport) unsigned int port = pport->port_number; unsigned int irq = pport->n_irq; u32 status_reg; + bool cawake_double_int = false; + + /* Clear CAWAKE backup interrupt */ + hsi_driver_ack_interrupt(pport, HSI_CAWAKEDETECTED, true); /* Process events for channels 0..7 */ status_reg = hsi_driver_int_proc(pport, HSI_SYS_MPU_STATUS_REG(port, irq), HSI_SYS_MPU_ENABLE_REG(port, irq), 0, - min(pport->max_ch, (u8) HSI_SSI_CHANNELS_MAX) - 1); + min(pport->max_ch, (u8) HSI_SSI_CHANNELS_MAX) - 1, + cawake_double_int); + + /* If another CAWAKE interrupt occured while previous is still being + * processed, mark it for extra processing */ + if (hsi_driver_is_interrupt_pending(pport, HSI_CAWAKEDETECTED, true) && + (status_reg & HSI_CAWAKEDETECTED)) { + dev_warn(pport->hsi_controller->dev, "New CAWAKE interrupt " + "detected during interrupt processing\n"); + /* Force processing of backup CAWAKE interrupt */ + cawake_double_int = true; + } - /* Process events for channels 8..15 */ - if (pport->max_ch > HSI_SSI_CHANNELS_MAX) + /* Process events for channels 8..15 or backup interrupt if needed */ + if ((pport->max_ch > HSI_SSI_CHANNELS_MAX) || cawake_double_int) status_reg |= hsi_driver_int_proc(pport, HSI_SYS_MPU_U_STATUS_REG(port, irq), HSI_SYS_MPU_U_ENABLE_REG(port, irq), - HSI_SSI_CHANNELS_MAX, pport->max_ch - 1); + HSI_SSI_CHANNELS_MAX, pport->max_ch - 1, + cawake_double_int); return status_reg; } @@ -636,24 +745,27 @@ static void do_hsi_tasklet(unsigned long hsi_port) status_reg = hsi_process_int_event(pport); pport->in_int_tasklet = false; + clear_bit(HSI_FLAGS_TASKLET_LOCK, &pport->flags); hsi_clocks_disable(hsi_ctrl->dev, __func__); spin_unlock(&hsi_ctrl->lock); - shceduled_already_flag = 0; + enable_irq(pport->irq); } static irqreturn_t hsi_mpu_handler(int irq, void *p) { - struct hsi_port *pport = p; - if (shceduled_already_flag == 0) { - shceduled_already_flag = 1; + struct hsi_port *pport = (struct hsi_port *) p; + + /* Check no other interrupt handler has already scheduled the tasklet */ + if (test_and_set_bit(HSI_FLAGS_TASKLET_LOCK, &pport->flags)) + return IRQ_HANDLED; + tasklet_hi_schedule(&pport->hsi_tasklet); - /* - * Disable interrupt until Bottom Half has cleared the - * IRQ status register - */ + + /* Disable interrupt until Bottom Half has cleared the IRQ status */ + /* register */ disable_irq_nosync(pport->irq); - } + return IRQ_HANDLED; } diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c index 2f2f9a6..8ca350a 100644 --- a/drivers/power/max17040_battery.c +++ b/drivers/power/max17040_battery.c @@ -20,6 +20,10 @@ #include <linux/power_supply.h> #include <linux/max17040_battery.h> #include <linux/slab.h> +#include <linux/android_alarm.h> +#include <linux/suspend.h> +#include <linux/interrupt.h> +#include <linux/reboot.h> #define MAX17040_VCELL_MSB 0x02 #define MAX17040_VCELL_LSB 0x03 @@ -34,12 +38,21 @@ #define MAX17040_CMD_MSB 0xFE #define MAX17040_CMD_LSB 0xFF -#define MAX17040_DELAY 1000 -#define MAX17040_BATTERY_FULL 95 +#define MAX17040_BATTERY_FULL 100 + +#define HAS_ALERT_INTERRUPT(ver) (ver >= 3) + +#define FAST_POLL (1 * 60) +#define SLOW_POLL (10 * 60) + +#define STATUS_CHARGABLE 0x0 +#define STATUS_CHARGE_FULL 0x1 +#define STATUS_ABNORMAL_TEMP 0x2 +#define STATUS_CHARGE_TIMEOVER 0x3 struct max17040_chip { struct i2c_client *client; - struct delayed_work work; + struct work_struct work; struct power_supply battery; struct max17040_platform_data *pdata; @@ -51,6 +64,25 @@ struct max17040_chip { int soc; /* State Of Charge */ int status; + /* Health of Battery */ + int bat_health; + /* Temperature of Battery */ + int bat_temp; + + struct notifier_block pm_notifier; + struct wake_lock work_wake_lock; + + struct alarm alarm; + ktime_t last_poll; + int slow_poll; + int shutdown; + /* chip version */ + u16 ver; + + int charger_status; + unsigned long chg_limit_time; + + bool is_timer_flag; }; static int max17040_get_property(struct power_supply *psy, @@ -64,6 +96,9 @@ static int max17040_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_STATUS: val->intval = chip->status; break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chip->bat_health; + break; case POWER_SUPPLY_PROP_ONLINE: val->intval = chip->online; break; @@ -73,17 +108,25 @@ static int max17040_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CAPACITY: val->intval = chip->soc; break; + case POWER_SUPPLY_PROP_TEMP: + if (!chip->pdata->get_bat_temp) + return -ENODATA; + val->intval = chip->bat_temp; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; default: return -EINVAL; } return 0; } -static int max17040_write_reg(struct i2c_client *client, int reg, u8 value) +static int max17040_write_reg(struct i2c_client *client, int reg, u16 val) { int ret; - ret = i2c_smbus_write_byte_data(client, reg, value); + ret = i2c_smbus_write_word_data(client, reg, cpu_to_be16(val)); if (ret < 0) dev_err(&client->dev, "%s: err %d\n", __func__, ret); @@ -91,57 +134,83 @@ static int max17040_write_reg(struct i2c_client *client, int reg, u8 value) return ret; } -static int max17040_read_reg(struct i2c_client *client, int reg) +static int max17040_read_reg(struct i2c_client *client, int reg, u16 *val) { int ret; - ret = i2c_smbus_read_byte_data(client, reg); + ret = i2c_smbus_read_word_data(client, reg); - if (ret < 0) + if (ret < 0) { dev_err(&client->dev, "%s: err %d\n", __func__, ret); + *val = 0; + return ret; + } - return ret; + *val = be16_to_cpu(ret); + return 0; } static void max17040_reset(struct i2c_client *client) { - max17040_write_reg(client, MAX17040_CMD_MSB, 0x54); - max17040_write_reg(client, MAX17040_CMD_LSB, 0x00); + max17040_write_reg(client, MAX17040_CMD_MSB, 0x5400); + + msleep(125); + + max17040_write_reg(client, MAX17040_MODE_MSB, 0x4000); } static void max17040_get_vcell(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - u8 msb; - u8 lsb; + u16 val; - msb = max17040_read_reg(client, MAX17040_VCELL_MSB); - lsb = max17040_read_reg(client, MAX17040_VCELL_LSB); - - chip->vcell = (msb << 4) + (lsb >> 4); + if (!max17040_read_reg(client, MAX17040_VCELL_MSB, &val)) + chip->vcell = (val >> 4) * 1250; + else + dev_warn(&client->dev, "i2c error, not updating vcell\n"); } +#define TO_FIXED(a,b) (((a) << 8) + (b)) +#define FIXED_TO_INT(x) ((int)((x) >> 8)) +#define FIXED_MULT(x,y) ((((u32)(x) * (u32)(y)) + (1 << 7)) >> 8) +#define FIXED_DIV(x,y) ((((u32)(x) << 8) + ((u32)(y) >> 1)) / (u32)(y)) + static void max17040_get_soc(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - u8 msb; - u8 lsb; + u32 val; + u32 fmin_cap = TO_FIXED(chip->pdata->min_capacity, 0); + u16 regval; - msb = max17040_read_reg(client, MAX17040_SOC_MSB); - lsb = max17040_read_reg(client, MAX17040_SOC_LSB); + if (max17040_read_reg(client, MAX17040_SOC_MSB, ®val)) { + dev_warn(&client->dev, "i2c error, not updating soc\n"); + return; + } - chip->soc = msb; + /* convert msb.lsb to Q8.8 */ + val = TO_FIXED(regval >> 8, regval & 0xff); + if (val <= fmin_cap) { + chip->soc = 0; + return; + } + + val = FIXED_MULT(TO_FIXED(100, 0), val - fmin_cap); + val = FIXED_DIV(val, TO_FIXED(100, 0) - fmin_cap); + chip->soc = clamp(FIXED_TO_INT(val), 0, 100); } static void max17040_get_version(struct i2c_client *client) { - u8 msb; - u8 lsb; - - msb = max17040_read_reg(client, MAX17040_VER_MSB); - lsb = max17040_read_reg(client, MAX17040_VER_LSB); + struct max17040_chip *chip = i2c_get_clientdata(client); + u16 val; - dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d%d\n", msb, lsb); + if (!max17040_read_reg(client, MAX17040_VER_MSB, &val)) { + chip->ver = val; + dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d\n", val); + } else { + dev_err(&client->dev, + "Error reading version, some features disabled\n"); + } } static void max17040_get_online(struct i2c_client *client) @@ -164,45 +233,276 @@ static void max17040_get_status(struct i2c_client *client) } if (chip->pdata->charger_online()) { - if (chip->pdata->charger_enable()) + if (chip->pdata->charger_enable()) { chip->status = POWER_SUPPLY_STATUS_CHARGING; - else - chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + chip->status = + chip->charger_status == STATUS_CHARGE_FULL ? + POWER_SUPPLY_STATUS_FULL : + POWER_SUPPLY_STATUS_NOT_CHARGING; + } } else { chip->status = POWER_SUPPLY_STATUS_DISCHARGING; + chip->chg_limit_time = 0; + chip->charger_status = STATUS_CHARGABLE; } +} + +static void max17040_get_temp_status(struct max17040_chip *chip) +{ + int r; + int t; + + if (!chip->pdata->get_bat_temp) + return; - if (chip->soc > MAX17040_BATTERY_FULL) - chip->status = POWER_SUPPLY_STATUS_FULL; + r = chip->pdata->get_bat_temp(&t); + + if (r < 0) { + dev_err(&chip->client->dev, + "error %d reading battery temperature\n", r); + chip->bat_health = POWER_SUPPLY_HEALTH_UNKNOWN; + return; + } + + chip->bat_temp = t; + + if (chip->bat_temp >= chip->pdata->high_block_temp) { + chip->bat_health = POWER_SUPPLY_HEALTH_OVERHEAT; + } else if (chip->bat_temp <= chip->pdata->high_recover_temp && + chip->bat_temp >= chip->pdata->low_recover_temp) { + chip->bat_health = POWER_SUPPLY_HEALTH_GOOD; + } else if (chip->bat_temp <= chip->pdata->low_block_temp) { + chip->bat_health = POWER_SUPPLY_HEALTH_COLD; + } } -static void max17040_work(struct work_struct *work) +static void max17040_charger_update(struct max17040_chip *chip) { - struct max17040_chip *chip; + ktime_t ktime; + struct timespec cur_time; - chip = container_of(work, struct max17040_chip, work.work); + if (!chip->pdata->is_full_charge || !chip->pdata->allow_charging) + return; + + ktime = alarm_get_elapsed_realtime(); + cur_time = ktime_to_timespec(ktime); + + switch (chip->charger_status) { + case STATUS_CHARGABLE: + if (chip->pdata->is_full_charge() && + chip->soc >= MAX17040_BATTERY_FULL && + chip->vcell > chip->pdata->fully_charged_vol) { + chip->charger_status = STATUS_CHARGE_FULL; + chip->is_timer_flag = true; + chip->chg_limit_time = 0; + chip->pdata->allow_charging(0); + } else if (chip->chg_limit_time && + cur_time.tv_sec > chip->chg_limit_time) { + chip->charger_status = STATUS_CHARGE_TIMEOVER; + chip->is_timer_flag = true; + chip->chg_limit_time = 0; + chip->pdata->allow_charging(0); + } else if (chip->bat_health == POWER_SUPPLY_HEALTH_OVERHEAT || + chip->bat_health == POWER_SUPPLY_HEALTH_COLD) { + chip->charger_status = STATUS_ABNORMAL_TEMP; + chip->chg_limit_time = 0; + chip->pdata->allow_charging(0); + } + break; + + case STATUS_CHARGE_FULL: + if (chip->vcell <= chip->pdata->recharge_vol) { + chip->charger_status = STATUS_CHARGABLE; + chip->pdata->allow_charging(1); + } + break; + + case STATUS_ABNORMAL_TEMP: + if (chip->bat_temp <= chip->pdata->high_recover_temp && + chip->bat_temp >= + chip->pdata->low_recover_temp) { + chip->charger_status = STATUS_CHARGABLE; + chip->pdata->allow_charging(1); + } + break; + + case STATUS_CHARGE_TIMEOVER: + if (chip->vcell <= chip->pdata->fully_charged_vol) { + chip->charger_status = STATUS_CHARGABLE; + chip->pdata->allow_charging(1); + } + break; + + default: + dev_err(&chip->client->dev, "%s : invalid status [%d]\n", + __func__, chip->charger_status); + } + + if (!chip->chg_limit_time && + chip->charger_status == STATUS_CHARGABLE) { + chip->chg_limit_time = + chip->is_timer_flag ? + cur_time.tv_sec + chip->pdata->limit_recharging_time : + cur_time.tv_sec + chip->pdata->limit_charging_time; + } + + dev_dbg(&chip->client->dev, "%s, Charger Status : %d, Limit Time : %ld\n", + __func__, chip->charger_status, chip->chg_limit_time); +} + +static void max17040_update(struct max17040_chip *chip) +{ + int prev_status = chip->status; + int prev_soc = chip->soc; max17040_get_vcell(chip->client); max17040_get_soc(chip->client); max17040_get_online(chip->client); + max17040_get_temp_status(chip); + if (chip->pdata->charger_online()) + max17040_charger_update(chip); + else + chip->is_timer_flag = false; max17040_get_status(chip->client); + if ((chip->soc != prev_soc) || (chip->status != prev_status)) + power_supply_changed(&chip->battery); + + dev_info(&chip->client->dev, "online = %d vcell = %d soc = %d " + "status = %d health = %d temp = %d " + "charger status = %d\n", chip->online, chip->vcell, + chip->soc, chip->status, chip->bat_health, chip->bat_temp, + chip->charger_status); +} + +static void max17040_program_alarm(struct max17040_chip *chip, int seconds) +{ + ktime_t low_interval = ktime_set(seconds - 10, 0); + ktime_t slack = ktime_set(20, 0); + ktime_t next; - schedule_delayed_work(&chip->work, MAX17040_DELAY); + next = ktime_add(chip->last_poll, low_interval); + alarm_start_range(&chip->alarm, next, ktime_add(next, slack)); +} + +static void max17040_work(struct work_struct *work) +{ + unsigned long flags; + struct timespec ts; + struct max17040_chip *chip; + + chip = container_of(work, struct max17040_chip, work); + + max17040_update(chip); + + chip->last_poll = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(chip->last_poll); + + local_irq_save(flags); + wake_unlock(&chip->work_wake_lock); + if (!chip->shutdown) + max17040_program_alarm(chip, FAST_POLL); + local_irq_restore(flags); +} + +static void max17040_battery_alarm(struct alarm *alarm) +{ + struct max17040_chip *chip = + container_of(alarm, struct max17040_chip, alarm); + + wake_lock(&chip->work_wake_lock); + schedule_work(&chip->work); + +} + +static void max17040_ext_power_changed(struct power_supply *psy) +{ + struct max17040_chip *chip = container_of(psy, + struct max17040_chip, battery); + + wake_lock(&chip->work_wake_lock); + schedule_work(&chip->work); } static enum power_supply_property max17040_battery_props[] = { POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TECHNOLOGY, + /* must be last */ + POWER_SUPPLY_PROP_TEMP, }; +static int max17040_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, + void *unused) +{ + struct max17040_chip *chip = + container_of(notifier, struct max17040_chip, pm_notifier); + + switch (pm_event) { + case PM_SUSPEND_PREPARE: + if (!chip->pdata->charger_enable()) { + cancel_work_sync(&chip->work); + max17040_program_alarm(chip, SLOW_POLL); + chip->slow_poll = 1; + } + break; + + case PM_POST_SUSPEND: + /* We might be on a slow sample cycle. If we're + * resuming we should resample the battery state + * if it's been over a minute since we last did + * so, and move back to sampling every minute until + * we suspend again. + */ + if (chip->slow_poll) { + max17040_program_alarm(chip, FAST_POLL); + chip->slow_poll = 0; + } + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block max17040_pm_notifier_block = { + .notifier_call = max17040_pm_notifier, +}; + +static irqreturn_t max17040_alert(int irq, void *data) +{ + struct max17040_chip *chip = data; + struct i2c_client *client = chip->client; + + max17040_get_vcell(chip->client); + max17040_get_soc(chip->client); + + dev_info(&client->dev, "Low battery alert fired: soc=%d vcell=%d\n", + chip->soc, chip->vcell); + + if (chip->soc != 0) { + dev_err(&client->dev, "false low battery alert, ignoring\n"); + goto out; + } + + dev_info(&client->dev, "shutting down due to low battery...\n"); + kernel_power_off(); + +out: + return IRQ_HANDLED; +} + static int __devinit max17040_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); struct max17040_chip *chip; int ret; + u16 val; + u16 athd; + int num_props = ARRAY_SIZE(max17040_battery_props); if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) return -EIO; @@ -214,66 +514,117 @@ static int __devinit max17040_probe(struct i2c_client *client, chip->client = client; chip->pdata = client->dev.platform_data; + if (!chip->pdata->get_bat_temp) + num_props--; + i2c_set_clientdata(client, chip); chip->battery.name = "battery"; chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; chip->battery.get_property = max17040_get_property; chip->battery.properties = max17040_battery_props; - chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props); + chip->battery.num_properties = num_props; + chip->battery.external_power_changed = max17040_ext_power_changed; + + chip->bat_health = POWER_SUPPLY_HEALTH_GOOD; + chip->charger_status = STATUS_CHARGABLE; + chip->is_timer_flag = false; + chip->chg_limit_time = 0; + + if (!chip->pdata->high_block_temp) + chip->pdata->high_block_temp = 500; + if (!chip->pdata->high_recover_temp) + chip->pdata->high_recover_temp = 420; + if (!chip->pdata->low_block_temp) + chip->pdata->low_block_temp = -50; + if (!chip->pdata->fully_charged_vol) + chip->pdata->fully_charged_vol = 4150000; + if (!chip->pdata->recharge_vol) + chip->pdata->recharge_vol = 4140000; + if (!chip->pdata->limit_charging_time) + chip->pdata->limit_charging_time = 21600; + if (!chip->pdata->limit_recharging_time) + chip->pdata->limit_recharging_time = 5400; + + chip->last_poll = alarm_get_elapsed_realtime(); + alarm_init(&chip->alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + max17040_battery_alarm); + + wake_lock_init(&chip->work_wake_lock, WAKE_LOCK_SUSPEND, + "max17040-battery"); + + if (!chip->pdata->skip_reset) + max17040_reset(client); + + max17040_get_version(client); + INIT_WORK(&chip->work, max17040_work); ret = power_supply_register(&client->dev, &chip->battery); if (ret) { dev_err(&client->dev, "failed: power supply register\n"); - kfree(chip); - return ret; + goto err_battery_supply_register; } - max17040_reset(client); - max17040_get_version(client); - - INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work); - schedule_delayed_work(&chip->work, MAX17040_DELAY); + /* i2c-core does not support dev_pm_ops.prepare and .complete + * So, used pm_notifier for use android_alarm. + */ + chip->pm_notifier = max17040_pm_notifier_block; + ret = register_pm_notifier(&chip->pm_notifier); + if (ret) { + dev_err(&client->dev, "failed: register pm notifier\n"); + goto err_pm_notifier; + } + schedule_work(&chip->work); + + if (HAS_ALERT_INTERRUPT(chip->ver) && chip->pdata->use_fuel_alert) { + /* setting the low SOC alert threshold */ + if (!max17040_read_reg(client, MAX17040_RCOMP_MSB, &val)) { + athd = chip->pdata->min_capacity > 1 ? + chip->pdata->min_capacity - 1 : 0; + max17040_write_reg(client, MAX17040_RCOMP_MSB, + (val & ~0x1f) | (-athd & 0x1f)); + } else { + dev_err(&client->dev, + "Error setting battery alert threshold\n"); + } + + /* add alert irq handler */ + ret = request_threaded_irq(client->irq, NULL, max17040_alert, + IRQF_TRIGGER_FALLING, "fuel gauge alert", chip); + if (ret < 0) { + dev_err(&client->dev, + "request_threaded_irq() failed: %d", ret); + goto err_pm_notifier; + } + } return 0; -} - -static int __devexit max17040_remove(struct i2c_client *client) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); +err_pm_notifier: power_supply_unregister(&chip->battery); - cancel_delayed_work(&chip->work); +err_battery_supply_register: + wake_lock_destroy(&chip->work_wake_lock); + alarm_cancel(&chip->alarm); kfree(chip); - return 0; -} - -#ifdef CONFIG_PM -static int max17040_suspend(struct i2c_client *client, - pm_message_t state) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); - - cancel_delayed_work(&chip->work); - return 0; + return ret; } -static int max17040_resume(struct i2c_client *client) +static int __devexit max17040_remove(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - - schedule_delayed_work(&chip->work, MAX17040_DELAY); + chip->shutdown = 1; + unregister_pm_notifier(&chip->pm_notifier); + power_supply_unregister(&chip->battery); + alarm_cancel(&chip->alarm); + cancel_work_sync(&chip->work); + wake_lock_destroy(&chip->work_wake_lock); + if (HAS_ALERT_INTERRUPT(chip->ver) && chip->pdata->use_fuel_alert) + free_irq(client->irq, chip); + kfree(chip); return 0; } -#else - -#define max17040_suspend NULL -#define max17040_resume NULL - -#endif /* CONFIG_PM */ - static const struct i2c_device_id max17040_id[] = { { "max17040", 0 }, { } @@ -286,8 +637,6 @@ static struct i2c_driver max17040_i2c_driver = { }, .probe = max17040_probe, .remove = __devexit_p(max17040_remove), - .suspend = max17040_suspend, - .resume = max17040_resume, .id_table = max17040_id, }; diff --git a/drivers/remoteproc/omap_remoteproc.c b/drivers/remoteproc/omap_remoteproc.c index 1e01a33..5548917 100644 --- a/drivers/remoteproc/omap_remoteproc.c +++ b/drivers/remoteproc/omap_remoteproc.c @@ -22,6 +22,7 @@ #include <linux/platform_device.h> #include <linux/remoteproc.h> #include <linux/sched.h> +#include <linux/rproc_drm.h> #include <plat/iommu.h> #include <plat/omap_device.h> @@ -446,9 +447,13 @@ static inline int omap_rproc_start(struct rproc *rproc, u64 bootaddr) int ret = 0; if (rproc->secure_mode) { - pr_err("TODO: Call secure service to authenticate\n"); - if (ret) + rproc->secure_reset = true; + ret = rproc_drm_invoke_service(rproc->secure_mode); + if (ret) { + dev_err(rproc->dev, "rproc_drm_invoke_service failed " + "for secure_enable ret = 0x%x\n", ret); return -ENXIO; + } } #ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND @@ -509,9 +514,18 @@ static inline int omap_rproc_stop(struct rproc *rproc) struct omap_rproc_pdata *pdata = dev->platform_data; struct omap_rproc_timers_info *timers = pdata->timers; int ret, i; + #ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND _destroy_pm_flags(rproc); #endif + if (rproc->secure_reset) { + ret = rproc_drm_invoke_service(false); + if (ret) + dev_err(rproc->dev, "rproc_drm_invoke_service failed " + "for secure disable ret = 0x%x\n", ret); + rproc->secure_reset = false; + } + ret = omap_device_idle(pdev); if (ret) goto err; diff --git a/drivers/rpmsg/rpmsg_omx.c b/drivers/rpmsg/rpmsg_omx.c index 972a918..bd417c4 100644 --- a/drivers/rpmsg/rpmsg_omx.c +++ b/drivers/rpmsg/rpmsg_omx.c @@ -234,8 +234,10 @@ static void rpmsg_omx_cb(struct rpmsg_channel *rpdev, void *data, int len, dev_dbg(&rpdev->dev, "%s: incoming msg src 0x%x type %d len %d\n", __func__, src, hdr->type, hdr->len); +#if 0 print_hex_dump(KERN_DEBUG, "rpmsg_omx RX: ", DUMP_PREFIX_NONE, 16, 1, data, len, true); +#endif switch (hdr->type) { case OMX_CONN_RSP: diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index 247e887..897b7f4 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -508,8 +508,10 @@ int rpmsg_send_offchannel_raw(struct rpmsg_channel *rpdev, u32 src, u32 dst, dev_dbg(dev, "TX From 0x%x, To 0x%x, Len %d, Flags %d, Unused %d\n", msg->src, msg->dst, msg->len, msg->flags, msg->unused); +#if 0 print_hex_dump(KERN_DEBUG, "rpmsg_virtio TX: ", DUMP_PREFIX_NONE, 16, 1, msg, sizeof(*msg) + msg->len, true); +#endif offset = ((unsigned long) msg) - ((unsigned long) vrp->rbufs); sim_addr = vrp->sim_base + offset; @@ -557,8 +559,10 @@ static void rpmsg_recv_done(struct virtqueue *rvq) dev_dbg(dev, "From: 0x%x, To: 0x%x, Len: %d, Flags: %d, Unused: %d\n", msg->src, msg->dst, msg->len, msg->flags, msg->unused); +#if 0 print_hex_dump(KERN_DEBUG, "rpmsg_virtio RX: ", DUMP_PREFIX_NONE, 16, 1, msg, sizeof(*msg) + msg->len, true); +#endif /* fetch the callback of the appropriate user */ spin_lock(&vrp->endpoints_lock); diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 34e3da5..530ad9a 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1332,6 +1332,22 @@ int usb_resume(struct device *dev, pm_message_t msg) * Unbind the interfaces that will need rebinding later. */ } else { + /* If a device aborts suspend, usb_resume may be called on a + * device whose parent has been auto-suspended but its dpm power + * state in_suspend==false. So dpm doesn't try to resume the + * parent and the device doesn't wait for the parent to resume. + * Recursively resume the parents when this happens. + */ + if (udev->parent && msg.event == PM_EVENT_RESUME + && udev->parent->state == USB_STATE_SUSPENDED) { + status = usb_resume(&udev->parent->dev, msg); + if (status) { + dev_err(dev, "%s: failed to resume parent\n", + __func__); + return status; + } + } + status = usb_resume_both(udev, msg); if (status == 0) { pm_runtime_disable(dev); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index a428aa0..a7c96f8 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1634,12 +1634,13 @@ void usb_disconnect(struct usb_device **pdev) { struct usb_device *udev = *pdev; int i; - struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct usb_hcd *hcd; if (!udev) { pr_debug ("%s nodev\n", __func__); return; } + hcd = bus_to_hcd(udev->bus); /* mark the device as inactive, so any further urb submissions for * this device (and any of its children) will fail immediately. @@ -2246,7 +2247,7 @@ static int check_port_resume_type(struct usb_device *udev, * so try a reset-resume instead. */ else if (!(portstatus & USB_PORT_STAT_ENABLE) && !udev->reset_resume) { - if (udev->persist_enabled) + if (udev->persist_enabled && !(udev->quirks & USB_QUIRK_NO_RESET_RESUME)) udev->reset_resume = 1; else status = -ENODEV; @@ -2418,6 +2419,9 @@ static int finish_port_resume(struct usb_device *udev) retry_reset_resume: status = usb_reset_and_verify_device(udev); + if (udev->quirks & USB_QUIRK_NO_GET_STATUS) + goto done; + /* 10.5.4.5 says be sure devices in the tree are still there. * For now let's assume the device didn't go crazy on resume, * and device drivers will know about any resume quirks. @@ -2429,7 +2433,8 @@ static int finish_port_resume(struct usb_device *udev) status = (status > 0 ? 0 : -ENODEV); /* If a normal resume failed, try doing a reset-resume */ - if (status && !udev->reset_resume && udev->persist_enabled) { + if (status && !udev->reset_resume && udev->persist_enabled && + !(udev->quirks & USB_QUIRK_NO_RESET_RESUME)) { dev_dbg(&udev->dev, "retry with reset-resume\n"); udev->reset_resume = 1; goto retry_reset_resume; @@ -2456,6 +2461,7 @@ static int finish_port_resume(struct usb_device *udev) } status = 0; } +done: return status; } @@ -4043,3 +4049,18 @@ void usb_queue_reset_device(struct usb_interface *iface) schedule_work(&iface->reset_ws); } EXPORT_SYMBOL_GPL(usb_queue_reset_device); + +void usb_force_disconnect(struct usb_device *udev) +{ + struct usb_hub *parent_hub; + int port1 = udev->portnum; + + if (!udev->parent) + return; + + parent_hub = hdev_to_hub(udev->parent); + if (!parent_hub) + return; + + hub_port_logical_disconnect(parent_hub, port1); +} diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 81ce6a8..6e6bfc9 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -96,6 +96,10 @@ static const struct usb_device_id usb_quirk_list[] = { /* INTEL VALUE SSD */ { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME }, + /* CMC221 LTE Modem */ + { USB_DEVICE(0x04e8, 0x6999), .driver_info = + USB_QUIRK_NO_RESET_RESUME | USB_QUIRK_NO_GET_STATUS }, + { } /* terminating entry must be last */ }; diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index 8df739c..8988772 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -55,6 +55,7 @@ #include "f_rndis.c" #include "rndis.c" #include "u_ether.c" +#include "f_dm.c" MODULE_AUTHOR("Mike Lockwood"); MODULE_DESCRIPTION("Android Composite USB Driver"); @@ -67,6 +68,9 @@ static const char longname[] = "Gadget Android"; #define VENDOR_ID 0x18D1 #define PRODUCT_ID 0x0001 +/* DM_PORT NUM : /dev/ttyGS* port number */ +#define DM_PORT_NUM 1 + struct android_usb_function { char *name; void *config; @@ -642,6 +646,17 @@ static struct android_usb_function accessory_function = { }; +static int dm_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return dm_bind_config(c, DM_PORT_NUM); +} + +static struct android_usb_function dm_function = { + .name = "dm", + .bind_config = dm_function_bind_config, +}; + static struct android_usb_function *supported_functions[] = { &adb_function, &acm_function, @@ -650,6 +665,7 @@ static struct android_usb_function *supported_functions[] = { &rndis_function, &mass_storage_function, &accessory_function, + &dm_function, NULL }; diff --git a/drivers/usb/gadget/f_dm.c b/drivers/usb/gadget/f_dm.c new file mode 100644 index 0000000..ca394c3 --- /dev/null +++ b/drivers/usb/gadget/f_dm.c @@ -0,0 +1,306 @@ +/* + * f_dm.c - generic USB serial function driver + * modified from f_serial.c and f_diag.c + * ttyGS* + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/device.h> + +#include "u_serial.h" +#include "gadget_chips.h" + +/* + * This function packages a simple "generic serial" port with no real + * control mechanisms, just raw data transfer over two bulk endpoints. + * + * Because it's not standardized, this isn't as interoperable as the + * CDC ACM driver. However, for many purposes it's just as functional + * if you can arrange appropriate host side drivers. + */ + +struct dm_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; +}; + +struct f_dm { + struct gserial port; + u8 data_id; + u8 port_num; + + struct dm_descs fs; + struct dm_descs hs; +}; + +static inline struct f_dm *func_to_dm(struct usb_function *f) +{ + return container_of(f, struct f_dm, port.func); +} + +/*-------------------------------------------------------------------------*/ + +/* interface descriptor: */ +static struct usb_interface_descriptor dm_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = 0x10, + .bInterfaceProtocol = 0x01, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor dm_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor dm_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *dm_fs_function[] = { + (struct usb_descriptor_header *) &dm_interface_desc, + (struct usb_descriptor_header *) &dm_fs_in_desc, + (struct usb_descriptor_header *) &dm_fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor dm_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor dm_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_descriptor_header *dm_hs_function[] = { + (struct usb_descriptor_header *) &dm_interface_desc, + (struct usb_descriptor_header *) &dm_hs_in_desc, + (struct usb_descriptor_header *) &dm_hs_out_desc, + NULL, +}; + +/* string descriptors: */ +#define F_DM_IDX 0 + +static struct usb_string dm_string_defs[] = { + [F_DM_IDX].s = "Samsung Android DM", + { /* ZEROES END LIST */ }, +}; + +static struct usb_gadget_strings dm_string_table = { + .language = 0x0409, /* en-us */ + .strings = dm_string_defs, +}; + +static struct usb_gadget_strings *dm_strings[] = { + &dm_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static int dm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_dm *dm = func_to_dm(f); + struct usb_composite_dev *cdev = f->config->cdev; + int status; + + /* we know alt == 0, so this is an activation or a reset */ + + if (dm->port.in->driver_data) { + DBG(cdev, "reset generic ttyGS%d\n", dm->port_num); + gserial_disconnect(&dm->port); + } else { + DBG(cdev, "activate generic ttyGS%d\n", dm->port_num); + } + dm->port.in_desc = ep_choose(cdev->gadget, + dm->hs.in, dm->fs.in); + dm->port.out_desc = ep_choose(cdev->gadget, + dm->hs.out, dm->fs.out); + status = gserial_connect(&dm->port, dm->port_num); + + if (status < 0) { + printk(KERN_ERR "fail to activate generic ttyGS%d\n", + dm->port_num); + + return status; + } + + return 0; +} + +static void dm_disable(struct usb_function *f) +{ + struct f_dm *dm = func_to_dm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "generic ttyGS%d deactivated\n", dm->port_num); + gserial_disconnect(&dm->port); +} + +/*-------------------------------------------------------------------------*/ + +/* serial function driver setup/binding */ + +static int +dm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_dm *dm = func_to_dm(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + dm->data_id = status; + dm_interface_desc.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &dm_fs_in_desc); + if (!ep) + goto fail; + dm->port.in = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &dm_fs_out_desc); + if (!ep) + goto fail; + dm->port.out = ep; + ep->driver_data = cdev; /* claim */ + printk(KERN_INFO "[%s] in =0x%x , out =0x%x\n", __func__, + dm->port.in, dm->port.out); + + /* copy descriptors, and track endpoint copies */ + f->descriptors = usb_copy_descriptors(dm_fs_function); + + dm->fs.in = usb_find_endpoint(dm_fs_function, + f->descriptors, &dm_fs_in_desc); + dm->fs.out = usb_find_endpoint(dm_fs_function, + f->descriptors, &dm_fs_out_desc); + + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + dm_hs_in_desc.bEndpointAddress = + dm_fs_in_desc.bEndpointAddress; + dm_hs_out_desc.bEndpointAddress = + dm_fs_out_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(dm_hs_function); + + dm->hs.in = usb_find_endpoint(dm_hs_function, + f->hs_descriptors, &dm_hs_in_desc); + dm->hs.out = usb_find_endpoint(dm_hs_function, + f->hs_descriptors, &dm_hs_out_desc); + } + + DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n", + dm->port_num, + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + dm->port.in->name, dm->port.out->name); + return 0; + +fail: + /* we might as well release our claims on endpoints */ + if (dm->port.out) + dm->port.out->driver_data = NULL; + if (dm->port.in) + dm->port.in->driver_data = NULL; + + printk(KERN_ERR "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +dm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + kfree(func_to_dm(f)); +} + +/* + * dm_bind_config - add a generic serial function to a configuration + * @c: the configuration to support the serial instance + * @port_num: /dev/ttyGS* port this interface will use + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gserial_setup() with enough ports to + * handle all the ones it binds. Caller is also responsible + * for calling @gserial_cleanup() before module unload. + */ +int dm_bind_config(struct usb_configuration *c, u8 port_num) +{ + struct f_dm *dm; + int status; + + /* REVISIT might want instance-specific strings to help + * distinguish instances ... + */ + + /* maybe allocate device-global string ID */ + if (dm_string_defs[F_DM_IDX].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + return status; + dm_string_defs[F_DM_IDX].id = status; + } + + /* allocate and initialize one new instance */ + dm = kzalloc(sizeof *dm, GFP_KERNEL); + if (!dm) + return -ENOMEM; + + dm->port_num = port_num; + + dm->port.func.name = "dm"; + dm->port.func.strings = dm_strings; + dm->port.func.bind = dm_bind; + dm->port.func.unbind = dm_unbind; + dm->port.func.set_alt = dm_set_alt; + dm->port.func.disable = dm_disable; + + status = usb_add_function(c, &dm->port.func); + if (status) + kfree(dm); + return status; +} diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 40a844c..e2fd8db 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -18,6 +18,9 @@ /* this file is part of ehci-hcd.c */ +#define DEBUG +#define BUF_SIZE (1096) + #define ehci_dbg(ehci, fmt, args...) \ dev_dbg (ehci_to_hcd(ehci)->self.controller , fmt , ## args ) #define ehci_err(ehci, fmt, args...) \ @@ -121,13 +124,16 @@ static inline void dbg_hcc_params (struct ehci_hcd *ehci, char *label) {} static void __maybe_unused dbg_qtd (const char *label, struct ehci_hcd *ehci, struct ehci_qtd *qtd) { - ehci_dbg(ehci, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, + //ehci_dbg(ehci, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, + printk("%s %p n%08x %08x t%08x p0=%08x\n", label, qtd, hc32_to_cpup(ehci, &qtd->hw_next), hc32_to_cpup(ehci, &qtd->hw_alt_next), hc32_to_cpup(ehci, &qtd->hw_token), hc32_to_cpup(ehci, &qtd->hw_buf [0])); if (qtd->hw_buf [1]) - ehci_dbg(ehci, " p1=%08x p2=%08x p3=%08x p4=%08x\n", + //ehci_dbg(ehci, " p1=%08x p2=%08x p3=%08x p4=%08x\n", + printk("%s p1=%08x p2=%08x p3=%08x p4=%08x\n", + label, hc32_to_cpup(ehci, &qtd->hw_buf[1]), hc32_to_cpup(ehci, &qtd->hw_buf[2]), hc32_to_cpup(ehci, &qtd->hw_buf[3]), @@ -139,7 +145,8 @@ dbg_qh (const char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) { struct ehci_qh_hw *hw = qh->hw; - ehci_dbg (ehci, "%s qh %p n%08x info %x %x qtd %x\n", label, + //ehci_dbg (ehci, "%s qh %p n%08x info %x %x qtd %x\n", label, + printk("%s qh %p n%08x info %x %x qtd %x\n", label, qh, hw->hw_next, hw->hw_info1, hw->hw_info2, hw->hw_current); dbg_qtd("overlay", ehci, (struct ehci_qtd *) &hw->hw_qtd_next); } @@ -467,6 +474,15 @@ static void qh_lines ( (cpu_to_hc32(ehci, QTD_TOGGLE) & hw->hw_token) ? "data1" : "data0", (hc32_to_cpup(ehci, &hw->hw_alt_next) >> 1) & 0x0f); + printk ("\nqh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)", + qh, scratch & 0x007f, + speed_char (scratch), + (scratch >> 8) & 0x000f, + scratch, hc32_to_cpup(ehci, &hw->hw_info2), + hc32_to_cpup(ehci, &hw->hw_token), mark, + (cpu_to_hc32(ehci, QTD_TOGGLE) & hw->hw_token) + ? "data1" : "data0", + (hc32_to_cpup(ehci, &hw->hw_alt_next) >> 1) & 0x0f); size -= temp; next += temp; @@ -497,6 +513,22 @@ static void qh_lines ( (scratch >> 16) & 0x7fff, scratch, td->urb); + printk("\n\t\ttd-%p%c%s len=%d %08x\n \t\turb %p t-flag-%x t-buf-%p t-dma-%x\n", + td, mark, ({ char *tmp; + switch ((scratch>>8)&0x03) { + case 0: tmp = "out"; break; + case 1: tmp = "in"; break; + case 2: tmp = "setup"; break; + default: tmp = "?"; break; + } tmp;}), + (scratch >> 16) & 0x7fff, + scratch, + td->urb, + td->urb->transfer_flags, + td->urb->transfer_buffer, + td->urb->transfer_dma); + /* print the td */ + dbg_qtd("\t\t", ehci, td); if (size < temp) temp = size; size -= temp; @@ -552,6 +584,43 @@ static ssize_t fill_async_buffer(struct debug_buffer *buf) return strlen(buf->output_buf); } + +static char array[BUF_SIZE]; +void print_async_list(void) +{ + struct debug_buffer dbg_buffer, *buf; + extern struct usb_hcd *ghcd_omap; + + memset(array, 0, BUF_SIZE); + buf = &dbg_buffer; + + buf->fill_func = fill_async_buffer; + buf->bus = hcd_to_bus(ghcd_omap); + buf->alloc_size = BUF_SIZE; + buf->output_buf = array; + + printk("\n EHCI registers \n"); + printk("HCCAPBASE : %08x\n", omap_readl(0x4A064C00)); + printk("HCSPARAMS : %08x\n", omap_readl(0x4A064C04)); + printk("HCCPARAMS : %08x\n", omap_readl(0x4A064C08)); + printk("USBCMD : %08x\n", omap_readl(0x4A064C10)); + printk("USBSTS : %08x\n", omap_readl(0x4A064C14)); + printk("USBINTR : %08x\n", omap_readl(0x4A064C18)); + printk("FRINDEX : %08x\n", omap_readl(0x4A064C1C)); + printk("CTRLDSSEGMENT : %08x\n", omap_readl(0x4A064C20)); + printk("PERIODICLISTBASE : %08x\n", omap_readl(0x4A064C24)); + printk("ASYNCLISTADDR : %08x\n", omap_readl(0x4A064C28)); + printk("CONFIGFLAG : %08x\n", omap_readl(0x4A064C50)); + printk("PORT0 : %08x\n", omap_readl(0x4A064C54)); + printk("PORT1 : %08x\n", omap_readl(0x4A064C58)); + + printk("EHCI async list \n"); + fill_async_buffer(buf); + + //printk("%s\n", array); + +} + #define DBG_SCHED_LIMIT 64 static ssize_t fill_periodic_buffer(struct debug_buffer *buf) { diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 0f3a724..848fd8f 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -28,6 +28,7 @@ /*-------------------------------------------------------------------------*/ #include <linux/usb/otg.h> +#include <linux/gpio.h> #define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) @@ -601,6 +602,13 @@ static int check_reset_complete ( return port_status; } + if (ehci->no_companion_port_handoff) { + /* on omap, we can't hand off companion port */ + ehci_dbg(ehci, "port %d FS device detected - cannot handoff port\n", + index + 1); + return port_status; + } + ehci_dbg (ehci, "port %d full speed --> companion\n", index + 1); @@ -735,6 +743,106 @@ ehci_hub_descriptor ( /*-------------------------------------------------------------------------*/ +int omap_ehci_ulpi_write(const struct usb_hcd *hcd, u8 val, u8 reg, u8 retry_times); +#define CM_L3INIT_HSUSBHOST_CLKCTRL (0x4A009358) +#define L3INIT_HSUSBHOST_CLKCTRL (0x4A009358) +#define OMAP_UHH_SYSCONFIG (0x4a064010) +#define OMAP_UHH_SYSSTATUS (0x4a064014) +#define OMAP_UHH_HOSTCONFIG (0x4a064040) + +void uhh_omap_reset_link(struct ehci_hcd *ehci) +{ + u32 usbcmd_backup; + u32 usbintr_backup; + u32 asynclistaddr_backup, periodiclistbase_backup; + u32 portsc0_backup; + u32 uhh_sysconfig_backup, uhh_hostconfig_backup; + u32 temp_reg; + u8 count; + u16 orig_val, val; + + /* switch to internal 60Mhz clock */ + temp_reg = omap_readl(L3INIT_HSUSBHOST_CLKCTRL); + temp_reg |= 1 << 8; + temp_reg &= ~(1 << 24); + omap_writel(temp_reg, L3INIT_HSUSBHOST_CLKCTRL); + + /* Backup current registers of EHCI */ + usbcmd_backup = ehci_readl(ehci, &ehci->regs->command); + ehci_writel(ehci, + usbcmd_backup & ~(CMD_IAAD | CMD_ASE | CMD_PSE | CMD_RUN), + &ehci->regs->command); + mdelay(3); + /* check controller stopped */ + handshake(ehci, &ehci->regs->status, STS_HALT, 1, 150); + asynclistaddr_backup = ehci_readl(ehci, &ehci->regs->async_next); + periodiclistbase_backup = ehci_readl(ehci, &ehci->regs->frame_list); + portsc0_backup = ehci_readl(ehci, &ehci->regs->port_status[0]); + usbintr_backup = ehci_readl(ehci, &ehci->regs->intr_enable); + uhh_sysconfig_backup = omap_readl(OMAP_UHH_SYSCONFIG); + uhh_hostconfig_backup = omap_readl(OMAP_UHH_HOSTCONFIG); + + /* Soft reset EHCI controller */ + omap_writel(omap_readl(OMAP_UHH_SYSCONFIG) | (1<<0), + OMAP_UHH_SYSCONFIG); + /* wait for reset done */ + count = 10; + while ((omap_readl(OMAP_UHH_SYSCONFIG) & (1<<0)) && count--) + mdelay(1); + if (!count) + pr_err("ehci:link_reset: soft-reset fail\n"); + + /* PHY reset via RESETB pin */ + gpio_set_value(159, 0); + mdelay(2); + gpio_set_value(159, 1); + mdelay(2); + + /* switch back to external 60Mhz clock */ + temp_reg &= ~(1 << 8); + temp_reg |= 1 << 24; + omap_writel(temp_reg, L3INIT_HSUSBHOST_CLKCTRL); + mdelay(5); + + /*soft reset ehci registers */ + ehci_writel(ehci, (1<<1), &ehci->regs->command); + count = 10; + while ((ehci_readl(ehci, &ehci->regs->command) & (1<<1)) && count--) + mdelay(1); + if (!count) + pr_err("ehci:link_reset: soft-reset fail\n"); + + /* Restore registers after reset */ + omap_writel(uhh_sysconfig_backup, OMAP_UHH_SYSCONFIG); + omap_writel(uhh_hostconfig_backup, OMAP_UHH_HOSTCONFIG); + ehci_writel(ehci, periodiclistbase_backup, &ehci->regs->frame_list); + ehci_writel(ehci, asynclistaddr_backup, &ehci->regs->async_next); + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + ehci_writel(ehci, usbcmd_backup, &ehci->regs->command); + mdelay(2); + ehci_writel(ehci, PORT_POWER, &ehci->regs->port_status[0]); + + /* Put PHY in good default state */ + if (omap_ehci_ulpi_write(ehci_to_hcd(ehci), 0x20, 0x5, 20) < 0) { + /* Toggle STP line */ + orig_val = val = omap_readw(0x4A1000C4); + val |= 0x1F; + omap_writew(val, 0x4A1000C4); + mdelay(3); + omap_writew(orig_val, 0x4A1000C4); + omap_ehci_ulpi_write(ehci_to_hcd(ehci), 0x20, 0x5, 20); + } + + omap_ehci_ulpi_write(ehci_to_hcd(ehci), 0x41, 0x4, 20); + omap_ehci_ulpi_write(ehci_to_hcd(ehci), 0x18, 0x7, 20); + omap_ehci_ulpi_write(ehci_to_hcd(ehci), 0x66, 0xA, 20); + omap_ehci_ulpi_write(ehci_to_hcd(ehci), 0x45, 0x4, 20); + + handshake(ehci, &ehci->regs->port_status[0], PORT_CONNECT, 1, 2000); + ehci_writel(ehci, usbintr_backup, &ehci->regs->intr_enable); +} + static int ehci_hub_control ( struct usb_hcd *hcd, u16 typeReq, @@ -914,6 +1022,7 @@ static int ehci_hub_control ( /* resume completed? */ else if (time_after_eq(jiffies, ehci->reset_done[wIndex])) { + clear_bit(wIndex, &ehci->suspended_ports); set_bit(wIndex, &ehci->port_c_suspend); ehci->reset_done[wIndex] = 0; @@ -929,8 +1038,20 @@ static int ehci_hub_control ( ehci_err(ehci, "port %d resume error %d\n", wIndex + 1, retval); - goto error; + + if (ehci->has_smsc_ulpi_bug) + ehci->resume_error_flag = 1; + + uhh_omap_reset_link(ehci); + goto error; + } + + /* restore registers value to its original state*/ + if (ehci->resume_error_flag) { + omap_ehci_ulpi_write(hcd, 0x00, 0x32, 20); + omap_ehci_ulpi_write(hcd, 0x04, 0x39, 20); } + temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); } } @@ -1055,6 +1176,16 @@ static int ehci_hub_control ( || (temp & PORT_RESET) != 0) goto error; + /* + * Special workaround for resume error + * - Write 04h to register 32h : inserts a 2uA source current on DP + * - Write 14h to register 39h : enables 125kohm pull up resistors on DP + */ + if (ehci->resume_error_flag) { + omap_ehci_ulpi_write(hcd, 0x04, 0x32, 20); + omap_ehci_ulpi_write(hcd, 0x14, 0x39, 20); + } + /* After above check the port must be connected. * Set appropriate bit thus could put phy into low power * mode if we have hostpc feature @@ -1062,6 +1193,31 @@ static int ehci_hub_control ( temp &= ~PORT_WKCONN_E; temp |= PORT_WKDISC_E | PORT_WKOC_E; ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); + + /* + * Special workaround sequence: + * - Set suspend bit + * - Wait 4ms for suspend to take effect + * - alternatively read the line state + * in PORTSC + * - switch to internal 60 MHz clock + * - wait 1ms + * - switch back to external clock + */ + { + u32 temp_reg; + mdelay(4); + temp_reg = omap_readl(L3INIT_HSUSBHOST_CLKCTRL); + temp_reg |= 1 << 8; + temp_reg &= ~(1 << 24); + omap_writel(temp_reg, L3INIT_HSUSBHOST_CLKCTRL); + + mdelay(1); + temp_reg &= ~(1 << 8); + temp_reg |= 1 << 24; + omap_writel(temp_reg, L3INIT_HSUSBHOST_CLKCTRL); + } + if (hostpc_reg) { spin_unlock_irqrestore(&ehci->lock, flags); msleep(5);/* 5ms for HCD enter low pwr mode */ diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index d534fa6..1839e56 100644..100755 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -46,6 +46,7 @@ #include <plat/omap_hwmod.h> #include <plat/usb.h> #include <plat/clock.h> +#include <plat/omap-pm.h> /* EHCI Register Set */ #define EHCI_INSNREG04 (0xA0) @@ -73,6 +74,73 @@ static inline u32 ehci_read(void __iomem *base, u32 reg) return __raw_readl(base + reg); } +u8 omap_ehci_ulpi_read(const struct usb_hcd *hcd, u8 reg) +{ + unsigned reg_internal = 0; + u8 val; + int count = 2000; + + reg_internal = ((reg) << EHCI_INSNREG05_ULPI_REGADD_SHIFT) + /* Write */ + | (3 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) + /* PORTn */ + | ((1) << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) + /* start ULPI access*/ + | (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT); + + ehci_write(hcd->regs, EHCI_INSNREG05_ULPI, reg_internal); + + /* Wait for ULPI access completion */ + while ((ehci_read(hcd->regs, EHCI_INSNREG05_ULPI) + & (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT))) { + udelay(1); + if (count-- == 0) { + pr_err("ehci: omap_ehci_ulpi_read: Error"); + break; + } + } + + val = ehci_read(hcd->regs, EHCI_INSNREG05_ULPI) & 0xFF; + return val; +} + +int omap_ehci_ulpi_write(const struct usb_hcd *hcd, u8 val, u8 reg, u8 retry_times) +{ + unsigned reg_internal = 0; + int status = 0; + int count; + +again: + count = 2000; + reg_internal = val | + ((reg) << EHCI_INSNREG05_ULPI_REGADD_SHIFT) + /* Write */ + | (2 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) + /* PORTn */ + | ((1) << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) + /* start ULPI access*/ + | (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT); + + ehci_write(hcd->regs, EHCI_INSNREG05_ULPI, reg_internal); + + /* Wait for ULPI access completion */ + while ((ehci_read(hcd->regs, EHCI_INSNREG05_ULPI) + & (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT))) { + udelay(1); + if (count-- == 0) { + if (retry_times--) { + ehci_write(hcd->regs, EHCI_INSNREG05_ULPI, 0); + goto again; + } else { + pr_err("ehci: omap_ehci_ulpi_write: Error"); + status = -ETIMEDOUT; + break; + } + } + } + return status; +} + static void omap_ehci_soft_phy_reset(struct platform_device *pdev, u8 port) { struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev); @@ -114,6 +182,9 @@ static void omap_ehci_soft_phy_reset(struct platform_device *pdev, u8 port) * then invokes the start() method for the HCD associated with it * through the hotplug entry's driver_data. */ +struct usb_hcd *ghcd_omap; +#define USBHS_OHCI_HWMODNAME "usbhs_ohci" +#define HCCONTROL_OFFSET (4) static int ehci_hcd_omap_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -121,7 +192,9 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) struct resource *res; struct usb_hcd *hcd; void __iomem *regs; + void __iomem *ohci_base; struct ehci_hcd *omap_ehci; + struct omap_hwmod *oh; int ret = -ENODEV; int irq; int i; @@ -162,6 +235,7 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) goto err_io; } + ghcd_omap = hcd; hcd->rsrc_start = res->start; hcd->rsrc_len = resource_size(res); hcd->regs = regs; @@ -206,6 +280,8 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) omap_ehci = hcd_to_ehci(hcd); omap_ehci->sbrn = 0x20; + omap_ehci->has_smsc_ulpi_bug = 1; + omap_ehci->no_companion_port_handoff = 1; /* we know this is the memory we want, no need to ioremap again */ omap_ehci->caps = hcd->regs; omap_ehci->regs = hcd->regs @@ -226,6 +302,12 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) /* root ports should always stay powered */ ehci_port_power(omap_ehci, 1); + /* Ensure OHCI is kept in suspended state */ + oh = omap_hwmod_lookup(USBHS_OHCI_HWMODNAME); + ohci_base = omap_hwmod_get_mpu_rt_va(oh); + __raw_writel(OHCI_USB_SUSPEND, ohci_base + HCCONTROL_OFFSET); + (void)__raw_readl(ohci_base + HCCONTROL_OFFSET); + return 0; err_add_hcd: @@ -300,6 +382,10 @@ static int ehci_omap_bus_suspend(struct usb_hcd *hcd) clk_disable(clk); } + omap_pm_set_min_bus_tput(dev, + OCP_INITIATOR_AGENT, + -1); + return ret; } @@ -320,6 +406,10 @@ static int ehci_omap_bus_resume(struct usb_hcd *hcd) clk_enable(clk); } + omap_pm_set_min_bus_tput(dev, + OCP_INITIATOR_AGENT, + (200*1000*4)); + if (dev->parent) { pm_runtime_get_sync(dev->parent); } diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 6c6013c..0515e69 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -111,8 +111,6 @@ qh_update (struct ehci_hcd *ehci, struct ehci_qh *qh, struct ehci_qtd *qtd) } } - /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ - wmb (); hw->hw_token &= cpu_to_hc32(ehci, QTD_TOGGLE | QTD_STS_PING); } @@ -375,6 +373,17 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) retry_xacterr: if ((token & QTD_STS_ACTIVE) == 0) { + /* Report Data Buffer Error: non-fatal but useful */ + if (token & QTD_STS_DBE) + ehci_dbg(ehci, + "detected DataBufferErr for urb %p ep%d%s len %d, qtd %p [qh %p]\n", + urb, + usb_endpoint_num(&urb->ep->desc), + usb_endpoint_dir_in(&urb->ep->desc) ? "in" : "out", + urb->transfer_buffer_length, + qtd, + qh); + /* on STALL, error, and short reads this urb must * complete and all its qtds must be recycled. */ @@ -498,11 +507,6 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) last = list_entry (qtd->qtd_list.prev, struct ehci_qtd, qtd_list); last->hw_next = qtd->hw_next; - /* - * Make sure the new hw_next pointer is visible - * to the HW before freeing the old one - */ - wmb(); } /* remove qtd; it's recycled after possible urb completion */ @@ -729,7 +733,8 @@ qh_urb_transaction ( /* * control requests may need a terminating data "status" ack; - * bulk ones may need a terminating short packet (zero length). + * other OUT ones may need a terminating short packet + * (zero length). */ if (likely (urb->transfer_buffer_length != 0)) { int one_more = 0; @@ -738,7 +743,7 @@ qh_urb_transaction ( one_more = 1; token ^= 0x0100; /* "in" <--> "out" */ token |= QTD_TOGGLE; /* force DATA1 */ - } else if (usb_pipebulk (urb->pipe) + } else if (usb_pipeout(urb->pipe) && (urb->transfer_flags & URB_ZERO_PACKET) && !(urb->transfer_buffer_length % maxpacket)) { one_more = 1; @@ -1000,12 +1005,6 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) head->qh_next.qh = qh; head->hw->hw_next = dma; - /* - * flush qh descriptor into memory immediately, - * see comments in qh_append_tds. - * */ - ehci_sync_mem(); - qh_get(qh); qh->xacterrs = 0; qh->qh_state = QH_STATE_LINKED; @@ -1069,7 +1068,7 @@ static struct ehci_qh *qh_append_tds ( */ token = qtd->hw_token; qtd->hw_token = HALT_BIT(ehci); - wmb (); + dummy = qh->dummy; dma = dummy->qtd_dma; @@ -1093,18 +1092,6 @@ static struct ehci_qh *qh_append_tds ( wmb (); dummy->hw_token = token; - /* - * Writing to dma coherent buffer on ARM may - * be delayed to reach memory, so HC may not see - * hw_token of dummy qtd in time, which can cause - * the qtd transaction to be executed very late, - * and degrade performance a lot. ehci_sync_mem - * is added to flush 'token' immediatelly into - * memory, so that ehci can execute the transaction - * ASAP. - * */ - ehci_sync_mem(); - urb->hcpriv = qh_get (qh); } } diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 9706c2b..5037b33 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -137,6 +137,11 @@ struct ehci_hcd { /* one per controller */ unsigned fs_i_thresh:1; /* Intel iso scheduling */ unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/ unsigned has_synopsys_hc_bug:1; /* Synopsys HC */ + unsigned no_companion_port_handoff:1; /* Omap */ + + /* Transceiver QUIRKS */ + unsigned has_smsc_ulpi_bug:1; /* Smsc */ + unsigned resume_error_flag:1; /* Smsc */ /* required for usb32 quirk */ #define OHCI_CTRL_HCFS (3 << 6) @@ -160,9 +165,9 @@ struct ehci_hcd { /* one per controller */ #endif /* debug files */ -#ifdef DEBUG +/* #ifdef DEBUG */ struct dentry *debug_dir; -#endif +/* #endif */ /* * OTG controllers and transceivers need software interaction */ @@ -736,28 +741,13 @@ static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x) #endif -/* - * Writing to dma coherent memory on ARM may be delayed via L2 - * writing buffer, so introduce the helper which can flush L2 writing - * buffer into memory immediately, especially used to flush ehci - * descriptor to memory. - * */ -#ifdef CONFIG_ARM_DMA_MEM_BUFFERABLE -static inline void ehci_sync_mem() -{ - mb(); -} -#else -static inline void ehci_sync_mem() -{ -} -#endif - /*-------------------------------------------------------------------------*/ +#if 0 #ifndef DEBUG #define STUB_DEBUG_FILES #endif /* DEBUG */ +#endif /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index e2648ff..b6e4fcb 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -689,7 +689,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, } musb_writew(musb->mregs, MUSB_INTRTXE, musb->epmask); musb_writew(musb->mregs, MUSB_INTRRXE, musb->epmask & 0xfffe); - musb_writeb(musb->mregs, MUSB_INTRUSBE, 0xf7); + musb_writeb(musb->mregs, MUSB_INTRUSBE, MUSB_INTR_ENABLED); #endif musb->port1_status &= ~(USB_PORT_STAT_LOW_SPEED |USB_PORT_STAT_HIGH_SPEED @@ -915,23 +915,23 @@ void musb_start(struct musb *musb) { void __iomem *regs = musb->mregs; u8 devctl = musb_readb(regs, MUSB_DEVCTL); + u8 temp; dev_dbg(musb->controller, "<== devctl %02x\n", devctl); /* Set INT enable registers, enable interrupts */ musb_writew(regs, MUSB_INTRTXE, musb->epmask); musb_writew(regs, MUSB_INTRRXE, musb->epmask & 0xfffe); - musb_writeb(regs, MUSB_INTRUSBE, 0xf7); + musb_writeb(regs, MUSB_INTRUSBE, MUSB_INTR_ENABLED); musb_writeb(regs, MUSB_TESTMODE, 0); /* put into basic highspeed mode and start session */ - musb_writeb(regs, MUSB_POWER, MUSB_POWER_ISOUPDATE - | MUSB_POWER_SOFTCONN - | MUSB_POWER_HSENAB - /* ENSUSPEND wedges tusb */ - /* | MUSB_POWER_ENSUSPEND */ - ); + temp = MUSB_POWER_ISOUPDATE | MUSB_POWER_HSENAB; + /* MUSB_POWER_ENSUSPEND wedges tusb */ + if (musb->softconnect) + temp |= MUSB_POWER_SOFTCONN; + musb_writeb(regs, MUSB_POWER, temp); musb->is_active = 0; devctl = musb_readb(regs, MUSB_DEVCTL); @@ -945,7 +945,7 @@ void musb_start(struct musb *musb) */ if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) musb->is_active = 1; - else + else if (musb->xceiv->state == OTG_STATE_A_HOST) devctl |= MUSB_DEVCTL_SESSION; } else if (is_host_enabled(musb)) { @@ -2048,10 +2048,6 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) if (!is_otg_enabled(musb) && is_host_enabled(musb)) { struct usb_hcd *hcd = musb_to_hcd(musb); - MUSB_HST_MODE(musb); - musb->xceiv->default_a = 1; - musb->xceiv->state = OTG_STATE_A_IDLE; - status = usb_add_hcd(musb_to_hcd(musb), -1, 0); hcd->self.uses_pio_for_control = 1; @@ -2063,9 +2059,6 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) ? 'B' : 'A')); } else /* peripheral is enabled */ { - MUSB_DEV_MODE(musb); - musb->xceiv->default_a = 0; - musb->xceiv->state = OTG_STATE_B_IDLE; status = musb_gadget_setup(musb); @@ -2078,6 +2071,10 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) if (status < 0) goto fail3; + if (is_otg_enabled(musb) || is_host_enabled(musb)) + wake_lock_init(&musb->musb_wakelock, WAKE_LOCK_SUSPEND, + "musb_autosuspend_wake_lock"); + pm_runtime_put(musb->controller); status = musb_init_debugfs(musb); @@ -2113,6 +2110,9 @@ fail4: else musb_gadget_cleanup(musb); + if (is_otg_enabled(musb) || is_host_enabled(musb)) + wake_lock_destroy(&musb->musb_wakelock); + fail3: if (musb->irq_wake) device_init_wakeup(dev, 0); @@ -2190,6 +2190,9 @@ static int __exit musb_remove(struct platform_device *pdev) #ifndef CONFIG_MUSB_PIO_ONLY pdev->dev.dma_mask = orig_dma_mask; #endif + if (is_otg_enabled(musb) || is_host_enabled(musb)) + wake_lock_destroy(&musb->musb_wakelock); + return 0; } diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 263d31c..695b790 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -47,6 +47,7 @@ #include <linux/usb.h> #include <linux/usb/otg.h> #include <linux/usb/musb.h> +#include <linux/wakelock.h> struct musb; struct musb_hw_ep; @@ -386,6 +387,7 @@ struct musb { struct musb_context_registers context; irqreturn_t (*isr)(int, void *); + struct wake_lock musb_wakelock; struct work_struct irq_work; struct workqueue_struct *otg_notifier_wq; u16 hwvers; diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 548338c..b3acbe7 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -634,6 +634,7 @@ static void rxstate(struct musb *musb, struct musb_request *req) u16 len; u16 csr = musb_readw(epio, MUSB_RXCSR); struct musb_hw_ep *hw_ep = &musb->endpoints[epnum]; + u8 use_mode_1; if (hw_ep->is_shared_fifo) musb_ep = &hw_ep->ep_in; @@ -683,6 +684,10 @@ static void rxstate(struct musb *musb, struct musb_request *req) if (csr & MUSB_RXCSR_RXPKTRDY) { len = musb_readw(epio, MUSB_RXCOUNT); + + /* Disable mode1 rx dma */ + use_mode_1 = 0; + if (request->actual < request->length) { #ifdef CONFIG_USB_INVENTRA_DMA if (is_buffer_mapped(req)) { @@ -714,37 +719,40 @@ static void rxstate(struct musb *musb, struct musb_request *req) * then becomes usable as a runtime "use mode 1" hint... */ - csr |= MUSB_RXCSR_DMAENAB; -#ifdef USE_MODE1 + /* Experimental: Mode1 works with mass storage use cases + */ + if (use_mode_1) { csr |= MUSB_RXCSR_AUTOCLEAR; - /* csr |= MUSB_RXCSR_DMAMODE; */ + musb_writew(epio, MUSB_RXCSR, csr); + csr |= MUSB_RXCSR_DMAENAB; + musb_writew(epio, MUSB_RXCSR, csr); - /* this special sequence (enabling and then - * disabling MUSB_RXCSR_DMAMODE) is required + /* this special sequence is required * to get DMAReq to activate */ - musb_writew(epio, MUSB_RXCSR, - csr | MUSB_RXCSR_DMAMODE); -#else + csr |= MUSB_RXCSR_DMAMODE; + musb_writew(epio, MUSB_RXCSR, csr); + csr |= MUSB_RXCSR_DMAENAB; + musb_writew(epio, MUSB_RXCSR, csr); + } else { if (!musb_ep->hb_mult && musb_ep->hw_ep->rx_double_buffered) csr |= MUSB_RXCSR_AUTOCLEAR; -#endif + csr |= MUSB_RXCSR_DMAENAB; musb_writew(epio, MUSB_RXCSR, csr); + } if (request->actual < request->length) { int transfer_size = 0; -#ifdef USE_MODE1 + if (use_mode_1) { transfer_size = min(request->length - request->actual, channel->max_len); -#else + musb_ep->dma->desired_mode = 1; + } else { transfer_size = min(request->length - request->actual, (unsigned)len); -#endif - if (transfer_size <= musb_ep->packet_sz) - musb_ep->dma->desired_mode = 0; - else - musb_ep->dma->desired_mode = 1; + musb_ep->dma->desired_mode = 0; + } use_dma = c->channel_program( channel, @@ -1919,21 +1927,10 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, spin_lock_irqsave(&musb->lock, flags); otg_set_peripheral(musb->xceiv, &musb->g); - musb->xceiv->state = OTG_STATE_B_IDLE; - musb->is_active = 1; - - /* - * FIXME this ignores the softconnect flag. Drivers are - * allowed hold the peripheral inactive until for example - * userspace hooks up printer hardware or DSP codecs, so - * hosts only see fully functional devices. - */ if (!is_otg_enabled(musb)) musb_start(musb); - otg_set_peripheral(musb->xceiv, &musb->g); - spin_unlock_irqrestore(&musb->lock, flags); if (is_otg_enabled(musb)) { @@ -1951,12 +1948,9 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, goto err2; } - if ((musb->xceiv->last_event == USB_EVENT_ID) - && musb->xceiv->set_vbus) - otg_set_vbus(musb->xceiv, 1); - hcd->self.uses_pio_for_control = 1; } + if (musb->xceiv->last_event == USB_EVENT_NONE) pm_runtime_put(musb->controller); diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index 8b2473f..b60c540 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -2266,6 +2266,8 @@ static int musb_bus_suspend(struct usb_hcd *hcd) struct musb *musb = hcd_to_musb(hcd); u8 devctl; + wake_unlock(&musb->musb_wakelock); + if (!is_host_active(musb)) return 0; @@ -2295,6 +2297,9 @@ static int musb_bus_suspend(struct usb_hcd *hcd) static int musb_bus_resume(struct usb_hcd *hcd) { + struct musb *musb = hcd_to_musb(hcd); + + wake_lock(&musb->musb_wakelock); /* resuming child port does the work */ return 0; } diff --git a/drivers/usb/musb/musb_regs.h b/drivers/usb/musb/musb_regs.h index 8241070..6e3cb3f 100644 --- a/drivers/usb/musb/musb_regs.h +++ b/drivers/usb/musb/musb_regs.h @@ -62,6 +62,10 @@ #define MUSB_INTR_SESSREQ 0x40 #define MUSB_INTR_VBUSERROR 0x80 /* For SESSION end */ +#define MUSB_INTR_ENABLED \ + (MUSB_INTR_SUSPEND | MUSB_INTR_RESUME | MUSB_INTR_RESET | \ + MUSB_INTR_CONNECT | MUSB_INTR_DISCONNECT) + /* DEVCTL */ #define MUSB_DEVCTL_BDEVICE 0x80 #define MUSB_DEVCTL_FSDEV 0x40 diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index e685787..1d22442 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -172,11 +172,7 @@ static void omap2430_musb_set_vbus(struct musb *musb, int is_on) if (ret && musb->xceiv->set_vbus) otg_set_vbus(musb->xceiv, 1); - } else { - musb->is_active = 1; musb->xceiv->default_a = 1; - musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; - devctl |= MUSB_DEVCTL_SESSION; MUSB_HST_MODE(musb); } } else { @@ -189,10 +185,9 @@ static void omap2430_musb_set_vbus(struct musb *musb, int is_on) musb->xceiv->default_a = 0; musb->xceiv->state = OTG_STATE_B_IDLE; devctl &= ~MUSB_DEVCTL_SESSION; - + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); MUSB_DEV_MODE(musb); } - musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); dev_dbg(musb->controller, "VBUS %s, devctl %02x " /* otg %3x conf %08x prcm %08x */ "\n", @@ -314,6 +309,7 @@ static void musb_otg_notifier_work(struct work_struct *data_notifier_work) } if (data->interface_type == MUSB_INTERFACE_UTMI) { + omap2430_musb_set_vbus(musb, 0); if (musb->xceiv->set_vbus) otg_set_vbus(musb->xceiv, 0); } diff --git a/drivers/usb/otg/otg-wakelock.c b/drivers/usb/otg/otg-wakelock.c index 2f11472..5725f27 100644 --- a/drivers/usb/otg/otg-wakelock.c +++ b/drivers/usb/otg/otg-wakelock.c @@ -90,11 +90,11 @@ static void otgwl_handle_event(unsigned long event) switch (event) { case USB_EVENT_VBUS: case USB_EVENT_ENUMERATED: + case USB_EVENT_ID: 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/Kconfig b/drivers/video/Kconfig index 1d04ca2..acd429e 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2394,6 +2394,13 @@ config HDMI_TI_4XXX_IP HDMI Library Interface , for TI OMAP4/Netra IP. See http://www.hdmi.org/ for HDMI specification. +config SII9234 + bool "Support Silicon Image 9234 MHL Bridge" + depends on I2C + default n + help + Enabled support for the Silicon Image 9234 MHL Bridge + source "drivers/video/omap/Kconfig" source "drivers/video/omap2/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 22eaeb6..bd33003 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -158,6 +158,8 @@ obj-$(CONFIG_FB_MX3) += mx3fb.o obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o obj-$(CONFIG_FB_MXS) += mxsfb.o +obj-$(CONFIG_SII9234) += sii9234.o + # the test framebuffer is last obj-$(CONFIG_FB_VIRTUAL) += vfb.o diff --git a/drivers/video/omap2/displays/Kconfig b/drivers/video/omap2/displays/Kconfig index 609a280..e08aeca 100644 --- a/drivers/video/omap2/displays/Kconfig +++ b/drivers/video/omap2/displays/Kconfig @@ -30,6 +30,13 @@ config PANEL_NEC_NL8048HL11_01B This NEC NL8048HL11-01B panel is TFT LCD used in the Zoom2/3/3630 sdp boards. +config PANEL_S6E8AA0 + tristate "S6E8AA0 DSI Panel" + depends on OMAP2_DSS_DSI + select BACKLIGHT_CLASS_DEVICE + help + S6E8AA0 video mode panel. + config PANEL_TAAL tristate "Taal DSI Panel" depends on OMAP2_DSS_DSI diff --git a/drivers/video/omap2/displays/Makefile b/drivers/video/omap2/displays/Makefile index 0f601ab3a..8b1dd23 100644 --- a/drivers/video/omap2/displays/Makefile +++ b/drivers/video/omap2/displays/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PANEL_GENERIC_DPI) += panel-generic-dpi.o obj-$(CONFIG_PANEL_LGPHILIPS_LB035Q02) += panel-lgphilips-lb035q02.o obj-$(CONFIG_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o obj-$(CONFIG_PANEL_NEC_NL8048HL11_01B) += panel-nec-nl8048hl11-01b.o +obj-$(CONFIG_PANEL_S6E8AA0) += panel-s6e8aa0.o obj-$(CONFIG_PANEL_TAAL) += panel-taal.o obj-$(CONFIG_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o diff --git a/drivers/video/omap2/displays/panel-generic-dpi.c b/drivers/video/omap2/displays/panel-generic-dpi.c index 611be7e..6aa2027 100644 --- a/drivers/video/omap2/displays/panel-generic-dpi.c +++ b/drivers/video/omap2/displays/panel-generic-dpi.c @@ -256,6 +256,31 @@ static struct panel_config generic_dpi_panels[] = { .power_off_delay = 0, .name = "powertip_ph480272t", }, + /* Samsung AMS452GN05 */ + { + { + .x_res = 480, + .y_res = 800, + + .pixel_clock = 25600, + + .hfp = 16, + .hsw = 2, + .hbp = 16, + + .vfp = 9, + .vsw = 2, + .vbp = 3, + }, + .acbi = 0x0, + .acb = 0x0, + .config = OMAP_DSS_LCD_TFT | OMAP_DSS_LCD_IVS | + OMAP_DSS_LCD_IHS | OMAP_DSS_LCD_IPC | + OMAP_DSS_LCD_IEO | OMAP_DSS_LCD_ONOFF, + .power_on_delay = 0, + .power_off_delay = 0, + .name = "samsung_ams452gn05", + }, }; struct panel_drv_data { diff --git a/drivers/video/omap2/displays/panel-s6e8aa0.c b/drivers/video/omap2/displays/panel-s6e8aa0.c new file mode 100644 index 0000000..8fd29cba --- /dev/null +++ b/drivers/video/omap2/displays/panel-s6e8aa0.c @@ -0,0 +1,1970 @@ +/* + * Samsung s6e8aa0 panel support + * + * Copyright 2011 Google, Inc. + * Author: Erik Gilling <konkers@google.com> + * + * based on d2l panel driver by Jerry Alexander <x0135174@ti.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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/seq_file.h> +#include <linux/backlight.h> +#include <linux/fb.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/sort.h> +#include <linux/regulator/consumer.h> +#include <linux/mutex.h> +#include <linux/i2c.h> +#include <linux/uaccess.h> + + +#include <video/omapdss.h> + +#include <linux/platform_data/panel-s6e8aa0.h> + +#include "../dss/dss.h" + +/* DSI Command Virtual channel */ +#define CMD_VC_CHANNEL 1 + +#define V1_ADJ_MAX 140 +#define V255_ADJ_MAX 430 +#define NUM_GAMMA_REGS 24 +#define NUM_DY_REGS (32) + +enum { + V1, + V15, + V35, + V59, + V87, + V171, + V255, + V_COUNT, +}; + +#define DRIVER_NAME "s6e8aa0_i2c" +#define DEVICE_NAME "s6e8aa0_i2c" + +static int s6e8aa0_update(struct omap_dss_device *dssdev, + u16 x, u16 y, u16 w, u16 h); + +static struct omap_video_timings s6e8aa0_timings = { + .x_res = 720, + .y_res = 1280, + .pixel_clock = 79494, + .hfp = 158, + .hsw = 2, + .hbp = 160, + .vfp = 13, + .vsw = 1, + .vbp = 2, +}; + +static const struct s6e8aa0_gamma_adj_points default_gamma_adj_points = { + .v1 = BV_1, + .v15 = BV_15, + .v35 = BV_35, + .v59 = BV_59, + .v87 = BV_87, + .v171 = BV_171, +}; + +static u32 s6e8aa0_srgb_dyi_to_b[NUM_DY_REGS] = { + 0x0027c8ac, /* 2 */ + 0x004f9159, /* 4 */ + 0x00775a06, /* 6 */ + 0x009f22b3, /* 8 */ + 0x00f0f18e, /* 12 */ + 0x0153936c, /* 16 */ + 0x02569c13, /* 24 */ + 0x03b2977b, /* 32 */ + 0x060e496b, /* 42.5 */ + 0x0803965b, /* 49.5 */ + 0x0bf23e3b, /* 61 */ + 0x10048613, /* 70.8 */ + 0x14041743, /* 79 */ + 0x180da7c6, /* 86.4 */ + 0x1c05aec3, /* 93 */ + 0x200e2a06, /* 99.2 */ + 0x280bf0ad, /* 110.2 */ + 0x301505aa, /* 120 */ + 0x38108cb8, /* 128.9 */ + 0x400a5f93, /* 137 */ + 0x5008b39d, /* 151.7 */ + 0x6010dbad, /* 164.8 */ + 0x700a375a, /* 176.6 */ + 0x801a9901, /* 187.6 */ + 0x901075cd, /* 197.7 */ + 0xa01b515c, /* 207.2 */ + 0xb00de3e4, /* 216.1 */ + 0xc01c269c, /* 224.7 */ + 0xd01871ca, /* 232.8 */ + 0xe010831c, /* 240.5 */ + 0xf02ca6b8, /* 247.9 */ + 0xffffffff, /* 255.1 */ +}; + +struct s6e8aa0_gamma_reg_offsets { + s16 v[2][3][7]; +}; + +struct s6e8aa0_data { + struct mutex lock; + + struct omap_dss_device *dssdev; + struct backlight_device *bldev; + struct dentry *debug_dir; + bool enabled; + u8 rotate; + bool mirror; + bool use_dsi_bl; + unsigned int bl; + const struct s6e8aa0_gamma_adj_points *gamma_adj_points; + const u32 *dyi_to_b; + struct s6e8aa0_gamma_reg_offsets gamma_reg_offsets; + struct s6e8aa0_gamma_entry *brightness_table; + int brightness_table_size; + u32 brightness_limit[3]; + unsigned long hw_guard_end; /* next value of jiffies when we can + * issue the next sleep in/out command + */ + unsigned long hw_guard_wait; /* max guard time in jiffies */ + + atomic_t do_update; + struct { + u16 x; + u16 y; + u16 w; + u16 h; + } update_region; + + bool cabc_broken; + unsigned cabc_mode; + + bool force_update; + struct omap_video_timings *timings; + + struct panel_s6e8aa0_data *pdata; + + unsigned int acl_cur; + bool acl_enable; + u8 acl_average; + unsigned int elvss_cur_i; + u8 panel_id[3]; +}; + +const u8 s6e8aa0_mtp_unlock[] = { + 0xF1, + 0x5A, + 0x5A, +}; + +const u8 s6e8aa0_mtp_lock[] = { + 0xF1, + 0xA5, + 0xA5, +}; + +static int s6e8aa0_write_reg(struct omap_dss_device *dssdev, u8 reg, u8 val) +{ + u8 buf[2]; + buf[0] = reg; + buf[1] = val; + + return dsi_vc_dcs_write(dssdev, 1, buf, 2); +} + +static int s6e8aa0_write_block(struct omap_dss_device *dssdev, const u8 *data, int len) +{ + // XXX: dsi_vc_dsc_write should take a const u8 * + return dsi_vc_dcs_write(dssdev, 1, (u8 *)data, len); +} + +static int s6e8aa0_write_block_nosync(struct omap_dss_device *dssdev, + const u8 *data, int len) +{ + return dsi_vc_dcs_write_nosync(dssdev, 1, (u8 *)data, len); +} + +static int s6e8aa0_read_block(struct omap_dss_device *dssdev, + u8 cmd, u8 *data, int len) +{ + return dsi_vc_dcs_read(dssdev, 1, cmd, data, len); +} + +static void s6e8aa0_write_sequence(struct omap_dss_device *dssdev, + const struct s6e8aa0_sequence_entry *seq, int seq_len) +{ + while (seq_len--) { + if (seq->cmd_len) + s6e8aa0_write_block(dssdev, seq->cmd, seq->cmd_len); + if (seq->msleep) + msleep(seq->msleep); + seq++; + } +} + +/*********************** +*** DUMMY FUNCTIONS **** +***********************/ + +static int s6e8aa0_rotate(struct omap_dss_device *dssdev, u8 rotate) +{ + return 0; +} + +static u8 s6e8aa0_get_rotate(struct omap_dss_device *dssdev) +{ + return 0; +} + +static int s6e8aa0_mirror(struct omap_dss_device *dssdev, bool enable) +{ + return 0; +} + +static bool s6e8aa0_get_mirror(struct omap_dss_device *dssdev) +{ + return 0; +} + +static void s6e8aa0_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + *timings = dssdev->panel.timings; +} + +static void s6e8aa0_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + dssdev->panel.timings.x_res = timings->x_res; + dssdev->panel.timings.y_res = timings->y_res; + dssdev->panel.timings.pixel_clock = timings->pixel_clock; + dssdev->panel.timings.hsw = timings->hsw; + dssdev->panel.timings.hfp = timings->hfp; + dssdev->panel.timings.hbp = timings->hbp; + dssdev->panel.timings.vsw = timings->vsw; + dssdev->panel.timings.vfp = timings->vfp; + dssdev->panel.timings.vbp = timings->vbp; +} + +static int s6e8aa0_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + return 0; +} + +static void s6e8aa0_get_resolution(struct omap_dss_device *dssdev, + u16 *xres, u16 *yres) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + + if (s6->rotate == 0 || s6->rotate == 2) { + *xres = dssdev->panel.timings.x_res; + *yres = dssdev->panel.timings.y_res; + } else { + *yres = dssdev->panel.timings.x_res; + *xres = dssdev->panel.timings.y_res; + } +} + +static int s6e8aa0_enable_te(struct omap_dss_device *dssdev, bool enable) +{ + return 0; +} + +static int s6e8aa0_hw_reset(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + + gpio_set_value(s6->pdata->reset_gpio, 0); + msleep(20); + gpio_set_value(s6->pdata->reset_gpio, 1); + msleep(40); + + return 0; +} + +static u32 s6e8aa0_table_lookup(u32 b, int c, + const struct s6e8aa0_gamma_entry *table, + int table_size) +{ + int i; + u32 ret; + u32 bl = 0; + u32 bh = 0; + u32 vl = 0; + u32 vh; + u64 tmp; + + if (!table_size) + return b; + + for (i = 0; i < table_size; i++) { + bl = bh; + bh = table[i].brightness; + if (bh >= b) + break; + } + vh = table[i].v[c]; + if (i == 0 || (b - bl) == 0) { + ret = vl = vh; + } else { + vl = table[i - 1].v[c]; + tmp = (u64)vh * (b - bl) + (u64)vl * (bh - b); + do_div(tmp, bh - bl); + ret = tmp; + } + pr_debug("%s: looking for c %d, %08x, " + "found %08x:%08x, v %7d:%7d, ret %7d\n", + __func__, c, b, bl, bh, vl, vh, ret); + return ret; +} + +static u32 s6e8aa0_raw_gamma_lookup(struct s6e8aa0_data *s6, u32 b, int c) +{ + struct panel_s6e8aa0_data *pdata = s6->pdata; + return s6e8aa0_table_lookup(b, c, pdata->gamma_table, + pdata->gamma_table_size); +} + +static u32 s6e8aa0_gamma_lookup(struct s6e8aa0_data *s6, + u8 brightness, u32 val, int c) +{ + u32 b; + u32 ret; + u64 tmp; + + tmp = val; + tmp *= brightness; + do_div(tmp, 255); + + b = s6e8aa0_table_lookup(tmp, c, s6->brightness_table, + s6->brightness_table_size); + + ret = s6e8aa0_raw_gamma_lookup(s6, b, c); + + pr_debug("%s: looking for %3d %08x c %d, %08x, got %7d\n", + __func__, brightness, val, c, b, ret); + + return ret; +} + +/* + * V1 = V0 - V0 * ( 5 + v1_adj ) / 600 + * V15 = V1 - (V1 - V35) * ( 20 + v15_adj ) / 320 + * V35 = V1 - (V1 - V59) * ( 65 + v35_adj ) / 320 + * V59 = V1 - (V1 - V87) * ( 65 + v59_adj ) / 320 + * V87 = V1 - (V1 - V171) * ( 65 + v87_adj ) / 320 + * V171 = V1 - (V1 - V255) * ( 65 + v171_adj ) / 320 + * V255 = V0 - V0 * ( 100 + v255_adj ) / 600 + * + * v_n_adj = v_n_reg + v_n_offset + */ + +static u32 v1adj_to_v1(u8 v1_adj, u32 v0) +{ + return DIV_ROUND_CLOSEST((600 - 5 - v1_adj) * v0, 600); +} + +static u32 v1_to_v1adj(u32 v1, u32 v0) +{ + return 600 - 5 - DIV_ROUND_CLOSEST(600 * v1, v0); +} + +static u32 vnadj_to_vn(int n, u8 v_n_adj, u32 v1, u32 v_next) +{ + int base = (n == V15) ? 20 : 65; + return v1 - DIV_ROUND_CLOSEST((v1 - v_next) * (base + v_n_adj), 320); +} + +static u32 vn_to_vnadj(int n, u32 v_n, u32 v1, u32 v_next) +{ + int base = (n == V15) ? 20 : 65; + return DIV_ROUND_CLOSEST(320 * (v1 - v_n), v1 - v_next) - base; +} + +static u32 v255adj_to_v255(u16 v255_adj, u32 v0) +{ + return DIV_ROUND_CLOSEST((600 - 100 - v255_adj) * v0, 600); +} + +static u32 v255_to_v255adj(u32 v255, u32 v0) +{ + return 600 - 100 - DIV_ROUND_CLOSEST(600 * v255, v0); +} + +static int gamma_reg_index(int c, int i) +{ + return 3 * i + c; +} + +static int gamma_reg_index_v255_h(int c) +{ + return 3 * V255 + 2 * c; +} + +static int gamma_reg_index_v255_l(int c) +{ + return gamma_reg_index_v255_h(c) + 1; +} + +static void s6e8aa0_setup_dy_regs(struct s6e8aa0_data *s6, int c, + u32 v0, u32 v[V_COUNT], u8 dy[NUM_DY_REGS]) +{ + static const struct { + int base; + u32 scale; + } output_table[256] = { + [0] = { }, + [1] = { V1, }, + [2] = { V15, 0x100000000ULL * 47 / 52, }, + [3] = { V15, 0x100000000ULL * 42 / 52, }, + [4] = { V15, 0x100000000ULL * 37 / 52, }, + [5] = { V15, 0x100000000ULL * 32 / 52, }, + [6] = { V15, 0x100000000ULL * 27 / 52, }, + [7] = { V15, 0x100000000ULL * 23 / 52, }, + [8] = { V15, 0x100000000ULL * 19 / 52, }, + [9] = { V15, 0x100000000ULL * 15 / 52, }, + [10] = { V15, 0x100000000ULL * 12 / 52, }, + [11] = { V15, 0x100000000ULL * 9 / 52, }, + [12] = { V15, 0x100000000ULL * 6 / 52, }, + [13] = { V15, 0x100000000ULL * 4 / 52, }, + [14] = { V15, 0x100000000ULL * 2 / 52, }, + [15] = { V15, }, + [16] = { V35, 0x100000000ULL * 66 / 70, }, + [17] = { V35, 0x100000000ULL * 62 / 70, }, + [18] = { V35, 0x100000000ULL * 58 / 70, }, + [19] = { V35, 0x100000000ULL * 54 / 70, }, + [20] = { V35, 0x100000000ULL * 50 / 70, }, + [21] = { V35, 0x100000000ULL * 46 / 70, }, + [22] = { V35, 0x100000000ULL * 42 / 70, }, + [23] = { V35, 0x100000000ULL * 38 / 70, }, + [24] = { V35, 0x100000000ULL * 34 / 70, }, + [25] = { V35, 0x100000000ULL * 30 / 70, }, + [26] = { V35, 0x100000000ULL * 27 / 70, }, + [27] = { V35, 0x100000000ULL * 24 / 70, }, + [28] = { V35, 0x100000000ULL * 21 / 70, }, + [29] = { V35, 0x100000000ULL * 18 / 70, }, + [30] = { V35, 0x100000000ULL * 15 / 70, }, + [31] = { V35, 0x100000000ULL * 12 / 70, }, + [32] = { V35, 0x100000000ULL * 9 / 70, }, + [33] = { V35, 0x100000000ULL * 6 / 70, }, + [34] = { V35, 0x100000000ULL * 3 / 70, }, + [35] = { V35, }, + [36] = { V59, 0x100000000ULL * 23 / 24, }, + [37] = { V59, 0x100000000ULL * 22 / 24, }, + [38] = { V59, 0x100000000ULL * 21 / 24, }, + [39] = { V59, 0x100000000ULL * 20 / 24, }, + [40] = { V59, 0x100000000ULL * 19 / 24, }, + [41] = { V59, 0x100000000ULL * 18 / 24, }, + [42] = { V59, 0x100000000ULL * 17 / 24, }, + [43] = { V59, 0x100000000ULL * 16 / 24, }, + [44] = { V59, 0x100000000ULL * 15 / 24, }, + [45] = { V59, 0x100000000ULL * 14 / 24, }, + [46] = { V59, 0x100000000ULL * 13 / 24, }, + [47] = { V59, 0x100000000ULL * 12 / 24, }, + [48] = { V59, 0x100000000ULL * 11 / 24, }, + [49] = { V59, 0x100000000ULL * 10 / 24, }, + [50] = { V59, 0x100000000ULL * 9 / 24, }, + [51] = { V59, 0x100000000ULL * 8 / 24, }, + [52] = { V59, 0x100000000ULL * 7 / 24, }, + [53] = { V59, 0x100000000ULL * 6 / 24, }, + [54] = { V59, 0x100000000ULL * 5 / 24, }, + [55] = { V59, 0x100000000ULL * 4 / 24, }, + [56] = { V59, 0x100000000ULL * 3 / 24, }, + [57] = { V59, 0x100000000ULL * 2 / 24, }, + [58] = { V59, 0x100000000ULL * 1 / 24, }, + [59] = { V59, }, + [60] = { V87, 0x100000000ULL * 27 / 28, }, + [61] = { V87, 0x100000000ULL * 26 / 28, }, + [62] = { V87, 0x100000000ULL * 25 / 28, }, + [63] = { V87, 0x100000000ULL * 24 / 28, }, + [64] = { V87, 0x100000000ULL * 23 / 28, }, + [65] = { V87, 0x100000000ULL * 22 / 28, }, + [66] = { V87, 0x100000000ULL * 21 / 28, }, + [67] = { V87, 0x100000000ULL * 20 / 28, }, + [68] = { V87, 0x100000000ULL * 19 / 28, }, + [69] = { V87, 0x100000000ULL * 18 / 28, }, + [70] = { V87, 0x100000000ULL * 17 / 28, }, + [71] = { V87, 0x100000000ULL * 16 / 28, }, + [72] = { V87, 0x100000000ULL * 15 / 28, }, + [73] = { V87, 0x100000000ULL * 14 / 28, }, + [74] = { V87, 0x100000000ULL * 13 / 28, }, + [75] = { V87, 0x100000000ULL * 12 / 28, }, + [76] = { V87, 0x100000000ULL * 11 / 28, }, + [77] = { V87, 0x100000000ULL * 10 / 28, }, + [78] = { V87, 0x100000000ULL * 9 / 28, }, + [79] = { V87, 0x100000000ULL * 8 / 28, }, + [80] = { V87, 0x100000000ULL * 7 / 28, }, + [81] = { V87, 0x100000000ULL * 6 / 28, }, + [82] = { V87, 0x100000000ULL * 5 / 28, }, + [83] = { V87, 0x100000000ULL * 4 / 28, }, + [84] = { V87, 0x100000000ULL * 3 / 28, }, + [85] = { V87, 0x100000000ULL * 2 / 28, }, + [86] = { V87, 0x100000000ULL * 1 / 28, }, + [87] = { V87, }, + [88] = { V171, 0x100000000ULL * 83 / 84, }, + [89] = { V171, 0x100000000ULL * 82 / 84, }, + [90] = { V171, 0x100000000ULL * 81 / 84, }, + [91] = { V171, 0x100000000ULL * 80 / 84, }, + [92] = { V171, 0x100000000ULL * 79 / 84, }, + [93] = { V171, 0x100000000ULL * 78 / 84, }, + [94] = { V171, 0x100000000ULL * 77 / 84, }, + [95] = { V171, 0x100000000ULL * 76 / 84, }, + [96] = { V171, 0x100000000ULL * 75 / 84, }, + [97] = { V171, 0x100000000ULL * 74 / 84, }, + [98] = { V171, 0x100000000ULL * 73 / 84, }, + [99] = { V171, 0x100000000ULL * 72 / 84, }, + [100] = { V171, 0x100000000ULL * 71 / 84, }, + [101] = { V171, 0x100000000ULL * 70 / 84, }, + [102] = { V171, 0x100000000ULL * 69 / 84, }, + [103] = { V171, 0x100000000ULL * 68 / 84, }, + [104] = { V171, 0x100000000ULL * 67 / 84, }, + [105] = { V171, 0x100000000ULL * 66 / 84, }, + [106] = { V171, 0x100000000ULL * 65 / 84, }, + [107] = { V171, 0x100000000ULL * 64 / 84, }, + [108] = { V171, 0x100000000ULL * 63 / 84, }, + [109] = { V171, 0x100000000ULL * 62 / 84, }, + [110] = { V171, 0x100000000ULL * 61 / 84, }, + [111] = { V171, 0x100000000ULL * 60 / 84, }, + [112] = { V171, 0x100000000ULL * 59 / 84, }, + [113] = { V171, 0x100000000ULL * 58 / 84, }, + [114] = { V171, 0x100000000ULL * 57 / 84, }, + [115] = { V171, 0x100000000ULL * 56 / 84, }, + [116] = { V171, 0x100000000ULL * 55 / 84, }, + [117] = { V171, 0x100000000ULL * 54 / 84, }, + [118] = { V171, 0x100000000ULL * 53 / 84, }, + [119] = { V171, 0x100000000ULL * 52 / 84, }, + [120] = { V171, 0x100000000ULL * 51 / 84, }, + [121] = { V171, 0x100000000ULL * 50 / 84, }, + [122] = { V171, 0x100000000ULL * 49 / 84, }, + [123] = { V171, 0x100000000ULL * 48 / 84, }, + [124] = { V171, 0x100000000ULL * 47 / 84, }, + [125] = { V171, 0x100000000ULL * 46 / 84, }, + [126] = { V171, 0x100000000ULL * 45 / 84, }, + [127] = { V171, 0x100000000ULL * 44 / 84, }, + [128] = { V171, 0x100000000ULL * 43 / 84, }, + [129] = { V171, 0x100000000ULL * 42 / 84, }, + [130] = { V171, 0x100000000ULL * 41 / 84, }, + [131] = { V171, 0x100000000ULL * 40 / 84, }, + [132] = { V171, 0x100000000ULL * 39 / 84, }, + [133] = { V171, 0x100000000ULL * 38 / 84, }, + [134] = { V171, 0x100000000ULL * 37 / 84, }, + [135] = { V171, 0x100000000ULL * 36 / 84, }, + [136] = { V171, 0x100000000ULL * 35 / 84, }, + [137] = { V171, 0x100000000ULL * 34 / 84, }, + [138] = { V171, 0x100000000ULL * 33 / 84, }, + [139] = { V171, 0x100000000ULL * 32 / 84, }, + [140] = { V171, 0x100000000ULL * 31 / 84, }, + [141] = { V171, 0x100000000ULL * 30 / 84, }, + [142] = { V171, 0x100000000ULL * 29 / 84, }, + [143] = { V171, 0x100000000ULL * 28 / 84, }, + [144] = { V171, 0x100000000ULL * 27 / 84, }, + [145] = { V171, 0x100000000ULL * 26 / 84, }, + [146] = { V171, 0x100000000ULL * 25 / 84, }, + [147] = { V171, 0x100000000ULL * 24 / 84, }, + [148] = { V171, 0x100000000ULL * 23 / 84, }, + [149] = { V171, 0x100000000ULL * 22 / 84, }, + [150] = { V171, 0x100000000ULL * 21 / 84, }, + [151] = { V171, 0x100000000ULL * 20 / 84, }, + [152] = { V171, 0x100000000ULL * 19 / 84, }, + [153] = { V171, 0x100000000ULL * 18 / 84, }, + [154] = { V171, 0x100000000ULL * 17 / 84, }, + [155] = { V171, 0x100000000ULL * 16 / 84, }, + [156] = { V171, 0x100000000ULL * 15 / 84, }, + [157] = { V171, 0x100000000ULL * 14 / 84, }, + [158] = { V171, 0x100000000ULL * 13 / 84, }, + [159] = { V171, 0x100000000ULL * 12 / 84, }, + [160] = { V171, 0x100000000ULL * 11 / 84, }, + [161] = { V171, 0x100000000ULL * 10 / 84, }, + [162] = { V171, 0x100000000ULL * 9 / 84, }, + [163] = { V171, 0x100000000ULL * 8 / 84, }, + [164] = { V171, 0x100000000ULL * 7 / 84, }, + [165] = { V171, 0x100000000ULL * 6 / 84, }, + [166] = { V171, 0x100000000ULL * 5 / 84, }, + [167] = { V171, 0x100000000ULL * 4 / 84, }, + [168] = { V171, 0x100000000ULL * 3 / 84, }, + [169] = { V171, 0x100000000ULL * 2 / 84, }, + [170] = { V171, 0x100000000ULL * 1 / 84, }, + [171] = { V171, }, + [172] = { V255, 0x100000000ULL * 83 / 84, }, + [173] = { V255, 0x100000000ULL * 82 / 84, }, + [174] = { V255, 0x100000000ULL * 81 / 84, }, + [175] = { V255, 0x100000000ULL * 80 / 84, }, + [176] = { V255, 0x100000000ULL * 79 / 84, }, + [177] = { V255, 0x100000000ULL * 78 / 84, }, + [178] = { V255, 0x100000000ULL * 77 / 84, }, + [179] = { V255, 0x100000000ULL * 76 / 84, }, + [180] = { V255, 0x100000000ULL * 75 / 84, }, + [181] = { V255, 0x100000000ULL * 74 / 84, }, + [182] = { V255, 0x100000000ULL * 73 / 84, }, + [183] = { V255, 0x100000000ULL * 72 / 84, }, + [184] = { V255, 0x100000000ULL * 71 / 84, }, + [185] = { V255, 0x100000000ULL * 70 / 84, }, + [186] = { V255, 0x100000000ULL * 69 / 84, }, + [187] = { V255, 0x100000000ULL * 68 / 84, }, + [188] = { V255, 0x100000000ULL * 67 / 84, }, + [189] = { V255, 0x100000000ULL * 66 / 84, }, + [190] = { V255, 0x100000000ULL * 65 / 84, }, + [191] = { V255, 0x100000000ULL * 64 / 84, }, + [192] = { V255, 0x100000000ULL * 63 / 84, }, + [193] = { V255, 0x100000000ULL * 62 / 84, }, + [194] = { V255, 0x100000000ULL * 61 / 84, }, + [195] = { V255, 0x100000000ULL * 60 / 84, }, + [196] = { V255, 0x100000000ULL * 59 / 84, }, + [197] = { V255, 0x100000000ULL * 58 / 84, }, + [198] = { V255, 0x100000000ULL * 57 / 84, }, + [199] = { V255, 0x100000000ULL * 56 / 84, }, + [200] = { V255, 0x100000000ULL * 55 / 84, }, + [201] = { V255, 0x100000000ULL * 54 / 84, }, + [202] = { V255, 0x100000000ULL * 53 / 84, }, + [203] = { V255, 0x100000000ULL * 52 / 84, }, + [204] = { V255, 0x100000000ULL * 51 / 84, }, + [205] = { V255, 0x100000000ULL * 50 / 84, }, + [206] = { V255, 0x100000000ULL * 49 / 84, }, + [207] = { V255, 0x100000000ULL * 48 / 84, }, + [208] = { V255, 0x100000000ULL * 47 / 84, }, + [209] = { V255, 0x100000000ULL * 46 / 84, }, + [210] = { V255, 0x100000000ULL * 45 / 84, }, + [211] = { V255, 0x100000000ULL * 44 / 84, }, + [212] = { V255, 0x100000000ULL * 43 / 84, }, + [213] = { V255, 0x100000000ULL * 42 / 84, }, + [214] = { V255, 0x100000000ULL * 41 / 84, }, + [215] = { V255, 0x100000000ULL * 40 / 84, }, + [216] = { V255, 0x100000000ULL * 39 / 84, }, + [217] = { V255, 0x100000000ULL * 38 / 84, }, + [218] = { V255, 0x100000000ULL * 37 / 84, }, + [219] = { V255, 0x100000000ULL * 36 / 84, }, + [220] = { V255, 0x100000000ULL * 35 / 84, }, + [221] = { V255, 0x100000000ULL * 34 / 84, }, + [222] = { V255, 0x100000000ULL * 33 / 84, }, + [223] = { V255, 0x100000000ULL * 32 / 84, }, + [224] = { V255, 0x100000000ULL * 31 / 84, }, + [225] = { V255, 0x100000000ULL * 30 / 84, }, + [226] = { V255, 0x100000000ULL * 29 / 84, }, + [227] = { V255, 0x100000000ULL * 28 / 84, }, + [228] = { V255, 0x100000000ULL * 27 / 84, }, + [229] = { V255, 0x100000000ULL * 26 / 84, }, + [230] = { V255, 0x100000000ULL * 25 / 84, }, + [231] = { V255, 0x100000000ULL * 24 / 84, }, + [232] = { V255, 0x100000000ULL * 23 / 84, }, + [233] = { V255, 0x100000000ULL * 22 / 84, }, + [234] = { V255, 0x100000000ULL * 21 / 84, }, + [235] = { V255, 0x100000000ULL * 20 / 84, }, + [236] = { V255, 0x100000000ULL * 19 / 84, }, + [237] = { V255, 0x100000000ULL * 18 / 84, }, + [238] = { V255, 0x100000000ULL * 17 / 84, }, + [239] = { V255, 0x100000000ULL * 16 / 84, }, + [240] = { V255, 0x100000000ULL * 15 / 84, }, + [241] = { V255, 0x100000000ULL * 14 / 84, }, + [242] = { V255, 0x100000000ULL * 13 / 84, }, + [243] = { V255, 0x100000000ULL * 12 / 84, }, + [244] = { V255, 0x100000000ULL * 11 / 84, }, + [245] = { V255, 0x100000000ULL * 10 / 84, }, + [246] = { V255, 0x100000000ULL * 9 / 84, }, + [247] = { V255, 0x100000000ULL * 8 / 84, }, + [248] = { V255, 0x100000000ULL * 7 / 84, }, + [249] = { V255, 0x100000000ULL * 6 / 84, }, + [250] = { V255, 0x100000000ULL * 5 / 84, }, + [251] = { V255, 0x100000000ULL * 4 / 84, }, + [252] = { V255, 0x100000000ULL * 3 / 84, }, + [253] = { V255, 0x100000000ULL * 2 / 84, }, + [254] = { V255, 0x100000000ULL * 1 / 84, }, + [255] = { V255, }, + }; + + u8 brightness = s6->bl; + int last_y = 0; + int i; + int j = 0; + u32 vh = v0; + u32 vl = vh; + u32 vt; + u32 vb; + u64 tmp; + int y; + u32 scale; + + for (i = 0; i < NUM_DY_REGS; i++) { + vt = s6e8aa0_gamma_lookup(s6, brightness, s6->dyi_to_b[i], c); + while (vl > vt && j < ARRAY_SIZE(output_table) - 1) { + j++; + vh = vl; + vl = v[output_table[j].base]; + scale = output_table[j].scale; + if (scale) { + vb = v[output_table[j].base - 1]; + tmp = vb - vl; + tmp *= scale; + tmp >>= 32; + vl += tmp; + } + } + y = j * 4; + if (vh > vl && vt >= vl) + y -= DIV_ROUND_CLOSEST(4 * (vt - vl), vh - vl); + pr_debug("%s: dy%d %d, v %d (vh %d @ %d, vl %d @ %d)\n", + __func__, i, y, vt, vh, j * 4 - 4, vl, j * 4); + if (y < last_y) + y = last_y; + dy[i] = y - last_y; + last_y = y; + } +} + +static void s6e8aa0_setup_gamma_regs(struct s6e8aa0_data *s6, u8 gamma_regs[], + u8 dy_regs[3][NUM_DY_REGS + 1]) +{ + int c, i; + u8 brightness = s6->bl; + const struct s6e8aa0_gamma_adj_points *bv = s6->gamma_adj_points; + + for (c = 0; c < 3; c++) { + u32 adj; + u32 adj_min; + u32 adj_max; + s16 offset; + u32 v0 = s6e8aa0_gamma_lookup(s6, brightness, BV_0, c); + u32 v[V_COUNT]; + + v[V1] = s6e8aa0_gamma_lookup(s6, brightness, bv->v1, c); + offset = s6->gamma_reg_offsets.v[1][c][V1]; + adj_max = min(V1_ADJ_MAX, V1_ADJ_MAX - offset); + adj_min = max(0, 0 - offset); + adj = v1_to_v1adj(v[V1], v0) - offset; + if (adj < adj_min || adj > adj_max) { + pr_debug("%s: bad adj value %d, v0 %d, v1 %d, c %d\n", + __func__, adj, v0, v[V1], c); + adj = clamp_t(int, adj, adj_min, adj_max); + } + gamma_regs[gamma_reg_index(c, V1)] = adj; + v[V1] = v1adj_to_v1(adj + offset, v0); + + v[V255] = s6e8aa0_gamma_lookup(s6, brightness, BV_255, c); + offset = s6->gamma_reg_offsets.v[1][c][V255]; + adj_max = min(V255_ADJ_MAX, V255_ADJ_MAX - offset); + adj_min = max(0, 0 - offset); + adj = v255_to_v255adj(v[V255], v0) - offset; + if (adj < adj_min || adj > adj_max) { + pr_debug("%s: bad adj value %d, v0 %d, v255 %d, c %d\n", + __func__, adj, v0, v[V255], c); + adj = clamp_t(int, adj, adj_min, adj_max); + } + gamma_regs[3 * V255 + 2 * c] = adj >> 8; + gamma_regs[3 * V255 + 2 * c + 1] = (adj & 0xff); + gamma_regs[gamma_reg_index_v255_h(c)] = adj >> 8; + gamma_regs[gamma_reg_index_v255_l(c)] = adj; + v[V255] = v255adj_to_v255(adj + offset, v0); + + v[V15] = s6e8aa0_gamma_lookup(s6, brightness, bv->v15, c); + v[V35] = s6e8aa0_gamma_lookup(s6, brightness, bv->v35, c); + v[V59] = s6e8aa0_gamma_lookup(s6, brightness, bv->v59, c); + v[V87] = s6e8aa0_gamma_lookup(s6, brightness, bv->v87, c); + v[V171] = s6e8aa0_gamma_lookup(s6, brightness, bv->v171, c); + + for (i = V171; i >= V15; i--) { + offset = s6->gamma_reg_offsets.v[1][c][i]; + adj_max = min(255, 255 - offset); + adj_min = max(0, 0 - offset); + if (v[V1] <= v[i + 1] || v[V1] <= v[i]) { + adj = -1; + } else { + adj = vn_to_vnadj(i, v[i], v[V1], v[i + 1]); + adj -= offset; + } + if (adj < adj_min || adj > adj_max) { + pr_debug("%s: bad adj value %d, " + "vh %d, v %d, c %d\n", + __func__, adj, v[i + 1], v[i], c); + adj = clamp_t(int, adj, adj_min, adj_max); + } + gamma_regs[gamma_reg_index(c, i)] = adj; + v[i] = vnadj_to_vn(i, adj + offset, v[V1], v[i + 1]); + } + + s6e8aa0_setup_dy_regs(s6, c, v0, v, dy_regs[c] + 1); + } +} + +static void s6e8aa0_update_acl_set(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + struct panel_s6e8aa0_data *pdata = s6->pdata; + int i; + unsigned int cd; + unsigned int max_cd = 0; + const struct s6e8aa0_acl_parameters *acl; + + /* Quietly return if you don't have a table */ + if (!pdata->acl_table_size) + return; + + max_cd = pdata->acl_table[pdata->acl_table_size - 1].cd; + + cd = s6->bl * max_cd / 255; + if (cd > max_cd) + cd = max_cd; + + if (s6->acl_enable) { + for (i = 0; i < pdata->acl_table_size; i++) + if (cd <= pdata->acl_table[i].cd) + break; + + if (i == pdata->acl_table_size) + i = pdata->acl_table_size - 1; + + acl = &pdata->acl_table[i]; + if (s6->acl_cur != acl->acl_val) { + s6e8aa0_write_block_nosync(dssdev, acl->regs, + sizeof(acl->regs)); + s6e8aa0_write_reg(dssdev, 0xC0, + 0x01 | (s6->acl_average << 4)); /* ACL ON */ + + s6->acl_cur = acl->acl_val; + } + } else { + if (s6->acl_cur != 0) { + s6->acl_cur = 0; + s6e8aa0_write_reg(dssdev, 0xC0, 0x00); /* ACL OFF */ + } + } + pr_debug("%s : cur_acl=%d, %d\n", __func__, s6->acl_cur, + s6->acl_enable); + return; +} + +static void s6e8aa0_update_elvss(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + struct panel_s6e8aa0_data *pdata = s6->pdata; + u8 elvss_cmd[3]; + u8 elvss; + u8 limit = 0x9F; + unsigned int i; + unsigned int cd; + unsigned int max_cd = 0; + + if (!pdata->elvss_table_size) + return; + + elvss_cmd[0] = 0xB1; + elvss_cmd[1] = 0x04; + + max_cd = pdata->elvss_table[pdata->elvss_table_size - 1].cd; + cd = s6->bl * max_cd / 255; + + for (i = 0; i < pdata->elvss_table_size - 1; i++) + if (cd <= pdata->elvss_table[i].cd) + break; + + if (i == s6->elvss_cur_i) + return; + + s6->elvss_cur_i = i; + + elvss = s6->panel_id[2] & 0x1F; /* ELVSS Pulse 0-4bits */ + elvss += pdata->elvss_table[i].elvss_val; + + if (elvss > limit) + elvss = limit; + + elvss_cmd[2] = elvss; + + s6e8aa0_write_block(dssdev, elvss_cmd, sizeof(elvss_cmd)); + pr_debug("%s - brightness : %d, cd : %d, elvss : %02x\n", + __func__, s6->bl, cd, elvss); + return; +} + +static int s6e8aa0_update_brightness(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + u8 gamma_regs[NUM_GAMMA_REGS + 2]; + u8 dy_regs[3][NUM_DY_REGS + 1]; + + gamma_regs[0] = 0xFA; + gamma_regs[1] = 0x01; + dy_regs[0][0] = 0xb8; + dy_regs[1][0] = 0xb9; + dy_regs[2][0] = 0xba; + + s6e8aa0_setup_gamma_regs(s6, gamma_regs + 2, dy_regs); + s6e8aa0_write_block_nosync(dssdev, gamma_regs, sizeof(gamma_regs)); + s6e8aa0_write_block_nosync(dssdev, dy_regs[0], sizeof(dy_regs[0])); + s6e8aa0_write_block_nosync(dssdev, dy_regs[1], sizeof(dy_regs[1])); + s6e8aa0_write_block_nosync(dssdev, dy_regs[2], sizeof(dy_regs[2])); + s6e8aa0_write_reg(dssdev, 0xF7, 0x01); + + s6e8aa0_update_acl_set(dssdev); + s6e8aa0_update_elvss(dssdev); + return 0; +} + +static u64 s6e8aa0_voltage_lookup(struct s6e8aa0_data *s6, int c, u32 v) +{ + int i; + u32 vh = ~0, vl = ~0; + u32 bl = 0, bh = 0; + u64 ret; + struct panel_s6e8aa0_data *pdata = s6->pdata; + + for (i = 0; i < pdata->gamma_table_size; i++) { + vh = vl; + vl = pdata->gamma_table[i].v[c]; + bh = bl; + bl = pdata->gamma_table[i].brightness; + if (vl <= v) + break; + } + if (i == 0 || (v - vl) == 0) { + ret = bl; + } else { + ret = (u64)bh * (s32)(v - vl) + (u64)bl * (vh - v); + do_div(ret, vh - vl); + } + pr_debug("%s: looking for %7d c %d, " + "found %7d:%7d, b %08x:%08x, ret %08llx\n", + __func__, v, c, vl, vh, bl, bh, ret); + return ret; +} + +static u64 s6e8aa0_limit_brightness(u64 bc[3], u64 bcmax) +{ + int c; + int shift; + + for (c = 0; c < 3; c++) + if (bc[c] > bcmax) + bcmax = bc[c]; + + if (bcmax != 0xffffffff) { + u64 tmp; + pr_warn("s6e8aa: factory calibration info is out of range: scale to 0x%llx\n", + bcmax); + shift = fls(bcmax >> 32); + tmp = (bcmax << (32 - shift)) - 1; + do_div(tmp, 0xffffffff); + tmp++; + pr_warn("s6e8aa: factory calibration info is out of range: scale to 0x%llx, shift %d\n", + tmp, shift); + for (c = 0; c < 3; c++) { + bc[c] <<= 32 - shift; + do_div(bc[c], tmp); + } + } + return bcmax; +} + +static void s6e8aa0_apply_color_adj( + const struct s6e8aa0_factory_calibration_info *fi, u64 bc[3]) +{ + int c; + int shift = fi->color_adj.rshift; + + if (!shift) + return; + + for (c = 0; c < 3; c++) { + u64 b = bc[c]; + u32 bh = b >> 32; + u32 bl = b; + u64 m = fi->color_adj.mult[c]; + /* + * Calculate ((b * m) >> shift). + * If b is greater than 2^32, The 64 by 32 to 64 bit (b * m) + * multiplication can overflow, even if the end result fits, + * so we split it into two 32 by 32 to 64 bit operations. + */ + bc[c] = ((bh * m) << (32 - shift)) + ((bl * m) >> shift); + } +} + +static int s6e8aa0_cmp_gamma_entry(const void *pa, const void *pb) +{ + u32 a = ((const struct s6e8aa0_gamma_entry *)pa)->brightness; + u32 b = ((const struct s6e8aa0_gamma_entry *)pb)->brightness; + if (a > b) + return 1; + if (a < b) + return -1; + return 0; +} + +static void s6e8aa0_adjust_brightness_from_mtp(struct s6e8aa0_data *s6) +{ + int b, c, i; + u32 v[2][3][V_COUNT]; + u64 bc[3]; + u64 bcmax; + struct panel_s6e8aa0_data *pdata = s6->pdata; + const struct s6e8aa0_gamma_reg_offsets *offset = &s6->gamma_reg_offsets; + struct s6e8aa0_factory_calibration_info *fi = pdata->factory_info; + struct s6e8aa0_gamma_entry *brightness_table; + int brightness_table_size = 1; + + for (b = 0; b < 2; b++) + for (i = 0; i < V_COUNT; i++) + if (fi->brightness[b][i]) + brightness_table_size++; + + brightness_table = kmalloc(sizeof(*brightness_table) * + brightness_table_size, GFP_KERNEL); + if (!brightness_table) { + dev_err(&s6->dssdev->dev, + "Failed to allocate brightness table\n"); + return; + } + brightness_table->brightness = 0; + for (c = 0; c < 3; c++) + brightness_table->v[c] = 0; + s6->brightness_table = brightness_table; + s6->brightness_table_size = brightness_table_size; + brightness_table++; + + for (b = 0; b < 2; b++) { + for (c = 0; c < 3; c++) { + u32 v0 = s6e8aa0_raw_gamma_lookup(s6, BV_0, c); + v[b][c][V1] = v1adj_to_v1(fi->regs[b][c][V1] + + offset->v[b][c][V1], v0); + v[b][c][V255] = v255adj_to_v255(fi->regs[b][c][V255] + + offset->v[b][c][V255], v0); + for (i = V171; i >= V15; i--) + v[b][c][i] = vnadj_to_vn(i, fi->regs[b][c][i] + + offset->v[b][c][i], + v[b][c][V1], v[b][c][i + 1]); + } + } + + for (b = 0; b < 2; b++) + for (i = 0; i < V_COUNT; i++) + pr_debug("%s: b %d, p %d, R %7dv, G %7dv, B %7dv\n", + __func__, b, i, + v[b][0][i], v[b][1][i], v[b][2][i]); + + bcmax = 0xffffffff; + for (b = 0; b < 2; b++) { + for (i = 0; i < V_COUNT; i++) { + if (!fi->brightness[b][i]) + continue; + + for (c = 0; c < 3; c++) + bc[c] = s6e8aa0_voltage_lookup(s6, c, + v[b][c][i]); + + s6e8aa0_apply_color_adj(fi, bc); + bcmax = s6e8aa0_limit_brightness(bc, bcmax); + } + } + + for (b = 0; b < 2; b++) { + for (i = 0; i < V_COUNT; i++) { + if (!fi->brightness[b][i]) + continue; + + for (c = 0; c < 3; c++) { + bc[c] = s6e8aa0_voltage_lookup(s6, c, + v[b][c][i]); + pr_debug("s6e8aa: c%d, %d, b-%08llx, before scaling\n", + c, i, bc[c]); + } + + s6e8aa0_apply_color_adj(fi, bc); + for (c = 0; c < 3; c++) { + pr_debug("s6e8aa: c%d, %d, b-%08llx, after color adj\n", + c, i, bc[c]); + } + + s6e8aa0_limit_brightness(bc, bcmax); + + brightness_table->brightness = fi->brightness[b][i]; + pr_info("s6e8aa: d/b %d, p %d, b-%08x\n", + b, i, fi->brightness[b][i]); + for (c = 0; c < 3; c++) { + if (bc[c] > s6->brightness_limit[c]) + s6->brightness_limit[c] = bc[c]; + brightness_table->v[c] = bc[c]; + pr_info("s6e8aa: c%d, %d, b-%08llx, got v %d, factory wants %d\n", + c, i, bc[c], + s6e8aa0_raw_gamma_lookup(s6, bc[c], c), + v[b][c][i]); + } + brightness_table++; + } + } + sort(s6->brightness_table + 1, s6->brightness_table_size - 1, + sizeof(*s6->brightness_table), s6e8aa0_cmp_gamma_entry, NULL); +} + +static s16 s9_to_s16(s16 v) +{ + return (s16)(v << 7) >> 7; +} + +static int mtp_reg_index(int c, int i) +{ + return c * (V_COUNT + 1) + i; +} + +static void s6e8aa0_read_id_info(struct s6e8aa0_data *s6) +{ + struct omap_dss_device *dssdev = s6->dssdev; + int ret; + u8 cmd = 0xD1; + + dsi_vc_set_max_rx_packet_size(dssdev, 1, 3); + ret = s6e8aa0_read_block(dssdev, cmd, s6->panel_id, + ARRAY_SIZE(s6->panel_id)); + dsi_vc_set_max_rx_packet_size(dssdev, 1, 1); + if (ret < 0) { + pr_err("%s: Failed to read id data\n", __func__); + return; + } +} + +static void s6e8aa0_read_mtp_info(struct s6e8aa0_data *s6, int b) +{ + int ret; + int c, i; + u8 mtp_data[24]; + u8 cmd = b ? 0xD3 : 0xD4; + struct omap_dss_device *dssdev = s6->dssdev; + + s6e8aa0_write_block(dssdev, s6e8aa0_mtp_unlock, + ARRAY_SIZE(s6e8aa0_mtp_unlock)); + dsi_vc_set_max_rx_packet_size(dssdev, 1, 24); + ret = s6e8aa0_read_block(dssdev, cmd, mtp_data, ARRAY_SIZE(mtp_data)); + dsi_vc_set_max_rx_packet_size(dssdev, 1, 1); + s6e8aa0_write_block(dssdev, s6e8aa0_mtp_lock, + ARRAY_SIZE(s6e8aa0_mtp_lock)); + if (ret < 0) { + pr_err("%s: Failed to read mtp data\n", __func__); + return; + } + for (c = 0; c < 3; c++) { + for (i = 0; i < V255; i++) + s6->gamma_reg_offsets.v[b][c][i] = + (s8)mtp_data[mtp_reg_index(c, i)]; + + s6->gamma_reg_offsets.v[b][c][V255] = + s9_to_s16(mtp_data[mtp_reg_index(c, V255)] << 8 | + mtp_data[mtp_reg_index(c, V255 + 1)]); + } +} + +static int s6e8aa0_set_brightness(struct backlight_device *bd) +{ + struct omap_dss_device *dssdev = dev_get_drvdata(&bd->dev); + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + int bl = bd->props.brightness; + int ret = 0; + + if (bl == s6->bl) + return 0; + + s6->bl = bl; + mutex_lock(&s6->lock); + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { + dsi_bus_lock(dssdev); + ret = s6e8aa0_update_brightness(dssdev); + dsi_bus_unlock(dssdev); + } + mutex_unlock(&s6->lock); + return ret; +} + +static int s6e8aa0_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static const struct backlight_ops s6e8aa0_backlight_ops = { + .get_brightness = s6e8aa0_get_brightness, + .update_status = s6e8aa0_set_brightness, +}; + +static void seq_print_gamma_regs(struct seq_file *m, const u8 gamma_regs[]) +{ + struct s6e8aa0_data *s6 = m->private; + int c, i; + const int adj_points[] = { 1, 15, 35, 59, 87, 171, 255 }; + const char color[] = { 'R', 'G', 'B' }; + u8 brightness = s6->bl; + const struct s6e8aa0_gamma_adj_points *bv = s6->gamma_adj_points; + const struct s6e8aa0_gamma_reg_offsets *offset = &s6->gamma_reg_offsets; + + for (c = 0; c < 3; c++) { + u32 adj[V_COUNT]; + u32 vt[V_COUNT]; + u32 v[V_COUNT]; + u32 v0 = s6e8aa0_gamma_lookup(s6, brightness, BV_0, c); + + vt[V1] = s6e8aa0_gamma_lookup(s6, brightness, bv->v1, c); + vt[V15] = s6e8aa0_gamma_lookup(s6, brightness, bv->v15, c); + vt[V35] = s6e8aa0_gamma_lookup(s6, brightness, bv->v35, c); + vt[V59] = s6e8aa0_gamma_lookup(s6, brightness, bv->v59, c); + vt[V87] = s6e8aa0_gamma_lookup(s6, brightness, bv->v87, c); + vt[V171] = s6e8aa0_gamma_lookup(s6, brightness, bv->v171, c); + vt[V255] = s6e8aa0_gamma_lookup(s6, brightness, BV_255, c); + + adj[V1] = gamma_regs[gamma_reg_index(c, V1)]; + v[V1] = v1adj_to_v1(adj[V1] + offset->v[1][c][V1], v0); + + adj[V255] = gamma_regs[gamma_reg_index_v255_h(c)] << 8 | + gamma_regs[gamma_reg_index_v255_l(c)]; + v[V255] = v255adj_to_v255(adj[V255] + offset->v[1][c][V255], + v0); + + for (i = V171; i >= V15; i--) { + adj[i] = gamma_regs[gamma_reg_index(c, i)]; + v[i] = vnadj_to_vn(i, adj[i] + offset->v[1][c][i], + v[V1], v[i + 1]); + } + seq_printf(m, "%c v0 %7d\n", + color[c], v0); + for (i = 0; i < V_COUNT; i++) { + seq_printf(m, "%c adj %3d (%02x) %+4d " + "v%-3d %7d - %7d %+8d\n", + color[c], adj[i], adj[i], offset->v[1][c][i], + adj_points[i], v[i], vt[i], v[i] - vt[i]); + } + } +} + +static void seq_print_dy_regs(struct seq_file *m, + u8 dy_regs[3][NUM_DY_REGS + 1]) +{ + int i, c; + u16 y[3] = {}; + seq_printf(m, " R y (rv) G y (rv) B y (rv)\n"); + for (i = 0; i < NUM_DY_REGS; i++) { + seq_printf(m, "%-2d:", i); + for (c = 0; c < 3; c++) { + y[c] += dy_regs[c][i + 1]; + seq_printf(m, " %4d (%02x)", y[c], dy_regs[c][i + 1]); + } + seq_printf(m, "\n"); + } +} + +static int s6e8aa0_current_gamma_show(struct seq_file *m, void *unused) +{ + struct s6e8aa0_data *s6 = m->private; + u8 gamma_regs[NUM_GAMMA_REGS]; + u8 dy_regs[3][NUM_DY_REGS + 1]; + + mutex_lock(&s6->lock); + s6e8aa0_setup_gamma_regs(s6, gamma_regs, dy_regs); + seq_printf(m, "brightness %3d:\n", s6->bl); + seq_print_gamma_regs(m, gamma_regs); + seq_printf(m, "\n"); + seq_print_dy_regs(m, dy_regs); + mutex_unlock(&s6->lock); + return 0; +} + +static int s6e8aa0_current_gamma_open(struct inode *inode, struct file *file) +{ + return single_open(file, s6e8aa0_current_gamma_show, inode->i_private); +} + +static int s6e8aa0_gamma_correction_show(struct seq_file *m, void *unused) +{ + struct s6e8aa0_data *s6 = m->private; + const struct s6e8aa0_gamma_entry *bte; + int n, c; + + mutex_lock(&s6->lock); + n = s6->brightness_table_size; + bte = s6->brightness_table; + while (n--) { + seq_printf(m, "0x%08x", bte->brightness); + for (c = 0; c < 3; c++) + seq_printf(m, " 0x%08x", bte->v[c]); + seq_printf(m, "\n"); + bte++; + } + seq_printf(m, "\n"); + seq_printf(m, "0x%08x", BV_255); + for (c = 0; c < 3; c++) + seq_printf(m, " 0x%08x", s6->brightness_limit[c]); + seq_printf(m, "\n"); + mutex_unlock(&s6->lock); + return 0; +} + +static int s6e8aa0_gamma_correction_open(struct inode *inode, struct file *file) +{ + return single_open(file, s6e8aa0_gamma_correction_show, + inode->i_private); +} + +static ssize_t s6e8aa0_gamma_correction_write(struct file *file, + const char __user *buf, + size_t size, loff_t *ppos) +{ + struct seq_file *m = file->private_data; + struct s6e8aa0_data *s6 = m->private; + struct omap_dss_device *dssdev = s6->dssdev; + char sbuf[80]; + u32 val[4] = { + }; + u32 last_val[4]; + struct s6e8aa0_gamma_entry *bt = NULL; + struct s6e8aa0_gamma_entry *new_bt; + int bt_size = 0; + + int ret; + size_t used; + size_t sbuf_len = sizeof(sbuf) - 1; + size_t rem = size; + int c; + int i; + + while (rem && val[0] != BV_255) { + if (sbuf_len > rem) + sbuf_len = rem; + if (copy_from_user(sbuf, buf, sbuf_len)) { + ret = -EFAULT; + goto err; + } + sbuf[sbuf_len] = '\0'; + + for (i = 0; i < ARRAY_SIZE(val); i++) + last_val[i] = val[i]; + ret = sscanf(sbuf, "%i %i %i %i\n%n", + &val[0], &val[1], &val[2], &val[3], &used); + if (ret < 4 || !used) + break; + + buf += used; + rem -= used; + + if (!bt_size) { + for (i = 0; i < ARRAY_SIZE(val); i++) { + if (val[i] != 0) { + pr_info("%s: invalid start value %d: " + "0x%08x != 0\n", + __func__, i, val[i]); + ret = -EINVAL; + goto err; + } + } + } else { + for (i = 0; i < ARRAY_SIZE(val); i++) { + if (val[i] <= last_val[i]) { + pr_info("%s: invalid value %d: " + "0x%08x <= 0x%08x\n", __func__, + i, val[i], last_val[i]); + ret = -EINVAL; + goto err; + } + } + for (c = 0; c < 3; c++) { + if (val[c + 1] > s6->brightness_limit[c]) { + pr_info("%s: invalid value %d: " + "0x%08x > 0x%08x\n", __func__, + c, val[c + 1], + s6->brightness_limit[c]); + ret = -EOVERFLOW; + goto err; + } + } + } + + new_bt = krealloc(bt, (bt_size + 1) * sizeof(*bt), GFP_KERNEL); + if (!new_bt) { + ret = -ENOMEM; + goto err; + } + bt = new_bt; + bt[bt_size].brightness = val[0]; + for (c = 0; c < 3; c++) + bt[bt_size].v[c] = val[c + 1]; + bt_size++; + } + if (val[0] != BV_255) { + ret = -EINVAL; + goto err; + } + + ret = size - rem; + mutex_lock(&s6->lock); + pr_debug("%s: got new brightness_table size %d\n", __func__, bt_size); + swap(bt, s6->brightness_table); + s6->brightness_table_size = bt_size; + + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { + dsi_bus_lock(dssdev); + s6e8aa0_update_brightness(dssdev); + dsi_bus_unlock(dssdev); + } + mutex_unlock(&s6->lock); + +err: + kfree(bt); + return ret; +} + +static ssize_t acl_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = dev_get_drvdata(dev); + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + + snprintf(buf, PAGE_SIZE, "%d\n", s6->acl_enable); + + return strlen(buf); +} + +static ssize_t acl_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct omap_dss_device *dssdev = dev_get_drvdata(dev); + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + long value; + bool enable; + int rc; + + rc = strict_strtol(buf, 0, &value); + + if (rc < 0) + return rc; + + enable = value; + + mutex_lock(&s6->lock); + if (s6->acl_enable != enable) { + dsi_bus_lock(dssdev); + + s6->acl_enable = enable; + s6e8aa0_update_acl_set(dssdev); + + dsi_bus_unlock(dssdev); + } + mutex_unlock(&s6->lock); + return size; +} + +static DEVICE_ATTR(acl_set, S_IRUGO|S_IWUSR, + acl_enable_show, acl_enable_store); + + +static ssize_t acl_average_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = dev_get_drvdata(dev); + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + + snprintf(buf, PAGE_SIZE, "%d\n", s6->acl_average); + + return strlen(buf); +} + +static ssize_t acl_average_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct omap_dss_device *dssdev = dev_get_drvdata(dev); + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + long value; + int rc; + + rc = strict_strtol(buf, 0, &value); + + if (rc < 0) + return rc; + + if (value < 0 || value > 7) + return -EINVAL; + + mutex_lock(&s6->lock); + if (s6->acl_average != value) { + dsi_bus_lock(dssdev); + + s6->acl_average = value; + s6->acl_cur = 0; + s6e8aa0_update_acl_set(dssdev); + + dsi_bus_unlock(dssdev); + } + mutex_unlock(&s6->lock); + return size; +} + +static DEVICE_ATTR(acl_average, S_IRUGO|S_IWUSR, + acl_average_show, acl_average_store); + +static struct attribute *s6e8aa0_bl_attributes[] = { + &dev_attr_acl_set.attr, + &dev_attr_acl_average.attr, + NULL +}; + +static const struct attribute_group s6e8aa0_bl_attr_group = { + .attrs = s6e8aa0_bl_attributes, +}; + +static const struct file_operations s6e8aa0_current_gamma_fops = { + .open = s6e8aa0_current_gamma_open, + .read = seq_read, + .release = single_release, +}; + +static const struct file_operations s6e8aa0_gamma_correction_fops = { + .open = s6e8aa0_gamma_correction_open, + .read = seq_read, + .write = s6e8aa0_gamma_correction_write, + .release = single_release, +}; + +static int s6e8aa0_probe(struct omap_dss_device *dssdev) +{ + int ret = 0; + struct backlight_properties props = { + .brightness = 255, + .max_brightness = 255, + .type = BACKLIGHT_RAW, + }; + struct s6e8aa0_data *s6 = NULL; + + dev_dbg(&dssdev->dev, "s6e8aa0_probe\n"); + + if (dssdev->data == NULL) { + dev_err(&dssdev->dev, "no platform data!\n"); + return -EINVAL; + } + + dssdev->panel.config = OMAP_DSS_LCD_TFT; + dssdev->panel.timings = s6e8aa0_timings; + + dssdev->ctrl.pixel_size = 24; + dssdev->panel.acbi = 0; + dssdev->panel.acb = 40; + + s6 = kzalloc(sizeof(*s6), GFP_KERNEL); + if (!s6) + return -ENOMEM; + + s6->dssdev = dssdev; + s6->pdata = dssdev->data; + + s6->bl = props.brightness; + + if (!s6->pdata->seq_display_set || !s6->pdata->seq_etc_set + || !s6->pdata->gamma_table) { + dev_err(&dssdev->dev, "Invalid platform data\n"); + ret = -EINVAL; + goto err; + } + s6->gamma_adj_points = + s6->pdata->gamma_adj_points ?: &default_gamma_adj_points; + s6->dyi_to_b = s6e8aa0_srgb_dyi_to_b; + + ret = gpio_request(s6->pdata->reset_gpio, "s6e8aa0_reset"); + if (ret < 0) { + dev_err(&dssdev->dev, "gpio_request %d failed!\n", s6->pdata->reset_gpio); + goto err; + } + gpio_direction_output(s6->pdata->reset_gpio, 1); + + mutex_init(&s6->lock); + + atomic_set(&s6->do_update, 0); + + dev_set_drvdata(&dssdev->dev, s6); + + /* Register DSI backlight control */ + s6->bldev = backlight_device_register("s6e8aa0", &dssdev->dev, dssdev, + &s6e8aa0_backlight_ops, &props); + if (IS_ERR(s6->bldev)) { + ret = PTR_ERR(s6->bldev); + goto err_backlight_device_register; + } + + s6->debug_dir = debugfs_create_dir("s6e8aa0", NULL); + if (!s6->debug_dir) { + dev_err(&dssdev->dev, "failed to create debug dir\n"); + } else { + debugfs_create_file("current_gamma", S_IRUGO, + s6->debug_dir, s6, &s6e8aa0_current_gamma_fops); + debugfs_create_file("gamma_correction", S_IRUGO | S_IWUSR, + s6->debug_dir, s6, &s6e8aa0_gamma_correction_fops); + } + + s6->acl_enable = true; + s6->acl_cur = 0; + s6->acl_average = s6->pdata->acl_average; + s6->elvss_cur_i = ~0; + + ret = sysfs_create_group(&s6->bldev->dev.kobj, &s6e8aa0_bl_attr_group); + if (ret < 0) { + dev_err(&dssdev->dev, "failed to add sysfs entries\n"); + goto err_backlight_device_register; + } + + if (cpu_is_omap44xx()) + s6->force_update = true; + + dev_dbg(&dssdev->dev, "s6e8aa0_probe\n"); + return ret; + +err_backlight_device_register: + mutex_destroy(&s6->lock); + gpio_free(s6->pdata->reset_gpio); +err: + kfree(s6); + + return ret; +} + +static void s6e8aa0_remove(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + sysfs_remove_group(&s6->bldev->dev.kobj, &s6e8aa0_bl_attr_group); + debugfs_remove_recursive(s6->debug_dir); + backlight_device_unregister(s6->bldev); + mutex_destroy(&s6->lock); + gpio_free(s6->pdata->reset_gpio); + kfree(s6); +} + +/** + * s6e8aa0_config - Configure S6E8AA0 + * + * Initial configuration for S6E8AA0 configuration registers, PLL... + */ +static void s6e8aa0_config(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + struct panel_s6e8aa0_data *pdata = s6->pdata; + if (!s6->brightness_table) { + s6e8aa0_read_id_info(s6); + s6e8aa0_read_mtp_info(s6, 0); + s6e8aa0_read_mtp_info(s6, 1); + s6e8aa0_adjust_brightness_from_mtp(s6); + } + + s6e8aa0_write_sequence(dssdev, pdata->seq_display_set, + pdata->seq_display_set_size); + + s6->acl_cur = 0; /* make sure acl table and elvss value gets written */ + s6->elvss_cur_i = ~0; + s6e8aa0_update_brightness(dssdev); + + s6e8aa0_write_sequence(dssdev, pdata->seq_etc_set, + pdata->seq_etc_set_size); +} + +static int s6e8aa0_power_on(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + int ret = 0; + + /* At power on the first vsync has not been received yet*/ + dssdev->first_vsync = false; + + if (s6->enabled != 1) { + if (s6->pdata->set_power) + s6->pdata->set_power(true); + + ret = omapdss_dsi_display_enable(dssdev); + if (ret) { + dev_err(&dssdev->dev, "failed to enable DSI\n"); + goto err; + } + + /* reset s6e8aa0 bridge */ + if(!dssdev->skip_init){ + s6e8aa0_hw_reset(dssdev); + + /* XXX */ + msleep(100); + s6e8aa0_config(dssdev); + + dsi_video_mode_enable(dssdev, 0x3E); /* DSI_DT_PXLSTREAM_24BPP_PACKED; */ + } + + s6->enabled = 1; + } + + if(dssdev->skip_init) + dssdev->skip_init = false; + +err: + return ret; +} + +static void s6e8aa0_power_off(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + + gpio_set_value(s6->pdata->reset_gpio, 0); + msleep(10); + + s6->enabled = 0; + omapdss_dsi_display_disable(dssdev, 0, 0); + + if (s6->pdata->set_power) + s6->pdata->set_power(false); + +} + +static int s6e8aa0_start(struct omap_dss_device *dssdev) +{ + int r = 0; + unsigned long pclk; + + dsi_bus_lock(dssdev); + + r = s6e8aa0_power_on(dssdev); + + dsi_bus_unlock(dssdev); + + if (r) { + dev_dbg(&dssdev->dev, "enable failed\n"); + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; + } else { + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + dssdev->manager->enable(dssdev->manager); + } + + /* fixup pclk based on pll config */ + pclk = dispc_pclk_rate(dssdev->channel); + if (pclk) + dssdev->panel.timings.pixel_clock = (pclk + 500) / 1000; + + return r; +} + +static void s6e8aa0_stop(struct omap_dss_device *dssdev) +{ + dssdev->manager->disable(dssdev->manager); + + dsi_bus_lock(dssdev); + + s6e8aa0_power_off(dssdev); + + dsi_bus_unlock(dssdev); +} + +static void s6e8aa0_disable(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + + dev_dbg(&dssdev->dev, "disable\n"); + + mutex_lock(&s6->lock); + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) + s6e8aa0_stop(dssdev); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; + mutex_unlock(&s6->lock); +} + +static int s6e8aa0_enable(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + int ret; + + dev_dbg(&dssdev->dev, "enable\n"); + + mutex_lock(&s6->lock); + if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) { + ret = -EINVAL; + goto out; + } + + ret = s6e8aa0_start(dssdev); +out: + mutex_unlock(&s6->lock); + return ret; +} + +static void s6e8aa0_framedone_cb(int err, void *data) +{ + struct omap_dss_device *dssdev = data; + dev_dbg(&dssdev->dev, "framedone, err %d\n", err); + dsi_bus_unlock(dssdev); +} + +static int s6e8aa0_update(struct omap_dss_device *dssdev, + u16 x, u16 y, u16 w, u16 h) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + int r; + dev_dbg(&dssdev->dev, "update %d, %d, %d x %d\n", x, y, w, h); + + mutex_lock(&s6->lock); + + dsi_bus_lock(dssdev); + + if (!s6->enabled) { + r = 0; + goto err; + } + + r = omap_dsi_prepare_update(dssdev, &x, &y, &w, &h, true); + if (r) + goto err; + + /* We use VC(0) for VideoPort Data and VC(1) for commands */ + r = omap_dsi_update(dssdev, 0, x, y, w, h, s6e8aa0_framedone_cb, dssdev); + if (r) + goto err; + + dsi_bus_unlock(dssdev); + /* note: no bus_unlock here. unlock is in framedone_cb */ + mutex_unlock(&s6->lock); + return 0; +err: + dsi_bus_unlock(dssdev); + mutex_unlock(&s6->lock); + return r; +} + +static int s6e8aa0_sync(struct omap_dss_device *dssdev) +{ + /* TODO? */ + return 0; +} + +static int s6e8aa0_set_update_mode(struct omap_dss_device *dssdev, + enum omap_dss_update_mode mode) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + + if (s6->force_update) { + if (mode != OMAP_DSS_UPDATE_AUTO) + return -EINVAL; + } else { + if (mode != OMAP_DSS_UPDATE_MANUAL) + return -EINVAL; + } + + return 0; +} + +static enum omap_dss_update_mode s6e8aa0_get_update_mode(struct omap_dss_device + *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + + if (s6->force_update) + return OMAP_DSS_UPDATE_AUTO; + else + return OMAP_DSS_UPDATE_MANUAL; +} + +#ifdef CONFIG_PM +static int s6e8aa0_resume(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + int ret; + + dev_dbg(&dssdev->dev, "resume\n"); + + mutex_lock(&s6->lock); + if (dssdev->state != OMAP_DSS_DISPLAY_SUSPENDED) { + ret = -EINVAL; + goto out; + } + + ret = s6e8aa0_start(dssdev); +out: + mutex_unlock(&s6->lock); + return ret; +} + +static int s6e8aa0_suspend(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + int ret = 0; + + dev_dbg(&dssdev->dev, "suspend\n"); + + mutex_lock(&s6->lock); + if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) { + ret = -EINVAL; + goto out; + } + + s6e8aa0_stop(dssdev); + dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; +out: + mutex_unlock(&s6->lock); + return ret; +} +#endif + +static struct omap_dss_driver s6e8aa0_driver = { + .probe = s6e8aa0_probe, + .remove = s6e8aa0_remove, + + .enable = s6e8aa0_enable, + .disable = s6e8aa0_disable, +#ifdef CONFIG_PM + .suspend = s6e8aa0_suspend, + .resume = s6e8aa0_resume, +#endif + + .set_update_mode = s6e8aa0_set_update_mode, + .get_update_mode = s6e8aa0_get_update_mode, + + .update = s6e8aa0_update, + .sync = s6e8aa0_sync, + + .get_resolution = s6e8aa0_get_resolution, + .get_recommended_bpp = omapdss_default_get_recommended_bpp, + + /* dummy entry start */ + .enable_te = s6e8aa0_enable_te, + .set_rotate = s6e8aa0_rotate, + .get_rotate = s6e8aa0_get_rotate, + .set_mirror = s6e8aa0_mirror, + .get_mirror = s6e8aa0_get_mirror, + /* dummy entry end */ + + .get_timings = s6e8aa0_get_timings, + .set_timings = s6e8aa0_set_timings, + .check_timings = s6e8aa0_check_timings, + + .driver = { + .name = "s6e8aa0", + .owner = THIS_MODULE, + }, +}; + +static int __init s6e8aa0_init(void) +{ + omap_dss_register_driver(&s6e8aa0_driver); + return 0; +} + +static void __exit s6e8aa0_exit(void) +{ + omap_dss_unregister_driver(&s6e8aa0_driver); +} + +module_init(s6e8aa0_init); +module_exit(s6e8aa0_exit); diff --git a/drivers/video/sii9234.c b/drivers/video/sii9234.c new file mode 100644 index 0000000..04fb348 --- /dev/null +++ b/drivers/video/sii9234.c @@ -0,0 +1,1438 @@ +/* + * Copyright (C) 2011 Samsung Electronics + * + * Authors: Adam Hampson <ahampson@sta.samsung.com> + * Erik Gilling <konkers@android.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 + * + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/sii9234.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <linux/usb/otg_id.h> + +#define T_SRC_VBUS_CBUS_TO_STABLE 200 +#define T_SRC_WAKE_PULSE_WIDTH_1 19 +#define T_SRC_WAKE_PULSE_WIDTH_2 60 +#define T_SRC_WAKE_TO_DISCOVER 500 +#define T_SRC_VBUS_CBUS_T0_STABLE 500 + +/* MHL TX Addr 0x72 Registers */ +#define MHL_TX_IDL_REG 0x02 +#define MHL_TX_IDH_REG 0x03 +#define MHL_TX_REV_REG 0x04 +#define MHL_TX_SRST 0x05 +#define MHL_TX_INTR1_REG 0x71 +#define MHL_TX_INTR2_REG 0x72 /* Not Documented */ +#define MHL_TX_INTR3_REG 0x73 /* Not Documented */ +#define MHL_TX_INTR4_REG 0x74 +#define MHL_TX_INTR1_ENABLE_REG 0x75 +#define MHL_TX_INTR2_ENABLE_REG 0x76 /* Not Documented */ +#define MHL_TX_INTR3_ENABLE_REG 0x77 /* Not Documented */ +#define MHL_TX_INTR4_ENABLE_REG 0x78 + +#define MHL_TX_INT_CTRL_REG 0x79 +#define INTR_POLARITY (1 << 1) +#define INTR_OPEN_DRAIN (1 << 2) +#define HPD_OUT_OVR_EN (1 << 4) +#define HPD_OUT_OVR_VAL (1 << 5) +#define HPD_OUT_OPEN_DRAIN (1 << 6) + +#define MHL_TX_TMDS_CCTRL 0x80 + +#define MHL_TX_DISC_CTRL1_REG 0x90 +#define MHL_TX_DISC_CTRL2_REG 0x91 +#define MHL_TX_DISC_CTRL3_REG 0x92 +#define MHL_TX_DISC_CTRL4_REG 0x93 /* Not Documented */ + +/* There doesn't seem to be any documentation for CTRL5 but it looks like + * it is some sort of pull up control register + */ +#define MHL_TX_DISC_CTRL5_REG 0x94 +#define MHL_TX_DISC_CTRL6_REG 0x95 +#define MHL_TX_DISC_CTRL7_REG 0x96 +#define MHL_TX_DISC_CTRL8_REG 0x97 /* Not Documented */ +#define MHL_TX_STAT1_REG 0x98 /* Not Documented */ +#define MHL_TX_STAT2_REG 0x99 + +#define MHL_TX_MHLTX_CTL1_REG 0xA0 +#define MHL_TX_MHLTX_CTL2_REG 0xA1 +#define MHL_TX_MHLTX_CTL4_REG 0xA3 +#define MHL_TX_MHLTX_CTL6_REG 0xA5 +#define MHL_TX_MHLTX_CTL7_REG 0xA6 + +/* MHL TX SYS STAT Registers + * Not Documented, mentioned only in reference of RSEN + */ +#define MHL_TX_SYSSTAT_REG 0x09 + +/* MHL TX SYS STAT Register Bits */ +#define RSEN_STATUS (1<<2) + +/* MHL TX INTR4 Register Bits */ +#define RGND_READY_INT (1<<6) +#define VBUS_LOW_INT (1<<5) +#define CBUS_LKOUT_INT (1<<4) +#define MHL_DISC_FAIL_INT (1<<3) +#define MHL_EST_INT (1<<2) + +/* MHL TX INTR4_ENABLE 0x78 Register Bits */ +#define RGND_READY_MASK (1<<6) +#define CBUS_LKOUT_MASK (1<<4) +#define MHL_DISC_FAIL_MASK (1<<3) +#define MHL_EST_MASK (1<<2) + +/* MHL TX INTR1 Register Bits*/ +#define HPD_CHANGE_INT (1<<6) +#define RSEN_CHANGE_INT (1<<5) + +/* MHL TX INTR1_ENABLE 0x75 Register Bits*/ +#define HPD_CHANGE_INT_MASK (1<<6) +#define RSEN_CHANGE_INT_MASK (1<<5) + +#define CBUS_CONFIG_REG 0x07 + +#define CBUS_INT_STATUS_1_REG 0x08 +#define CBUS_INT_1_MASK 0x09 + +#define CBUS_MSC_COMMAND_START 0x12 +#define START_MSC_MSG (1 << 1) +#define START_READ_DEVCAP (1 << 2) +#define START_WRITE_STAT_INT (1 << 3) +#define START_WRITE_BURST (1 << 4) + +#define CBUS_MSC_OFFSET_REG 0x13 +#define CBUS_MSC_FIRST_DATA_OUT 0x14 +#define CBUS_MSC_SECOND_DATA_OUT 0x15 +#define CBUS_MSC_FIRST_DATA_IN 0x16 +#define CBUS_MSC_MSG_CMD_IN 0x18 +#define CBUS_MSC_MSG_DATA_IN 0x19 +#define CBUS_INT_STATUS_2_REG 0x1E +#define CBUS_INT_2_MASK 0x1F +#define CBUS_LINK_CONTROL_2_REG 0x31 + +#define CBUS_INT_STATUS_2_REG 0x1E + +/* MHL Interrupt Registers */ +#define CBUS_MHL_INTR_REG_0 0xA0 + +#define CBUS_MHL_INTR_REG_1 0xA1 +#define MHL_INT_EDID_CHG (1<<1) + +#define CBUS_MHL_INTR_REG_2 0xA2 +#define CBUS_MHL_INTR_REG_3 0xA3 + +/* MHL Status Registers */ +#define CBUS_MHL_STATUS_REG_0 0xB0 +#define MHL_STATUS_DCAP_READY (1<<0) + +#define CBUS_MHL_STATUS_REG_1 0xB1 +#define CBUS_MHL_STATUS_REG_2 0xB2 +#define CBUS_MHL_STATUS_REG_3 0xB3 + +/* CBUS INTR1 STATUS Register bits */ +#define MSC_RESP_ABORT (1<<6) +#define MSC_REQ_ABORT (1<<5) +#define MSC_REQ_DONE (1<<4) +#define MSC_MSG_RECD (1<<3) +#define CBUS_DDC_ABORT (1<<2) + +/* CBUS INTR1 STATUS 0x09 Enable Mask*/ +#define MSC_RESP_ABORT_MASK (1<<6) +#define MSC_REQ_ABORT_MASK (1<<5) +#define MSC_REQ_DONE_MASK (1<<4) +#define MSC_MSG_RECD_MASK (1<<3) +#define CBUS_DDC_ABORT_MASK (1<<2) + +/* CBUS INTR2 STATUS Register bits */ +#define WRT_STAT_RECD (1<<3) +#define SET_INT_RECD (1<<2) +#define WRT_BURST_RECD (1<<0) + +/* CBUS INTR2 STATUS 0x1F Enable Mask*/ +#define WRT_STAT_RECD_MASK (1<<3) +#define SET_INT_RECD_MASK (1<<2) +#define WRT_BURST_RECD_MASK (1<<0) + +/* CBUS Control Registers*/ +/* Retry count for all MSC commands*/ +#define MSC_RETRY_FAIL_LIM_REG 0x1D + +/* reason for MSC_REQ_ABORT interrupt on CBUS */ +#define MSC_REQ_ABORT_REASON_REG 0x0D + +#define MSC_RESP_ABORT_REASON_REG 0x0E + +/* MSC Requestor Abort Reason Register bits*/ +#define ABORT_BY_PEER (1<<7) +#define UNDEF_CMD (1<<3) +#define TIMEOUT (1<<2) +#define PROTO_ERROR (1<<1) +#define MAX_FAIL (1<<0) + +/* MSC Responder Abort Reason Register bits*/ +#define ABORT_BY_PEER (1<<7) +#define UNDEF_CMD (1<<3) +#define TIMEOUT (1<<2) + +/* Set HPD came from Downstream, not documented */ +#define SET_HPD_DOWNSTREAM (1<<6) + +/* MHL TX DISC1 Register Bits */ +#define DISC_EN (1<<0) + +/* MHL TX DISC2 Register Bits */ +#define SKIP_GND (1<<6) +#define ATT_THRESH_SHIFT 0x04 +#define ATT_THRESH_MASK (0x03 << ATT_THRESH_SHIFT) +#define USB_D_OEN (1<<3) +#define DEGLITCH_TIME_MASK 0x07 +#define DEGLITCH_TIME_2MS 0 +#define DEGLITCH_TIME_4MS 1 +#define DEGLITCH_TIME_8MS 2 +#define DEGLITCH_TIME_16MS 3 +#define DEGLITCH_TIME_40MS 4 +#define DEGLITCH_TIME_50MS 5 +#define DEGLITCH_TIME_60MS 6 +#define DEGLITCH_TIME_128MS 7 + +#define DISC_CTRL3_COMM_IMME (1<<7) +#define DISC_CTRL3_FORCE_MHL (1<<6) +#define DISC_CTRL3_FORCE_USB (1<<4) +#define DISC_CTRL3_USB_EN (1<<3) + +/* MHL TX DISC4 0x93 Register Bits: undocumented */ +#define CBUS_DISC_PUP_SEL_SHIFT 6 +#define CBUS_DISC_PUP_SEL_MASK (3<<CBUS_DISC_PUP_SEL_SHIFT) +#define CBUS_DISC_PUP_SEL_10K (2<<CBUS_DISC_PUP_SEL_SHIFT) +#define CBUS_DISC_PUP_SEL_OPEN (0<<CBUS_DISC_PUP_SEL_SHIFT) +#define CBUS_IDLE_PUP_SEL_SHIFT 4 +#define CBUS_IDLE_PUP_SEL_MASK (3<<CBUS_IDLE_PUP_SEL_SHIFT) +#define CBUS_IDLE_PUP_SEL_OPEN (0<<CBUS_IDLE_PUP_SEL_SHIFT) + +/* MHL TX DISC5 0x94 Register Bits */ +#define CBUS_MHL_PUP_SEL_MASK 0x03 /* Not Documented */ +#define CBUS_MHL_PUP_SEL_5K 0x01 /* Not Documented */ +#define CBUS_MHL_PUP_SEL_OPEN 0x00 + +/* MHL TX DISC6 0x95 Register Bits */ +#define USB_D_OVR (1<<7) +#define USB_ID_OVR (1<<6) +#define DVRFLT_SEL (1<<5) +#define BLOCK_RGND_INT (1<<4) +#define SKIP_DEG (1<<3) +#define CI2CA_POL (1<<2) +#define CI2CA_WKUP (1<<1) +#define SINGLE_ATT (1<<0) + +/* MHL TX DISC7 0x96 Register Bits + * + * Bits 7 and 6 are labeled as reserved but seem to be related to toggling + * the CBUS signal when generating the wake pulse sequence. + */ +#define USB_D_ODN (1<<5) +#define VBUS_CHECK (1<<2) +#define RGND_INTP_MASK 0x03 +#define RGND_INTP_OPEN 0 +#define RGND_INTP_2K 1 +#define RGND_INTP_1K 2 +#define RGND_INTP_SHORT 3 + +/* TPI Addr 0x7A Registers */ +#define TPI_DPD_REG 0x3D + +#define TPI_PD_TMDS (1<<5) +#define TPI_PD_OSC_EN (1<<4) +#define TPI_TCLK_PHASE (1<<3) +#define TPI_PD_IDCK (1<<2) +#define TPI_PD_OSC (1<<1) +#define TPI_PD (1<<0) + + + +/* HDMI RX Registers */ +#define HDMI_RX_TMDS0_CCTRL1_REG 0x10 +#define HDMI_RX_TMDS_CLK_EN_REG 0x11 +#define HDMI_RX_TMDS_CH_EN_REG 0x12 +#define HDMI_RX_PLL_CALREFSEL_REG 0x17 +#define HDMI_RX_PLL_VCOCAL_REG 0x1A +#define HDMI_RX_EQ_DATA0_REG 0x22 +#define HDMI_RX_EQ_DATA1_REG 0x23 +#define HDMI_RX_EQ_DATA2_REG 0x24 +#define HDMI_RX_EQ_DATA3_REG 0x25 +#define HDMI_RX_EQ_DATA4_REG 0x26 +#define HDMI_RX_TMDS_ZONE_CTRL_REG 0x4C +#define HDMI_RX_TMDS_MODE_CTRL_REG 0x4D + +enum rgnd_state { + RGND_UNKNOWN = 0, + RGND_OPEN, + RGND_1K, + RGND_2K, + RGND_SHORT +}; + +enum mhl_state { + STATE_DISCONNECTED = 0, + STATE_DISCOVERY_FAILED, + STATE_CBUS_LOCKOUT, + STATE_ESTABLISHED, +}; + +static inline bool mhl_state_is_error(enum mhl_state state) +{ + return state == STATE_DISCOVERY_FAILED || + state == STATE_CBUS_LOCKOUT; +} + +struct sii9234_data { + struct sii9234_platform_data *pdata; + struct otg_id_notifier_block otg_id_nb; + wait_queue_head_t wq; + + bool claimed; + enum mhl_state state; + enum rgnd_state rgnd; + int irq; + bool rsen; + + struct mutex lock; + + bool msc_ready; + struct mutex msc_lock; + struct completion msc_complete; + + u8 devcap[16]; +}; + +static irqreturn_t sii9234_irq_thread(int irq, void *data); + +static int mhl_tx_write_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 value) +{ + return i2c_smbus_write_byte_data(sii9234->pdata->mhl_tx_client, offset, + value); +} + +static int mhl_tx_read_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 *value) +{ + int ret; + + if (!value) + return -EINVAL; + + ret = i2c_smbus_write_byte(sii9234->pdata->mhl_tx_client, offset); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte(sii9234->pdata->mhl_tx_client); + if (ret < 0) + return ret; + + *value = ret & 0x000000FF; + + return 0; +} + +static int mhl_tx_set_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 mask) +{ + int ret; + u8 value; + + ret = mhl_tx_read_reg(sii9234, offset, &value); + if (ret < 0) + return ret; + + value |= mask; + + return mhl_tx_write_reg(sii9234, offset, value); +} + +static int mhl_tx_clear_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 mask) +{ + int ret; + u8 value; + + ret = mhl_tx_read_reg(sii9234, offset, &value); + if (ret < 0) + return ret; + + value &= ~mask; + + return mhl_tx_write_reg(sii9234, offset, value); +} + +static int tpi_write_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 value) +{ + return i2c_smbus_write_byte_data(sii9234->pdata->tpi_client, offset, + value); +} + +static int tpi_read_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 *value) +{ + int ret; + + if (!value) + return -EINVAL; + + ret = i2c_smbus_write_byte(sii9234->pdata->tpi_client, offset); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte(sii9234->pdata->tpi_client); + if (ret < 0) + return ret; + + *value = ret & 0x000000FF; + + return 0; +} + +static int hdmi_rx_write_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 value) +{ + return i2c_smbus_write_byte_data(sii9234->pdata->hdmi_rx_client, offset, + value); +} + +static int cbus_write_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 value) +{ + return i2c_smbus_write_byte_data(sii9234->pdata->cbus_client, offset, + value); +} + +static int cbus_read_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 *value) +{ + int ret; + + if (!value) + return -EINVAL; + + ret = i2c_smbus_write_byte(sii9234->pdata->cbus_client, offset); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte(sii9234->pdata->cbus_client); + if (ret < 0) + return ret; + + *value = ret & 0x000000FF; + + return 0; +} + +static int cbus_set_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 mask) +{ + int ret; + u8 value; + + ret = cbus_read_reg(sii9234, offset, &value); + if (ret < 0) + return ret; + + value |= mask; + + return cbus_write_reg(sii9234, offset, value); +} + +static int mhl_wake_toggle(struct sii9234_data *sii9234, + unsigned long high_period, + unsigned long low_period) +{ + int ret; + + /* These bits are not documented. */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL7_REG, (1<<7) | (1<<6)); + if (ret < 0) + return ret; + + usleep_range(high_period * USEC_PER_MSEC, high_period * USEC_PER_MSEC); + + ret = mhl_tx_clear_reg(sii9234, MHL_TX_DISC_CTRL7_REG, (1<<7) | (1<<6)); + if (ret < 0) + return ret; + + usleep_range(low_period * USEC_PER_MSEC, low_period * USEC_PER_MSEC); + + return 0; +} + +static int mhl_send_wake_pulses(struct sii9234_data *sii9234) +{ + int ret; + + ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1, + T_SRC_WAKE_PULSE_WIDTH_1); + if (ret < 0) + return ret; + + ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1, + T_SRC_WAKE_PULSE_WIDTH_2); + if (ret < 0) + return ret; + + ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1, + T_SRC_WAKE_PULSE_WIDTH_1); + if (ret < 0) + return ret; + + ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1, + T_SRC_WAKE_TO_DISCOVER); + if (ret < 0) + return ret; + + return 0; +} +static int sii9234_cbus_reset(struct sii9234_data *sii9234) +{ + int ret; + /* Reset CBUS */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_SRST, 0x03); + if (ret < 0) + return ret; + + usleep_range(2000, 3000); + + ret = mhl_tx_clear_reg(sii9234, MHL_TX_SRST, 0x03); + if (ret < 0) + return ret; + + /* Adjust interrupt mask everytime reset is performed.*/ + ret = cbus_write_reg(sii9234, + CBUS_INT_1_MASK, + MSC_RESP_ABORT_MASK | + MSC_REQ_ABORT_MASK | + MSC_REQ_DONE_MASK | + MSC_MSG_RECD_MASK | + CBUS_DDC_ABORT_MASK); + if (ret < 0) + return ret; + + ret = cbus_write_reg(sii9234, + CBUS_INT_2_MASK, + WRT_STAT_RECD_MASK | + SET_INT_RECD_MASK); + if (ret < 0) + return ret; + + return 0; +} + +static int sii9234_cbus_init(struct sii9234_data *sii9234) +{ + u8 value; + + cbus_write_reg(sii9234, 0x07, 0x32); + cbus_write_reg(sii9234, 0x40, 0x03); + cbus_write_reg(sii9234, 0x42, 0x06); + cbus_write_reg(sii9234, 0x36, 0x0C); + + cbus_write_reg(sii9234, 0x3D, 0xFD); + cbus_write_reg(sii9234, 0x1C, 0x00); + + cbus_write_reg(sii9234, 0x44, 0x02); + + /* Setup our devcap*/ + cbus_write_reg(sii9234, 0x80, 0x04); + cbus_write_reg(sii9234, 0x81, 0x10); + cbus_write_reg(sii9234, 0x82, 0x02); + cbus_write_reg(sii9234, 0x83, 0); + cbus_write_reg(sii9234, 0x84, 0); + cbus_write_reg(sii9234, 0x85, 0x01 | 0x02); + cbus_write_reg(sii9234, 0x86, 0x01); + cbus_write_reg(sii9234, 0x87, 0); + cbus_write_reg(sii9234, 0x88, (1<<2) | (1<<1) | (1<<3) | (1<<7)); + cbus_write_reg(sii9234, 0x89, 0x0F); + cbus_write_reg(sii9234, 0x8A, (1<<0) | (1<<1) | (1<<2)); + cbus_write_reg(sii9234, 0x8B, 0); + cbus_write_reg(sii9234, 0x8C, 0); + cbus_write_reg(sii9234, 0x8D, 16); + cbus_write_reg(sii9234, 0x8E, 0x44); + cbus_write_reg(sii9234, 0x8F, 0); + + cbus_read_reg(sii9234, 0x31, &value); + value |= 0x0C; + cbus_write_reg(sii9234, 0x31, value); + + cbus_read_reg(sii9234, 0x22, &value); + value &= 0x0F; + cbus_write_reg(sii9234, 0x22, value); + + cbus_write_reg(sii9234, 0x30, 0x01); + + return 0; +} + +static int sii9234_power_init(struct sii9234_data *sii9234) +{ + int ret; + + /* Force the SiI9234 into the D0 state. */ + ret = tpi_write_reg(sii9234, TPI_DPD_REG, 0x3F); + if (ret < 0) + return ret; + + /* Enable TxPLL Clock */ + ret = hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_CLK_EN_REG, 0x01); + if (ret < 0) + return ret; + + /* Enable Tx Clock Path & Equalizer*/ + ret = hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_CH_EN_REG, 0x15); + if (ret < 0) + return ret; + + /* Power Up TMDS*/ + ret = mhl_tx_write_reg(sii9234, 0x08, 0x35); + if (ret < 0) + return ret; + + return 0; +} + +static void sii9234_hdmi_init(struct sii9234_data *sii9234) +{ + /* Analog PLL Control + * bits 5:4 = 2b00 as per characterization team. + */ + hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1); + + /* PLL Calrefsel */ + hdmi_rx_write_reg(sii9234, HDMI_RX_PLL_CALREFSEL_REG, 0x03); + + /* VCO Cal */ + hdmi_rx_write_reg(sii9234, HDMI_RX_PLL_VCOCAL_REG, 0x20); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA0_REG, 0x8A); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA1_REG, 0x6A); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA2_REG, 0xAA); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA3_REG, 0xCA); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA4_REG, 0xEA); + + /* Manual zone */ + hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_ZONE_CTRL_REG, 0xA0); + + /* PLL Mode Value */ + hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_MODE_CTRL_REG, 0x00); + + mhl_tx_write_reg(sii9234, MHL_TX_TMDS_CCTRL, 0x34); + + hdmi_rx_write_reg(sii9234, 0x45, 0x44); + + /* Rx PLL BW ~ 4MHz */ + hdmi_rx_write_reg(sii9234, 0x31, 0x0A); + + /* Analog PLL Control + * bits 5:4 = 2b00 as per characterization team. + */ + hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1); +} + +static void sii9234_mhl_tx_ctl_int(struct sii9234_data *sii9234) +{ + mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL1_REG, 0xD0); + mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL2_REG, 0xFC); + mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL4_REG, 0xEB); + mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL7_REG, 0x0C); +} + +static void sii9234_power_down(struct sii9234_data *sii9234) +{ + if (sii9234->claimed) + sii9234->pdata->connect(false, NULL); + + sii9234->state = STATE_DISCONNECTED; + sii9234->claimed = false; + + tpi_write_reg(sii9234, TPI_DPD_REG, 0); + + sii9234->pdata->power(0); + sii9234->pdata->enable(0); +} + +/* toggle hpd line low for 100ms */ +static void sii9234_toggle_hpd(struct sii9234_data *sii9234) +{ + mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_EN); + mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_VAL); + msleep(100); + mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_VAL); + mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_EN); +} + +/* Must call with sii9234->lock held */ +static int sii9234_msc_req_locked(struct sii9234_data *sii9234, u8 req_type, + u8 offset) +{ + int ret; + + if (sii9234->state != STATE_ESTABLISHED) + return -ENOENT; + + mutex_unlock(&sii9234->lock); + ret = wait_event_timeout(sii9234->wq, sii9234->msc_ready, + msecs_to_jiffies(2000)); + mutex_lock(&sii9234->lock); + if (!sii9234->msc_ready) + return -EIO; + + mutex_lock(&sii9234->msc_lock); + + init_completion(&sii9234->msc_complete); + + cbus_write_reg(sii9234, CBUS_MSC_OFFSET_REG, offset); + cbus_write_reg(sii9234, CBUS_MSC_COMMAND_START, req_type); + + mutex_unlock(&sii9234->lock); + ret = wait_for_completion_timeout(&sii9234->msc_complete, + msecs_to_jiffies(500)); + mutex_lock(&sii9234->lock); + + mutex_unlock(&sii9234->msc_lock); + + return ret ? 0 : -EIO; +} + +/* Must call with sii9234->lock held */ +static int sii9234_devcap_read_locked(struct sii9234_data *sii9234, u8 offset) +{ + int ret; + u8 val; + + if (offset > 0xf) + return -EINVAL; + + ret = sii9234_msc_req_locked(sii9234, START_READ_DEVCAP, offset); + if (ret < 0) + return ret; + + ret = cbus_read_reg(sii9234, CBUS_MSC_FIRST_DATA_IN, &val); + if (ret < 0) + return ret; + + return val; +} + +static int sii9234_detection_callback(struct otg_id_notifier_block *nb) +{ + struct sii9234_data *sii9234 = container_of(nb, struct sii9234_data, + otg_id_nb); + int ret; + int i; + u8 value; + int handled = OTG_ID_UNHANDLED; + + pr_debug("si9234: detection started\n"); + + mutex_lock(&sii9234->lock); + sii9234->rgnd = RGND_UNKNOWN; + sii9234->state = STATE_DISCONNECTED; + sii9234->rsen = false; + sii9234->msc_ready = false; + + /* Set the board configuration so the SiI9234 has access to the + * external connector. + */ + sii9234->pdata->enable(1); + sii9234->pdata->power(1); + + ret = sii9234_power_init(sii9234); + if (ret < 0) + goto unhandled; + + sii9234_hdmi_init(sii9234); + + sii9234_mhl_tx_ctl_int(sii9234); + + /* Enable HDCP Compliance safety*/ + ret = mhl_tx_write_reg(sii9234, 0x2B, 0x01); + if (ret < 0) + goto unhandled; + + /* CBUS discovery cycle time for each drive and float = 150us*/ + ret = mhl_tx_read_reg(sii9234, MHL_TX_DISC_CTRL1_REG, &value); + if (ret < 0) + goto unhandled; + + value &= ~(1<<2); + value |= (1<<3); + + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL1_REG, value); + if (ret < 0) + goto unhandled; + + /* Clear bit 6 (reg_skip_rgnd) */ + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL2_REG, + (1<<7) /* Reserved Bit */ | + 2 << ATT_THRESH_SHIFT | + DEGLITCH_TIME_128MS); + if (ret < 0) + goto unhandled; + + /* Changed from 66 to 65 for 94[1:0] = 01 = 5k reg_cbusmhl_pup_sel */ + /* 1.8V CBUS VTH & GND threshold */ + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL5_REG, 0x75); + if (ret < 0) + goto unhandled; + + /* set bit 2 and 3, which is Initiator Timeout */ + ret = cbus_read_reg(sii9234, CBUS_LINK_CONTROL_2_REG, &value); + if (ret < 0) + goto unhandled; + + value |= 0x0C; + + ret = cbus_write_reg(sii9234, CBUS_LINK_CONTROL_2_REG, value); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL6_REG, 0xA0); + if (ret < 0) + goto unhandled; + + /* RGND & single discovery attempt (RGND blocking) */ + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL6_REG, BLOCK_RGND_INT | + DVRFLT_SEL | SINGLE_ATT); + if (ret < 0) + goto unhandled; + + /* Use VBUS path of discovery state machine*/ + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL8_REG, 0); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL6_REG, USB_ID_OVR); + if (ret < 0) + goto unhandled; + + /* To allow RGND engine to operate correctly. + * When moving the chip from D2 to D0 (power up, init regs) + * the values should be + * 94[1:0] = 01 reg_cbusmhl_pup_sel[1:0] should be set for 5k + * 93[7:6] = 10 reg_cbusdisc_pup_sel[1:0] should be + * set for 10k (default) + * 93[5:4] = 00 reg_cbusidle_pup_sel[1:0] = open (default) + */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL3_REG, 0x86); + if (ret < 0) + goto unhandled; + + /* change from CC to 8C to match 5K*/ + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL4_REG, 0x8C); + if (ret < 0) + goto unhandled; + + /* Configure the interrupt as active high */ + ret = mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, (1<<2) | (1<<1)); + if (ret < 0) + goto unhandled; + + msleep(25); + + ret = mhl_tx_clear_reg(sii9234, MHL_TX_DISC_CTRL6_REG, USB_ID_OVR); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL1_REG, 0x27); + if (ret < 0) + goto unhandled; + + /* Reset CBUS */ + ret = sii9234_cbus_reset(sii9234); + if (ret < 0) + goto unhandled; + + sii9234_cbus_init(sii9234); + + /* Enable Auto soft reset on SCDT = 0*/ + ret = mhl_tx_write_reg(sii9234, 0x05, 0x04); + + if (ret < 0) + goto unhandled; + + /* HDMI Transcode mode enable*/ + ret = mhl_tx_write_reg(sii9234, 0x0D, 0x1C); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_write_reg(sii9234, MHL_TX_INTR4_ENABLE_REG, + RGND_READY_MASK | CBUS_LKOUT_MASK | + MHL_DISC_FAIL_MASK | MHL_EST_MASK); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_write_reg(sii9234, MHL_TX_INTR1_ENABLE_REG, + (1<<5) | (1<<6)); + if (ret < 0) + goto unhandled; + + + pr_debug("sii9234: waiting for RGND measurement\n"); + enable_irq(sii9234->irq); + + /* SiI9244 Programmer's Reference Section 2.4.3 + * State : RGND Ready + */ + mutex_unlock(&sii9234->lock); + ret = wait_event_timeout(sii9234->wq, + ((sii9234->rgnd != RGND_UNKNOWN) || + mhl_state_is_error(sii9234->state)), + msecs_to_jiffies(2000)); + + mutex_lock(&sii9234->lock); + if (sii9234->rgnd == RGND_UNKNOWN || mhl_state_is_error(sii9234->state)) + goto unhandled; + + if (sii9234->rgnd != RGND_1K) + goto unhandled; + + mutex_unlock(&sii9234->lock); + + pr_debug("sii9234: waiting for detection\n"); + ret = wait_event_timeout(sii9234->wq, + sii9234->state != STATE_DISCONNECTED, + msecs_to_jiffies(500)); + mutex_lock(&sii9234->lock); + if (sii9234->state == STATE_DISCONNECTED) + goto unhandled; + + if (sii9234->state == STATE_DISCOVERY_FAILED) { + handled = OTG_ID_PROXY_WAIT; + goto unhandled; + } + + if (mhl_state_is_error(sii9234->state)) + goto unhandled; + + mutex_unlock(&sii9234->lock); + wait_event_timeout(sii9234->wq, sii9234->rsen, msecs_to_jiffies(400)); + mutex_lock(&sii9234->lock); + if (!sii9234->rsen) + goto unhandled; + + memset(sii9234->devcap, 0x0, sizeof(sii9234->devcap)); + for (i = 0; i < 16; i++) { + ret = sii9234_devcap_read_locked(sii9234, i); + if (ret < 0) + break; + sii9234->devcap[i] = ret; + } + +#ifdef DEBUG + if (ret >= 0) + print_hex_dump(KERN_DEBUG, "sii9234: devcap = ", DUMP_PREFIX_NONE, + 16, 1, sii9234->devcap, 16, false); +#endif + + /* It's possible for devcap reading to fail but the adapter still + * be connected. Therefore we must keep ownership of the port + * as long as it's still connected. + */ + if (sii9234->state != STATE_ESTABLISHED) + goto unhandled; + + pr_info("si9234: connection established\n"); + + sii9234->claimed = true; + sii9234->pdata->connect(true, ret >= 0 ? sii9234->devcap : NULL); + mutex_unlock(&sii9234->lock); + + return OTG_ID_HANDLED; + +unhandled: + pr_info("sii9234: Detection failed"); + if (sii9234->state == STATE_DISCONNECTED) + pr_cont(" (timeout)"); + else if (sii9234->state == STATE_DISCOVERY_FAILED) + pr_cont(" (discovery failed)"); + else if (sii9234->state == STATE_CBUS_LOCKOUT) + pr_cont(" (cbus_lockout)"); + pr_cont("\n"); + + disable_irq_nosync(sii9234->irq); + + sii9234_power_down(sii9234); + + mutex_unlock(&sii9234->lock); + return handled; +} + +static void sii9234_cancel_callback(struct otg_id_notifier_block *nb) +{ + struct sii9234_data *sii9234 = container_of(nb, struct sii9234_data, + otg_id_nb); + + mutex_lock(&sii9234->lock); + sii9234_power_down(sii9234); + mutex_unlock(&sii9234->lock); +} + +static int sii9234_cbus_irq(struct sii9234_data *sii9234) +{ + u8 cbus_intr1, cbus_intr2; + u8 mhl_intr0, mhl_intr1; + u8 mhl_status0, mhl_status1, mhl_status2, mhl_status3; + + int ret = 0; + + cbus_read_reg(sii9234, CBUS_INT_STATUS_1_REG, &cbus_intr1); + cbus_read_reg(sii9234, CBUS_INT_STATUS_2_REG, &cbus_intr2); + cbus_read_reg(sii9234, CBUS_MHL_INTR_REG_0, &mhl_intr0); + cbus_read_reg(sii9234, CBUS_MHL_INTR_REG_1, &mhl_intr1); + cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_0, &mhl_status0); + cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_1, &mhl_status1); + cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_2, &mhl_status2); + cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_3, &mhl_status3); + + pr_debug("sii9234: cbus_intr %02x %02x\n", cbus_intr1, cbus_intr2); + + if (cbus_intr1 & MSC_RESP_ABORT) + pr_warn("sii9234: msc resp abort\n"); + + if (cbus_intr1 & MSC_REQ_ABORT) + pr_warn("sii9234: msc req abort\n"); + + if (cbus_intr1 & CBUS_DDC_ABORT) + pr_warn("sii9234: ddc abort\n"); + + if (cbus_intr1 & MSC_REQ_DONE) { + pr_debug("sii9234: msc request done\n"); + complete(&sii9234->msc_complete); + } + + if (cbus_intr1 & MSC_MSG_RECD) + pr_debug("sii9234: msc msg received\n"); + + + if (cbus_intr2 & WRT_STAT_RECD) { + pr_debug("sii9234: write stat received\n"); + sii9234->msc_ready = mhl_status0 & MHL_STATUS_DCAP_READY; + } + + if (cbus_intr2 & SET_INT_RECD) { + if (mhl_intr1 & MHL_INT_EDID_CHG) + sii9234_toggle_hpd(sii9234); + } + + if (cbus_intr2 & WRT_BURST_RECD) + pr_debug("sii9234: write burst received\n"); + + cbus_write_reg(sii9234, CBUS_MHL_INTR_REG_0, mhl_intr0); + cbus_write_reg(sii9234, CBUS_MHL_INTR_REG_1, mhl_intr1); + cbus_write_reg(sii9234, CBUS_INT_STATUS_1_REG, cbus_intr1); + cbus_write_reg(sii9234, CBUS_INT_STATUS_2_REG, cbus_intr2); + + return ret; +} + +static irqreturn_t sii9234_irq_thread(int irq, void *data) +{ + struct sii9234_data *sii9234 = data; + int ret; + u8 intr1, intr4, value; + u8 intr1_en, intr4_en; + bool release_otg = false; + + mutex_lock(&sii9234->lock); + mhl_tx_read_reg(sii9234, MHL_TX_INTR1_REG, &intr1); + mhl_tx_read_reg(sii9234, MHL_TX_INTR4_REG, &intr4); + + mhl_tx_read_reg(sii9234, MHL_TX_INTR1_ENABLE_REG, &intr1_en); + mhl_tx_read_reg(sii9234, MHL_TX_INTR4_ENABLE_REG, &intr4_en); + pr_debug("sii9234: irq %02x/%02x %02x/%02x\n", intr1, intr1_en, + intr4, intr4_en); + + if (intr4 & RGND_READY_INT) { + ret = mhl_tx_read_reg(sii9234, MHL_TX_STAT2_REG, &value); + if (ret < 0) { + dev_err(&sii9234->pdata->mhl_tx_client->dev, + "STAT2 reg, err %d\n", ret); + goto err_exit; + } + + switch (value & RGND_INTP_MASK) { + case RGND_INTP_OPEN: + pr_debug("RGND Open\n"); + sii9234->rgnd = RGND_OPEN; + break; + case RGND_INTP_1K: + pr_debug("RGND 1K\n"); + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL1_REG, + 0x25); + + ret = mhl_send_wake_pulses(sii9234); + sii9234->rgnd = RGND_1K; + break; + case RGND_INTP_2K: + pr_debug("RGND 2K\n"); + ret = mhl_send_wake_pulses(sii9234); + sii9234->rgnd = RGND_2K; + break; + case RGND_INTP_SHORT: + pr_debug("RGND Short\n"); + sii9234->rgnd = RGND_SHORT; + break; + }; + } + + if (intr4 & CBUS_LKOUT_INT) { + pr_debug("sii9234: CBUS Lockout Interrupt\n"); + sii9234->state = STATE_CBUS_LOCKOUT; + } + + if (intr4 & MHL_DISC_FAIL_INT) + sii9234->state = STATE_DISCOVERY_FAILED; + + if (intr4 & MHL_EST_INT) { + /* discovery override */ + ret = mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL1_REG, 0x10); + + /* increase DDC translation layer timer (byte mode) */ + cbus_write_reg(sii9234, 0x07, 0x32); + cbus_set_reg(sii9234, 0x44, 1<<1); + + /* Keep the discovery enabled. Need RGND interrupt */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL1_REG, (1<<0)); + + sii9234->state = STATE_ESTABLISHED; + } + + if (intr1 & HPD_CHANGE_INT) { + ret = cbus_read_reg(sii9234, MSC_REQ_ABORT_REASON_REG, &value); + + if (value & SET_HPD_DOWNSTREAM) { + /* Downstream HPD Highi */ + + /* Do we need to send HPD upstream using + * Register 0x79(page0)? Is HPD need to be overriden?? + * TODO: See if we need code for overriding HPD OUT + * as per Page 0,0x79 Register + */ + + /* Enable TMDS */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_TMDS_CCTRL, + (1<<4)); + pr_debug("sii9234: MHL HPD High, enabled TMDS\n"); + + ret = mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG, + (1<<4) | (1<<5)); + } else { + /*Downstream HPD Low*/ + + /* Similar to above comments. + * TODO:Do we need to override HPD OUT value + * and do we need to disable TMDS here? + */ + + /* Disable TMDS */ + ret = mhl_tx_clear_reg(sii9234, MHL_TX_TMDS_CCTRL, + (1<<4)); + pr_debug("sii9234 MHL HPD low, disabled TMDS\n"); + ret = mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, + (1<<4) | (1<<5)); + } + } + + if (intr1 & RSEN_CHANGE_INT) { + ret = mhl_tx_read_reg(sii9234, MHL_TX_SYSSTAT_REG, &value); + + sii9234->rsen = value & RSEN_STATUS; + + if (value & RSEN_STATUS) { + pr_info("sii9234: MHL cable connected.. RESN High\n"); + } else { + pr_info("sii9234: RSEN lost\n"); + /* Once RSEN loss is confirmed,we need to check + * based on cable status and chip power status,whether + * it is SINK Loss(HDMI cable not connected, TV Off) + * or MHL cable disconnection + * TODO: Define the below mhl_disconnection() + */ + /* mhl_disconnection(); */ + /* Notify Disconnection to OTG */ + if (sii9234->claimed == true) { + disable_irq_nosync(sii9234->irq); + release_otg = true; + } + sii9234_power_down(sii9234); + } + } + + if (sii9234->state == STATE_ESTABLISHED) { + ret = sii9234_cbus_irq(sii9234); + if (ret < 0) { + if (sii9234->claimed == true) { + disable_irq_nosync(sii9234->irq); + release_otg = true; + } + sii9234_power_down(sii9234); + } + } + +err_exit: + mhl_tx_write_reg(sii9234, MHL_TX_INTR1_REG, intr1); + mhl_tx_write_reg(sii9234, MHL_TX_INTR4_REG, intr4); + + mutex_unlock(&sii9234->lock); + + pr_debug("si9234: wake_up\n"); + wake_up(&sii9234->wq); + + if (release_otg) + otg_id_notify(); + + return IRQ_HANDLED; +} + +static int __devinit sii9234_mhl_tx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct sii9234_data *sii9234; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + sii9234 = kzalloc(sizeof(struct sii9234_data), GFP_KERNEL); + if (!sii9234) { + dev_err(&client->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + sii9234->pdata = client->dev.platform_data; + sii9234->pdata->mhl_tx_client = client; + if (!sii9234->pdata) { + ret = -EINVAL; + goto err_exit1; + } + + i2c_set_clientdata(client, sii9234); + + sii9234->irq = client->irq; + + init_waitqueue_head(&sii9234->wq); + mutex_init(&sii9234->lock); + mutex_init(&sii9234->msc_lock); + + ret = request_threaded_irq(client->irq, NULL, sii9234_irq_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "sii9234", sii9234); + if (ret < 0) + goto err_exit2; + + disable_irq(client->irq); + + sii9234->otg_id_nb.detect = sii9234_detection_callback; + sii9234->otg_id_nb.cancel = sii9234_cancel_callback; + sii9234->otg_id_nb.priority = sii9234->pdata->prio; + + plist_node_init(&sii9234->otg_id_nb.p, sii9234->pdata->prio); + + ret = otg_id_register_notifier(&sii9234->otg_id_nb); + if (ret < 0) { + dev_err(&client->dev, "Unable to register notifier\n"); + goto err_exit2; + } + + return 0; + +err_exit2: +err_exit1: + kfree(sii9234); + return ret; +} + +static int __devinit sii9234_tpi_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sii9234_platform_data *pdata = client->dev.platform_data; + pdata->tpi_client = client; + return 0; +} + +static int __devinit sii9234_hdmi_rx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sii9234_platform_data *pdata = client->dev.platform_data; + pdata->hdmi_rx_client = client; + return 0; +} + +static int __devinit sii9234_cbus_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sii9234_platform_data *pdata = client->dev.platform_data; + pdata->cbus_client = client; + return 0; +} + +static int __devexit sii9234_mhl_tx_remove(struct i2c_client *client) +{ + return 0; +} + +static int __devexit sii9234_tpi_remove(struct i2c_client *client) +{ + return 0; +} + +static int __devexit sii9234_hdmi_rx_remove(struct i2c_client *client) +{ + return 0; +} + +static int __devexit sii9234_cbus_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id sii9234_mhl_tx_id[] = { + {"sii9234_mhl_tx", 0}, + {} +}; + +static const struct i2c_device_id sii9234_tpi_id[] = { + {"sii9234_tpi", 0}, + {} +}; + +static const struct i2c_device_id sii9234_hdmi_rx_id[] = { + {"sii9234_hdmi_rx", 0}, + {} +}; + +static const struct i2c_device_id sii9234_cbus_id[] = { + {"sii9234_cbus", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sii9234_mhl_tx_id); +MODULE_DEVICE_TABLE(i2c, sii9234_tpi_id); +MODULE_DEVICE_TABLE(i2c, sii9234_hdmi_rx_id); +MODULE_DEVICE_TABLE(i2c, sii9234_cbus_id); + +static struct i2c_driver sii9234_mhl_tx_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sii9234_mhl_tx", + }, + .id_table = sii9234_mhl_tx_id, + .probe = sii9234_mhl_tx_i2c_probe, + .remove = __devexit_p(sii9234_mhl_tx_remove), + .command = NULL, +}; + +static struct i2c_driver sii9234_tpi_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sii9234_tpi", + }, + .id_table = sii9234_tpi_id, + .probe = sii9234_tpi_i2c_probe, + .remove = __devexit_p(sii9234_tpi_remove), +}; + +static struct i2c_driver sii9234_hdmi_rx_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sii9234_hdmi_rx", + }, + .id_table = sii9234_hdmi_rx_id, + .probe = sii9234_hdmi_rx_i2c_probe, + .remove = __devexit_p(sii9234_hdmi_rx_remove), +}; + +static struct i2c_driver sii9234_cbus_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sii9234_cbus", + }, + .id_table = sii9234_cbus_id, + .probe = sii9234_cbus_i2c_probe, + .remove = __devexit_p(sii9234_cbus_remove), +}; + +static int __init sii9234_init(void) +{ + int ret; + + ret = i2c_add_driver(&sii9234_mhl_tx_i2c_driver); + if (ret < 0) + return ret; + + ret = i2c_add_driver(&sii9234_tpi_i2c_driver); + if (ret < 0) + goto err_exit1; + + ret = i2c_add_driver(&sii9234_hdmi_rx_i2c_driver); + if (ret < 0) + goto err_exit2; + + ret = i2c_add_driver(&sii9234_cbus_i2c_driver); + if (ret < 0) + goto err_exit3; + + return 0; + +err_exit3: + i2c_del_driver(&sii9234_hdmi_rx_i2c_driver); +err_exit2: + i2c_del_driver(&sii9234_tpi_i2c_driver); +err_exit1: + i2c_del_driver(&sii9234_mhl_tx_i2c_driver); + return ret; +} + +static void __exit sii9234_exit(void) +{ + i2c_del_driver(&sii9234_cbus_i2c_driver); + i2c_del_driver(&sii9234_hdmi_rx_i2c_driver); + i2c_del_driver(&sii9234_tpi_i2c_driver); + i2c_del_driver(&sii9234_mhl_tx_i2c_driver); +} + +module_init(sii9234_init); +module_exit(sii9234_exit); diff --git a/include/linux/gp2a.h b/include/linux/gp2a.h new file mode 100644 index 0000000..084516f --- /dev/null +++ b/include/linux/gp2a.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010 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_GP2A_H +#define __LINUX_GP2A_H + +#include <linux/types.h> + +#ifdef __KERNEL__ +#define GP2A_OPT "gp2a-opt" +struct gp2a_platform_data { + int p_out; /* proximity-sensor-output gpio */ + void (*power)(bool); /* power to the chip */ + int (*light_adc_value)(void); /* get light level from adc */ +}; +#endif /* __KERNEL__ */ + +#endif diff --git a/include/linux/hsi_char.h b/include/linux/hsi_char.h index cfa6580..7845fe2 100644 --- a/include/linux/hsi_char.h +++ b/include/linux/hsi_char.h @@ -39,6 +39,8 @@ #define CS_GET_TX CS_IOW(10, struct hsi_tx_config) #define CS_SW_RESET CS_IO(11) #define CS_GET_FIFO_OCCUPANCY CS_IOR(12, size_t) +#define CS_GET_CAWAKELINE CS_IOR(13, unsigned int) +#define CS_SET_WAKE_RX_3WIRES_MODE CS_IOR(14, unsigned int) #define HSI_MODE_SLEEP 0 #define HSI_MODE_STREAM 1 @@ -50,22 +52,45 @@ #define WAKE_UP 1 #define WAKE_DOWN 0 +/** + * struct hsi_tx_config - HSI TX configuration data + * @mode: Bit transmission mode + * @flow: Data flow type + * @frame_size: frame payload size + * @channels: Number of active channels + * @divisor: Transmission bit rate divisor + * @arb_mode: Arbitration type for the transmit FIFOs + */ struct hsi_tx_config { - __u32 mode; - __u32 flow; - __u32 frame_size; - __u32 channels; - __u32 divisor; - __u32 arb_mode; + __u32 mode; /* Stream:1, Frame:2 */ + __u32 flow; /* Synchronized:0, Pipelined:1. No Realtime support */ + __u32 frame_size; /* HSI: 31, SSI: <= 31 */ + __u32 channels; /* 1, 2, 4, 8, 16 (HSI only) */ + __u32 divisor; /* For HSI: <= 0xFF, for SSI: <= 0x7F */ + __u32 arb_mode; /* Round Robin: 0, Priority: 1 */ }; +/** + * struct hsi_rx_config - HSI RX configuration data + * @mode: Bit transmission mode + * @flow: Data flow type + * @frame_size: frame payload size + * @channels: Number of active channels + * @divisor: Transmission bit rate divisor + * Auto mode:ON:0x1000, OFF(SSI):0x1001) + * Normal range : HSI <= 255, SSI <= 127 + * @counters: Counters settings for error generation. + * Use HSI_HSR_COMBINE_COUNTERS for formatting the register value + */ struct hsi_rx_config { - __u32 mode; - __u32 flow; - __u32 frame_size; - __u32 channels; - __u32 divisor; /* not used for SSI */ - __u32 counters; + __u32 mode; /* Stream:1, Frame:2 */ + __u32 flow; /* Synchronized:0, Pipelined:1. No Realtime support */ + __u32 frame_size; /* HSI: 31, SSI: <= 31 */ + __u32 channels; /* 1, 2, 4, 8, 16(HSI only) */ + __u32 divisor; /* Normal range : HSI <= 255, SSI <= 127 */ + __u32 counters; /* HSI: FB[31..24], TB[23..20], FT[19..0] */ + /* SSI: FT[8..0] */ }; + #endif /* HSI_CHAR_H */ diff --git a/include/linux/hsi_driver_if.h b/include/linux/hsi_driver_if.h index 547b30e..6e7a0da 100644 --- a/include/linux/hsi_driver_if.h +++ b/include/linux/hsi_driver_if.h @@ -29,7 +29,7 @@ /* The number of ports handled by the driver (MAX:2). Reducing this value * optimizes the driver memory footprint. */ -#define HSI_MAX_PORTS 1 +#define HSI_MAX_PORTS 2 /* bit-field definition for allowed controller IDs and channels */ #define ANY_HSI_CONTROLLER -1 @@ -62,10 +62,8 @@ enum { HSI_IOCTL_GET_TX, /* Get HST configuration */ HSI_IOCTL_SW_RESET, /* Force a HSI SW RESET */ HSI_IOCTL_GET_FIFO_OCCUPANCY, /* Get amount of words in RX FIFO */ - HSI_IOCTL_SET_ACREADY_SAFEMODE, - HSI_IOCTL_SET_ACREADY_NORMAL, - HSI_IOCTL_SET_3WIRE_MODE, - HSI_IOCTL_SET_4WIRE_MODE, + HSI_IOCTL_SET_WAKE_RX_3WIRES_MODE, /* Enable RX wakeup 3-wires mode */ + HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, /* Enable RX wakeup 4-wires mode */ }; /* Forward references */ @@ -91,28 +89,30 @@ struct hsr_ctx { u32 channels; }; -struct port_ctx { +struct hsi_port_ctx { + int port_number; /* Range [1, 2] */ u32 sys_mpu_enable[2]; struct hst_ctx hst; struct hsr_ctx hsr; + const char *cawake_padconf_name; + int cawake_padconf_hsi_mode; }; /** - * struct ctrl_ctx - hsi controller regs context + * struct hsi_ctrl_ctx - hsi controller regs context * @sysconfig: keeps HSI_SYSCONFIG reg state * @gdd_gcr: keeps DMA_GCR reg state * @dll: keeps HSR_DLL state * @pctx: array of port context */ -struct ctrl_ctx { +struct hsi_ctrl_ctx { u32 sysconfig; u32 gdd_gcr; u32 dll; - struct port_ctx *pctx; + struct hsi_port_ctx *pctx; }; /* END DPS */ - /** * struct hsi_device - HSI device object (Virtual) * @n_ctrl: associated HSI controller platform id number diff --git a/include/linux/i2c/twl.h b/include/linux/i2c/twl.h index 3c6e9a0..0fd6e49 100644 --- a/include/linux/i2c/twl.h +++ b/include/linux/i2c/twl.h @@ -458,6 +458,12 @@ static inline int twl6030_mmc_card_detect(struct device *dev, int slot) #define TWL6030_PHOENIX_DEV_ON 0x06 /* + * TWL6030 PM Master module register offsets (use TWL_MODULE_PM_MASTER) + */ + +#define TWL6030_VBATMIN_HI_THRESHOLD 0x05 + +/* * PM Slave resource module register offsets (use TWL6030_MODULE_SLAVE_RES) */ diff --git a/include/linux/leds-an30259a.h b/include/linux/leds-an30259a.h new file mode 100644 index 0000000..7a97efa --- /dev/null +++ b/include/linux/leds-an30259a.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011 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. + * + */ + +#ifndef _LEDS_AN30259A_H +#define _LEDS_AN30259A_H + +#include <linux/ioctl.h> +#include <linux/types.h> + +#define LED_LIGHT_OFF 0 +#define LED_LIGHT_ON 1 +#define LED_LIGHT_PULSE 2 +#define LED_LIGHT_SLOPE 3 + +/* + * This struct gets passed to the ioctl call. + * If only one of struct gets passed to the ioctl then it is assumed to define + * the behavior for all 3 color components: R, G and B. + * If 3 structs are passed, then each one is assumed to describe a single color: + * R first, then G, then B. + * + * Requesting a color value of 0 is equivalent to requesting LED_LIGHT_OFF + * + * If only describing a single color (ie passing a single struct), then + * start_delay will get ignored + * + * Other parameters may get ignored depending on the requested state: + * LIGHT_ON only requires color + * LIGHT_PULSE requires color, time_on and time_off + * + * Total time for time_slope_up_1 + time_slope_up_2 + time_on as well as for + * time_slope_down_1 + time_slope_down_2 + time_off will be rounded up to the + * nearest .5 seconds. + * + * Each of the time_slope_* values will get rounded up to the nearest multiple + * of 4ms up to 7680ms + */ + +struct an30259a_pr_control { + /* LED color in RGB format */ + __u32 color; + /* see defines above */ + __u32 state; + /* initial delay in ms */ + __u16 start_delay; + /* time to reach mid_brightness_up from off in ms */ + __u16 time_slope_up_1; + /* time to reach color from mid_brightness_up in ms */ + __u16 time_slope_up_2; + /* time at max brightness in ms */ + __u16 time_on; + /* time to reach mid_brightness_down from max brightness in ms */ + __u16 time_slope_down_1; + /* time to reach off from mid_brightness_down in ms */ + __u16 time_slope_down_2; + /* time off in ms */ + __u16 time_off; + /* mid point brightness in 1/128 increments of color */ + __u8 mid_brightness; +} __packed; + +#define AN30259A_PR_SET_LED _IOW('S', 42, struct an30259a_pr_control) +#define AN30259A_PR_SET_LEDS _IOW('S', 43, struct an30259a_pr_control[3]) +#define AN30259A_PR_SET_IMAX _IOW('S', 44, __u8) +#endif /* _LEDS_AN30259A_H */ diff --git a/include/linux/max17040_battery.h b/include/linux/max17040_battery.h index ad97b06..253d08a 100644 --- a/include/linux/max17040_battery.h +++ b/include/linux/max17040_battery.h @@ -14,6 +14,22 @@ struct max17040_platform_data { int (*battery_online)(void); int (*charger_online)(void); int (*charger_enable)(void); + bool(*is_full_charge)(void); + int (*get_bat_temp)(int *); + bool skip_reset; + int min_capacity; /* minimum allowable capacity. The reported capacity + will be scaled from [<min_capacity>,100] to + [0,100] */ + void (*allow_charging)(int en); + int high_block_temp; + int high_recover_temp; + int low_block_temp; + int low_recover_temp; + int fully_charged_vol; + int recharge_vol; + int limit_charging_time; + int limit_recharging_time; + bool use_fuel_alert; }; #endif diff --git a/include/linux/mpu.h b/include/linux/mpu.h new file mode 100644 index 0000000..03fb90d --- /dev/null +++ b/include/linux/mpu.h @@ -0,0 +1,330 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>. + $ + */ + +#ifndef __MPU_H_ +#define __MPU_H_ + +#include <linux/types.h> +#include <linux/ioctl.h> + +/* Number of axes on each sensor */ +#define GYRO_NUM_AXES (3) +#define ACCEL_NUM_AXES (3) +#define COMPASS_NUM_AXES (3) + +struct mpu_read_write { + /* Memory address or register address depending on ioctl */ + unsigned short address; + unsigned short length; + unsigned char *data; +}; + +enum mpuirq_data_type { + MPUIRQ_DATA_TYPE_MPU_IRQ, + MPUIRQ_DATA_TYPE_SLAVE_IRQ, + MPUIRQ_DATA_TYPE_PM_EVENT, + MPUIRQ_DATA_TYPE_NUM_TYPES, +}; + +/* User space PM event notification */ +#define MPU_PM_EVENT_SUSPEND_PREPARE (3) +#define MPU_PM_EVENT_POST_SUSPEND (4) + +struct mpuirq_data { + int interruptcount; + unsigned long long irqtime; + int data_type; + long data; +}; + +enum ext_slave_config_key { + MPU_SLAVE_CONFIG_ODR_SUSPEND, + MPU_SLAVE_CONFIG_ODR_RESUME, + MPU_SLAVE_CONFIG_FSR_SUSPEND, + MPU_SLAVE_CONFIG_FSR_RESUME, + MPU_SLAVE_CONFIG_MOT_THS, + MPU_SLAVE_CONFIG_NMOT_THS, + MPU_SLAVE_CONFIG_MOT_DUR, + MPU_SLAVE_CONFIG_NMOT_DUR, + MPU_SLAVE_CONFIG_IRQ_SUSPEND, + MPU_SLAVE_CONFIG_IRQ_RESUME, + MPU_SLAVE_WRITE_REGISTERS, + MPU_SLAVE_READ_REGISTERS, + /* AMI 306 specific config keys */ + MPU_SLAVE_PARAM, + MPU_SLAVE_WINDOW, + MPU_SLAVE_READWINPARAMS, + MPU_SLAVE_SEARCHOFFSET, + /* AKM specific config keys */ + MPU_SLAVE_READ_SCALE, + /* YAS specific config keys */ + MPU_SLAVE_OFFSET_VALS, + MPU_SLAVE_RANGE_CHECK, + + MPU_SLAVE_CONFIG_NUM_CONFIG_KEYS, +}; + +/* For the MPU_SLAVE_CONFIG_IRQ_SUSPEND and MPU_SLAVE_CONFIG_IRQ_RESUME */ +enum ext_slave_config_irq_type { + MPU_SLAVE_IRQ_TYPE_NONE, + MPU_SLAVE_IRQ_TYPE_MOTION, + MPU_SLAVE_IRQ_TYPE_DATA_READY, +}; + +/* Structure for the following IOCTS's + * MPU_CONFIG_ACCEL + * MPU_CONFIG_COMPASS + * MPU_CONFIG_PRESSURE + * MPU_GET_CONFIG_ACCEL + * MPU_GET_CONFIG_COMPASS + * MPU_GET_CONFIG_PRESSURE + * + * @key one of enum ext_slave_config_key + * @len length of data pointed to by data + * @apply zero if communication with the chip is not necessary, false otherwise + * This flag can be used to select cached data or to refresh cashed data + * cache data to be pushed later or push immediately. If true and the + * slave is on the secondary bus the MPU will first enger bypass mode + * before calling the slaves .config or .get_config funcion + * @data pointer to the data to confgure or get + */ +struct ext_slave_config { + int key; + int len; + int apply; + void *data; +}; + +enum ext_slave_type { + EXT_SLAVE_TYPE_GYROSCOPE, + EXT_SLAVE_TYPE_ACCELEROMETER, + EXT_SLAVE_TYPE_COMPASS, + EXT_SLAVE_TYPE_PRESSURE, + /*EXT_SLAVE_TYPE_TEMPERATURE */ + + EXT_SLAVE_NUM_TYPES +}; + +enum ext_slave_id { + ID_INVALID = 0, + + ACCEL_ID_LIS331, + ACCEL_ID_LSM303A, + 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_AK8975, + COMPASS_ID_AMI30X, + COMPASS_ID_AMI306, + COMPASS_ID_YAS529, + COMPASS_ID_YAS530, + COMPASS_ID_HMC5883, + COMPASS_ID_LSM303M, + COMPASS_ID_MMC314X, + COMPASS_ID_HSCDTD002B, + COMPASS_ID_HSCDTD004A, + + PRESSURE_ID_BMA085, +}; + +enum ext_slave_endian { + EXT_SLAVE_BIG_ENDIAN, + EXT_SLAVE_LITTLE_ENDIAN, + EXT_SLAVE_FS8_BIG_ENDIAN, + EXT_SLAVE_FS16_BIG_ENDIAN, +}; + +enum ext_slave_bus { + EXT_SLAVE_BUS_INVALID = -1, + EXT_SLAVE_BUS_PRIMARY = 0, + EXT_SLAVE_BUS_SECONDARY = 1 +}; + + +/** + * struct ext_slave_platform_data - Platform data for mpu3050 and mpu6050 + * slave devices + * + * @get_slave_descr: Function pointer to retrieve the struct ext_slave_descr + * for this slave + * @irq: the irq number attached to the slave if any. + * @adapt_num: the I2C adapter number. + * @bus: the bus the slave is attached to: enum ext_slave_bus + * @address: the I2C slave address of the slave device. + * @orientation: the mounting matrix of the device relative to MPU. + * @irq_data: private data for the slave irq handler + * @private_data: additional data, user customizable. Not touched by the MPU + * driver. + * + * 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 ext_slave_platform_data { + struct ext_slave_descr *(*get_slave_descr) (void); + int irq; + int adapt_num; + int bus; + unsigned char address; + signed char orientation[9]; + void *irq_data; + void *private_data; +}; + +struct fix_pnt_range { + long mantissa; + long fraction; +}; + +static inline long range_fixedpoint_to_long_mg(struct fix_pnt_range rng) +{ + return (long)(rng.mantissa * 1000 + rng.fraction / 10); +} + +struct ext_slave_read_trigger { + unsigned char reg; + unsigned char value; +}; + +/** + * struct ext_slave_descr - Description of the slave device for programming. + * + * @suspend: function pointer to put the device in suspended state + * @resume: function pointer to put the device in running state + * @read: function that reads the device data + * @init: function used to preallocate memory used by the driver + * @exit: function used to free memory allocated for the driver + * @config: function used to configure the device + * @get_config:function used to get the device's configuration + * + * @name: text name of the device + * @type: device type. enum ext_slave_type + * @id: enum ext_slave_id + * @reg: starting register address to retrieve data. + * @len: length in bytes of the sensor data. Should be 6. + * @endian: byte order of the data. enum ext_slave_endian + * @range: full scale range of the slave ouput: struct fix_pnt_range + * @trigger: If reading data first requires writing a register this is the + * data to write. + * + * Defines the functions and information about the slave the mpu3050 and + * mpu6050 needs to use the slave device. + */ +struct ext_slave_descr { + int (*init) (void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata); + int (*exit) (void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata); + int (*suspend) (void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata); + int (*resume) (void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata); + int (*read) (void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data); + int (*config) (void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *config); + int (*get_config) (void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *config); + + char *name; + unsigned char type; + unsigned char id; + unsigned char read_reg; + unsigned int read_len; + unsigned char endian; + struct fix_pnt_range range; + struct ext_slave_read_trigger *trigger; +}; + +/** + * struct mpu_platform_data - Platform data for the mpu driver + * @int_config: Bits [7:3] of the int config register. + * @orientation: Orientation matrix of the gyroscope + * @level_shifter: 0: VLogic, 1: VDD + * @accel: Accel platform data + * @compass: Compass platform data + * @pressure: Pressure platform data + * + * 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 { + unsigned char int_config; + signed char orientation[GYRO_NUM_AXES * GYRO_NUM_AXES]; + unsigned char level_shifter; + struct ext_slave_platform_data accel; + struct ext_slave_platform_data compass; + struct ext_slave_platform_data pressure; +}; + +#define MPU_IOCTL (0x81) /* Magic number for MPU Iocts */ +/* IOCTL commands for /dev/mpu */ +#define MPU_SET_MPU_CONFIG _IOWR(MPU_IOCTL, 0x00, struct mldl_cfg) +#define MPU_GET_MPU_CONFIG _IOW(MPU_IOCTL, 0x00, struct mldl_cfg) + +#define MPU_SET_PLATFORM_DATA _IOWR(MPU_IOCTL, 0x01, struct mldl_cfg) + +#define MPU_READ _IOWR(MPU_IOCTL, 0x10, struct mpu_read_write) +#define MPU_WRITE _IOW(MPU_IOCTL, 0x10, struct mpu_read_write) +#define MPU_READ_MEM _IOWR(MPU_IOCTL, 0x11, struct mpu_read_write) +#define MPU_WRITE_MEM _IOW(MPU_IOCTL, 0x11, struct mpu_read_write) +#define MPU_READ_FIFO _IOWR(MPU_IOCTL, 0x12, struct mpu_read_write) +#define MPU_WRITE_FIFO _IOW(MPU_IOCTL, 0x12, struct mpu_read_write) + +#define MPU_READ_COMPASS _IOR(MPU_IOCTL, 0x12, unsigned char) +#define MPU_READ_ACCEL _IOR(MPU_IOCTL, 0x13, unsigned char) +#define MPU_READ_PRESSURE _IOR(MPU_IOCTL, 0x14, unsigned char) + +#define MPU_CONFIG_ACCEL _IOW(MPU_IOCTL, 0x20, struct ext_slave_config) +#define MPU_CONFIG_COMPASS _IOW(MPU_IOCTL, 0x21, struct ext_slave_config) +#define MPU_CONFIG_PRESSURE _IOW(MPU_IOCTL, 0x22, struct ext_slave_config) + +#define MPU_GET_CONFIG_ACCEL _IOWR(MPU_IOCTL, 0x20, struct ext_slave_config) +#define MPU_GET_CONFIG_COMPASS _IOWR(MPU_IOCTL, 0x21, struct ext_slave_config) +#define MPU_GET_CONFIG_PRESSURE _IOWR(MPU_IOCTL, 0x22, struct ext_slave_config) + +#define MPU_SUSPEND _IO(MPU_IOCTL, 0x30) +#define MPU_RESUME _IO(MPU_IOCTL, 0x31) +/* Userspace PM Event response */ +#define MPU_PM_EVENT_HANDLED _IO(MPU_IOCTL, 0x32) + + +#endif /* __MPU_H_ */ diff --git a/include/linux/platform_data/fsa9480.h b/include/linux/platform_data/fsa9480.h new file mode 100644 index 0000000..ad9d70c --- /dev/null +++ b/include/linux/platform_data/fsa9480.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010 Samsung Electronics + * Minkyu Kang <mk7.kang@samsung.com> + * Wonguk Jeong <wonguk.jeong@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_USB_SWITCH_FSA9480_H_ +#define __LINUX_USB_SWITCH_FSA9480_H_ + +#include <linux/types.h> + +#define FSA9480_DETECT_NONE (0) +#define FSA9480_DETECT_USB (1 << 0) +#define FSA9480_DETECT_USB_HOST (1 << 1) +#define FSA9480_DETECT_CHARGER (1 << 2) +#define FSA9480_DETECT_JIG (1 << 3) +#define FSA9480_DETECT_UART (1 << 4) +#define FSA9480_DETECT_AV_365K (1 << 5) +#define FSA9480_DETECT_AV_365K_CHARGER (1 << 6) + +#define FSA9480_DETECT_ALL (FSA9480_DETECT_USB | \ + FSA9480_DETECT_USB_HOST | \ + FSA9480_DETECT_CHARGER | \ + FSA9480_DETECT_JIG | \ + FSA9480_DETECT_UART | \ + FSA9480_DETECT_AV_365K | \ + FSA9480_DETECT_AV_365K_CHARGER) + +struct fsa9480_detect_set { + int prio; + u32 mask; + bool fallback; +}; + +struct fsa9480_platform_data { + int detect_time; + + void (*enable)(bool enable); + void (*detected)(int device); + + /* The FSA9480 has a bug that prevents its from detecting a detach when + * the cable was identified as a USB OTG cable. As a workaround we + * provide the FSA9480 with a GPIO that is hooked up to the USB ID + * signal. + */ + int external_id; + + struct fsa9480_detect_set *detect_sets; + int num_sets; + + void (*mask_vbus_irq)(void); + void (*unmask_vbus_irq)(void); + bool (*vbus_present)(void); + int external_vbus_irq; + unsigned long external_vbus_flags; +}; + +#endif diff --git a/include/linux/platform_data/lte_modem_bootloader.h b/include/linux/platform_data/lte_modem_bootloader.h new file mode 100755 index 0000000..27a0450 --- /dev/null +++ b/include/linux/platform_data/lte_modem_bootloader.h @@ -0,0 +1,40 @@ +/* Lte modem bootloader support for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2011 Samsung Electronics. + * + * 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 __LTE_MODEM_BOOTLOADER_H +#define __LTE_MODEM_BOOTLOADER_H + +#define LTE_MODEM_BOOTLOADER_DRIVER_NAME "lte_modem_bootloader" + +#define IOCTL_LTE_MODEM_XMIT_BOOT _IOW('o', 0x23, unsigned int) +#define IOCTL_LTE_MODEM_LTE2AP_STATUS _IOR('o', 0x24, unsigned int) + +#define AIRPLAIN_MODE_TEST + +#ifdef AIRPLAIN_MODE_TEST +#define IOCTL_LTE_MODEM_AIRPLAIN_ON _IOWR('o', 0x25, unsigned int) +#define IOCTL_LTE_MODEM_AIRPLAIN_OFF _IOWR('o', 0x26, unsigned int) +#endif + +struct lte_modem_bootloader_param { + char __user *buf; + int len; +}; + +struct lte_modem_bootloader_platform_data { + const char *name; + unsigned int gpio_lte2ap_status; +}; +#endif/* LTE_MODEM_BOOTLOADER_H */ diff --git a/include/linux/platform_data/mms_ts.h b/include/linux/platform_data/mms_ts.h new file mode 100644 index 0000000..2ab2c75 --- /dev/null +++ b/include/linux/platform_data/mms_ts.h @@ -0,0 +1,34 @@ +/* + * mms_ts.h - Platform data for Melfas MMS-series touch driver + * + * Copyright (C) 2011 Google Inc. + * Author: Dima Zavin <dima@android.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. + * + */ + +#ifndef _LINUX_MMS_TOUCH_H +#define _LINUX_MMS_TOUCH_H + +struct mms_ts_platform_data { + int max_x; + int max_y; + + bool invert_x; + bool invert_y; + + int gpio_sda; + int gpio_scl; + int gpio_resetb; + int gpio_vdd_en; + + int (*mux_fw_flash)(bool to_gpios); + const char *fw_name; +}; + +#endif /* _LINUX_MMS_TOUCH_H */ diff --git a/include/linux/platform_data/modem.h b/include/linux/platform_data/modem.h new file mode 100755 index 0000000..e1e6603 --- /dev/null +++ b/include/linux/platform_data/modem.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * 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 __MODEM_IF_H__ +#define __MODEM_IF_H__ + +enum modem_t { + IMC_XMM6260, + VIA_CBP71, + SEC_CMC221, +}; + +enum dev_format { + IPC_FMT, + IPC_RAW, + IPC_RFS, + IPC_CMD, + IPC_BOOT, + IPC_MULTI_RAW, + IPC_RAMDUMP, +}; + +enum modem_io { + IODEV_MISC, + IODEV_NET, + IODEV_DUMMY, +}; + +enum modem_link { + LINKDEV_MIPI, + LINKDEV_DPRAM, + LINKDEV_SPI, + LINKDEV_USB, + LINKDEV_MAX, +}; + +enum modem_network { + UMTS_NETWORK, + CDMA_NETWORK, + LTE_NETWORK, +}; + +/* This structure is used in board-tuna-modem.c */ +struct modem_io_t { + char *name; + int id; + enum dev_format format; + enum modem_io io_type; + enum modem_link link; +}; + +/* platform data */ +struct modem_data { + char *name; + + unsigned gpio_cp_on; + unsigned gpio_cp_off; + unsigned gpio_reset_req_n; + unsigned gpio_cp_reset; + unsigned gpio_pda_active; + unsigned gpio_phone_active; + unsigned gpio_cp_dump_int; + unsigned gpio_flm_uart_sel; + unsigned gpio_cp_warm_reset; +#ifdef CONFIG_LTE_MODEM_CMC221 + unsigned gpio_slave_wakeup; + unsigned gpio_host_wakeup; + unsigned gpio_host_active; + int irq_host_wakeup; +#endif + /* modem component */ + enum modem_t modem_type; + enum modem_link link_type; + enum modem_network modem_net; + unsigned num_iodevs; + struct modem_io_t *iodevs; +}; + +#endif diff --git a/include/linux/platform_data/panel-s6e8aa0.h b/include/linux/platform_data/panel-s6e8aa0.h new file mode 100644 index 0000000..553d516 --- /dev/null +++ b/include/linux/platform_data/panel-s6e8aa0.h @@ -0,0 +1,124 @@ +/* + * Samsung s6e8aa0 panel support + * + * Copyright 2011 Google, Inc. + * Author: Erik Gilling <konkers@google.com> + * + * based on d2l panel driver by Jerry Alexander <x0135174@ti.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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __LINUX_PLATFORM_DATA_PANEL_S6E8AA0_H +#define __LINUX_PLATFORM_DATA_PANEL_S6E8AA0_H + +struct s6e8aa0_sequence_entry { + const u8 *cmd; + int cmd_len; + unsigned int msleep; +}; + +enum { + BV_0 = 0x0, + BV_1 = 0x13E456, + BV_15 = 0x1390FFB, + BV_35 = 0x44D7CF9, + BV_59 = 0xB323808, + BV_87 = 0x186611F4, + BV_171 = 0x6840E4FF, + BV_255 = 0xFFFFFFFF, +}; + +struct s6e8aa0_gamma_entry { + u32 brightness; + u32 v[3]; +}; + +struct s6e8aa0_gamma_adj_points { + const u32 v1; + const u32 v15; + const u32 v35; + const u32 v59; + const u32 v87; + const u32 v171; +}; + +struct s6e8aa0_color_adj { + u32 mult[3]; + int rshift; +}; + +/** + * struct s6e8aa0_factory_calibration_info - factory calibration parameters + * @regs: Register values used during factory calibration. The first array + * index is 0 for dark gamma offsets and 1 for bright gamma + * offsets. The second array index is color (0: red, 1: green, + * 2: blue). The last array index is the adjusment point (0: V1, + * 1: V15, 2: V35, 3: V59, 4: V87, 5: V171, 6: V255). + * @brightness: Brightness target used for each adjustment point scaled linearly + * so the maximum brightness is BV_255 (0xffffffff).The first array + * index is 0 for dark gamma offsets and 1 for bright gamma + * offsets. The last array index is the adjusment point (0: V1, + * 1: V15, 2: V35, 3: V59, 4: V87, 5: V171, 6: V255). If the value + * is 0 adjustment point is ignored. + * @color_adj: Per color brightness multiplier. + */ + +struct s6e8aa0_factory_calibration_info { + u16 regs[2][3][7]; + u32 brightness[2][7]; + struct s6e8aa0_color_adj color_adj; +}; + +struct s6e8aa0_acl_parameters { + unsigned int cd; + unsigned int acl_val; + u8 regs[29]; +}; + +/** + * struct s6e8aa0_elvs_parameters -- elvss parameters + * @cd: look up value. if the argument cd is <= this value, + * then the elvss_val is added to byte 2 of the panel + * id to generate the elvss value passed to the controller. + * @elvss_val: Value to add to the elvss for this cd + */ +struct s6e8aa0_elvss_parameters { + unsigned int cd; + u8 elvss_val; +}; + +struct panel_s6e8aa0_data { + int reset_gpio; + void (* set_power)(bool enable); + + const struct s6e8aa0_sequence_entry *seq_display_set; + int seq_display_set_size; + const struct s6e8aa0_sequence_entry *seq_etc_set; + int seq_etc_set_size; + + struct s6e8aa0_factory_calibration_info *factory_info; + + const struct s6e8aa0_gamma_adj_points *gamma_adj_points; + const struct s6e8aa0_gamma_entry *gamma_table; + int gamma_table_size; + + const struct s6e8aa0_acl_parameters *acl_table; + unsigned int acl_table_size; + unsigned int acl_average; + + const struct s6e8aa0_elvss_parameters *elvss_table; + int elvss_table_size; +}; + +#endif diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index 0cae9f6..6c3cefb 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -246,6 +246,7 @@ enum rproc_event { * @secure_restart: completion event notifier for the secure restart process * @secure_mode: flag to dictate whether to enable secure loading * @secure_ok: restart status flag to be looked up upon the event's completion + * @secure_reset: flag to uninstall the firewalls */ struct rproc { struct list_head next; @@ -283,6 +284,7 @@ struct rproc { struct mutex secure_lock; bool secure_mode; bool secure_ok; + bool secure_reset; bool halt_on_crash; char *header; int header_len; diff --git a/include/linux/rproc_drm.h b/include/linux/rproc_drm.h new file mode 100644 index 0000000..475a498 --- /dev/null +++ b/include/linux/rproc_drm.h @@ -0,0 +1,45 @@ +/* + * Remote Processor DRM Service API + * + * Copyright(c) 2011 Texas Instruments. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Texas Instruments nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RPROC_DRM_H +#define RPROC_DRM_H + +#ifdef CONFIG_SECURITY_MIDDLEWARE_COMPONENT +int rproc_drm_invoke_service(bool enable); +#else +static inline int rproc_drm_invoke_service(bool enable) +{ + return -EACCES; +} +#endif + +#endif /* RPROC_DRM_H */ 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/sii9234.h b/include/linux/sii9234.h new file mode 100644 index 0000000..305e4f8 --- /dev/null +++ b/include/linux/sii9234.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 Samsung Electronics + * + * Author: Adam Hampson <ahampson@sta.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 _SII9234_H_ +#define _SII9234_H_ + +#include <linux/i2c.h> + +#define MHL_DEVCAP_DEVSTATE 0x0 +#define MHL_DEVCAP_MHL_VERSION 0x1 +#define MHL_DEVCAP_DEV_CAT 0x2 +#define MHL_DEVCAP_ADOPTER_ID_H 0x3 +#define MHL_DEVCAP_ADOPTER_ID_L 0x4 +#define MHL_DEVCAP_VID_LINK_MODE 0x5 +#define MHL_DEVCAP_AUD_LINK_MODE 0x6 +#define MHL_DEVCAP_VIDEO_TYPE 0x7 +#define MHL_DEVCAP_LOG_DEV_MAP 0x8 +#define MHL_DEVCAP_BANDWIDTH 0x9 +#define MHL_DEVCAP_FEATURE_FLAG 0xa +#define MHL_DEVCAP_DEVICE_ID_H 0xb +#define MHL_DEVCAP_DEVICE_ID_L 0xc +#define MHL_DEVCAP_SCRATHPAD_SIZE 0xd +#define MHL_DEVCAP_INT_STAT_SIZE 0xe +#define MHL_DEVCAP_RESERVED 0xf + +struct sii9234_platform_data { + int prio; + void (*enable)(bool enable); + void (*power)(int on); + void (*enable_vbus)(bool enable); + void (*connect)(bool connected, u8 *devcap); + struct i2c_client *mhl_tx_client; + struct i2c_client *tpi_client; + struct i2c_client *hdmi_rx_client; + struct i2c_client *cbus_client; +}; + +#endif /* _SII9234_H_ */ diff --git a/include/linux/usb.h b/include/linux/usb.h index 73c7df4..7639cab 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1419,6 +1419,7 @@ extern int usb_string(struct usb_device *dev, int index, /* wrappers that also update important state inside usbcore */ extern int usb_clear_halt(struct usb_device *dev, int pipe); extern int usb_reset_configuration(struct usb_device *dev); +extern void usb_force_disconnect(struct usb_device *udev); extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate); extern void usb_reset_endpoint(struct usb_device *dev, unsigned int epaddr); diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h index 3e93de7..9569d4b 100644 --- a/include/linux/usb/quirks.h +++ b/include/linux/usb/quirks.h @@ -30,4 +30,10 @@ descriptor */ #define USB_QUIRK_DELAY_INIT 0x00000040 +/* device does not support reset-resume */ +#define USB_QUIRK_NO_RESET_RESUME 0x00000080 + +/* device does not need GET_STATUS request */ +#define USB_QUIRK_NO_GET_STATUS 0x00000100 + #endif /* __LINUX_USB_QUIRKS_H */ diff --git a/security/Kconfig b/security/Kconfig index d4ffb55..f76afcc 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -187,7 +187,6 @@ source security/tomoyo/Kconfig source security/apparmor/Kconfig source security/integrity/ima/Kconfig - source security/smc/Kconfig choice diff --git a/security/smc/Kconfig b/security/smc/Kconfig index 9fcd1f6..7a933ac 100644 --- a/security/smc/Kconfig +++ b/security/smc/Kconfig @@ -1,11 +1,11 @@ -config TF_MSHIELD +config TF_ZEBRA bool config SECURITY_MIDDLEWARE_COMPONENT bool "Enable SMC Driver" depends on ARCH_OMAP3 || ARCH_OMAP4 default n - select TF_MSHIELD + select TF_ZEBRA help This option adds kernel support for communication with the SMC Protected Application. diff --git a/security/smc/Makefile b/security/smc/Makefile index 80cf430..abf0095 100644 --- a/security/smc/Makefile +++ b/security/smc/Makefile @@ -1,3 +1,48 @@ -ifeq ($(CONFIG_SECURITY_MIDDLEWARE_COMPONENT),y) -obj-$(CONFIG_ARCH_OMAP4) += omap4/ +# +# Copyright (c) 2006-2010 Trusted Logic S.A. +# 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 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 +# + +ifdef S_VERSION_BUILD +EXTRA_CFLAGS += -DS_VERSION_BUILD=$(S_VERSION_BUILD) endif + +EXTRA_CFLAGS += -Iarch/arm/mach-omap2 +EXTRA_CFLAGS += -Iarch/arm/plat-omap/include/plat +EXTRA_CFLAGS += -DCONFIG_TF_TEEC +EXTRA_CFLAGS += -DCONFIG_TF_ION + +tf_driver-objs += tf_util.o +tf_driver-objs += tf_conn.o +tf_driver-objs += tf_device.o +tf_driver-objs += tf_comm.o +tf_driver-objs += tf_crypto.o +tf_driver-objs += tf_crypto_digest.o +tf_driver-objs += tf_crypto_aes.o +tf_driver-objs += tf_crypto_des.o +tf_driver-objs += tf_dma.o +tf_driver-objs += tf_comm_mshield.o +tf_driver-objs += tf_device_mshield.o +tf_driver-objs += bridge_pub2sec.o +tf_driver-objs += tf_teec.o + +obj-$(CONFIG_SECURITY_MIDDLEWARE_COMPONENT) += tf_driver.o +obj-$(CONFIG_SECURITY_MIDDLEWARE_COMPONENT) += rproc_drm.o + +plus_sec := $(call as-instr,.arch_extension sec,+sec) +AFLAGS_bridge_pub2sec.o :=-Wa,-march=armv7-a$(plus_sec) diff --git a/security/smc/bridge_pub2sec.S b/security/smc/bridge_pub2sec.S new file mode 100644 index 0000000..15cd3b7 --- /dev/null +++ b/security/smc/bridge_pub2sec.S @@ -0,0 +1,242 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +.text + +#define SMICODEPUB_IRQ_END 0xFE +#define SMICODEPUB_FIQ_END 0xFD +#define SMICODEPUB_RPC_END 0xFC + +#define PUB2SEC_NOCST 0xFF +#define SMICODEPUB_NEWTASK 0x00 + +/* + * RPC status: + * - 0: the secure world yielded due to an interrupt + * - 1: the secure world yielded on an RPC (no public thread is handling it) + * - 2: the secure world yielded on an RPC and the response is ready + */ +#define RPC_ADVANCEMENT_NONE 0 +#define RPC_ADVANCEMENT_PENDING 1 +#define RPC_ADVANCEMENT_FINISHED 2 + +#ifdef CONFIG_ARM_ERRATA_430973 +#define INVALIDATE_BTB MCR p15, 0, R0, c7, c5, 6 +#else +#define INVALIDATE_BTB +#endif + +schedule_secure_world: + .global schedule_secure_world + + /* Save registers */ + push {r4-r12, lr} + + /* Copy the Secure Service ID in r12 */ + mov r12, r0 + + cmp r0, #SMICODEPUB_IRQ_END + beq return_from_irq + + cmp r0, #SMICODEPUB_RPC_END + beq return_from_rpc + + mov r6, #PUB2SEC_NOCST + mov r12, #SMICODEPUB_NEWTASK + + b label_smc + +return_from_rpc: + ldr r9, =g_RPC_parameters + ldm r9, {r0-r3} + /* fall through */ + +return_from_irq: + ldr r10, =g_secure_task_id + ldr r6, [r10] + + b label_smc + +label_smc: + INVALIDATE_BTB + dsb + dmb + +#ifdef CONFIG_BENCH_SECURE_CYCLE + /* Come from Non Secure: activate counter 1 (write to 0 are ignored) */ + mov r4, #0x00000002 + + /* Read Count Enable Set Register */ + mcr p15, 0x0, r4, c9, c12, 1 + + /* Come from Non Secure: stop counter 0 (write to 0 are ignored) */ + mov r4, #0x00000001 + + /* Write Count Enable Clear Register */ + mcr p15, 0x0, r4, c9, c12, 2 +#endif + + smc #0 + b service_end + nop + +#ifdef CONFIG_BENCH_SECURE_CYCLE + /* Come from Secure: activate counter 0 (write to 0 are ignored) */ + mov r4, #0x00000001 + + /* Write Count Enable Set Register */ + mcr p15, 0x0, r4, c9, c12, 1 + + /* Come from Secure: stop counter 1 (write to 0 are ignored) */ + mov r4, #0x00000002 + + /* Write Count Enable Clear Register */ + mcr p15, 0x0, r4, c9, c12, 2 +#endif + + INVALIDATE_BTB + ldr r8, =g_secure_task_id + str r6, [r8] + + mov r0, #0x00 + ldr r8, =g_service_end + str r0, [r8] + + b schedule_secure_world_exit + +service_end: + +schedule_secure_world_exit: +#ifdef CONFIG_BENCH_SECURE_CYCLE + /* Come from Secure: activate counter 0 (write to 0 are ignored) */ + mov r4, #0x00000001 + + /* Write Count Enable Set Register */ + mcr p15, 0x0, r4, c9, c12, 1 + + /* Come from Secure: stop counter 1 (write to 0 are ignored) */ + mov r4, #0x00000002 + + /* Write Count Enable Clear Register */ + mcr p15, 0x0, r4, c9, c12, 2 +#endif + + INVALIDATE_BTB + + /* Restore registers */ + pop {r4-r12, pc} + +rpc_handler: + .global rpc_handler + +#ifdef CONFIG_BENCH_SECURE_CYCLE + /* Come from Secure: activate counter 0 (write to 0 are ignored) */ + mov r4, #0x00000001 + + /* Write Count Enable Set Register */ + mcr p15, 0x0, r4, c9, c12, 1 + + /* Come from Secure: stop counter 1 (write to 0 are ignored) */ + mov r4, #0x00000002 + + /* Write Count Enable Clear Register */ + mcr p15, 0x0, r4, c9, c12, 2 +#endif + INVALIDATE_BTB + + /* g_RPC_advancement = RPC_ADVANCEMENT_PENDING */ + ldr r8, =g_RPC_advancement + mov r9, #RPC_ADVANCEMENT_PENDING + str r9, [r8] + + ldr r8, =g_RPC_parameters + stm r8, {r0-r3} + + ldr r8, =g_secure_task_id + str r6, [r8] + + mov r0, #0x00 + ldr r8, =g_service_end + str r0, [r8] + + /* Restore registers */ + pop {r4-r12, pc} + +#ifdef CONFIG_BENCH_SECURE_CYCLE + +setup_counters: + .global setup_counters + + push {r14} + + mrc p15, 0, r2, c9, c12, 0 + orr r2, r2, #0x3 + mcr p15, 0, r2, c9, c12, 0 + + mrc p15, 0, r2, c9, c12, 1 + orr r2, r2, #0x80000000 + mcr p15, 0, r2, c9, c12, 1 + + pop {pc} + +run_code_speed: + .global run_code_speed + + push {r14} + + /* Reset cycle counter */ + mov r2, #0 + mcr p15, 0, r2, c9, c13, 0 + +run_code_speed_loop: + sub r0, r0, #1 + cmp r0, #0 + bne run_code_speed_loop + + /* Read cycle counter */ + mrc p15, 0, r0, c9, c13, 0 + + pop {pc} + +run_data_speed: + .global run_data_speed + + push {r14} + + /* Reset cycle counter */ + mov r2, #0 + mcr p15, 0, r2, c9, c13, 0 + +run_data_speed_loop: + sub r0, r0, #1 + ldr r2, [r1] + cmp r0, #0 + bne run_data_speed_loop + + /* read cycle counter */ + mrc p15, 0, r0, c9, c13, 0 + + pop {pc} + +#endif + +read_mpidr: + .global read_mpidr + mrc p15, 0, r0, c0, c0, 5 + bx lr diff --git a/security/smc/omap4/Makefile b/security/smc/omap4/Makefile deleted file mode 100644 index de75cc2..0000000 --- a/security/smc/omap4/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (c) 2006-2010 Trusted Logic S.A. -# 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 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 -# - -ifdef S_VERSION_BUILD -EXTRA_CFLAGS += -DS_VERSION_BUILD=$(S_VERSION_BUILD) -endif - -EXTRA_CFLAGS += -Iarch/arm/mach-omap2 - -tf_driver-objs += scxlnx_util.o -tf_driver-objs += scxlnx_device.o -tf_driver-objs += scx_public_crypto.o -tf_driver-objs += scx_public_crypto_Digest.o -tf_driver-objs += scx_public_crypto_AES.o -tf_driver-objs += scx_public_dma.o -tf_driver-objs += scxlnx_comm_mshield.o - -obj-$(CONFIG_SECURITY_MIDDLEWARE_COMPONENT) += tf_driver.o diff --git a/security/smc/omap4/scx_protocol.h b/security/smc/omap4/scx_protocol.h deleted file mode 100644 index 80653eb..0000000 --- a/security/smc/omap4/scx_protocol.h +++ /dev/null @@ -1,676 +0,0 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. - * 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., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - */ - -#ifndef __SCX_PROTOCOL_H__ -#define __SCX_PROTOCOL_H__ - -/*---------------------------------------------------------------------------- - * - * This header file defines the structure used in the SChannel Protocol. - * See your Product Reference Manual for a specification of the SChannel - * protocol. - *---------------------------------------------------------------------------*/ - -/* - * The driver interface version returned by the version ioctl - */ -#define SCX_DRIVER_INTERFACE_VERSION 0x04000000 - -/* - * Protocol version handling - */ -#define SCX_S_PROTOCOL_MAJOR_VERSION (0x06) -#define GET_PROTOCOL_MAJOR_VERSION(a) (a >> 24) -#define GET_PROTOCOL_MINOR_VERSION(a) ((a >> 16) & 0xFF) - -/* - * The size, in bytes, of the L1 Shared Buffer. - */ -#define SCX_COMM_BUFFER_SIZE (0x1000) /* 4kB*/ - -/* - * The S flag of the nConfigFlags_S register. - */ -#define SCX_CONFIG_FLAG_S (1 << 3) - -/* - * The TimeSlot field of the nSyncSerial_N register. - */ -#define SCX_SYNC_SERIAL_TIMESLOT_N (1) - -/* - * nStatus_S related defines. - */ -#define SCX_STATUS_P_MASK (0X00000001) -#define SCX_STATUS_POWER_STATE_SHIFT (3) -#define SCX_STATUS_POWER_STATE_MASK (0x1F << SCX_STATUS_POWER_STATE_SHIFT) - -/* - * Possible power states of the POWER_STATE field of the nStatus_S register - */ -#define SCX_POWER_MODE_COLD_BOOT (0) -#define SCX_POWER_MODE_WARM_BOOT (1) -#define SCX_POWER_MODE_ACTIVE (3) -#define SCX_POWER_MODE_READY_TO_SHUTDOWN (5) -#define SCX_POWER_MODE_READY_TO_HIBERNATE (7) -#define SCX_POWER_MODE_WAKEUP (8) -#define SCX_POWER_MODE_PANIC (15) - -/* - * Possible nCommand values for MANAGEMENT commands - */ -#define SCX_MANAGEMENT_HIBERNATE (1) -#define SCX_MANAGEMENT_SHUTDOWN (2) -#define SCX_MANAGEMENT_PREPARE_FOR_CORE_OFF (3) -#define SCX_MANAGEMENT_RESUME_FROM_CORE_OFF (4) - -/* - * The capacity of the Normal Word message queue, in number of slots. - */ -#define SCX_N_MESSAGE_QUEUE_CAPACITY (512) - -/* - * The capacity of the Secure World message answer queue, in number of slots. - */ -#define SCX_S_ANSWER_QUEUE_CAPACITY (256) - -/* - * The value of the S-timeout register indicating an infinite timeout. - */ -#define SCX_S_TIMEOUT_0_INFINITE (0xFFFFFFFF) -#define SCX_S_TIMEOUT_1_INFINITE (0xFFFFFFFF) - -/* - * The value of the S-timeout register indicating an immediate timeout. - */ -#define SCX_S_TIMEOUT_0_IMMEDIATE (0x0) -#define SCX_S_TIMEOUT_1_IMMEDIATE (0x0) - -/* - * Identifies the get protocol version SMC. - */ -#define SCX_SMC_GET_PROTOCOL_VERSION (0XFFFFFFFB) - -/* - * Identifies the init SMC. - */ -#define SCX_SMC_INIT (0XFFFFFFFF) - -/* - * Identifies the reset irq SMC. - */ -#define SCX_SMC_RESET_IRQ (0xFFFFFFFE) - -/* - * Identifies the SET_W3B SMC. - */ -#define SCX_SMC_WAKE_UP (0xFFFFFFFD) - -/* - * Identifies the STOP SMC. - */ -#define SCX_SMC_STOP (0xFFFFFFFC) - -/* - * Identifies the n-yield SMC. - */ -#define SCX_SMC_N_YIELD (0X00000003) - - -/* Possible stop commands for SMC_STOP */ -#define SCSTOP_HIBERNATE (0xFFFFFFE1) -#define SCSTOP_SHUTDOWN (0xFFFFFFE2) - -/* - * representation of an UUID. - */ -struct SCX_UUID { - u32 time_low; - u16 time_mid; - u16 time_hi_and_version; - u8 clock_seq_and_node[8]; -}; - - -/** - * Command parameters. - */ -struct SCX_COMMAND_PARAM_VALUE { - u32 a; - u32 b; -}; - -struct SCX_COMMAND_PARAM_TEMP_MEMREF { - u32 nDescriptor; /* data pointer for exchange message.*/ - u32 nSize; - u32 nOffset; -}; - -struct SCX_COMMAND_PARAM_MEMREF { - u32 hBlock; - u32 nSize; - u32 nOffset; -}; - -union SCX_COMMAND_PARAM { - struct SCX_COMMAND_PARAM_VALUE sValue; - struct SCX_COMMAND_PARAM_TEMP_MEMREF sTempMemref; - struct SCX_COMMAND_PARAM_MEMREF sMemref; -}; - -/** - * Answer parameters. - */ -struct SCX_ANSWER_PARAM_VALUE { - u32 a; - u32 b; -}; - -struct SCX_ANSWER_PARAM_SIZE { - u32 _ignored; - u32 nSize; -}; - -union SCX_ANSWER_PARAM { - struct SCX_ANSWER_PARAM_SIZE sSize; - struct SCX_ANSWER_PARAM_VALUE sValue; -}; - -/* - * Descriptor tables capacity - */ -#define SCX_MAX_W3B_COARSE_PAGES (2) -#define SCX_MAX_COARSE_PAGES (8) -#define SCX_DESCRIPTOR_TABLE_CAPACITY_BIT_SHIFT (8) -#define SCX_DESCRIPTOR_TABLE_CAPACITY \ - (1 << SCX_DESCRIPTOR_TABLE_CAPACITY_BIT_SHIFT) -#define SCX_DESCRIPTOR_TABLE_CAPACITY_MASK \ - (SCX_DESCRIPTOR_TABLE_CAPACITY - 1) -/* Shared memories coarse pages can map up to 1MB */ -#define SCX_MAX_COARSE_PAGE_MAPPED_SIZE \ - (PAGE_SIZE * SCX_DESCRIPTOR_TABLE_CAPACITY) -/* Shared memories cannot exceed 8MB */ -#define SCX_MAX_SHMEM_SIZE \ - (SCX_MAX_COARSE_PAGE_MAPPED_SIZE << 3) - -/* - * Buffer size for version description fields - */ -#define SCX_DESCRIPTION_BUFFER_LENGTH 64 - -/* - * Shared memory type flags. - */ -#define SCX_SHMEM_TYPE_READ (0x00000001) -#define SCX_SHMEM_TYPE_WRITE (0x00000002) - -/* - * Shared mem flags - */ -#define SCX_SHARED_MEM_FLAG_INPUT 1 -#define SCX_SHARED_MEM_FLAG_OUTPUT 2 -#define SCX_SHARED_MEM_FLAG_INOUT 3 - - -/* - * Parameter types - */ -#define SCX_PARAM_TYPE_NONE 0x0 -#define SCX_PARAM_TYPE_VALUE_INPUT 0x1 -#define SCX_PARAM_TYPE_VALUE_OUTPUT 0x2 -#define SCX_PARAM_TYPE_VALUE_INOUT 0x3 -#define SCX_PARAM_TYPE_MEMREF_TEMP_INPUT 0x5 -#define SCX_PARAM_TYPE_MEMREF_TEMP_OUTPUT 0x6 -#define SCX_PARAM_TYPE_MEMREF_TEMP_INOUT 0x7 -#define SCX_PARAM_TYPE_MEMREF_INPUT 0xD -#define SCX_PARAM_TYPE_MEMREF_OUTPUT 0xE -#define SCX_PARAM_TYPE_MEMREF_INOUT 0xF - -#define SCX_PARAM_TYPE_MEMREF_FLAG 0x4 -#define SCX_PARAM_TYPE_REGISTERED_MEMREF_FLAG 0x8 - - -#define SCX_MAKE_PARAM_TYPES(t0, t1, t2, t3) \ - ((t0) | ((t1) << 4) | ((t2) << 8) | ((t3) << 12)) -#define SCX_GET_PARAM_TYPE(t, i) (((t) >> (4 * i)) & 0xF) - -/* - * Login types. - */ -#define SCX_LOGIN_PUBLIC 0x00000000 -#define SCX_LOGIN_USER 0x00000001 -#define SCX_LOGIN_GROUP 0x00000002 -#define SCX_LOGIN_APPLICATION 0x00000004 -#define SCX_LOGIN_APPLICATION_USER 0x00000005 -#define SCX_LOGIN_APPLICATION_GROUP 0x00000006 -#define SCX_LOGIN_AUTHENTICATION 0x80000000 -#define SCX_LOGIN_PRIVILEGED 0x80000002 - -/* Login variants */ - -#define SCX_LOGIN_VARIANT(mainType, os, variant) \ - ((mainType) | (1 << 27) | ((os) << 16) | ((variant) << 8)) - -#define SCX_LOGIN_GET_MAIN_TYPE(type) \ - ((type) & ~SCX_LOGIN_VARIANT(0, 0xFF, 0xFF)) - -#define SCX_LOGIN_OS_ANY 0x00 -#define SCX_LOGIN_OS_LINUX 0x01 -#define SCX_LOGIN_OS_ANDROID 0x04 - -/* OS-independent variants */ -#define SCX_LOGIN_USER_NONE \ - SCX_LOGIN_VARIANT(SCX_LOGIN_USER, SCX_LOGIN_OS_ANY, 0xFF) -#define SCX_LOGIN_GROUP_NONE \ - SCX_LOGIN_VARIANT(SCX_LOGIN_GROUP, SCX_LOGIN_OS_ANY, 0xFF) -#define SCX_LOGIN_APPLICATION_USER_NONE \ - SCX_LOGIN_VARIANT(SCX_LOGIN_APPLICATION_USER, SCX_LOGIN_OS_ANY, 0xFF) -#define SCX_LOGIN_AUTHENTICATION_BINARY_SHA1_HASH \ - SCX_LOGIN_VARIANT(SCX_LOGIN_AUTHENTICATION, SCX_LOGIN_OS_ANY, 0x01) -#define SCX_LOGIN_PRIVILEGED_KERNEL \ - SCX_LOGIN_VARIANT(SCX_LOGIN_PRIVILEGED, SCX_LOGIN_OS_ANY, 0x01) - -/* Linux variants */ -#define SCX_LOGIN_USER_LINUX_EUID \ - SCX_LOGIN_VARIANT(SCX_LOGIN_USER, SCX_LOGIN_OS_LINUX, 0x01) -#define SCX_LOGIN_GROUP_LINUX_GID \ - SCX_LOGIN_VARIANT(SCX_LOGIN_GROUP, SCX_LOGIN_OS_LINUX, 0x01) -#define SCX_LOGIN_APPLICATION_LINUX_PATH_SHA1_HASH \ - SCX_LOGIN_VARIANT(SCX_LOGIN_APPLICATION, SCX_LOGIN_OS_LINUX, 0x01) -#define SCX_LOGIN_APPLICATION_USER_LINUX_PATH_EUID_SHA1_HASH \ - SCX_LOGIN_VARIANT(SCX_LOGIN_APPLICATION_USER, SCX_LOGIN_OS_LINUX, 0x01) -#define SCX_LOGIN_APPLICATION_GROUP_LINUX_PATH_GID_SHA1_HASH \ - SCX_LOGIN_VARIANT(SCX_LOGIN_APPLICATION_GROUP, SCX_LOGIN_OS_LINUX, 0x01) - -/* Android variants */ -#define SCX_LOGIN_USER_ANDROID_EUID \ - SCX_LOGIN_VARIANT(SCX_LOGIN_USER, SCX_LOGIN_OS_ANDROID, 0x01) -#define SCX_LOGIN_GROUP_ANDROID_GID \ - SCX_LOGIN_VARIANT(SCX_LOGIN_GROUP, SCX_LOGIN_OS_ANDROID, 0x01) -#define SCX_LOGIN_APPLICATION_ANDROID_UID \ - SCX_LOGIN_VARIANT(SCX_LOGIN_APPLICATION, SCX_LOGIN_OS_ANDROID, 0x01) -#define SCX_LOGIN_APPLICATION_USER_ANDROID_UID_EUID \ - SCX_LOGIN_VARIANT(SCX_LOGIN_APPLICATION_USER, SCX_LOGIN_OS_ANDROID, \ - 0x01) -#define SCX_LOGIN_APPLICATION_GROUP_ANDROID_UID_GID \ - SCX_LOGIN_VARIANT(SCX_LOGIN_APPLICATION_GROUP, SCX_LOGIN_OS_ANDROID, \ - 0x01) - -/* - * return origins - */ -#define SCX_ORIGIN_COMMS 2 -#define SCX_ORIGIN_TEE 3 -#define SCX_ORIGIN_TRUSTED_APP 4 -/* - * The SCX message types. - */ -#define SCX_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT 0x02 -#define SCX_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT 0xFD -#define SCX_MESSAGE_TYPE_REGISTER_SHARED_MEMORY 0xF7 -#define SCX_MESSAGE_TYPE_RELEASE_SHARED_MEMORY 0xF9 -#define SCX_MESSAGE_TYPE_OPEN_CLIENT_SESSION 0xF0 -#define SCX_MESSAGE_TYPE_CLOSE_CLIENT_SESSION 0xF2 -#define SCX_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND 0xF5 -#define SCX_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND 0xF4 -#define SCX_MESSAGE_TYPE_MANAGEMENT 0xFE - - -/* - * The error codes - */ -#define S_SUCCESS 0x00000000 -#define S_ERROR_NO_DATA 0xFFFF000B -#define S_ERROR_OUT_OF_MEMORY 0xFFFF000C - - -struct SCX_COMMAND_HEADER { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo; - u32 nOperationID; -}; - -struct SCX_ANSWER_HEADER { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo; - u32 nOperationID; - u32 nErrorCode; -}; - -/* - * CREATE_DEVICE_CONTEXT command message. - */ -struct SCX_COMMAND_CREATE_DEVICE_CONTEXT { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - u32 nOperationID; - u32 nDeviceContextID; -}; - -/* - * CREATE_DEVICE_CONTEXT answer message. - */ -struct SCX_ANSWER_CREATE_DEVICE_CONTEXT { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - /* an opaque Normal World identifier for the operation */ - u32 nOperationID; - u32 nErrorCode; - /* an opaque Normal World identifier for the device context */ - u32 hDeviceContext; -}; - -/* - * DESTROY_DEVICE_CONTEXT command message. - */ -struct SCX_COMMAND_DESTROY_DEVICE_CONTEXT { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - u32 nOperationID; - u32 hDeviceContext; -}; - -/* - * DESTROY_DEVICE_CONTEXT answer message. - */ -struct SCX_ANSWER_DESTROY_DEVICE_CONTEXT { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - /* an opaque Normal World identifier for the operation */ - u32 nOperationID; - u32 nErrorCode; - u32 nDeviceContextID; -}; - -/* - * OPEN_CLIENT_SESSION command message. - */ -struct SCX_COMMAND_OPEN_CLIENT_SESSION { - u8 nMessageSize; - u8 nMessageType; - u16 nParamTypes; - /* an opaque Normal World identifier for the operation */ - u32 nOperationID; - u32 hDeviceContext; - u32 nCancellationID; - u64 sTimeout; - struct SCX_UUID sDestinationUUID; - union SCX_COMMAND_PARAM sParams[4]; - u32 nLoginType; - /* - * Size = 0 for public, [16] for group identification, [20] for - * authentication - */ - u8 sLoginData[20]; -}; - -/* - * OPEN_CLIENT_SESSION answer message. - */ -struct SCX_ANSWER_OPEN_CLIENT_SESSION { - u8 nMessageSize; - u8 nMessageType; - u8 nReturnOrigin; - u8 __nReserved; - /* an opaque Normal World identifier for the operation */ - u32 nOperationID; - u32 nErrorCode; - u32 hClientSession; - union SCX_ANSWER_PARAM sAnswers[4]; -}; - -/* - * CLOSE_CLIENT_SESSION command message. - */ -struct SCX_COMMAND_CLOSE_CLIENT_SESSION { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - /* an opaque Normal World identifier for the operation */ - u32 nOperationID; - u32 hDeviceContext; - u32 hClientSession; -}; - -/* - * CLOSE_CLIENT_SESSION answer message. - */ -struct SCX_ANSWER_CLOSE_CLIENT_SESSION { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - /* an opaque Normal World identifier for the operation */ - u32 nOperationID; - u32 nErrorCode; -}; - - -/* - * REGISTER_SHARED_MEMORY command message - */ -struct SCX_COMMAND_REGISTER_SHARED_MEMORY { - u8 nMessageSize; - u8 nMessageType; - u16 nMemoryFlags; - u32 nOperationID; - u32 hDeviceContext; - u32 nBlockID; - u32 nSharedMemSize; - u32 nSharedMemStartOffset; - u32 nSharedMemDescriptors[SCX_MAX_COARSE_PAGES]; -}; - -/* - * REGISTER_SHARED_MEMORY answer message. - */ -struct SCX_ANSWER_REGISTER_SHARED_MEMORY { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - /* an opaque Normal World identifier for the operation */ - u32 nOperationID; - u32 nErrorCode; - u32 hBlock; -}; - -/* - * RELEASE_SHARED_MEMORY command message. - */ -struct SCX_COMMAND_RELEASE_SHARED_MEMORY { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - /* an opaque Normal World identifier for the operation */ - u32 nOperationID; - u32 hDeviceContext; - u32 hBlock; -}; - -/* - * RELEASE_SHARED_MEMORY answer message. - */ -struct SCX_ANSWER_RELEASE_SHARED_MEMORY { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - u32 nOperationID; - u32 nErrorCode; - u32 nBlockID; -}; - -/* - * INVOKE_CLIENT_COMMAND command message. - */ -struct SCX_COMMAND_INVOKE_CLIENT_COMMAND { - u8 nMessageSize; - u8 nMessageType; - u16 nParamTypes; - u32 nOperationID; - u32 hDeviceContext; - u32 hClientSession; - u64 sTimeout; - u32 nCancellationID; - u32 nClientCommandIdentifier; - union SCX_COMMAND_PARAM sParams[4]; -}; - -/* - * INVOKE_CLIENT_COMMAND command answer. - */ -struct SCX_ANSWER_INVOKE_CLIENT_COMMAND { - u8 nMessageSize; - u8 nMessageType; - u8 nReturnOrigin; - u8 __nReserved; - u32 nOperationID; - u32 nErrorCode; - union SCX_ANSWER_PARAM sAnswers[4]; -}; - -/* - * CANCEL_CLIENT_OPERATION command message. - */ -struct SCX_COMMAND_CANCEL_CLIENT_OPERATION { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - /* an opaque Normal World identifier for the operation */ - u32 nOperationID; - u32 hDeviceContext; - u32 hClientSession; - u32 nCancellationID; -}; - -struct SCX_ANSWER_CANCEL_CLIENT_OPERATION { - u8 nMessageSize; - u8 nMessageType; - u16 nMessageInfo_RFU; - u32 nOperationID; - u32 nErrorCode; -}; - -/* - * MANAGEMENT command message. - */ -struct SCX_COMMAND_MANAGEMENT { - u8 nMessageSize; - u8 nMessageType; - u16 nCommand; - u32 nOperationID; - u32 nW3BSize; - u32 nW3BStartOffset; - u32 nSharedMemDescriptors[1]; -}; - -/* - * POWER_MANAGEMENT answer message. - * The message does not provide message specific parameters. - * Therefore no need to define a specific answer structure - */ - -/* - * Structure for L2 messages - */ -union SCX_COMMAND_MESSAGE { - struct SCX_COMMAND_HEADER sHeader; - struct SCX_COMMAND_CREATE_DEVICE_CONTEXT sCreateDeviceContextMessage; - struct SCX_COMMAND_DESTROY_DEVICE_CONTEXT sDestroyDeviceContextMessage; - struct SCX_COMMAND_OPEN_CLIENT_SESSION sOpenClientSessionMessage; - struct SCX_COMMAND_CLOSE_CLIENT_SESSION sCloseClientSessionMessage; - struct SCX_COMMAND_REGISTER_SHARED_MEMORY sRegisterSharedMemoryMessage; - struct SCX_COMMAND_RELEASE_SHARED_MEMORY sReleaseSharedMemoryMessage; - struct SCX_COMMAND_INVOKE_CLIENT_COMMAND sInvokeClientCommandMessage; - struct SCX_COMMAND_CANCEL_CLIENT_OPERATION - sCancelClientOperationMessage; - struct SCX_COMMAND_MANAGEMENT sManagementMessage; -}; - -/* - * Structure for any L2 answer - */ - -union SCX_ANSWER_MESSAGE { - struct SCX_ANSWER_HEADER sHeader; - struct SCX_ANSWER_CREATE_DEVICE_CONTEXT sCreateDeviceContextAnswer; - struct SCX_ANSWER_OPEN_CLIENT_SESSION sOpenClientSessionAnswer; - struct SCX_ANSWER_CLOSE_CLIENT_SESSION sCloseClientSessionAnswer; - struct SCX_ANSWER_REGISTER_SHARED_MEMORY sRegisterSharedMemoryAnswer; - struct SCX_ANSWER_RELEASE_SHARED_MEMORY sReleaseSharedMemoryAnswer; - struct SCX_ANSWER_INVOKE_CLIENT_COMMAND sInvokeClientCommandAnswer; - struct SCX_ANSWER_DESTROY_DEVICE_CONTEXT sDestroyDeviceContextAnswer; - struct SCX_ANSWER_CANCEL_CLIENT_OPERATION sCancelClientOperationAnswer; -}; - -/* Structure of the Communication Buffer */ -struct SCHANNEL_C1S_BUFFER { - u32 nConfigFlags_S; - u32 nW3BSizeMax_S; - u32 nReserved0; - u32 nW3BSizeCurrent_S; - u8 sReserved1[48]; - u8 sVersionDescription[SCX_DESCRIPTION_BUFFER_LENGTH]; - u32 nStatus_S; - u32 sReserved2; - u32 nSyncSerial_N; - u32 nSyncSerial_S; - u64 sTime_N[2]; - u64 sTimeout_S[2]; - u32 nFirstCommand; - u32 nFirstFreeCommand; - u32 nFirstAnswer; - u32 nFirstFreeAnswer; - u32 nW3BDescriptors[128]; - #ifdef CONFIG_TF_MSHIELD - u8 sRPCTraceBuffer[140]; - u8 sRPCShortcutBuffer[180]; - #else - u8 sReserved3[320]; - #endif - u32 sCommandQueue[SCX_N_MESSAGE_QUEUE_CAPACITY]; - u32 sAnswerQueue[SCX_S_ANSWER_QUEUE_CAPACITY]; -}; - - -/* - * SCX_VERSION_INFORMATION_BUFFER structure description - * Description of the sVersionBuffer handed over from user space to kernel space - * This field is filled by the driver during a CREATE_DEVICE_CONTEXT ioctl - * and handed back to user space - */ -struct SCX_VERSION_INFORMATION_BUFFER { - u8 sDriverDescription[65]; - u8 sSecureWorldDescription[65]; -}; - - -/* The IOCTLs the driver supports */ -#include <linux/ioctl.h> - -#define IOCTL_SCX_GET_VERSION _IO('z', 0) -#define IOCTL_SCX_EXCHANGE _IOWR('z', 1, union SCX_COMMAND_MESSAGE) -#define IOCTL_SCX_GET_DESCRIPTION _IOR('z', 2, \ - struct SCX_VERSION_INFORMATION_BUFFER) - -#endif /* !defined(__SCX_PROTOCOL_H__) */ diff --git a/security/smc/omap4/scx_public_crypto.c b/security/smc/omap4/scx_public_crypto.c deleted file mode 100644 index d6b751c..0000000 --- a/security/smc/omap4/scx_public_crypto.c +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. - * 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., 59 Temple - * Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "scxlnx_defs.h" -#include "scxlnx_util.h" -#include "scxlnx_mshield.h" -#include "scx_public_crypto.h" -#include "scx_public_dma.h" - -#define IO_ADDRESS OMAP2_L4_IO_ADDRESS - -#define S_SUCCESS 0x00000000 -#define S_ERROR_GENERIC 0xFFFF0000 -#define S_ERROR_ACCESS_DENIED 0xFFFF0001 -#define S_ERROR_BAD_FORMAT 0xFFFF0005 -#define S_ERROR_BAD_PARAMETERS 0xFFFF0006 -#define S_ERROR_OUT_OF_MEMORY 0xFFFF000C -#define S_ERROR_SHORT_BUFFER 0xFFFF0010 -#define S_ERROR_UNREACHABLE 0xFFFF3013 -#define S_ERROR_SERVICE 0xFFFF1000 - -#define CKR_OK 0x00000000 - -#define PUBLIC_CRYPTO_TIMEOUT_CONST 0x000FFFFF - -#define RPC_AES1_CODE PUBLIC_CRYPTO_HWA_AES1 -#define RPC_AES2_CODE PUBLIC_CRYPTO_HWA_AES2 -#define RPC_DES_CODE PUBLIC_CRYPTO_HWA_DES -#define RPC_SHA_CODE PUBLIC_CRYPTO_HWA_SHA - -#define RPC_CRYPTO_COMMAND_MASK 0x000003c0 - -#define RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR 0x200 -#define RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_UNLOCK 0x000 -#define RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_LOCK 0x001 - -#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT 0x240 -#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_AES1 RPC_AES1_CODE -#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_AES2 RPC_AES2_CODE -#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_DES RPC_DES_CODE -#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_SHA RPC_SHA_CODE -#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_SUSPEND 0x010 -#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_UNINSTALL 0x020 - -#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS 0x280 -#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_AES1 RPC_AES1_CODE -#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_AES2 RPC_AES2_CODE -#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_DES RPC_DES_CODE -#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_SHA RPC_SHA_CODE -#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_RESUME 0x010 - -#define RPC_CLEAR_GLOBAL_KEY_CONTEXT 0x2c0 -#define RPC_CLEAR_GLOBAL_KEY_CONTEXT_CLEARED_AES 0x001 -#define RPC_CLEAR_GLOBAL_KEY_CONTEXT_CLEARED_DES 0x002 - -#define ENABLE_CLOCK true -#define DISABLE_CLOCK false - -/*---------------------------------------------------------------------------*/ -/*RPC IN/OUT structures for CUS implementation */ -/*---------------------------------------------------------------------------*/ - -struct RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_OUT { - u32 nShortcutID; - u32 nError; -}; - -struct RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_IN { - u32 nDeviceContextID; - u32 hClientSession; - u32 nCommandID; - u32 hKeyContext; - /** - *The identifier of the HWA accelerator that this shortcut uses! - *Possible values are: - *- 1 (RPC_AES1_CODE) - *- 2 (RPC_AES2_CODE) - *- 4 (RPC_DES_CODE) - *- 8 (RPC_SHA_CODE) - **/ - u32 nHWAID; - /** - *This field defines the algorithm, direction, mode, key size. - *It contains some of the bits of the corresponding "CTRL" register - *of the accelerator. - * - *More precisely: - *For AES1 accelerator, nHWA_CTRL contains the following bits: - *- CTR (bit 6): - * when 1, selects CTR mode. - * when 0, selects CBC or ECB mode (according to CBC bit) - *- CBC (bit 5) - * when 1, selects CBC mode (but only if CTR=0) - * when 0, selects EBC mode (but only if CTR=0) - *- DIRECTION (bit 2) - * 0: decryption - * 1: encryption - * - *For the DES2 accelerator, nHWA_CTRL contains the following bits: - *- CBC (bit 4): 1 for CBC, 0 for ECB - *- DIRECTION (bit 2): 0 for decryption, 1 for encryption - * - *For the SHA accelerator, nHWA_CTRL contains the following bits: - *- ALGO (bit 2:1): - * 0x0: MD5 - * 0x1: SHA1 - * 0x2: SHA-224 - * 0x3: SHA-256 - **/ - u32 nHWA_CTRL; - union PUBLIC_CRYPTO_OPERATION_STATE sOperationState; -}; - -struct RPC_LOCK_HWA_SUSPEND_SHORTCUT_OUT { - union PUBLIC_CRYPTO_OPERATION_STATE sOperationState; -}; - -struct RPC_LOCK_HWA_SUSPEND_SHORTCUT_IN { - u32 nShortcutID; -}; - -struct RPC_RESUME_SHORTCUT_UNLOCK_HWA_IN { - u32 nShortcutID; - u32 hAES1KeyContext; - u32 hAES2KeyContext; - u32 hDESKeyContext; - union PUBLIC_CRYPTO_OPERATION_STATE sOperationState; -}; - -/*------------------------------------------------------------------------- */ -/* - * HWA public lock or unlock one HWA according algo specified by nHWAID - */ -void PDrvCryptoLockUnlockHWA(u32 nHWAID, bool bDoLock) -{ - int is_sem = 0; - struct semaphore *s = NULL; - struct mutex *m = NULL; - struct SCXLNX_DEVICE *dev = SCXLNXGetDevice(); - - dprintk(KERN_INFO "PDrvCryptoLockUnlockHWA:nHWAID=0x%04X bDoLock=%d\n", - nHWAID, bDoLock); - - switch (nHWAID) { - case RPC_AES1_CODE: - s = &dev->sAES1CriticalSection; - is_sem = 1; - break; - case RPC_AES2_CODE: - s = &dev->sAES2CriticalSection; - is_sem = 1; - break; - case RPC_DES_CODE: - m = &dev->sDESCriticalSection; - break; - default: - case RPC_SHA_CODE: - m = &dev->sSHACriticalSection; - break; - } - - if (bDoLock == LOCK_HWA) { - dprintk(KERN_INFO "PDrvCryptoLockUnlockHWA: " - "Wait for HWAID=0x%04X\n", nHWAID); - if (is_sem) { - while (down_trylock(s)) - cpu_relax(); - } else { - while (!mutex_trylock(m)) - cpu_relax(); - } - dprintk(KERN_INFO "PDrvCryptoLockUnlockHWA: " - "Locked on HWAID=0x%04X\n", nHWAID); - } else { - if (is_sem) - up(s); - else - mutex_unlock(m); - dprintk(KERN_INFO "PDrvCryptoLockUnlockHWA: " - "Released for HWAID=0x%04X\n", nHWAID); - } -} - -/*------------------------------------------------------------------------- */ -/** - *Initialize the public crypto DMA channels, global HWA semaphores and handles - */ -u32 SCXPublicCryptoInit(void) -{ - struct SCXLNX_DEVICE *pDevice = SCXLNXGetDevice(); - u32 nError = PUBLIC_CRYPTO_OPERATION_SUCCESS; - - /* Initialize HWAs */ - PDrvCryptoAESInit(); - PDrvCryptoDigestInit(); - - /*initialize the HWA semaphores */ - sema_init(&pDevice->sAES1CriticalSection, 1); - sema_init(&pDevice->sAES2CriticalSection, 1); - mutex_init(&pDevice->sSHACriticalSection); - - /*initialize the current key handle loaded in the AESn/DES HWA */ - pDevice->hAES1SecureKeyContext = 0; - pDevice->hAES2SecureKeyContext = 0; - pDevice->bSHAM1IsPublic = false; - - /*initialize the DMA semaphores */ - mutex_init(&pDevice->sm.sDMALock); - - /*allocate DMA buffer */ - pDevice->nDMABufferLength = PAGE_SIZE * 16; - pDevice->pDMABuffer = dma_alloc_coherent(NULL, - pDevice->nDMABufferLength, - &(pDevice->pDMABufferPhys), - GFP_KERNEL); - if (pDevice->pDMABuffer == NULL) { - printk(KERN_ERR - "SCXPublicCryptoInit: Out of memory for DMA buffer\n"); - nError = S_ERROR_OUT_OF_MEMORY; - } - - return nError; -} - -/*------------------------------------------------------------------------- */ -/* - *Initialize the device context CUS fields (shortcut semaphore and public CUS - *list) - */ -void SCXPublicCryptoInitDeviceContext(struct SCXLNX_CONNECTION *pDeviceContext) -{ - /*initialize the CUS list in the given device context */ - spin_lock_init(&(pDeviceContext->shortcutListCriticalSectionLock)); - INIT_LIST_HEAD(&(pDeviceContext->ShortcutList)); -} - -/*------------------------------------------------------------------------- */ -/** - *Terminate the public crypto (including DMA) - */ -void SCXPublicCryptoTerminate() -{ - struct SCXLNX_DEVICE *pDevice = SCXLNXGetDevice(); - - if (pDevice->pDMABuffer != NULL) { - dma_free_coherent(NULL, pDevice->nDMABufferLength, - pDevice->pDMABuffer, - pDevice->pDMABufferPhys); - pDevice->pDMABuffer = NULL; - } - - PDrvCryptoDigestExit(); - PDrvCryptoAESExit(); -} - -/*------------------------------------------------------------------------- */ - -void SCXPublicCryptoWaitForReadyBitInfinitely(u32 *pRegister, u32 vBit) -{ - while (!(INREG32(pRegister) & vBit)) - ; -} - -/*------------------------------------------------------------------------- */ - -u32 SCXPublicCryptoWaitForReadyBit(u32 *pRegister, u32 vBit) -{ - u32 timeoutCounter = PUBLIC_CRYPTO_TIMEOUT_CONST; - - while ((!(INREG32(pRegister) & vBit)) && ((--timeoutCounter) != 0)) - ; - - if (timeoutCounter == 0) - return PUBLIC_CRYPTO_ERR_TIMEOUT; - - return PUBLIC_CRYPTO_OPERATION_SUCCESS; -} - -/*------------------------------------------------------------------------- */ - -static DEFINE_SPINLOCK(clk_lock); - -void SCXPublicCryptoDisableClock(uint32_t vClockPhysAddr) -{ - u32 *pClockReg; - u32 val; - unsigned long flags; - - dprintk(KERN_INFO "SCXPublicCryptoDisableClock: " \ - "vClockPhysAddr=0x%08X\n", - vClockPhysAddr); - - /* Ensure none concurrent access when changing clock registers */ - spin_lock_irqsave(&clk_lock, flags); - - pClockReg = (u32 *)IO_ADDRESS(vClockPhysAddr); - - val = __raw_readl(pClockReg); - val &= ~(0x3); - __raw_writel(val, pClockReg); - - /* Wait for clock to be fully disabled */ - while ((__raw_readl(pClockReg) & 0x30000) == 0) - ; - - spin_unlock_irqrestore(&clk_lock, flags); - - tf_l4sec_clkdm_allow_idle(false, true); -} - -/*------------------------------------------------------------------------- */ - -void SCXPublicCryptoEnableClock(uint32_t vClockPhysAddr) -{ - u32 *pClockReg; - u32 val; - unsigned long flags; - - dprintk(KERN_INFO "SCXPublicCryptoEnableClock: " \ - "vClockPhysAddr=0x%08X\n", - vClockPhysAddr); - - tf_l4sec_clkdm_wakeup(false, true); - - /* Ensure none concurrent access when changing clock registers */ - spin_lock_irqsave(&clk_lock, flags); - - pClockReg = (u32 *)IO_ADDRESS(vClockPhysAddr); - - val = __raw_readl(pClockReg); - val |= 0x2; - __raw_writel(val, pClockReg); - - /* Wait for clock to be fully enabled */ - while ((__raw_readl(pClockReg) & 0x30000) != 0) - ; - - spin_unlock_irqrestore(&clk_lock, flags); -} - diff --git a/security/smc/omap4/scx_public_dma.c b/security/smc/omap4/scx_public_dma.c deleted file mode 100644 index 743c333..0000000 --- a/security/smc/omap4/scx_public_dma.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. - * 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., 59 Temple - * Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "scxlnx_defs.h" -#include "scxlnx_util.h" -#include "scx_public_dma.h" - -#include <asm/atomic.h> - -static atomic_t g_dmaEventFlag = ATOMIC_INIT(0); - -/*------------------------------------------------------------------------ */ -/* - * Internal functions - */ - -static void scxPublicDMACallback(int lch, u16 ch_status, void *data) -{ - atomic_inc(&g_dmaEventFlag); -} - -/*------------------------------------------------------------------------ */ -/* - * Public DMA API - */ - -u32 scxPublicDMARequest(int *lch) -{ - int dma_ch_out = 0; - - if (lch == NULL) - return PUBLIC_CRYPTO_ERR_BAD_PARAMETERS; - - if (omap_request_dma(0, "SMC Public Crypto", - scxPublicDMACallback, NULL, &dma_ch_out) != 0) - return PUBLIC_CRYPTO_ERR_OUT_OF_MEMORY; - - omap_disable_dma_irq(dma_ch_out, OMAP_DMA_DROP_IRQ | - OMAP_DMA_BLOCK_IRQ); - - *lch = dma_ch_out; - - return PUBLIC_CRYPTO_OPERATION_SUCCESS; -} - -/*------------------------------------------------------------------------ */ -/* - * Release a DMA channel - */ -u32 scxPublicDMARelease(int lch) -{ - omap_free_dma(lch); - - return PUBLIC_CRYPTO_OPERATION_SUCCESS; -} - -/*------------------------------------------------------------------------ */ - -void scxPublicDMASetParams(int lch, struct omap_dma_channel_params *pParams) -{ - omap_set_dma_params(lch, pParams); -} - -/*------------------------------------------------------------------------ */ - -void scxPublicDMAStart(int lch, int interruptMask) -{ - atomic_set(&g_dmaEventFlag, 0); - omap_enable_dma_irq(lch, interruptMask); - omap_start_dma(lch); -} - -/*------------------------------------------------------------------------ */ - -void scxPublicDMADisableChannel(int lch) -{ - omap_stop_dma(lch); -} - -/*------------------------------------------------------------------------ */ - -void scxPublicDMAClearChannel(int lch) -{ - omap_clear_dma(lch); -} - -/*------------------------------------------------------------------------ */ - -void scxPublicDMAWait(int nr_of_cb) -{ - while (atomic_read(&g_dmaEventFlag) < nr_of_cb) - cpu_relax(); -} - -/*------------------------------------------------------------------------ */ -/* - * Perform common DMA channel setup, used to factorize the code - * - * Output: struct omap_dma_channel_params *pDMAChannel - * Inputs: u32 nbBlocks Number of block of the transfer - * u32 nbElements Number of elements of the transfer - * u32 nDstStart Destination address - * u32 nSrcStart Source address - * u32 nTriggerID Trigger ID - */ -void scxPublicSetDMAChannelCommonParams( - struct omap_dma_channel_params *pDMAChannel, - u32 nbBlocks, u32 nbElements, - u32 nDstStart, u32 nSrcStart, u32 nTriggerID) -{ - pDMAChannel->data_type = OMAP_DMA_DATA_TYPE_S32; - pDMAChannel->elem_count = nbElements; - pDMAChannel->frame_count = nbBlocks; - pDMAChannel->src_ei = 0; - pDMAChannel->src_fi = 0; - pDMAChannel->dst_ei = 0; - pDMAChannel->dst_fi = 0; - pDMAChannel->sync_mode = OMAP_DMA_SYNC_FRAME; - pDMAChannel->src_start = nSrcStart; - pDMAChannel->dst_start = nDstStart; - pDMAChannel->trigger = nTriggerID; -} diff --git a/security/smc/omap4/scx_public_dma.h b/security/smc/omap4/scx_public_dma.h deleted file mode 100644 index ddd19b2..0000000 --- a/security/smc/omap4/scx_public_dma.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. - * 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., 59 Temple - * Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef __SCX_PUBLIC_DMA_H -#define __SCX_PUBLIC_DMA_H - -#include <linux/dma-mapping.h> -#include <plat/dma.h> -#include <plat/dma-44xx.h> - -#include "scx_public_crypto.h" - -/*--------------------------------------------------------------------------- - * Cache management (implemented in the assembler file) - *-------------------------------------------------------------------------- */ - -u32 v7_dma_flush_range(u32 nVAStart, u32 nVAEnd); -u32 v7_dma_inv_range(u32 nVAStart, u32 nVAEnd); - -/*-------------------------------------------------------------------------- */ -/* - * Public DMA API - */ - -/* - * CEN Masks - */ -#define DMA_CEN_Elts_per_Frame_AES 4 -#define DMA_CEN_Elts_per_Frame_DES 2 -#define DMA_CEN_Elts_per_Frame_SHA 16 - -/* - * Request a DMA channel - */ -u32 scxPublicDMARequest(int *lch); - -/* - * Release a DMA channel - */ -u32 scxPublicDMARelease(int lch); - -/** - * This function waits for the DMA IRQ. - */ -void scxPublicDMAWait(int nr_of_cb); - -/* - * This function starts a DMA operation. - * - * lch DMA channel ID. - * interruptMask Configures the Channel Interrupt Control Register. - */ -void scxPublicDMAStart(int lch, int interruptMask); - -void scxPublicSetDMAChannelCommonParams( - struct omap_dma_channel_params *pDMAChannel, - u32 nbBlocks, u32 nbElements, u32 nDstStart, - u32 nSrcStart, u32 nTriggerID); -void scxPublicDMASetParams(int lch, struct omap_dma_channel_params *pParams); -void scxPublicDMADisableChannel(int lch); -void scxPublicDMAClearChannel(int lch); - -#endif /*__SCX_PUBLIC_DMA_H */ diff --git a/security/smc/omap4/scxlnx_comm_mshield.c b/security/smc/omap4/scxlnx_comm_mshield.c deleted file mode 100644 index ccd2098..0000000 --- a/security/smc/omap4/scxlnx_comm_mshield.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2010 Trusted Logic S.A. - * 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., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - */ - -#include <asm/div64.h> -#include <asm/system.h> -#include <asm/cputype.h> -#include <linux/uaccess.h> -#include <linux/io.h> -#include <linux/interrupt.h> -#include <linux/page-flags.h> -#include <linux/pagemap.h> -#include <linux/vmalloc.h> -#include <linux/version.h> -#include <linux/jiffies.h> -#include <linux/dma-mapping.h> -#include <linux/cpu.h> - -#include <asm/cacheflush.h> - -#include <clockdomain.h> - -#include "scxlnx_defs.h" - -#ifdef CONFIG_HAS_WAKELOCK -static struct wake_lock g_tf_wake_lock; -static atomic_t tf_wake_lock_count = ATOMIC_INIT(0); -#endif - -static struct clockdomain *smc_l4_sec_clkdm; -static atomic_t smc_l4_sec_clkdm_use_count = ATOMIC_INIT(0); - -static int __init tf_early_init(void) -{ - smc_l4_sec_clkdm = clkdm_lookup("l4_secure_clkdm"); - if (smc_l4_sec_clkdm == NULL) - return -EFAULT; - -#ifdef CONFIG_HAS_WAKELOCK - wake_lock_init(&g_tf_wake_lock, WAKE_LOCK_SUSPEND, - SCXLNX_DEVICE_BASE_NAME); -#endif - - return 0; -} -early_initcall(tf_early_init); - -/*-------------------------------------------------------------------------- - * L4 SEC Clock domain handling - *-------------------------------------------------------------------------- */ - -void tf_l4sec_clkdm_wakeup(bool use_spin_lock, bool wakelock) -{ - if (use_spin_lock) - spin_lock(&SCXLNXGetDevice()->sm.lock); -#ifdef CONFIG_HAS_WAKELOCK - if (wakelock) { - atomic_inc(&tf_wake_lock_count); - wake_lock(&g_tf_wake_lock); - } -#endif - atomic_inc(&smc_l4_sec_clkdm_use_count); - clkdm_wakeup(smc_l4_sec_clkdm); - if (use_spin_lock) - spin_unlock(&SCXLNXGetDevice()->sm.lock); -} - -void tf_l4sec_clkdm_allow_idle(bool use_spin_lock, bool wakeunlock) -{ - if (use_spin_lock) - spin_lock(&SCXLNXGetDevice()->sm.lock); - if (atomic_dec_return(&smc_l4_sec_clkdm_use_count) == 0) - clkdm_allow_idle(smc_l4_sec_clkdm); -#ifdef CONFIG_HAS_WAKELOCK - if (wakeunlock) - if (atomic_dec_return(&tf_wake_lock_count) == 0) - wake_unlock(&g_tf_wake_lock); -#endif - if (use_spin_lock) - spin_unlock(&SCXLNXGetDevice()->sm.lock); -} - diff --git a/security/smc/omap4/scxlnx_device.c b/security/smc/omap4/scxlnx_device.c deleted file mode 100644 index cd9d56b..0000000 --- a/security/smc/omap4/scxlnx_device.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. - * 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., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - */ - -#include <asm/atomic.h> -#include <linux/uaccess.h> -#include <linux/module.h> -#include <linux/errno.h> -#include <linux/mm.h> -#include <linux/page-flags.h> -#include <linux/pm.h> -#include <linux/sysdev.h> -#include <linux/vmalloc.h> -#include <linux/signal.h> -#ifdef CONFIG_ANDROID -#include <linux/device.h> -#endif - -#include "scx_protocol.h" -#include "scxlnx_defs.h" -#include "scxlnx_util.h" -#ifdef CONFIG_TF_MSHIELD -#include <plat/cpu.h> -#include "scx_public_crypto.h" -#endif - -/* The single device supported by this driver */ -static struct SCXLNX_DEVICE g_SCXLNXDevice = {0, }; - -/*---------------------------------------------------------------------------- - * Implementations - *----------------------------------------------------------------------------*/ - -struct SCXLNX_DEVICE *SCXLNXGetDevice(void) -{ - return &g_SCXLNXDevice; -} - -/*----------------------------------------------------------------------------*/ - -static int __init register_dmcrypt_engines(void) -{ - int ret; - - printk(KERN_INFO "Entered register_dmcrypt_engines"); - - ret = SCXPublicCryptoInit(); - if (ret) { - printk(KERN_ERR "register_dmcrypt_engines():" - " SCXPublicCryptoInit failed, (error %d)!\n", ret); - goto out; - } - - ret = register_smc_public_crypto_aes(); - if (ret) { - printk(KERN_ERR "register_dmcrypt_engines():" - " regiser_smc_public_crypto_aes failed, (error %d)!\n", ret); - goto out; - } - - ret = register_smc_public_crypto_digest(); - if (ret) { - printk(KERN_ERR "register_dmcrypt_engines():" - " regiser_smc_public_crypto_digest failed, (error %d)!\n", ret); - goto out; - } - -out: - return ret; -} -module_init(register_dmcrypt_engines); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Trusted Logic S.A."); diff --git a/security/smc/rproc_drm.c b/security/smc/rproc_drm.c new file mode 100644 index 0000000..b86b0b8 --- /dev/null +++ b/security/smc/rproc_drm.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2011 Texas Instruments, Inc. + * Copyright (c) 2011 Trusted Logic S.A. + * + * 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. + */ + +/* + * This file implements the non-secure rproc and smc interface/integration + */ + +#include <linux/types.h> +#include <linux/module.h> + +#include "tee_client_api.h" +#include "tf_defs.h" + +/* 7B1DD682-1077-4939-9755-B6192C5CC5FD */ +#define WVDRM_UUID {0x7B1DD682, 0x1077, 0x4939, \ + {0x97, 0x55, 0xB6, 0x19, 0x2C, 0x5C, 0xC5, 0xFD} } + +#define WVDRM_ENTER_SECURE_PLAYBACK 0x00003000 + +#define WVDRM_EXIT_SECURE_PLAYBACK 0x00003001 + +enum rproc_drm_s_state { + RPROC_DRM_SECURE_LEAVE, + RPROC_DRM_SECURE_ENTER +}; + +static enum rproc_drm_s_state s_state; + +static TEEC_Result rproc_drm_initialize(TEEC_Context *teec_context, + TEEC_Session *teec_session) +{ + static const TEEC_UUID drm_uuid = WVDRM_UUID; + static u32 drm_gid = 1019; + TEEC_Result result; + + result = TEEC_InitializeContext(NULL, teec_context); + if (result != TEEC_SUCCESS) + goto exit; + + result = TEEC_OpenSession(teec_context, teec_session, &drm_uuid, + TEEC_LOGIN_PRIVILEGED, &drm_gid, NULL, NULL); + if (result != TEEC_SUCCESS) + TEEC_FinalizeContext(teec_context); + +exit: + return result; +} + +static TEEC_Result rproc_drm_finalize(TEEC_Context *teec_context, + TEEC_Session *teec_session) +{ + TEEC_CloseSession(teec_session); + TEEC_FinalizeContext(teec_context); + return TEEC_SUCCESS; +} + +static TEEC_Result _rproc_drm_invoke_secure_service(bool enable) +{ + TEEC_Result result; + TEEC_Operation operation; + TEEC_Context teec_context; + TEEC_Session teec_session; + u32 command; + + result = rproc_drm_initialize(&teec_context, &teec_session); + if (result != TEEC_SUCCESS) + goto out; + + operation.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE, TEEC_NONE, + TEEC_NONE, TEEC_NONE); + command = (enable ? WVDRM_ENTER_SECURE_PLAYBACK : + WVDRM_EXIT_SECURE_PLAYBACK); + result = TEEC_InvokeCommand(&teec_session, command, &operation, NULL); + rproc_drm_finalize(&teec_context, &teec_session); +out: + return result; +} + +int rproc_drm_invoke_service(bool enable) +{ + int ret; + + if ((s_state == RPROC_DRM_SECURE_ENTER && enable) || + (s_state == RPROC_DRM_SECURE_LEAVE && !enable)) + return 0; + + ret = _rproc_drm_invoke_secure_service(enable); + s_state = (enum rproc_drm_s_state) enable; + + return ret == TEEC_SUCCESS ? 0 : -EACCES; +} +EXPORT_SYMBOL(rproc_drm_invoke_service); diff --git a/security/smc/s_version.h b/security/smc/s_version.h new file mode 100644 index 0000000..a16d548 --- /dev/null +++ b/security/smc/s_version.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __S_VERSION_H__ +#define __S_VERSION_H__ + +/* + * Usage: define S_VERSION_BUILD on the compiler's command line. + * + * Then set: + * - S_VERSION_OS + * - S_VERSION_PLATFORM + * - S_VERSION_MAIN + * - S_VERSION_ENG is optional + * - S_VERSION_PATCH is optional + * - S_VERSION_BUILD = 0 if S_VERSION_BUILD not defined or empty + */ + +#define S_VERSION_OS "A" /* "A" for all Android */ +#define S_VERSION_PLATFORM "G" /* "G" for 4430 */ + +/* + * This version number must be updated for each new release + */ +#define S_VERSION_MAIN "01.04" + +/* +* If this is a patch or engineering version use the following +* defines to set the version number. Else set these values to 0. +*/ +#define S_VERSION_PATCH 6 +#define S_VERSION_ENG 0 + +#ifdef S_VERSION_BUILD +/* TRICK: detect if S_VERSION is defined but empty */ +#if 0 == S_VERSION_BUILD-0 +#undef S_VERSION_BUILD +#define S_VERSION_BUILD 0 +#endif +#else +/* S_VERSION_BUILD is not defined */ +#define S_VERSION_BUILD 0 +#endif + +#define __STRINGIFY(X) #X +#define __STRINGIFY2(X) __STRINGIFY(X) + +#if S_VERSION_ENG != 0 +#define _S_VERSION_ENG "e" __STRINGIFY2(S_VERSION_ENG) +#else +#define _S_VERSION_ENG "" +#endif + +#if S_VERSION_PATCH != 0 +#define _S_VERSION_PATCH "p" __STRINGIFY2(S_VERSION_PATCH) +#else +#define _S_VERSION_PATCH "" +#endif + +#if !defined(NDEBUG) || defined(_DEBUG) +#define S_VERSION_VARIANT "D " +#else +#define S_VERSION_VARIANT " " +#endif + +#define S_VERSION_STRING \ + "SMC" \ + S_VERSION_OS \ + S_VERSION_PLATFORM \ + S_VERSION_MAIN \ + _S_VERSION_PATCH \ + _S_VERSION_ENG \ + "." __STRINGIFY2(S_VERSION_BUILD) " " \ + S_VERSION_VARIANT + +#endif /* __S_VERSION_H__ */ diff --git a/security/smc/tee_client_api.h b/security/smc/tee_client_api.h new file mode 100644 index 0000000..d57be69 --- /dev/null +++ b/security/smc/tee_client_api.h @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* + * This header file corresponds to V1.0 of the GlobalPlatform + * TEE Client API Specification + */ +#ifndef __TEE_CLIENT_API_H__ +#define __TEE_CLIENT_API_H__ + +#include <linux/types.h> + +#ifndef TEEC_EXPORT +#define TEEC_EXPORT +#endif + +/* The header tee_client_api_imp.h must define implementation-dependent + types, constants and macros. + + The implementation-dependent types are: + - TEEC_Context_IMP + - TEEC_Session_IMP + - TEEC_SharedMemory_IMP + - TEEC_Operation_IMP + + The implementation-dependent constants are: + - TEEC_CONFIG_SHAREDMEM_MAX_SIZE + The implementation-dependent macros are: + - TEEC_PARAM_TYPES +*/ +#include "tee_client_api_imp.h" + +/* Type definitions */ +typedef struct TEEC_Context +{ + TEEC_Context_IMP imp; +} TEEC_Context; + +typedef struct TEEC_Session +{ + TEEC_Session_IMP imp; +} TEEC_Session; + +typedef struct TEEC_SharedMemory +{ + void* buffer; + size_t size; + uint32_t flags; + TEEC_SharedMemory_IMP imp; +} TEEC_SharedMemory; + +typedef struct +{ + void* buffer; + size_t size; +} TEEC_TempMemoryReference; + +typedef struct +{ + TEEC_SharedMemory * parent; + size_t size; + size_t offset; +} TEEC_RegisteredMemoryReference; + +typedef struct +{ + uint32_t a; + uint32_t b; +} TEEC_Value; + +typedef union +{ + TEEC_TempMemoryReference tmpref; + TEEC_RegisteredMemoryReference memref; + TEEC_Value value; +} TEEC_Parameter; + +typedef struct TEEC_Operation +{ + volatile uint32_t started; + uint32_t paramTypes; + TEEC_Parameter params[4]; + TEEC_Operation_IMP imp; +} TEEC_Operation; + +#define TEEC_SUCCESS ((TEEC_Result)0x00000000) +#define TEEC_ERROR_GENERIC ((TEEC_Result)0xFFFF0000) +#define TEEC_ERROR_ACCESS_DENIED ((TEEC_Result)0xFFFF0001) +#define TEEC_ERROR_CANCEL ((TEEC_Result)0xFFFF0002) +#define TEEC_ERROR_ACCESS_CONFLICT ((TEEC_Result)0xFFFF0003) +#define TEEC_ERROR_EXCESS_DATA ((TEEC_Result)0xFFFF0004) +#define TEEC_ERROR_BAD_FORMAT ((TEEC_Result)0xFFFF0005) +#define TEEC_ERROR_BAD_PARAMETERS ((TEEC_Result)0xFFFF0006) +#define TEEC_ERROR_BAD_STATE ((TEEC_Result)0xFFFF0007) +#define TEEC_ERROR_ITEM_NOT_FOUND ((TEEC_Result)0xFFFF0008) +#define TEEC_ERROR_NOT_IMPLEMENTED ((TEEC_Result)0xFFFF0009) +#define TEEC_ERROR_NOT_SUPPORTED ((TEEC_Result)0xFFFF000A) +#define TEEC_ERROR_NO_DATA ((TEEC_Result)0xFFFF000B) +#define TEEC_ERROR_OUT_OF_MEMORY ((TEEC_Result)0xFFFF000C) +#define TEEC_ERROR_BUSY ((TEEC_Result)0xFFFF000D) +#define TEEC_ERROR_COMMUNICATION ((TEEC_Result)0xFFFF000E) +#define TEEC_ERROR_SECURITY ((TEEC_Result)0xFFFF000F) +#define TEEC_ERROR_SHORT_BUFFER ((TEEC_Result)0xFFFF0010) + +#define TEEC_ORIGIN_API 0x00000001 +#define TEEC_ORIGIN_COMMS 0x00000002 +#define TEEC_ORIGIN_TEE 0x00000003 +#define TEEC_ORIGIN_TRUSTED_APP 0x00000004 + +#define TEEC_MEM_INPUT 0x00000001 +#define TEEC_MEM_OUTPUT 0x00000002 + +#define TEEC_NONE 0x0 +#define TEEC_VALUE_INPUT 0x1 +#define TEEC_VALUE_OUTPUT 0x2 +#define TEEC_VALUE_INOUT 0x3 +#define TEEC_MEMREF_TEMP_INPUT 0x5 +#define TEEC_MEMREF_TEMP_OUTPUT 0x6 +#define TEEC_MEMREF_TEMP_INOUT 0x7 +#define TEEC_MEMREF_WHOLE 0xC +#define TEEC_MEMREF_PARTIAL_INPUT 0xD +#define TEEC_MEMREF_PARTIAL_OUTPUT 0xE +#define TEEC_MEMREF_PARTIAL_INOUT 0xF + +#define TEEC_LOGIN_PUBLIC 0x00000000 +#define TEEC_LOGIN_USER 0x00000001 +#define TEEC_LOGIN_GROUP 0x00000002 +#define TEEC_LOGIN_APPLICATION 0x00000004 +#define TEEC_LOGIN_USER_APPLICATION 0x00000005 +#define TEEC_LOGIN_GROUP_APPLICATION 0x00000006 + +TEEC_Result TEEC_EXPORT TEEC_InitializeContext( + const char* name, + TEEC_Context* context); + +void TEEC_EXPORT TEEC_FinalizeContext( + TEEC_Context* context); + +TEEC_Result TEEC_EXPORT TEEC_RegisterSharedMemory( + TEEC_Context* context, + TEEC_SharedMemory* sharedMem); + +TEEC_Result TEEC_EXPORT TEEC_AllocateSharedMemory( + TEEC_Context* context, + TEEC_SharedMemory* sharedMem); + +void TEEC_EXPORT TEEC_ReleaseSharedMemory ( + TEEC_SharedMemory* sharedMem); + +TEEC_Result TEEC_EXPORT TEEC_OpenSession ( + TEEC_Context* context, + TEEC_Session* session, + const TEEC_UUID* destination, + uint32_t connectionMethod, + void* connectionData, + TEEC_Operation* operation, + uint32_t* errorOrigin); + +void TEEC_EXPORT TEEC_CloseSession ( + TEEC_Session* session); + +TEEC_Result TEEC_EXPORT TEEC_InvokeCommand( + TEEC_Session* session, + uint32_t commandID, + TEEC_Operation* operation, + uint32_t* errorOrigin); + +void TEEC_EXPORT TEEC_RequestCancellation( + TEEC_Operation* operation); + +#include "tee_client_api_ex.h" + +#endif /* __TEE_CLIENT_API_H__ */ diff --git a/security/smc/tee_client_api_ex.h b/security/smc/tee_client_api_ex.h new file mode 100644 index 0000000..4988904 --- /dev/null +++ b/security/smc/tee_client_api_ex.h @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* + * This header file contains extensions to the TEE Client API that are + * specific to the Trusted Foundations implementations + */ +#ifndef __TEE_CLIENT_API_EX_H__ +#define __TEE_CLIENT_API_EX_H__ + +#include <linux/types.h> + +/* Implementation-defined login types */ +#define TEEC_LOGIN_AUTHENTICATION 0x80000000 +#define TEEC_LOGIN_PRIVILEGED 0x80000002 + +/* Type definitions */ + +typedef u64 TEEC_TimeLimit; + +void TEEC_EXPORT TEEC_GetTimeLimit( + TEEC_Context* context, + uint32_t timeout, + TEEC_TimeLimit* timeLimit); + +TEEC_Result TEEC_EXPORT TEEC_OpenSessionEx ( + TEEC_Context* context, + TEEC_Session* session, + const TEEC_TimeLimit* timeLimit, + const TEEC_UUID* destination, + uint32_t connectionMethod, + void* connectionData, + TEEC_Operation* operation, + uint32_t* errorOrigin); + +TEEC_Result TEEC_EXPORT TEEC_InvokeCommandEx( + TEEC_Session* session, + const TEEC_TimeLimit* timeLimit, + uint32_t commandID, + TEEC_Operation* operation, + uint32_t* errorOrigin); + +#endif /* __TEE_CLIENT_API_EX_H__ */ diff --git a/security/smc/tee_client_api_imp.h b/security/smc/tee_client_api_imp.h new file mode 100644 index 0000000..3073d63 --- /dev/null +++ b/security/smc/tee_client_api_imp.h @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* + * This header file defines the implementation-dependent types, + * constants and macros for all the Trusted Foundations implementations + * of the TEE Client API + */ +#ifndef __TEE_CLIENT_API_IMP_H__ +#define __TEE_CLIENT_API_IMP_H__ + +#include <linux/types.h> + +typedef u32 TEEC_Result; + +typedef struct TEEC_UUID +{ + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_and_node[8]; +} TEEC_UUID; + +typedef struct { + struct tf_connection *_connection; +} TEEC_Context_IMP; + +typedef struct { + struct TEEC_Context* _context; + u32 _client_session; +} TEEC_Session_IMP; + +typedef struct { + struct TEEC_Context* _context; + u32 _block; + bool _allocated; +} TEEC_SharedMemory_IMP; + +typedef struct { + struct TEEC_Session* _pSession; +} TEEC_Operation_IMP; + +/* There is no natural, compile-time limit on the shared memory, but a specific + implementation may introduce a limit (in particular on TrustZone) */ +#define TEEC_CONFIG_SHAREDMEM_MAX_SIZE ((size_t)0xFFFFFFFF) + +#define TEEC_PARAM_TYPES(entry0Type, entry1Type, entry2Type, entry3Type) \ + ((entry0Type) | ((entry1Type) << 4) | \ + ((entry2Type) << 8) | ((entry3Type) << 12)) + + +#endif /* __TEE_CLIENT_API_IMP_H__ */ diff --git a/security/smc/tf_comm.c b/security/smc/tf_comm.c new file mode 100644 index 0000000..79b4034 --- /dev/null +++ b/security/smc/tf_comm.c @@ -0,0 +1,1746 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <asm/div64.h> +#include <asm/system.h> +#include <linux/version.h> +#include <asm/cputype.h> +#include <linux/interrupt.h> +#include <linux/page-flags.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/jiffies.h> +#include <linux/freezer.h> + +#include "tf_defs.h" +#include "tf_comm.h" +#include "tf_protocol.h" +#include "tf_util.h" +#include "tf_conn.h" + +#ifdef CONFIG_TF_ZEBRA +#include "tf_zebra.h" +#endif + +/*--------------------------------------------------------------------------- + * Internal Constants + *---------------------------------------------------------------------------*/ + +/* + * shared memories descriptor constants + */ +#define DESCRIPTOR_B_MASK (1 << 2) +#define DESCRIPTOR_C_MASK (1 << 3) +#define DESCRIPTOR_S_MASK (1 << 10) + +#define L1_COARSE_DESCRIPTOR_BASE (0x00000001) +#define L1_COARSE_DESCRIPTOR_ADDR_MASK (0xFFFFFC00) +#define L1_COARSE_DESCRIPTOR_V13_12_SHIFT (5) + +#define L2_PAGE_DESCRIPTOR_BASE (0x00000003) +#define L2_PAGE_DESCRIPTOR_AP_APX_READ (0x220) +#define L2_PAGE_DESCRIPTOR_AP_APX_READ_WRITE (0x30) + +#define L2_INIT_DESCRIPTOR_BASE (0x00000003) +#define L2_INIT_DESCRIPTOR_V13_12_SHIFT (4) + +/* + * Reject an attempt to share a strongly-Ordered or Device memory + * Strongly-Ordered: TEX=0b000, C=0, B=0 + * Shared Device: TEX=0b000, C=0, B=1 + * Non-Shared Device: TEX=0b010, C=0, B=0 + */ +#define L2_TEX_C_B_MASK \ + ((1<<8) | (1<<7) | (1<<6) | (1<<3) | (1<<2)) +#define L2_TEX_C_B_STRONGLY_ORDERED \ + ((0<<8) | (0<<7) | (0<<6) | (0<<3) | (0<<2)) +#define L2_TEX_C_B_SHARED_DEVICE \ + ((0<<8) | (0<<7) | (0<<6) | (0<<3) | (1<<2)) +#define L2_TEX_C_B_NON_SHARED_DEVICE \ + ((0<<8) | (1<<7) | (0<<6) | (0<<3) | (0<<2)) + +#define CACHE_S(x) ((x) & (1 << 24)) +#define CACHE_DSIZE(x) (((x) >> 12) & 4095) + +#define TIME_IMMEDIATE ((u64) 0x0000000000000000ULL) +#define TIME_INFINITE ((u64) 0xFFFFFFFFFFFFFFFFULL) + +/*--------------------------------------------------------------------------- + * atomic operation definitions + *---------------------------------------------------------------------------*/ + +/* + * Atomically updates the sync_serial_n and time_n register + * sync_serial_n and time_n modifications are thread safe + */ +void tf_set_current_time(struct tf_comm *comm) +{ + u32 new_sync_serial; + struct timeval now; + u64 time64; + + /* + * lock the structure while updating the L1 shared memory fields + */ + spin_lock(&comm->lock); + + /* read sync_serial_n and change the TimeSlot bit field */ + new_sync_serial = + tf_read_reg32(&comm->pBuffer->sync_serial_n) + 1; + + do_gettimeofday(&now); + time64 = now.tv_sec; + time64 = (time64 * 1000) + (now.tv_usec / 1000); + + /* Write the new time64 and nSyncSerial into shared memory */ + tf_write_reg64(&comm->pBuffer->time_n[new_sync_serial & + TF_SYNC_SERIAL_TIMESLOT_N], time64); + tf_write_reg32(&comm->pBuffer->sync_serial_n, + new_sync_serial); + + spin_unlock(&comm->lock); +} + +/* + * Performs the specific read timeout operation + * The difficulty here is to read atomically 2 u32 + * values from the L1 shared buffer. + * This is guaranteed by reading before and after the operation + * the timeslot given by the Secure World + */ +static inline void tf_read_timeout(struct tf_comm *comm, u64 *time) +{ + u32 sync_serial_s_initial = 0; + u32 sync_serial_s_final = 1; + u64 time64; + + spin_lock(&comm->lock); + + while (sync_serial_s_initial != sync_serial_s_final) { + sync_serial_s_initial = tf_read_reg32( + &comm->pBuffer->sync_serial_s); + time64 = tf_read_reg64( + &comm->pBuffer->timeout_s[sync_serial_s_initial&1]); + + sync_serial_s_final = tf_read_reg32( + &comm->pBuffer->sync_serial_s); + } + + spin_unlock(&comm->lock); + + *time = time64; +} + +/*---------------------------------------------------------------------------- + * SIGKILL signal handling + *----------------------------------------------------------------------------*/ + +static bool sigkill_pending(void) +{ + if (signal_pending(current)) { + dprintk(KERN_INFO "A signal is pending\n"); + if (sigismember(¤t->pending.signal, SIGKILL)) { + dprintk(KERN_INFO "A SIGKILL is pending\n"); + return true; + } else if (sigismember( + ¤t->signal->shared_pending.signal, SIGKILL)) { + dprintk(KERN_INFO "A SIGKILL is pending (shared)\n"); + return true; + } + } + return false; +} + +/*---------------------------------------------------------------------------- + * Shared memory related operations + *----------------------------------------------------------------------------*/ + +struct tf_coarse_page_table *tf_alloc_coarse_page_table( + struct tf_coarse_page_table_allocation_context *alloc_context, + u32 type) +{ + struct tf_coarse_page_table *coarse_pg_table = NULL; + + spin_lock(&(alloc_context->lock)); + + if (!(list_empty(&(alloc_context->free_coarse_page_tables)))) { + /* + * The free list can provide us a coarse page table + * descriptor + */ + coarse_pg_table = list_first_entry( + &alloc_context->free_coarse_page_tables, + struct tf_coarse_page_table, list); + list_del(&(coarse_pg_table->list)); + + coarse_pg_table->parent->ref_count++; + } else { + /* no array of coarse page tables, create a new one */ + struct tf_coarse_page_table_array *array; + void *page; + int i; + + spin_unlock(&(alloc_context->lock)); + + /* first allocate a new page descriptor */ + array = internal_kmalloc(sizeof(*array), GFP_KERNEL); + if (array == NULL) { + dprintk(KERN_ERR "tf_alloc_coarse_page_table(%p):" + " failed to allocate a table array\n", + alloc_context); + return NULL; + } + + array->type = type; + INIT_LIST_HEAD(&(array->list)); + + /* now allocate the actual page the page descriptor describes */ + page = (void *) internal_get_zeroed_page(GFP_KERNEL); + if (page == NULL) { + dprintk(KERN_ERR "tf_alloc_coarse_page_table(%p):" + " failed allocate a page\n", + alloc_context); + internal_kfree(array); + return NULL; + } + + spin_lock(&(alloc_context->lock)); + + /* initialize the coarse page table descriptors */ + for (i = 0; i < 4; i++) { + INIT_LIST_HEAD(&(array->coarse_page_tables[i].list)); + array->coarse_page_tables[i].descriptors = + page + (i * SIZE_1KB); + array->coarse_page_tables[i].parent = array; + + if (i == 0) { + /* + * the first element is kept for the current + * coarse page table allocation + */ + coarse_pg_table = + &(array->coarse_page_tables[i]); + array->ref_count++; + } else { + /* + * The other elements are added to the free list + */ + list_add(&(array->coarse_page_tables[i].list), + &(alloc_context-> + free_coarse_page_tables)); + } + } + + list_add(&(array->list), + &(alloc_context->coarse_page_table_arrays)); + } + spin_unlock(&(alloc_context->lock)); + + return coarse_pg_table; +} + + +void tf_free_coarse_page_table( + struct tf_coarse_page_table_allocation_context *alloc_context, + struct tf_coarse_page_table *coarse_pg_table, + int force) +{ + struct tf_coarse_page_table_array *array; + + spin_lock(&(alloc_context->lock)); + + array = coarse_pg_table->parent; + + (array->ref_count)--; + + if (array->ref_count == 0) { + /* + * no coarse page table descriptor is used + * check if we should free the whole page + */ + + if ((array->type == TF_PAGE_DESCRIPTOR_TYPE_PREALLOCATED) + && (force == 0)) + /* + * This is a preallocated page, + * add the page back to the free list + */ + list_add(&(coarse_pg_table->list), + &(alloc_context->free_coarse_page_tables)); + else { + /* + * None of the page's coarse page table descriptors + * are in use, free the whole page + */ + int i; + u32 *descriptors; + + /* + * remove the page's associated coarse page table + * descriptors from the free list + */ + for (i = 0; i < 4; i++) + if (&(array->coarse_page_tables[i]) != + coarse_pg_table) + list_del(&(array-> + coarse_page_tables[i].list)); + + descriptors = + array->coarse_page_tables[0].descriptors; + array->coarse_page_tables[0].descriptors = NULL; + + /* remove the coarse page table from the array */ + list_del(&(array->list)); + + spin_unlock(&(alloc_context->lock)); + /* + * Free the page. + * The address of the page is contained in the first + * element + */ + internal_free_page((unsigned long) descriptors); + /* finaly free the array */ + internal_kfree(array); + + spin_lock(&(alloc_context->lock)); + } + } else { + /* + * Some coarse page table descriptors are in use. + * Add the descriptor to the free list + */ + list_add(&(coarse_pg_table->list), + &(alloc_context->free_coarse_page_tables)); + } + + spin_unlock(&(alloc_context->lock)); +} + + +void tf_init_coarse_page_table_allocator( + struct tf_coarse_page_table_allocation_context *alloc_context) +{ + spin_lock_init(&(alloc_context->lock)); + INIT_LIST_HEAD(&(alloc_context->coarse_page_table_arrays)); + INIT_LIST_HEAD(&(alloc_context->free_coarse_page_tables)); +} + +void tf_release_coarse_page_table_allocator( + struct tf_coarse_page_table_allocation_context *alloc_context) +{ + spin_lock(&(alloc_context->lock)); + + /* now clean up the list of page descriptors */ + while (!list_empty(&(alloc_context->coarse_page_table_arrays))) { + struct tf_coarse_page_table_array *page_desc; + u32 *descriptors; + + page_desc = list_first_entry( + &alloc_context->coarse_page_table_arrays, + struct tf_coarse_page_table_array, list); + + descriptors = page_desc->coarse_page_tables[0].descriptors; + list_del(&(page_desc->list)); + + spin_unlock(&(alloc_context->lock)); + + if (descriptors != NULL) + internal_free_page((unsigned long)descriptors); + + internal_kfree(page_desc); + + spin_lock(&(alloc_context->lock)); + } + + spin_unlock(&(alloc_context->lock)); +} + +/* + * Returns the L1 coarse page descriptor for + * a coarse page table located at address coarse_pg_table_descriptors + */ +u32 tf_get_l1_coarse_descriptor( + u32 coarse_pg_table_descriptors[256]) +{ + u32 descriptor = L1_COARSE_DESCRIPTOR_BASE; + unsigned int info = read_cpuid(CPUID_CACHETYPE); + + descriptor |= (virt_to_phys((void *) coarse_pg_table_descriptors) + & L1_COARSE_DESCRIPTOR_ADDR_MASK); + + if (CACHE_S(info) && (CACHE_DSIZE(info) & (1 << 11))) { + dprintk(KERN_DEBUG "tf_get_l1_coarse_descriptor " + "V31-12 added to descriptor\n"); + /* the 16k alignment restriction applies */ + descriptor |= (DESCRIPTOR_V13_12_GET( + (u32)coarse_pg_table_descriptors) << + L1_COARSE_DESCRIPTOR_V13_12_SHIFT); + } + + return descriptor; +} + + +#define dprintk_desc(...) +/* + * Returns the L2 descriptor for the specified user page. + */ +u32 tf_get_l2_descriptor_common(u32 vaddr, struct mm_struct *mm) +{ + pgd_t *pgd; + pud_t *pud; + pmd_t *pmd; + pte_t *ptep; + u32 *hwpte; + u32 tex = 0; + u32 descriptor = 0; + + dprintk_desc(KERN_INFO "VirtAddr = %x\n", vaddr); + pgd = pgd_offset(mm, vaddr); + dprintk_desc(KERN_INFO "pgd = %x, value=%x\n", (unsigned int) pgd, + (unsigned int) *pgd); + if (pgd_none(*pgd)) + goto error; + pud = pud_offset(pgd, vaddr); + dprintk_desc(KERN_INFO "pud = %x, value=%x\n", (unsigned int) pud, + (unsigned int) *pud); + if (pud_none(*pud)) + goto error; + pmd = pmd_offset(pud, vaddr); + dprintk_desc(KERN_INFO "pmd = %x, value=%x\n", (unsigned int) pmd, + (unsigned int) *pmd); + if (pmd_none(*pmd)) + goto error; + + if (PMD_TYPE_SECT&(*pmd)) { + /* We have a section */ + dprintk_desc(KERN_INFO "Section descr=%x\n", + (unsigned int)*pmd); + if ((*pmd) & PMD_SECT_BUFFERABLE) + descriptor |= DESCRIPTOR_B_MASK; + if ((*pmd) & PMD_SECT_CACHEABLE) + descriptor |= DESCRIPTOR_C_MASK; + if ((*pmd) & PMD_SECT_S) + descriptor |= DESCRIPTOR_S_MASK; + tex = ((*pmd) >> 12) & 7; + } else { + /* We have a table */ + ptep = pte_offset_map(pmd, vaddr); + if (pte_present(*ptep)) { + dprintk_desc(KERN_INFO "L2 descr=%x\n", + (unsigned int) *ptep); + if ((*ptep) & L_PTE_MT_BUFFERABLE) + descriptor |= DESCRIPTOR_B_MASK; + if ((*ptep) & L_PTE_MT_WRITETHROUGH) + descriptor |= DESCRIPTOR_C_MASK; + if ((*ptep) & L_PTE_MT_DEV_SHARED) + descriptor |= DESCRIPTOR_S_MASK; + + /* + * Linux's pte doesn't keep track of TEX value. + * Have to jump to hwpte see include/asm/pgtable.h + */ + hwpte = (u32 *) (((u32) ptep) - 0x800); + if (((*hwpte) & L2_DESCRIPTOR_ADDR_MASK) != + ((*ptep) & L2_DESCRIPTOR_ADDR_MASK)) + goto error; + dprintk_desc(KERN_INFO "hw descr=%x\n", *hwpte); + tex = ((*hwpte) >> 6) & 7; + pte_unmap(ptep); + } else { + pte_unmap(ptep); + goto error; + } + } + + descriptor |= (tex << 6); + + return descriptor; + +error: + dprintk(KERN_ERR "Error occured in %s\n", __func__); + return 0; +} + + +/* + * Changes an L2 page descriptor back to a pointer to a physical page + */ +inline struct page *tf_l2_page_descriptor_to_page(u32 l2_page_descriptor) +{ + return pte_page(l2_page_descriptor & L2_DESCRIPTOR_ADDR_MASK); +} + +#define TF_DEFAULT_COMMON_DESCRIPTORS 0x0000044C + +/* + * Returns the L1 descriptor for the 1KB-aligned coarse page table. The address + * must be in the kernel address space. + */ +static void tf_get_l2_page_descriptor( + u32 *l2_page_descriptor, + u32 flags, struct mm_struct *mm, struct vm_area_struct *vmas) +{ + u32 descriptor; + struct page *page; + + dprintk(KERN_INFO + "%s *l2_page_descriptor=%x vm_flags=%lx\n", + __func__, *l2_page_descriptor, vmas->vm_flags); + + if (*l2_page_descriptor == L2_DESCRIPTOR_FAULT) + return; + + if (vmas->vm_flags & VM_IO) { + *l2_page_descriptor = L2_DESCRIPTOR_FAULT; + dprintk(KERN_ERR "Memory mapped I/O or similar detected\n"); + return; + } + page = (struct page *) (*l2_page_descriptor); + + descriptor = TF_DEFAULT_COMMON_DESCRIPTORS; + descriptor |= L2_PAGE_DESCRIPTOR_BASE; + + descriptor |= (page_to_phys(page) & L2_DESCRIPTOR_ADDR_MASK); + + if (!(flags & TF_SHMEM_TYPE_WRITE)) + /* only read access */ + descriptor |= L2_PAGE_DESCRIPTOR_AP_APX_READ; + else + /* read and write access */ + descriptor |= L2_PAGE_DESCRIPTOR_AP_APX_READ_WRITE; + + + *l2_page_descriptor = descriptor; +} + + +/* + * Unlocks the physical memory pages + * and frees the coarse pages that need to + */ +void tf_cleanup_shared_memory( + struct tf_coarse_page_table_allocation_context *alloc_context, + struct tf_shmem_desc *shmem_desc, + u32 full_cleanup) +{ + u32 coarse_page_index; + + dprintk(KERN_INFO "tf_cleanup_shared_memory(%p)\n", + shmem_desc); + +#ifdef DEBUG_COARSE_TABLES + printk(KERN_DEBUG "tf_cleanup_shared_memory " + "- number of coarse page tables=%d\n", + shmem_desc->coarse_pg_table_count); + + for (coarse_page_index = 0; + coarse_page_index < shmem_desc->coarse_pg_table_count; + coarse_page_index++) { + u32 j; + + printk(KERN_DEBUG " Descriptor=%p address=%p index=%d\n", + shmem_desc->coarse_pg_table[coarse_page_index], + shmem_desc->coarse_pg_table[coarse_page_index]-> + descriptors, + coarse_page_index); + if (shmem_desc->coarse_pg_table[coarse_page_index] != NULL) { + for (j = 0; + j < TF_DESCRIPTOR_TABLE_CAPACITY; + j += 8) { + int k; + printk(KERN_DEBUG " "); + for (k = j; k < j + 8; k++) + printk(KERN_DEBUG "%p ", + shmem_desc->coarse_pg_table[ + coarse_page_index]-> + descriptors); + printk(KERN_DEBUG "\n"); + } + } + } + printk(KERN_DEBUG "tf_cleanup_shared_memory() - done\n\n"); +#endif + + /* Parse the coarse page descriptors */ + for (coarse_page_index = 0; + coarse_page_index < shmem_desc->coarse_pg_table_count; + coarse_page_index++) { + u32 j; + u32 found = 0; + + /* parse the page descriptors of the coarse page */ + for (j = 0; j < TF_DESCRIPTOR_TABLE_CAPACITY; j++) { + u32 l2_page_descriptor = (u32) (shmem_desc-> + coarse_pg_table[coarse_page_index]-> + descriptors[j]); + + if (l2_page_descriptor != L2_DESCRIPTOR_FAULT) { + struct page *page = + tf_l2_page_descriptor_to_page( + l2_page_descriptor); + + if (!PageReserved(page)) + SetPageDirty(page); + internal_page_cache_release(page); + + found = 1; + } else if (found == 1) { + break; + } + } + + /* + * Only free the coarse pages of descriptors not preallocated + */ + if ((shmem_desc->type == TF_SHMEM_TYPE_REGISTERED_SHMEM) || + (full_cleanup != 0)) + tf_free_coarse_page_table(alloc_context, + shmem_desc->coarse_pg_table[coarse_page_index], + 0); + } + + shmem_desc->coarse_pg_table_count = 0; + dprintk(KERN_INFO "tf_cleanup_shared_memory(%p) done\n", + shmem_desc); +} + +/* + * Make sure the coarse pages are allocated. If not allocated, do it Locks down + * the physical memory pages + * Verifies the memory attributes depending on flags + */ +int tf_fill_descriptor_table( + struct tf_coarse_page_table_allocation_context *alloc_context, + struct tf_shmem_desc *shmem_desc, + u32 buffer, + struct vm_area_struct **vmas, + u32 descriptors[TF_MAX_COARSE_PAGES], + u32 buffer_size, + u32 *buffer_start_offset, + bool in_user_space, + u32 flags, + u32 *descriptor_count) +{ + u32 coarse_page_index; + u32 coarse_page_count; + u32 page_count; + u32 page_shift = 0; + int error; + unsigned int info = read_cpuid(CPUID_CACHETYPE); + + dprintk(KERN_INFO "tf_fill_descriptor_table" + "(%p, buffer=0x%08X, size=0x%08X, user=%01x " + "flags = 0x%08x)\n", + shmem_desc, + buffer, + buffer_size, + in_user_space, + flags); + + /* + * Compute the number of pages + * Compute the number of coarse pages + * Compute the page offset + */ + page_count = ((buffer & ~PAGE_MASK) + + buffer_size + ~PAGE_MASK) >> PAGE_SHIFT; + + /* check whether the 16k alignment restriction applies */ + if (CACHE_S(info) && (CACHE_DSIZE(info) & (1 << 11))) + /* + * The 16k alignment restriction applies. + * Shift data to get them 16k aligned + */ + page_shift = DESCRIPTOR_V13_12_GET(buffer); + page_count += page_shift; + + + /* + * Check the number of pages fit in the coarse pages + */ + if (page_count > (TF_DESCRIPTOR_TABLE_CAPACITY * + TF_MAX_COARSE_PAGES)) { + dprintk(KERN_ERR "tf_fill_descriptor_table(%p): " + "%u pages required to map shared memory!\n", + shmem_desc, page_count); + error = -ENOMEM; + goto error; + } + + /* coarse page describe 256 pages */ + coarse_page_count = ((page_count + + TF_DESCRIPTOR_TABLE_CAPACITY_MASK) >> + TF_DESCRIPTOR_TABLE_CAPACITY_BIT_SHIFT); + + /* + * Compute the buffer offset + */ + *buffer_start_offset = (buffer & ~PAGE_MASK) | + (page_shift << PAGE_SHIFT); + + /* map each coarse page */ + for (coarse_page_index = 0; + coarse_page_index < coarse_page_count; + coarse_page_index++) { + u32 j; + struct tf_coarse_page_table *coarse_pg_table; + + /* compute a virtual address with appropriate offset */ + u32 buffer_offset_vaddr = buffer + + (coarse_page_index * TF_MAX_COARSE_PAGE_MAPPED_SIZE); + u32 pages_to_get; + + /* + * Compute the number of pages left for this coarse page. + * Decrement page_count each time + */ + pages_to_get = (page_count >> + TF_DESCRIPTOR_TABLE_CAPACITY_BIT_SHIFT) ? + TF_DESCRIPTOR_TABLE_CAPACITY : page_count; + page_count -= pages_to_get; + + /* + * Check if the coarse page has already been allocated + * If not, do it now + */ + if ((shmem_desc->type == TF_SHMEM_TYPE_REGISTERED_SHMEM) + || (shmem_desc->type == + TF_SHMEM_TYPE_PM_HIBERNATE)) { + coarse_pg_table = tf_alloc_coarse_page_table( + alloc_context, + TF_PAGE_DESCRIPTOR_TYPE_NORMAL); + + if (coarse_pg_table == NULL) { + dprintk(KERN_ERR + "tf_fill_descriptor_table(%p):" + " SCXLNXConnAllocateCoarsePageTable " + "failed for coarse page %d\n", + shmem_desc, coarse_page_index); + error = -ENOMEM; + goto error; + } + + shmem_desc->coarse_pg_table[coarse_page_index] = + coarse_pg_table; + } else { + coarse_pg_table = + shmem_desc->coarse_pg_table[coarse_page_index]; + } + + /* + * The page is not necessarily filled with zeroes. + * Set the fault descriptors ( each descriptor is 4 bytes long) + */ + memset(coarse_pg_table->descriptors, 0x00, + TF_DESCRIPTOR_TABLE_CAPACITY * sizeof(u32)); + + if (in_user_space) { + int pages; + + /* + * TRICK: use pCoarsePageDescriptor->descriptors to + * hold the (struct page*) items before getting their + * physical address + */ + down_read(&(current->mm->mmap_sem)); + pages = internal_get_user_pages( + current, + current->mm, + buffer_offset_vaddr, + /* + * page_shift is cleared after retrieving first + * coarse page + */ + (pages_to_get - page_shift), + (flags & TF_SHMEM_TYPE_WRITE) ? 1 : 0, + 0, + (struct page **) (coarse_pg_table->descriptors + + page_shift), + vmas); + up_read(&(current->mm->mmap_sem)); + + if ((pages <= 0) || + (pages != (pages_to_get - page_shift))) { + dprintk(KERN_ERR"tf_fill_descriptor_table:" + " get_user_pages got %d pages while " + "trying to get %d pages!\n", + pages, pages_to_get - page_shift); + error = -EFAULT; + goto error; + } + + for (j = page_shift; + j < page_shift + pages; + j++) { + /* Get the actual L2 descriptors */ + tf_get_l2_page_descriptor( + &coarse_pg_table->descriptors[j], + flags, + current->mm, + vmas[j]); + /* + * Reject Strongly-Ordered or Device Memory + */ +#define IS_STRONGLY_ORDERED_OR_DEVICE_MEM(x) \ + ((((x) & L2_TEX_C_B_MASK) == L2_TEX_C_B_STRONGLY_ORDERED) || \ + (((x) & L2_TEX_C_B_MASK) == L2_TEX_C_B_SHARED_DEVICE) || \ + (((x) & L2_TEX_C_B_MASK) == L2_TEX_C_B_NON_SHARED_DEVICE)) + + if (IS_STRONGLY_ORDERED_OR_DEVICE_MEM( + coarse_pg_table-> + descriptors[j])) { + dprintk(KERN_ERR + "tf_fill_descriptor_table:" + " descriptor 0x%08X use " + "strongly-ordered or device " + "memory. Rejecting!\n", + coarse_pg_table-> + descriptors[j]); + error = -EFAULT; + goto error; + } + } + } else { + /* Kernel-space memory */ + for (j = page_shift; + j < pages_to_get; + j++) { + void *addr = + (void *)(buffer_offset_vaddr + + (j - page_shift) * PAGE_SIZE); + if (!is_vmalloc_addr(addr)) { + dprintk(KERN_ERR + "tf_fill_descriptor_table: " + "cannot handle address %p\n", + addr); + goto error; + } + struct page *page = vmalloc_to_page(addr); + if (page == NULL) { + dprintk(KERN_ERR + "tf_fill_descriptor_table: " + "cannot map %p to page\n", + addr); + goto error; + } + coarse_pg_table->descriptors[j] = (u32)page; + get_page(page); + + /* change coarse page "page address" */ + tf_get_l2_page_descriptor( + &coarse_pg_table->descriptors[j], + flags, + &init_mm, + vmas[j]); + } + } + + dmac_flush_range((void *)coarse_pg_table->descriptors, + (void *)(((u32)(coarse_pg_table->descriptors)) + + TF_DESCRIPTOR_TABLE_CAPACITY * sizeof(u32))); + + outer_clean_range( + __pa(coarse_pg_table->descriptors), + __pa(coarse_pg_table->descriptors) + + TF_DESCRIPTOR_TABLE_CAPACITY * sizeof(u32)); + wmb(); + + /* Update the coarse page table address */ + descriptors[coarse_page_index] = + tf_get_l1_coarse_descriptor( + coarse_pg_table->descriptors); + + /* + * The next coarse page has no page shift, reset the + * page_shift + */ + page_shift = 0; + } + + *descriptor_count = coarse_page_count; + shmem_desc->coarse_pg_table_count = coarse_page_count; + +#ifdef DEBUG_COARSE_TABLES + printk(KERN_DEBUG "ntf_fill_descriptor_table - size=0x%08X " + "numberOfCoarsePages=%d\n", buffer_size, + shmem_desc->coarse_pg_table_count); + for (coarse_page_index = 0; + coarse_page_index < shmem_desc->coarse_pg_table_count; + coarse_page_index++) { + u32 j; + struct tf_coarse_page_table *coarse_page_table = + shmem_desc->coarse_pg_table[coarse_page_index]; + + printk(KERN_DEBUG " Descriptor=%p address=%p index=%d\n", + coarse_page_table, + coarse_page_table->descriptors, + coarse_page_index); + for (j = 0; + j < TF_DESCRIPTOR_TABLE_CAPACITY; + j += 8) { + int k; + printk(KERN_DEBUG " "); + for (k = j; k < j + 8; k++) + printk(KERN_DEBUG "0x%08X ", + coarse_page_table->descriptors[k]); + printk(KERN_DEBUG "\n"); + } + } + printk(KERN_DEBUG "ntf_fill_descriptor_table() - done\n\n"); +#endif + + return 0; + +error: + tf_cleanup_shared_memory( + alloc_context, + shmem_desc, + 0); + + return error; +} + + +/*---------------------------------------------------------------------------- + * Standard communication operations + *----------------------------------------------------------------------------*/ + +u8 *tf_get_description(struct tf_comm *comm) +{ + if (test_bit(TF_COMM_FLAG_L1_SHARED_ALLOCATED, &(comm->flags))) + return comm->pBuffer->version_description; + + return NULL; +} + +/* + * Returns a non-zero value if the specified S-timeout has expired, zero + * otherwise. + * + * The placeholder referenced to by relative_timeout_jiffies gives the relative + * timeout from now in jiffies. It is set to zero if the S-timeout has expired, + * or to MAX_SCHEDULE_TIMEOUT if the S-timeout is infinite. + */ +static int tf_test_s_timeout( + u64 timeout, + signed long *relative_timeout_jiffies) +{ + struct timeval now; + u64 time64; + + *relative_timeout_jiffies = 0; + + /* immediate timeout */ + if (timeout == TIME_IMMEDIATE) + return 1; + + /* infinite timeout */ + if (timeout == TIME_INFINITE) { + dprintk(KERN_DEBUG "tf_test_s_timeout: " + "timeout is infinite\n"); + *relative_timeout_jiffies = MAX_SCHEDULE_TIMEOUT; + return 0; + } + + do_gettimeofday(&now); + time64 = now.tv_sec; + /* will not overflow as operations are done on 64bit values */ + time64 = (time64 * 1000) + (now.tv_usec / 1000); + + /* timeout expired */ + if (time64 >= timeout) { + dprintk(KERN_DEBUG "tf_test_s_timeout: timeout expired\n"); + return 1; + } + + /* + * finite timeout, compute relative_timeout_jiffies + */ + /* will not overflow as time64 < timeout */ + timeout -= time64; + + /* guarantee *relative_timeout_jiffies is a valid timeout */ + if ((timeout >> 32) != 0) + *relative_timeout_jiffies = MAX_JIFFY_OFFSET; + else + *relative_timeout_jiffies = + msecs_to_jiffies((unsigned int) timeout); + + dprintk(KERN_DEBUG "tf_test_s_timeout: timeout is 0x%lx\n", + *relative_timeout_jiffies); + return 0; +} + +static void tf_copy_answers(struct tf_comm *comm) +{ + u32 first_answer; + u32 first_free_answer; + struct tf_answer_struct *answerStructureTemp; + + if (test_bit(TF_COMM_FLAG_L1_SHARED_ALLOCATED, &(comm->flags))) { + spin_lock(&comm->lock); + first_free_answer = tf_read_reg32( + &comm->pBuffer->first_free_answer); + first_answer = tf_read_reg32( + &comm->pBuffer->first_answer); + + while (first_answer != first_free_answer) { + /* answer queue not empty */ + union tf_answer sComAnswer; + struct tf_answer_header header; + + /* + * the size of the command in words of 32bit, not in + * bytes + */ + u32 command_size; + u32 i; + u32 *temp = (uint32_t *) &header; + + dprintk(KERN_INFO + "[pid=%d] tf_copy_answers(%p): " + "Read answers from L1\n", + current->pid, comm); + + /* Read the answer header */ + for (i = 0; + i < sizeof(struct tf_answer_header)/sizeof(u32); + i++) + temp[i] = comm->pBuffer->answer_queue[ + (first_answer + i) % + TF_S_ANSWER_QUEUE_CAPACITY]; + + /* Read the answer from the L1_Buffer*/ + command_size = header.message_size + + sizeof(struct tf_answer_header)/sizeof(u32); + temp = (uint32_t *) &sComAnswer; + for (i = 0; i < command_size; i++) + temp[i] = comm->pBuffer->answer_queue[ + (first_answer + i) % + TF_S_ANSWER_QUEUE_CAPACITY]; + + answerStructureTemp = (struct tf_answer_struct *) + sComAnswer.header.operation_id; + + tf_dump_answer(&sComAnswer); + + memcpy(answerStructureTemp->answer, &sComAnswer, + command_size * sizeof(u32)); + answerStructureTemp->answer_copied = true; + + first_answer += command_size; + tf_write_reg32(&comm->pBuffer->first_answer, + first_answer); + } + spin_unlock(&(comm->lock)); + } +} + +static void tf_copy_command( + struct tf_comm *comm, + union tf_command *command, + struct tf_connection *connection, + enum TF_COMMAND_STATE *command_status) +{ + if ((test_bit(TF_COMM_FLAG_L1_SHARED_ALLOCATED, &(comm->flags))) + && (command != NULL)) { + /* + * Write the message in the message queue. + */ + + if (*command_status == TF_COMMAND_STATE_PENDING) { + u32 command_size; + u32 queue_words_count; + u32 i; + u32 first_free_command; + u32 first_command; + + spin_lock(&comm->lock); + + first_command = tf_read_reg32( + &comm->pBuffer->first_command); + first_free_command = tf_read_reg32( + &comm->pBuffer->first_free_command); + + queue_words_count = first_free_command - first_command; + command_size = command->header.message_size + + sizeof(struct tf_command_header)/sizeof(u32); + if ((queue_words_count + command_size) < + TF_N_MESSAGE_QUEUE_CAPACITY) { + /* + * Command queue is not full. + * If the Command queue is full, + * the command will be copied at + * another iteration + * of the current function. + */ + + /* + * Change the conn state + */ + if (connection == NULL) + goto copy; + + spin_lock(&(connection->state_lock)); + + if ((connection->state == + TF_CONN_STATE_NO_DEVICE_CONTEXT) + && + (command->header.message_type == + TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT)) { + + dprintk(KERN_INFO + "tf_copy_command(%p):" + "Conn state is DEVICE_CONTEXT_SENT\n", + connection); + connection->state = + TF_CONN_STATE_CREATE_DEVICE_CONTEXT_SENT; + } else if ((connection->state != + TF_CONN_STATE_VALID_DEVICE_CONTEXT) + && + (command->header.message_type != + TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT)) { + /* The connection + * is no longer valid. + * We may not send any command on it, + * not even another + * DESTROY_DEVICE_CONTEXT. + */ + dprintk(KERN_INFO + "[pid=%d] tf_copy_command(%p): " + "Connection no longer valid." + "ABORT\n", + current->pid, connection); + *command_status = + TF_COMMAND_STATE_ABORTED; + spin_unlock( + &(connection->state_lock)); + spin_unlock( + &comm->lock); + return; + } else if ( + (command->header.message_type == + TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT) && + (connection->state == + TF_CONN_STATE_VALID_DEVICE_CONTEXT) + ) { + dprintk(KERN_INFO + "[pid=%d] tf_copy_command(%p): " + "Conn state is " + "DESTROY_DEVICE_CONTEXT_SENT\n", + current->pid, connection); + connection->state = + TF_CONN_STATE_DESTROY_DEVICE_CONTEXT_SENT; + } + spin_unlock(&(connection->state_lock)); +copy: + /* + * Copy the command to L1 Buffer + */ + dprintk(KERN_INFO + "[pid=%d] tf_copy_command(%p): " + "Write Message in the queue\n", + current->pid, command); + tf_dump_command(command); + + for (i = 0; i < command_size; i++) + comm->pBuffer->command_queue[ + (first_free_command + i) % + TF_N_MESSAGE_QUEUE_CAPACITY] = + ((uint32_t *) command)[i]; + + *command_status = + TF_COMMAND_STATE_SENT; + first_free_command += command_size; + + tf_write_reg32( + &comm-> + pBuffer->first_free_command, + first_free_command); + } + spin_unlock(&comm->lock); + } + } +} + +/* + * Sends the specified message through the specified communication channel. + * + * This function sends the command and waits for the answer + * + * Returns zero upon successful completion, or an appropriate error code upon + * failure. + */ +static int tf_send_recv(struct tf_comm *comm, + union tf_command *command, + struct tf_answer_struct *answerStruct, + struct tf_connection *connection, + int bKillable + #ifdef CONFIG_TF_ZEBRA + , bool *secure_is_idle + #endif + ) +{ + int result; + u64 timeout; + signed long nRelativeTimeoutJiffies; + bool wait_prepared = false; + enum TF_COMMAND_STATE command_status = TF_COMMAND_STATE_PENDING; + DEFINE_WAIT(wait); +#ifdef CONFIG_FREEZER + unsigned long saved_flags; +#endif + dprintk(KERN_INFO "[pid=%d] tf_send_recv(%p)\n", + current->pid, command); + +#ifdef CONFIG_FREEZER + saved_flags = current->flags; + current->flags |= PF_FREEZER_NOSIG; +#endif + + /* + * Read all answers from the answer queue + */ +copy_answers: + tf_copy_answers(comm); + + tf_copy_command(comm, command, connection, &command_status); + + /* + * Notify all waiting threads + */ + wake_up(&(comm->wait_queue)); + +#ifdef CONFIG_FREEZER + if (unlikely(freezing(current))) { + +#ifdef CONFIG_TF_ZEBRA + if (!(*secure_is_idle)) { + if (tf_schedule_secure_world(comm, true) == + STATUS_PENDING) + goto copy_answers; + + tf_l4sec_clkdm_allow_idle(true); + *secure_is_idle = true; + } +#endif + + dprintk(KERN_INFO + "Entering refrigerator.\n"); + refrigerator(); + dprintk(KERN_INFO + "Left refrigerator.\n"); + goto copy_answers; + } +#endif + +#ifndef CONFIG_PREEMPT + if (need_resched()) + schedule(); +#endif + +#ifdef CONFIG_TF_ZEBRA + /* + * Handle RPC (if any) + */ + if (tf_rpc_execute(comm) == RPC_NON_YIELD) + goto schedule_secure_world; +#endif + + /* + * Join wait queue + */ + /*dprintk(KERN_INFO "[pid=%d] tf_send_recv(%p): Prepare to wait\n", + current->pid, command);*/ + prepare_to_wait(&comm->wait_queue, &wait, + bKillable ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + wait_prepared = true; + + /* + * Check if our answer is available + */ + if (command_status == TF_COMMAND_STATE_ABORTED) { + /* Not waiting for an answer, return error code */ + result = -EINTR; + dprintk(KERN_ERR "[pid=%d] tf_send_recv: " + "Command status is ABORTED." + "Exit with 0x%x\n", + current->pid, result); + goto exit; + } + if (answerStruct->answer_copied) { + dprintk(KERN_INFO "[pid=%d] tf_send_recv: " + "Received answer (type 0x%02X)\n", + current->pid, + answerStruct->answer->header.message_type); + result = 0; + goto exit; + } + + /* + * Check if a signal is pending + */ + if (bKillable && (sigkill_pending())) { + if (command_status == TF_COMMAND_STATE_PENDING) + /*Command was not sent. */ + result = -EINTR; + else + /* Command was sent but no answer was received yet. */ + result = -EIO; + + dprintk(KERN_ERR "[pid=%d] tf_send_recv: " + "Signal Pending. Return error %d\n", + current->pid, result); + goto exit; + } + + /* + * Check if secure world is schedulable. It is schedulable if at + * least one of the following conditions holds: + * + it is still initializing (TF_COMM_FLAG_L1_SHARED_ALLOCATED + * is not set); + * + there is a command in the queue; + * + the secure world timeout is zero. + */ + if (test_bit(TF_COMM_FLAG_L1_SHARED_ALLOCATED, &(comm->flags))) { + u32 first_free_command; + u32 first_command; + spin_lock(&comm->lock); + first_command = tf_read_reg32( + &comm->pBuffer->first_command); + first_free_command = tf_read_reg32( + &comm->pBuffer->first_free_command); + spin_unlock(&comm->lock); + tf_read_timeout(comm, &timeout); + if ((first_free_command == first_command) && + (tf_test_s_timeout(timeout, + &nRelativeTimeoutJiffies) == 0)) + /* + * If command queue is empty and if timeout has not + * expired secure world is not schedulable + */ + goto wait; + } + + finish_wait(&comm->wait_queue, &wait); + wait_prepared = false; + + /* + * Yield to the Secure World + */ +#ifdef CONFIG_TF_ZEBRA +schedule_secure_world: + if (*secure_is_idle) { + tf_l4sec_clkdm_wakeup(true); + *secure_is_idle = false; + } +#endif + + result = tf_schedule_secure_world(comm, false); + if (result < 0) + goto exit; + goto copy_answers; + +wait: + if (bKillable && (sigkill_pending())) { + if (command_status == TF_COMMAND_STATE_PENDING) + result = -EINTR; /* Command was not sent. */ + else + /* Command was sent but no answer was received yet. */ + result = -EIO; + + dprintk(KERN_ERR "[pid=%d] tf_send_recv: " + "Signal Pending while waiting. Return error %d\n", + current->pid, result); + goto exit; + } + + if (nRelativeTimeoutJiffies == MAX_SCHEDULE_TIMEOUT) + dprintk(KERN_INFO "[pid=%d] tf_send_recv: " + "prepare to sleep infinitely\n", current->pid); + else + dprintk(KERN_INFO "tf_send_recv: " + "prepare to sleep 0x%lx jiffies\n", + nRelativeTimeoutJiffies); + +#ifdef CONFIG_TF_ZEBRA + if (!(*secure_is_idle)) { + if (tf_schedule_secure_world(comm, true) == STATUS_PENDING) { + finish_wait(&comm->wait_queue, &wait); + wait_prepared = false; + goto copy_answers; + } + tf_l4sec_clkdm_allow_idle(true); + *secure_is_idle = true; + } +#endif + + /* go to sleep */ + if (schedule_timeout(nRelativeTimeoutJiffies) == 0) + dprintk(KERN_INFO + "tf_send_recv: timeout expired\n"); + else + dprintk(KERN_INFO + "tf_send_recv: signal delivered\n"); + + finish_wait(&comm->wait_queue, &wait); + wait_prepared = false; + goto copy_answers; + +exit: + if (wait_prepared) { + finish_wait(&comm->wait_queue, &wait); + wait_prepared = false; + } + +#ifdef CONFIG_TF_ZEBRA + if ((!(*secure_is_idle)) && (result != -EIO)) { + if (tf_schedule_secure_world(comm, true) == STATUS_PENDING) + goto copy_answers; + + tf_l4sec_clkdm_allow_idle(true); + *secure_is_idle = true; + } +#endif + +#ifdef CONFIG_FREEZER + current->flags &= ~(PF_FREEZER_NOSIG); + current->flags |= (saved_flags & PF_FREEZER_NOSIG); +#endif + + return result; +} + +/* + * Sends the specified message through the specified communication channel. + * + * This function sends the message and waits for the corresponding answer + * It may return if a signal needs to be delivered. + * + * Returns zero upon successful completion, or an appropriate error code upon + * failure. + */ +int tf_send_receive(struct tf_comm *comm, + union tf_command *command, + union tf_answer *answer, + struct tf_connection *connection, + bool bKillable) +{ + int error; + struct tf_answer_struct answerStructure; +#ifdef CONFIG_SMP + long ret_affinity; + cpumask_t saved_cpu_mask; + cpumask_t local_cpu_mask = CPU_MASK_NONE; +#endif +#ifdef CONFIG_TF_ZEBRA + bool secure_is_idle = true; +#endif + + answerStructure.answer = answer; + answerStructure.answer_copied = false; + + if (command != NULL) + command->header.operation_id = (u32) &answerStructure; + + dprintk(KERN_INFO "tf_send_receive\n"); + +#ifdef CONFIG_TF_ZEBRA + if (!test_bit(TF_COMM_FLAG_PA_AVAILABLE, &comm->flags)) { + dprintk(KERN_ERR "tf_send_receive(%p): " + "Secure world not started\n", comm); + + return -EFAULT; + } +#endif + + if (test_bit(TF_COMM_FLAG_TERMINATING, &(comm->flags)) != 0) { + dprintk(KERN_DEBUG + "tf_send_receive: Flag Terminating is set\n"); + return 0; + } + +#ifdef CONFIG_SMP + cpu_set(0, local_cpu_mask); + sched_getaffinity(0, &saved_cpu_mask); + ret_affinity = sched_setaffinity(0, &local_cpu_mask); + if (ret_affinity != 0) + dprintk(KERN_ERR "sched_setaffinity #1 -> 0x%lX", ret_affinity); +#endif + + + /* + * Send the command + */ + error = tf_send_recv(comm, + command, &answerStructure, connection, bKillable + #ifdef CONFIG_TF_ZEBRA + , &secure_is_idle + #endif + ); + + if (!bKillable && sigkill_pending()) { + if ((command->header.message_type == + TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT) && + (answer->create_device_context.error_code == + S_SUCCESS)) { + + /* + * CREATE_DEVICE_CONTEXT was interrupted. + */ + dprintk(KERN_INFO "tf_send_receive: " + "sending DESTROY_DEVICE_CONTEXT\n"); + answerStructure.answer = answer; + answerStructure.answer_copied = false; + + command->header.message_type = + TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT; + command->header.message_size = + (sizeof(struct + tf_command_destroy_device_context) - + sizeof(struct tf_command_header))/sizeof(u32); + command->header.operation_id = + (u32) &answerStructure; + command->destroy_device_context.device_context = + answer->create_device_context. + device_context; + + goto destroy_context; + } + } + + if (error == 0) { + /* + * tf_send_recv returned Success. + */ + if (command->header.message_type == + TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT) { + spin_lock(&(connection->state_lock)); + connection->state = TF_CONN_STATE_VALID_DEVICE_CONTEXT; + spin_unlock(&(connection->state_lock)); + } else if (command->header.message_type == + TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT) { + spin_lock(&(connection->state_lock)); + connection->state = TF_CONN_STATE_NO_DEVICE_CONTEXT; + spin_unlock(&(connection->state_lock)); + } + } else if (error == -EINTR) { + /* + * No command was sent, return failure. + */ + dprintk(KERN_ERR + "tf_send_receive: " + "tf_send_recv failed (error %d) !\n", + error); + } else if (error == -EIO) { + /* + * A command was sent but its answer is still pending. + */ + + /* means bKillable is true */ + dprintk(KERN_ERR + "tf_send_receive: " + "tf_send_recv interrupted (error %d)." + "Send DESTROY_DEVICE_CONTEXT.\n", error); + + /* Send the DESTROY_DEVICE_CONTEXT. */ + answerStructure.answer = answer; + answerStructure.answer_copied = false; + + command->header.message_type = + TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT; + command->header.message_size = + (sizeof(struct tf_command_destroy_device_context) - + sizeof(struct tf_command_header))/sizeof(u32); + command->header.operation_id = + (u32) &answerStructure; + command->destroy_device_context.device_context = + connection->device_context; + + error = tf_send_recv(comm, + command, &answerStructure, connection, false + #ifdef CONFIG_TF_ZEBRA + , &secure_is_idle + #endif + ); + if (error == -EINTR) { + /* + * Another thread already sent + * DESTROY_DEVICE_CONTEXT. + * We must still wait for the answer + * to the original command. + */ + command = NULL; + goto destroy_context; + } else { + /* An answer was received. + * Check if it is the answer + * to the DESTROY_DEVICE_CONTEXT. + */ + spin_lock(&comm->lock); + if (answer->header.message_type != + TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT) { + answerStructure.answer_copied = false; + } + spin_unlock(&comm->lock); + if (!answerStructure.answer_copied) { + /* Answer to DESTROY_DEVICE_CONTEXT + * was not yet received. + * Wait for the answer. + */ + dprintk(KERN_INFO + "[pid=%d] tf_send_receive:" + "Answer to DESTROY_DEVICE_CONTEXT" + "not yet received.Retry\n", + current->pid); + command = NULL; + goto destroy_context; + } + } + } + + dprintk(KERN_INFO "tf_send_receive(): Message answer ready\n"); + goto exit; + +destroy_context: + error = tf_send_recv(comm, + command, &answerStructure, connection, false + #ifdef CONFIG_TF_ZEBRA + , &secure_is_idle + #endif + ); + + /* + * tf_send_recv cannot return an error because + * it's not killable and not within a connection + */ + BUG_ON(error != 0); + + /* Reset the state, so a new CREATE DEVICE CONTEXT can be sent */ + spin_lock(&(connection->state_lock)); + connection->state = TF_CONN_STATE_NO_DEVICE_CONTEXT; + spin_unlock(&(connection->state_lock)); + +exit: + +#ifdef CONFIG_SMP + ret_affinity = sched_setaffinity(0, &saved_cpu_mask); + if (ret_affinity != 0) + dprintk(KERN_ERR "sched_setaffinity #2 -> 0x%lX", ret_affinity); +#endif + return error; +} + +/*---------------------------------------------------------------------------- + * Power management + *----------------------------------------------------------------------------*/ + + +/* + * Handles all the power management calls. + * The operation is the type of power management + * operation to be performed. + * + * This routine will only return if a failure occured or if + * the required opwer management is of type "resume". + * "Hibernate" and "Shutdown" should lock when doing the + * corresponding SMC to the Secure World + */ +int tf_power_management(struct tf_comm *comm, + enum TF_POWER_OPERATION operation) +{ + u32 status; + int error = 0; + + dprintk(KERN_INFO "tf_power_management(%d)\n", operation); + +#ifdef CONFIG_TF_ZEBRA + if (!test_bit(TF_COMM_FLAG_PA_AVAILABLE, &comm->flags)) { + dprintk(KERN_INFO "tf_power_management(%p): " + "succeeded (not started)\n", comm); + + return 0; + } +#endif + + status = ((tf_read_reg32(&(comm->pBuffer->status_s)) + & TF_STATUS_POWER_STATE_MASK) + >> TF_STATUS_POWER_STATE_SHIFT); + + switch (operation) { + case TF_POWER_OPERATION_SHUTDOWN: + switch (status) { + case TF_POWER_MODE_ACTIVE: + error = tf_pm_shutdown(comm); + + if (error) { + dprintk(KERN_ERR "tf_power_management(): " + "Failed with error code 0x%08x\n", + error); + goto error; + } + break; + + default: + goto not_allowed; + } + break; + + case TF_POWER_OPERATION_HIBERNATE: + switch (status) { + case TF_POWER_MODE_ACTIVE: + error = tf_pm_hibernate(comm); + + if (error) { + dprintk(KERN_ERR "tf_power_management(): " + "Failed with error code 0x%08x\n", + error); + goto error; + } + break; + + default: + goto not_allowed; + } + break; + + case TF_POWER_OPERATION_RESUME: + error = tf_pm_resume(comm); + + if (error != 0) { + dprintk(KERN_ERR "tf_power_management(): " + "Failed with error code 0x%08x\n", + error); + goto error; + } + break; + } + + dprintk(KERN_INFO "tf_power_management(): succeeded\n"); + return 0; + +not_allowed: + dprintk(KERN_ERR "tf_power_management(): " + "Power command not allowed in current " + "Secure World state %d\n", status); + error = -ENOTTY; +error: + return error; +} diff --git a/security/smc/tf_comm.h b/security/smc/tf_comm.h new file mode 100644 index 0000000..48bd934 --- /dev/null +++ b/security/smc/tf_comm.h @@ -0,0 +1,204 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __TF_COMM_H__ +#define __TF_COMM_H__ + +#include "tf_defs.h" +#include "tf_protocol.h" + +/*---------------------------------------------------------------------------- + * Misc + *----------------------------------------------------------------------------*/ + +void tf_set_current_time(struct tf_comm *comm); + +/* + * Atomic accesses to 32-bit variables in the L1 Shared buffer + */ +static inline u32 tf_read_reg32(const u32 *comm_buffer) +{ + u32 result; + + __asm__ __volatile__("@ tf_read_reg32\n" + "ldrex %0, [%1]\n" + : "=&r" (result) + : "r" (comm_buffer) + ); + + return result; +} + +static inline void tf_write_reg32(void *comm_buffer, u32 value) +{ + u32 tmp; + + __asm__ __volatile__("@ tf_write_reg32\n" + "1: ldrex %0, [%2]\n" + " strex %0, %1, [%2]\n" + " teq %0, #0\n" + " bne 1b" + : "=&r" (tmp) + : "r" (value), "r" (comm_buffer) + : "cc" + ); +} + +/* + * Atomic accesses to 64-bit variables in the L1 Shared buffer + */ +static inline u64 tf_read_reg64(void *comm_buffer) +{ + u64 result; + + __asm__ __volatile__("@ tf_read_reg64\n" + "ldrexd %0, [%1]\n" + : "=&r" (result) + : "r" (comm_buffer) + ); + + return result; +} + +static inline void tf_write_reg64(void *comm_buffer, u64 value) +{ + u64 tmp; + + __asm__ __volatile__("@ tf_write_reg64\n" + "1: ldrexd %0, [%2]\n" + " strexd %0, %1, [%2]\n" + " teq %0, #0\n" + " bne 1b" + : "=&r" (tmp) + : "r" (value), "r" (comm_buffer) + : "cc" + ); +} + +/*---------------------------------------------------------------------------- + * SMC operations + *----------------------------------------------------------------------------*/ + +/* RPC return values */ +#define RPC_NO 0x00 /* No RPC to execute */ +#define RPC_YIELD 0x01 /* Yield RPC */ +#define RPC_NON_YIELD 0x02 /* non-Yield RPC */ + +int tf_rpc_execute(struct tf_comm *comm); + +/*---------------------------------------------------------------------------- + * Shared memory related operations + *----------------------------------------------------------------------------*/ + +#define L1_DESCRIPTOR_FAULT (0x00000000) +#define L2_DESCRIPTOR_FAULT (0x00000000) + +#define L2_DESCRIPTOR_ADDR_MASK (0xFFFFF000) + +#define DESCRIPTOR_V13_12_MASK (0x3 << PAGE_SHIFT) +#define DESCRIPTOR_V13_12_GET(a) ((a & DESCRIPTOR_V13_12_MASK) >> PAGE_SHIFT) + +struct tf_coarse_page_table *tf_alloc_coarse_page_table( + struct tf_coarse_page_table_allocation_context *alloc_context, + u32 type); + +void tf_free_coarse_page_table( + struct tf_coarse_page_table_allocation_context *alloc_context, + struct tf_coarse_page_table *coarse_pg_table, + int force); + +void tf_init_coarse_page_table_allocator( + struct tf_coarse_page_table_allocation_context *alloc_context); + +void tf_release_coarse_page_table_allocator( + struct tf_coarse_page_table_allocation_context *alloc_context); + +struct page *tf_l2_page_descriptor_to_page(u32 l2_page_descriptor); + +u32 tf_get_l2_descriptor_common(u32 vaddr, struct mm_struct *mm); + +void tf_cleanup_shared_memory( + struct tf_coarse_page_table_allocation_context *alloc_context, + struct tf_shmem_desc *shmem_desc, + u32 full_cleanup); + +int tf_fill_descriptor_table( + struct tf_coarse_page_table_allocation_context *alloc_context, + struct tf_shmem_desc *shmem_desc, + u32 buffer, + struct vm_area_struct **vmas, + u32 descriptors[TF_MAX_COARSE_PAGES], + u32 buffer_size, + u32 *buffer_start_offset, + bool in_user_space, + u32 flags, + u32 *descriptor_count); + +/*---------------------------------------------------------------------------- + * Standard communication operations + *----------------------------------------------------------------------------*/ + +#define STATUS_PENDING 0x00000001 + +int tf_schedule_secure_world(struct tf_comm *comm, bool prepare_exit); + +int tf_send_receive( + struct tf_comm *comm, + union tf_command *command, + union tf_answer *answer, + struct tf_connection *connection, + bool bKillable); + + +/** + * get a pointer to the secure world description. + * This points directly into the L1 shared buffer + * and is valid only once the communication has + * been initialized + **/ +u8 *tf_get_description(struct tf_comm *comm); + +/*---------------------------------------------------------------------------- + * Power management + *----------------------------------------------------------------------------*/ + +enum TF_POWER_OPERATION { + TF_POWER_OPERATION_HIBERNATE = 1, + TF_POWER_OPERATION_SHUTDOWN = 2, + TF_POWER_OPERATION_RESUME = 3, +}; + +int tf_pm_hibernate(struct tf_comm *comm); +int tf_pm_resume(struct tf_comm *comm); +int tf_pm_shutdown(struct tf_comm *comm); + +int tf_power_management(struct tf_comm *comm, + enum TF_POWER_OPERATION operation); + + +/*---------------------------------------------------------------------------- + * Communication initialization and termination + *----------------------------------------------------------------------------*/ + +int tf_init(struct tf_comm *comm); + +void tf_terminate(struct tf_comm *comm); + + +#endif /* __TF_COMM_H__ */ diff --git a/security/smc/tf_comm_mshield.c b/security/smc/tf_comm_mshield.c new file mode 100644 index 0000000..c36473e --- /dev/null +++ b/security/smc/tf_comm_mshield.c @@ -0,0 +1,1013 @@ +/** + * Copyright (c) 2010 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <asm/div64.h> +#include <asm/system.h> +#include <asm/cputype.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/page-flags.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/version.h> +#include <linux/jiffies.h> +#include <linux/dma-mapping.h> +#include <linux/cpu.h> + +#include <asm/cacheflush.h> + +#include "tf_defs.h" +#include "tf_comm.h" +#include "tf_util.h" +#include "tf_conn.h" +#include "tf_zebra.h" +#include "tf_crypto.h" + +/*-------------------------------------------------------------------------- + * Internal constants + *-------------------------------------------------------------------------- */ + +/* RPC commands */ +#define RPC_CMD_YIELD 0x00 +#define RPC_CMD_INIT 0x01 +#define RPC_CMD_TRACE 0x02 + +/* RPC return values to secure world */ +#define RPC_SUCCESS 0x00000000 +#define RPC_ERROR_BAD_PARAMETERS 0xFFFF0006 +#define RPC_ERROR_CONNECTION_PROTOCOL 0xFFFF3020 + +/* + * RPC call status + * + * 0: the secure world yielded due to an interrupt + * 1: the secure world yielded on an RPC (no public world thread is handling it) + * 2: the secure world yielded on an RPC and the response to that RPC is now in + * place + */ +#define RPC_ADVANCEMENT_NONE 0 +#define RPC_ADVANCEMENT_PENDING 1 +#define RPC_ADVANCEMENT_FINISHED 2 + +u32 g_RPC_advancement; +u32 g_RPC_parameters[4] = {0, 0, 0, 0}; +u32 g_secure_task_id; +u32 g_service_end; + +/* + * Secure ROMCode HAL API Identifiers + */ +#define API_HAL_SDP_RUNTIMEINIT_INDEX 0x04 +#define API_HAL_LM_PALOAD_INDEX 0x05 +#define API_HAL_LM_PAUNLOADALL_INDEX 0x07 +#define API_HAL_TASK_MGR_RPCINIT_INDEX 0x08 +#define API_HAL_KM_GETSECUREROMCODECRC_INDEX 0x0B +#define API_HAL_SEC_L3_RAM_RESIZE_INDEX 0x17 + +#define API_HAL_RET_VALUE_OK 0x0 + +/* SE entry flags */ +#define FLAG_START_HAL_CRITICAL 0x4 +#define FLAG_IRQFIQ_MASK 0x3 +#define FLAG_IRQ_ENABLE 0x2 +#define FLAG_FIQ_ENABLE 0x1 + +#define SMICODEPUB_IRQ_END 0xFE +#define SMICODEPUB_FIQ_END 0xFD +#define SMICODEPUB_RPC_END 0xFC + +#define SEC_RAM_SIZE_40KB 0x0000A000 +#define SEC_RAM_SIZE_48KB 0x0000C000 +#define SEC_RAM_SIZE_52KB 0x0000D000 +#define SEC_RAM_SIZE_60KB 0x0000F000 +#define SEC_RAM_SIZE_64KB 0x00010000 + +struct tf_ns_pa_info { + void *certificate; + void *parameters; + void *results; +}; + +/* + * AFY: I would like to remove the L0 buffer altogether: + * - you can use the L1 shared buffer to pass the RPC parameters and results: + * I think these easily fit in 256 bytes and you can use the area at + * offset 0x2C0-0x3BF in the L1 shared buffer + */ +struct tf_init_buffer { + u32 init_status; + u32 protocol_version; + u32 l1_shared_buffer_descr; + u32 backing_store_addr; + u32 backext_storage_addr; + u32 workspace_addr; + u32 workspace_size; + u32 properties_length; + u8 properties_buffer[1]; +}; + +#ifdef CONFIG_HAS_WAKELOCK +static struct wake_lock g_tf_wake_lock; +static u32 tf_wake_lock_count = 0; +#endif + +static struct clockdomain *smc_l4_sec_clkdm; +static u32 smc_l4_sec_clkdm_use_count = 0; + +static int __init tf_early_init(void) +{ + g_secure_task_id = 0; + + dprintk(KERN_INFO "SMC early init\n"); + + smc_l4_sec_clkdm = clkdm_lookup("l4_secure_clkdm"); + if (smc_l4_sec_clkdm == NULL) + return -EFAULT; + +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&g_tf_wake_lock, WAKE_LOCK_SUSPEND, + TF_DEVICE_BASE_NAME); +#endif + + return 0; +} +early_initcall(tf_early_init); + +/* + * Function responsible for formatting parameters to pass from NS world to + * S world + */ +u32 omap4_secure_dispatcher(u32 app_id, u32 flags, u32 nargs, + u32 arg1, u32 arg2, u32 arg3, u32 arg4) +{ + u32 ret; + unsigned long iflags; + u32 pub2sec_args[5] = {0, 0, 0, 0, 0}; + + /*dprintk(KERN_INFO "omap4_secure_dispatcher: " + "app_id=0x%08x, flags=0x%08x, nargs=%u\n", + app_id, flags, nargs);*/ + + /*if (nargs != 0) + dprintk(KERN_INFO + "omap4_secure_dispatcher: args=%08x, %08x, %08x, %08x\n", + arg1, arg2, arg3, arg4);*/ + + pub2sec_args[0] = nargs; + pub2sec_args[1] = arg1; + pub2sec_args[2] = arg2; + pub2sec_args[3] = arg3; + pub2sec_args[4] = arg4; + + /* Make sure parameters are visible to the secure world */ + dmac_flush_range((void *)pub2sec_args, + (void *)(((u32)(pub2sec_args)) + 5*sizeof(u32))); + outer_clean_range(__pa(pub2sec_args), + __pa(pub2sec_args) + 5*sizeof(u32)); + wmb(); + + /* + * Put L4 Secure clock domain to SW_WKUP so that modules are accessible + */ + tf_l4sec_clkdm_wakeup(false); + + local_irq_save(iflags); +#ifdef DEBUG + BUG_ON((read_mpidr() & 0x00000003) != 0); +#endif + /* proc_id is always 0 */ + ret = schedule_secure_world(app_id, 0, flags, __pa(pub2sec_args)); + local_irq_restore(iflags); + + /* Restore the HW_SUP on L4 Sec clock domain so hardware can idle */ + tf_l4sec_clkdm_allow_idle(false); + + /*dprintk(KERN_INFO "omap4_secure_dispatcher()\n");*/ + + return ret; +} + +/* Yields the Secure World */ +int tf_schedule_secure_world(struct tf_comm *comm, bool prepare_exit) +{ + int status = 0; + int ret; + unsigned long iflags; + u32 appli_id; + + tf_set_current_time(comm); + + local_irq_save(iflags); + + switch (g_RPC_advancement) { + case RPC_ADVANCEMENT_NONE: + /* Return from IRQ */ + appli_id = SMICODEPUB_IRQ_END; + if (prepare_exit) + status = STATUS_PENDING; + break; + case RPC_ADVANCEMENT_PENDING: + /* nothing to do in this case */ + goto exit; + default: + case RPC_ADVANCEMENT_FINISHED: + if (prepare_exit) + goto exit; + appli_id = SMICODEPUB_RPC_END; + g_RPC_advancement = RPC_ADVANCEMENT_NONE; + break; + } + + g_service_end = 1; + /* yield to the Secure World */ + ret = omap4_secure_dispatcher(appli_id, /* app_id */ + 0, 0, /* flags, nargs */ + 0, 0, 0, 0); /* arg1, arg2, arg3, arg4 */ + if (g_service_end != 0) { + dprintk(KERN_ERR "Service End ret=%X\n", ret); + + if (ret == 0) { + dmac_flush_range((void *)comm->init_shared_buffer, + (void *)(((u32)(comm->init_shared_buffer)) + + PAGE_SIZE)); + outer_inv_range(__pa(comm->init_shared_buffer), + __pa(comm->init_shared_buffer) + + PAGE_SIZE); + + ret = ((struct tf_init_buffer *) + (comm->init_shared_buffer))->init_status; + + dprintk(KERN_ERR "SMC PA failure ret=%X\n", ret); + if (ret == 0) + ret = -EFAULT; + } + clear_bit(TF_COMM_FLAG_PA_AVAILABLE, &comm->flags); + omap4_secure_dispatcher(API_HAL_LM_PAUNLOADALL_INDEX, + FLAG_START_HAL_CRITICAL, 0, 0, 0, 0, 0); + status = ret; + } + +exit: + local_irq_restore(iflags); + + return status; +} + +/* Initializes the SE (SDP, SRAM resize, RPC handler) */ +static int tf_se_init(struct tf_comm *comm, + u32 sdp_backing_store_addr, u32 sdp_bkext_store_addr) +{ + int error; + unsigned int crc; + + if (comm->se_initialized) { + dprintk(KERN_INFO "tf_se_init: SE already initialized... " + "nothing to do\n"); + return 0; + } + + /* Secure CRC read */ + dprintk(KERN_INFO "tf_se_init: Secure CRC Read...\n"); + + crc = omap4_secure_dispatcher(API_HAL_KM_GETSECUREROMCODECRC_INDEX, + 0, 0, 0, 0, 0, 0); + printk(KERN_INFO "SMC: SecureCRC=0x%08X\n", crc); + + /* + * Flush caches before resize, just to be sure there is no + * pending public data writes back to SRAM that could trigger a + * security violation once their address space is marked as + * secure. + */ +#define OMAP4_SRAM_PA 0x40300000 +#define OMAP4_SRAM_SIZE 0xe000 + flush_cache_all(); + outer_flush_range(OMAP4_SRAM_PA, + OMAP4_SRAM_PA + OMAP4_SRAM_SIZE); + wmb(); + + /* SRAM resize */ + dprintk(KERN_INFO "tf_se_init: SRAM resize (52KB)...\n"); + error = omap4_secure_dispatcher(API_HAL_SEC_L3_RAM_RESIZE_INDEX, + FLAG_FIQ_ENABLE | FLAG_START_HAL_CRITICAL, 1, + SEC_RAM_SIZE_52KB, 0, 0, 0); + + if (error == API_HAL_RET_VALUE_OK) { + dprintk(KERN_INFO "tf_se_init: SRAM resize OK\n"); + } else { + dprintk(KERN_ERR "tf_se_init: " + "SRAM resize failed [0x%x]\n", error); + goto error; + } + + /* SDP init */ + dprintk(KERN_INFO "tf_se_init: SDP runtime init..." + "(sdp_backing_store_addr=%x, sdp_bkext_store_addr=%x)\n", + sdp_backing_store_addr, sdp_bkext_store_addr); + error = omap4_secure_dispatcher(API_HAL_SDP_RUNTIMEINIT_INDEX, + FLAG_FIQ_ENABLE | FLAG_START_HAL_CRITICAL, 2, + sdp_backing_store_addr, sdp_bkext_store_addr, 0, 0); + + if (error == API_HAL_RET_VALUE_OK) { + dprintk(KERN_INFO "tf_se_init: SDP runtime init OK\n"); + } else { + dprintk(KERN_ERR "tf_se_init: " + "SDP runtime init failed [0x%x]\n", error); + goto error; + } + + /* RPC init */ + dprintk(KERN_INFO "tf_se_init: RPC init...\n"); + error = omap4_secure_dispatcher(API_HAL_TASK_MGR_RPCINIT_INDEX, + FLAG_START_HAL_CRITICAL, 1, + (u32) (u32(*const) (u32, u32, u32, u32)) &rpc_handler, 0, 0, 0); + + if (error == API_HAL_RET_VALUE_OK) { + dprintk(KERN_INFO "tf_se_init: RPC init OK\n"); + } else { + dprintk(KERN_ERR "tf_se_init: " + "RPC init failed [0x%x]\n", error); + goto error; + } + + comm->se_initialized = true; + + return 0; + +error: + return -EFAULT; +} + +/* Check protocol version returned by the PA */ +static u32 tf_rpc_init(struct tf_comm *comm) +{ + u32 protocol_version; + u32 rpc_error = RPC_SUCCESS; + + dprintk(KERN_INFO "tf_rpc_init(%p)\n", comm); + + spin_lock(&(comm->lock)); + + dmac_flush_range((void *)comm->init_shared_buffer, + (void *)(((u32)(comm->init_shared_buffer)) + PAGE_SIZE)); + outer_inv_range(__pa(comm->init_shared_buffer), + __pa(comm->init_shared_buffer) + PAGE_SIZE); + + protocol_version = ((struct tf_init_buffer *) + (comm->init_shared_buffer))->protocol_version; + + if ((GET_PROTOCOL_MAJOR_VERSION(protocol_version)) + != TF_S_PROTOCOL_MAJOR_VERSION) { + dprintk(KERN_ERR "SMC: Unsupported SMC Protocol PA Major " + "Version (0x%02x, expected 0x%02x)!\n", + GET_PROTOCOL_MAJOR_VERSION(protocol_version), + TF_S_PROTOCOL_MAJOR_VERSION); + rpc_error = RPC_ERROR_CONNECTION_PROTOCOL; + } else { + rpc_error = RPC_SUCCESS; + } + + spin_unlock(&(comm->lock)); + + register_smc_public_crypto_digest(); + register_smc_public_crypto_aes(); + + return rpc_error; +} + +static u32 tf_rpc_trace(struct tf_comm *comm) +{ + dprintk(KERN_INFO "tf_rpc_trace(%p)\n", comm); + +#ifdef CONFIG_SECURE_TRACE + spin_lock(&(comm->lock)); + printk(KERN_INFO "SMC PA: %s", + comm->pBuffer->rpc_trace_buffer); + spin_unlock(&(comm->lock)); +#endif + return RPC_SUCCESS; +} + +/* + * Handles RPC calls + * + * Returns: + * - RPC_NO if there was no RPC to execute + * - RPC_YIELD if there was a Yield RPC + * - RPC_NON_YIELD if there was a non-Yield RPC + */ + +int tf_rpc_execute(struct tf_comm *comm) +{ + u32 rpc_command; + u32 rpc_error = RPC_NO; + +#ifdef DEBUG + BUG_ON((read_mpidr() & 0x00000003) != 0); +#endif + + /* Lock the RPC */ + mutex_lock(&(comm->rpc_mutex)); + + rpc_command = g_RPC_parameters[1]; + + if (g_RPC_advancement == RPC_ADVANCEMENT_PENDING) { + dprintk(KERN_INFO "tf_rpc_execute: " + "Executing CMD=0x%x\n", + g_RPC_parameters[1]); + + switch (rpc_command) { + case RPC_CMD_YIELD: + dprintk(KERN_INFO "tf_rpc_execute: " + "RPC_CMD_YIELD\n"); + + rpc_error = RPC_YIELD; + g_RPC_parameters[0] = RPC_SUCCESS; + break; + + case RPC_CMD_TRACE: + rpc_error = RPC_NON_YIELD; + g_RPC_parameters[0] = tf_rpc_trace(comm); + break; + + default: + if (tf_crypto_execute_rpc(rpc_command, + comm->pBuffer->rpc_cus_buffer) != 0) + g_RPC_parameters[0] = RPC_ERROR_BAD_PARAMETERS; + else + g_RPC_parameters[0] = RPC_SUCCESS; + rpc_error = RPC_NON_YIELD; + break; + } + g_RPC_advancement = RPC_ADVANCEMENT_FINISHED; + } + + mutex_unlock(&(comm->rpc_mutex)); + + dprintk(KERN_INFO "tf_rpc_execute: Return 0x%x\n", + rpc_error); + + return rpc_error; +} + +/*-------------------------------------------------------------------------- + * L4 SEC Clock domain handling + *-------------------------------------------------------------------------- */ + +static DEFINE_SPINLOCK(clk_lock); +void tf_l4sec_clkdm_wakeup(bool wakelock) +{ + unsigned long flags; + spin_lock_irqsave(&clk_lock, flags); +#ifdef CONFIG_HAS_WAKELOCK + if (wakelock) { + tf_wake_lock_count++; + wake_lock(&g_tf_wake_lock); + } +#endif + smc_l4_sec_clkdm_use_count++; + clkdm_wakeup(smc_l4_sec_clkdm); + spin_unlock_irqrestore(&clk_lock, flags); +} + +void tf_l4sec_clkdm_allow_idle(bool wakeunlock) +{ + unsigned long flags; + spin_lock_irqsave(&clk_lock, flags); + smc_l4_sec_clkdm_use_count--; + if (smc_l4_sec_clkdm_use_count == 0) + clkdm_allow_idle(smc_l4_sec_clkdm); +#ifdef CONFIG_HAS_WAKELOCK + if (wakeunlock){ + tf_wake_lock_count--; + if (tf_wake_lock_count == 0) + wake_unlock(&g_tf_wake_lock); + } +#endif + spin_unlock_irqrestore(&clk_lock, flags); +} + +/*-------------------------------------------------------------------------- + * Power management + *-------------------------------------------------------------------------- */ + /* + * Perform a Secure World shutdown operation. + * The routine does not return if the operation succeeds. + * the routine returns an appropriate error code if + * the operation fails. + */ +int tf_pm_shutdown(struct tf_comm *comm) +{ + + int error; + union tf_command command; + union tf_answer answer; + + dprintk(KERN_INFO "tf_pm_shutdown()\n"); + + memset(&command, 0, sizeof(command)); + + command.header.message_type = TF_MESSAGE_TYPE_MANAGEMENT; + command.header.message_size = + (sizeof(struct tf_command_management) - + sizeof(struct tf_command_header))/sizeof(u32); + + command.management.command = TF_MANAGEMENT_SHUTDOWN; + + error = tf_send_receive( + comm, + &command, + &answer, + NULL, + false); + + if (error != 0) { + dprintk(KERN_ERR "tf_pm_shutdown(): " + "tf_send_receive failed (error %d)!\n", + error); + return error; + } + +#ifdef CONFIG_TF_DRIVER_DEBUG_SUPPORT + if (answer.header.error_code != 0) + dprintk(KERN_ERR "tf_driver: shutdown failed.\n"); + else + dprintk(KERN_INFO "tf_driver: shutdown succeeded.\n"); +#endif + + return answer.header.error_code; +} + + +int tf_pm_hibernate(struct tf_comm *comm) +{ + struct tf_device *dev = tf_get_device(); + + dprintk(KERN_INFO "tf_pm_hibernate()\n"); + + /* + * As we enter in CORE OFF, the keys are going to be cleared. + * Reset the global key context. + * When the system leaves CORE OFF, this will force the driver to go + * through the secure world which will reconfigure the accelerators. + */ + dev->aes1_key_context = 0; + dev->des_key_context = 0; +#ifndef CONFIG_SMC_KERNEL_CRYPTO + dev->sham1_is_public = false; +#endif + return 0; +} + +#ifdef CONFIG_SMC_KERNEL_CRYPTO +#define DELAYED_RESUME_NONE 0 +#define DELAYED_RESUME_PENDING 1 +#define DELAYED_RESUME_ONGOING 2 + +static DEFINE_SPINLOCK(tf_delayed_resume_lock); +static int tf_need_delayed_resume = DELAYED_RESUME_NONE; + +int tf_delayed_secure_resume(void) +{ + int ret; + union tf_command message; + union tf_answer answer; + struct tf_device *dev = tf_get_device(); + + spin_lock(&tf_delayed_resume_lock); + if (likely(tf_need_delayed_resume == DELAYED_RESUME_NONE)) { + spin_unlock(&tf_delayed_resume_lock); + return 0; + } + + if (unlikely(tf_need_delayed_resume == DELAYED_RESUME_ONGOING)) { + spin_unlock(&tf_delayed_resume_lock); + + /* + * Wait for the other caller to actually finish the delayed + * resume operation + */ + while (tf_need_delayed_resume != DELAYED_RESUME_NONE) + cpu_relax(); + + return 0; + } + + tf_need_delayed_resume = DELAYED_RESUME_ONGOING; + spin_unlock(&tf_delayed_resume_lock); + + /* + * When the system leaves CORE OFF, HWA are configured as secure. We + * need them as public for the Linux Crypto API. + */ + memset(&message, 0, sizeof(message)); + + message.header.message_type = TF_MESSAGE_TYPE_MANAGEMENT; + message.header.message_size = + (sizeof(struct tf_command_management) - + sizeof(struct tf_command_header))/sizeof(u32); + message.management.command = + TF_MANAGEMENT_RESUME_FROM_CORE_OFF; + + ret = tf_send_receive(&dev->sm, &message, &answer, NULL, false); + if (ret) { + printk(KERN_ERR "tf_pm_resume(%p): " + "tf_send_receive failed (error %d)!\n", + &dev->sm, ret); + + unregister_smc_public_crypto_digest(); + unregister_smc_public_crypto_aes(); + return ret; + } + + if (answer.header.error_code) { + unregister_smc_public_crypto_digest(); + unregister_smc_public_crypto_aes(); + } + + spin_lock(&tf_delayed_resume_lock); + tf_need_delayed_resume = DELAYED_RESUME_NONE; + spin_unlock(&tf_delayed_resume_lock); + + return answer.header.error_code; +} +#endif + +int tf_pm_resume(struct tf_comm *comm) +{ + + dprintk(KERN_INFO "tf_pm_resume()\n"); + #if 0 + { + void *workspace_va; + struct tf_device *dev = tf_get_device(); + workspace_va = ioremap(dev->workspace_addr, + dev->workspace_size); + printk(KERN_INFO + "Read first word of workspace [0x%x]\n", + *(uint32_t *)workspace_va); + } + #endif + +#ifdef CONFIG_SMC_KERNEL_CRYPTO + spin_lock(&tf_delayed_resume_lock); + tf_need_delayed_resume = DELAYED_RESUME_PENDING; + spin_unlock(&tf_delayed_resume_lock); +#endif + return 0; +} + +/*-------------------------------------------------------------------------- + * Initialization + *-------------------------------------------------------------------------- */ + +int tf_init(struct tf_comm *comm) +{ + spin_lock_init(&(comm->lock)); + comm->flags = 0; + comm->pBuffer = NULL; + comm->init_shared_buffer = NULL; + + comm->se_initialized = false; + + init_waitqueue_head(&(comm->wait_queue)); + mutex_init(&(comm->rpc_mutex)); + + if (tf_crypto_init() != PUBLIC_CRYPTO_OPERATION_SUCCESS) + return -EFAULT; + + if (omap_type() == OMAP2_DEVICE_TYPE_GP) { + register_smc_public_crypto_digest(); + register_smc_public_crypto_aes(); + } + + return 0; +} + +/* Start the SMC PA */ +int tf_start(struct tf_comm *comm, + u32 workspace_addr, u32 workspace_size, + u8 *pa_buffer, u32 pa_size, + u8 *properties_buffer, u32 properties_length) +{ + struct tf_init_buffer *init_shared_buffer = NULL; + struct tf_l1_shared_buffer *l1_shared_buffer = NULL; + u32 l1_shared_buffer_descr; + struct tf_ns_pa_info pa_info; + int ret; + u32 descr; + u32 sdp_backing_store_addr; + u32 sdp_bkext_store_addr; +#ifdef CONFIG_SMP + long ret_affinity; + cpumask_t saved_cpu_mask; + cpumask_t local_cpu_mask = CPU_MASK_NONE; + + /* OMAP4 Secure ROM Code can only be called from CPU0. */ + cpu_set(0, local_cpu_mask); + sched_getaffinity(0, &saved_cpu_mask); + ret_affinity = sched_setaffinity(0, &local_cpu_mask); + if (ret_affinity != 0) + dprintk(KERN_ERR "sched_setaffinity #1 -> 0x%lX", ret_affinity); +#endif + + tf_l4sec_clkdm_wakeup(true); + + workspace_size -= SZ_1M; + sdp_backing_store_addr = workspace_addr + workspace_size; + workspace_size -= 0x20000; + sdp_bkext_store_addr = workspace_addr + workspace_size; + + /* + * Implementation notes: + * + * 1/ The PA buffer (pa_buffer)is now owned by this function. + * In case of error, it is responsible for releasing the buffer. + * + * 2/ The PA Info and PA Buffer will be freed through a RPC call + * at the beginning of the PA entry in the SE. + */ + + if (test_bit(TF_COMM_FLAG_PA_AVAILABLE, &comm->flags)) { + dprintk(KERN_ERR "tf_start(%p): " + "The SMC PA is already started\n", comm); + + ret = -EFAULT; + goto error1; + } + + if (sizeof(struct tf_l1_shared_buffer) != PAGE_SIZE) { + dprintk(KERN_ERR "tf_start(%p): " + "The L1 structure size is incorrect!\n", comm); + ret = -EFAULT; + goto error1; + } + + ret = tf_se_init(comm, sdp_backing_store_addr, + sdp_bkext_store_addr); + if (ret != 0) { + dprintk(KERN_ERR "tf_start(%p): " + "SE initialization failed\n", comm); + goto error1; + } + + init_shared_buffer = + (struct tf_init_buffer *) + internal_get_zeroed_page(GFP_KERNEL); + if (init_shared_buffer == NULL) { + dprintk(KERN_ERR "tf_start(%p): " + "Ouf of memory!\n", comm); + + ret = -ENOMEM; + goto error1; + } + /* Ensure the page is mapped */ + __set_page_locked(virt_to_page(init_shared_buffer)); + + l1_shared_buffer = + (struct tf_l1_shared_buffer *) + internal_get_zeroed_page(GFP_KERNEL); + + if (l1_shared_buffer == NULL) { + dprintk(KERN_ERR "tf_start(%p): " + "Ouf of memory!\n", comm); + + ret = -ENOMEM; + goto error1; + } + /* Ensure the page is mapped */ + __set_page_locked(virt_to_page(l1_shared_buffer)); + + dprintk(KERN_INFO "tf_start(%p): " + "L0SharedBuffer={0x%08x, 0x%08x}\n", comm, + (u32) init_shared_buffer, (u32) __pa(init_shared_buffer)); + dprintk(KERN_INFO "tf_start(%p): " + "L1SharedBuffer={0x%08x, 0x%08x}\n", comm, + (u32) l1_shared_buffer, (u32) __pa(l1_shared_buffer)); + + descr = tf_get_l2_descriptor_common((u32) l1_shared_buffer, + current->mm); + l1_shared_buffer_descr = ( + ((u32) __pa(l1_shared_buffer) & 0xFFFFF000) | + (descr & 0xFFF)); + + pa_info.certificate = (void *) __pa(pa_buffer); + pa_info.parameters = (void *) __pa(init_shared_buffer); + pa_info.results = (void *) __pa(init_shared_buffer); + + init_shared_buffer->l1_shared_buffer_descr = l1_shared_buffer_descr; + + init_shared_buffer->backing_store_addr = sdp_backing_store_addr; + init_shared_buffer->backext_storage_addr = sdp_bkext_store_addr; + init_shared_buffer->workspace_addr = workspace_addr; + init_shared_buffer->workspace_size = workspace_size; + + init_shared_buffer->properties_length = properties_length; + if (properties_length == 0) { + init_shared_buffer->properties_buffer[0] = 0; + } else { + /* Test for overflow */ + if ((init_shared_buffer->properties_buffer + + properties_length + > init_shared_buffer->properties_buffer) && + (properties_length <= + init_shared_buffer->properties_length)) { + memcpy(init_shared_buffer->properties_buffer, + properties_buffer, + properties_length); + } else { + dprintk(KERN_INFO "tf_start(%p): " + "Configuration buffer size from userland is " + "incorrect(%d, %d)\n", + comm, (u32) properties_length, + init_shared_buffer->properties_length); + ret = -EFAULT; + goto error1; + } + } + + dprintk(KERN_INFO "tf_start(%p): " + "System Configuration (%d bytes)\n", comm, + init_shared_buffer->properties_length); + dprintk(KERN_INFO "tf_start(%p): " + "Starting PA (%d bytes)...\n", comm, pa_size); + + /* + * Make sure all data is visible to the secure world + */ + dmac_flush_range((void *)init_shared_buffer, + (void *)(((u32)init_shared_buffer) + PAGE_SIZE)); + outer_clean_range(__pa(init_shared_buffer), + __pa(init_shared_buffer) + PAGE_SIZE); + + dmac_flush_range((void *)pa_buffer, + (void *)(pa_buffer + pa_size)); + outer_clean_range(__pa(pa_buffer), + __pa(pa_buffer) + pa_size); + + dmac_flush_range((void *)&pa_info, + (void *)(((u32)&pa_info) + sizeof(struct tf_ns_pa_info))); + outer_clean_range(__pa(&pa_info), + __pa(&pa_info) + sizeof(struct tf_ns_pa_info)); + wmb(); + + spin_lock(&(comm->lock)); + comm->init_shared_buffer = init_shared_buffer; + comm->pBuffer = l1_shared_buffer; + spin_unlock(&(comm->lock)); + init_shared_buffer = NULL; + l1_shared_buffer = NULL; + + /* + * Set the OS current time in the L1 shared buffer first. The secure + * world uses it as itw boot reference time. + */ + tf_set_current_time(comm); + + /* Workaround for issue #6081 */ + if ((omap_rev() && 0xFFF000FF) == OMAP443X_CLASS) + disable_nonboot_cpus(); + + /* + * Start the SMC PA + */ + ret = omap4_secure_dispatcher(API_HAL_LM_PALOAD_INDEX, + FLAG_IRQ_ENABLE | FLAG_FIQ_ENABLE | FLAG_START_HAL_CRITICAL, 1, + __pa(&pa_info), 0, 0, 0); + if (ret != API_HAL_RET_VALUE_OK) { + printk(KERN_ERR "SMC: Error while loading the PA [0x%x]\n", + ret); + goto error2; + } + + /* Loop until the first S Yield RPC is received */ +loop: + mutex_lock(&(comm->rpc_mutex)); + + if (g_RPC_advancement == RPC_ADVANCEMENT_PENDING) { + dprintk(KERN_INFO "tf_rpc_execute: " + "Executing CMD=0x%x\n", + g_RPC_parameters[1]); + + switch (g_RPC_parameters[1]) { + case RPC_CMD_YIELD: + dprintk(KERN_INFO "tf_rpc_execute: " + "RPC_CMD_YIELD\n"); + set_bit(TF_COMM_FLAG_L1_SHARED_ALLOCATED, + &(comm->flags)); + g_RPC_parameters[0] = RPC_SUCCESS; + break; + + case RPC_CMD_INIT: + dprintk(KERN_INFO "tf_rpc_execute: " + "RPC_CMD_INIT\n"); + g_RPC_parameters[0] = tf_rpc_init(comm); + break; + + case RPC_CMD_TRACE: + g_RPC_parameters[0] = tf_rpc_trace(comm); + break; + + default: + g_RPC_parameters[0] = RPC_ERROR_BAD_PARAMETERS; + break; + } + g_RPC_advancement = RPC_ADVANCEMENT_FINISHED; + } + + mutex_unlock(&(comm->rpc_mutex)); + + ret = tf_schedule_secure_world(comm, false); + if (ret != 0) { + printk(KERN_ERR "SMC: Error while loading the PA [0x%x]\n", + ret); + goto error2; + } + + if (!test_bit(TF_COMM_FLAG_L1_SHARED_ALLOCATED, &(comm->flags))) + goto loop; + + set_bit(TF_COMM_FLAG_PA_AVAILABLE, &comm->flags); + wake_up(&(comm->wait_queue)); + ret = 0; + + #if 0 + { + void *workspace_va; + workspace_va = ioremap(workspace_addr, workspace_size); + printk(KERN_INFO + "Read first word of workspace [0x%x]\n", + *(uint32_t *)workspace_va); + } + #endif + + /* Workaround for issue #6081 */ + if ((omap_rev() && 0xFFF000FF) == OMAP443X_CLASS) + enable_nonboot_cpus(); + + goto exit; + +error2: + /* Workaround for issue #6081 */ + if ((omap_rev() && 0xFFF000FF) == OMAP443X_CLASS) + enable_nonboot_cpus(); + + spin_lock(&(comm->lock)); + l1_shared_buffer = comm->pBuffer; + init_shared_buffer = comm->init_shared_buffer; + comm->pBuffer = NULL; + comm->init_shared_buffer = NULL; + spin_unlock(&(comm->lock)); + +error1: + if (init_shared_buffer != NULL) { + __clear_page_locked(virt_to_page(init_shared_buffer)); + internal_free_page((unsigned long) init_shared_buffer); + } + if (l1_shared_buffer != NULL) { + __clear_page_locked(virt_to_page(l1_shared_buffer)); + internal_free_page((unsigned long) l1_shared_buffer); + } + +exit: +#ifdef CONFIG_SMP + ret_affinity = sched_setaffinity(0, &saved_cpu_mask); + if (ret_affinity != 0) + dprintk(KERN_ERR "sched_setaffinity #2 -> 0x%lX", ret_affinity); +#endif + + tf_l4sec_clkdm_allow_idle(true); + + if (ret > 0) + ret = -EFAULT; + + return ret; +} + +void tf_terminate(struct tf_comm *comm) +{ + dprintk(KERN_INFO "tf_terminate(%p)\n", comm); + + spin_lock(&(comm->lock)); + + tf_crypto_terminate(); + + spin_unlock(&(comm->lock)); +} diff --git a/security/smc/tf_conn.c b/security/smc/tf_conn.c new file mode 100644 index 0000000..4ab7a0a --- /dev/null +++ b/security/smc/tf_conn.c @@ -0,0 +1,1647 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <asm/atomic.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/types.h> + +#include "s_version.h" + +#include "tf_protocol.h" +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_comm.h" +#include "tf_conn.h" + +#ifdef CONFIG_TF_ZEBRA +#include "tf_crypto.h" +#endif + +/*---------------------------------------------------------------------------- + * Management of the shared memory blocks. + * + * Shared memory blocks are the blocks registered through + * the commands REGISTER_SHARED_MEMORY and POWER_MANAGEMENT + *----------------------------------------------------------------------------*/ + +/** + * Unmaps a shared memory + **/ +static void tf_unmap_shmem( + struct tf_connection *connection, + struct tf_shmem_desc *shmem_desc, + u32 full_cleanup) +{ + /* check shmem_desc contains a descriptor */ + if (shmem_desc == NULL) + return; + + dprintk(KERN_DEBUG "tf_unmap_shmem(%p)\n", shmem_desc); + +retry: + mutex_lock(&(connection->shmem_mutex)); + if (atomic_read(&shmem_desc->ref_count) > 1) { + /* + * Shared mem still in use, wait for other operations completion + * before actually unmapping it. + */ + dprintk(KERN_INFO "Descriptor in use\n"); + mutex_unlock(&(connection->shmem_mutex)); + schedule(); + goto retry; + } + + tf_cleanup_shared_memory( + &(connection->cpt_alloc_context), + shmem_desc, + full_cleanup); + + list_del(&(shmem_desc->list)); + + if ((shmem_desc->type == TF_SHMEM_TYPE_REGISTERED_SHMEM) || + (full_cleanup != 0)) { + internal_kfree(shmem_desc); + + atomic_dec(&(connection->shmem_count)); + } else { + /* + * This is a preallocated shared memory, add to free list + * Since the device context is unmapped last, it is + * always the first element of the free list if no + * device context has been created + */ + shmem_desc->block_identifier = 0; + list_add(&(shmem_desc->list), &(connection->free_shmem_list)); + } + + mutex_unlock(&(connection->shmem_mutex)); +} + + +/** + * Find the first available slot for a new block of shared memory + * and map the user buffer. + * Update the descriptors to L1 descriptors + * Update the buffer_start_offset and buffer_size fields + * shmem_desc is updated to the mapped shared memory descriptor + **/ +static int tf_map_shmem( + struct tf_connection *connection, + u32 buffer, + /* flags for read-write access rights on the memory */ + u32 flags, + bool in_user_space, + u32 descriptors[TF_MAX_COARSE_PAGES], + u32 *buffer_start_offset, + u32 buffer_size, + struct tf_shmem_desc **shmem_desc, + u32 *descriptor_count) +{ + struct tf_shmem_desc *desc = NULL; + int error; + + dprintk(KERN_INFO "tf_map_shmem(%p, %p, flags = 0x%08x)\n", + connection, + (void *) buffer, + flags); + + mutex_lock(&(connection->shmem_mutex)); + + /* + * Check the list of free shared memory + * is not empty + */ + if (list_empty(&(connection->free_shmem_list))) { + if (atomic_read(&(connection->shmem_count)) == + TF_SHMEM_MAX_COUNT) { + printk(KERN_ERR "tf_map_shmem(%p):" + " maximum shared memories already registered\n", + connection); + error = -ENOMEM; + goto error; + } + + /* no descriptor available, allocate a new one */ + + desc = (struct tf_shmem_desc *) internal_kmalloc( + sizeof(*desc), GFP_KERNEL); + if (desc == NULL) { + printk(KERN_ERR "tf_map_shmem(%p):" + " failed to allocate descriptor\n", + connection); + error = -ENOMEM; + goto error; + } + + /* Initialize the structure */ + desc->type = TF_SHMEM_TYPE_REGISTERED_SHMEM; + atomic_set(&desc->ref_count, 1); + INIT_LIST_HEAD(&(desc->list)); + + atomic_inc(&(connection->shmem_count)); + } else { + /* take the first free shared memory descriptor */ + desc = list_first_entry(&(connection->free_shmem_list), + struct tf_shmem_desc, list); + list_del(&(desc->list)); + } + + /* Add the descriptor to the used list */ + list_add(&(desc->list), &(connection->used_shmem_list)); + + error = tf_fill_descriptor_table( + &(connection->cpt_alloc_context), + desc, + buffer, + connection->vmas, + descriptors, + buffer_size, + buffer_start_offset, + in_user_space, + flags, + descriptor_count); + + if (error != 0) { + dprintk(KERN_ERR "tf_map_shmem(%p):" + " tf_fill_descriptor_table failed with error " + "code %d!\n", + connection, + error); + goto error; + } + desc->pBuffer = (u8 *) buffer; + + /* + * Successful completion. + */ + *shmem_desc = desc; + mutex_unlock(&(connection->shmem_mutex)); + dprintk(KERN_DEBUG "tf_map_shmem: success\n"); + return 0; + + + /* + * Error handling. + */ +error: + mutex_unlock(&(connection->shmem_mutex)); + dprintk(KERN_ERR "tf_map_shmem: failure with error code %d\n", + error); + + tf_unmap_shmem( + connection, + desc, + 0); + + return error; +} + + + +/* This function is a copy of the find_vma() function +in linux kernel 2.6.15 version with some fixes : + - memory block may end on vm_end + - check the full memory block is in the memory area + - guarantee NULL is returned if no memory area is found */ +struct vm_area_struct *tf_find_vma(struct mm_struct *mm, + unsigned long addr, unsigned long size) +{ + struct vm_area_struct *vma = NULL; + + dprintk(KERN_INFO + "tf_find_vma addr=0x%lX size=0x%lX\n", addr, size); + + if (mm) { + /* Check the cache first. */ + /* (Cache hit rate is typically around 35%.) */ + vma = mm->mmap_cache; + if (!(vma && vma->vm_end >= (addr+size) && + vma->vm_start <= addr)) { + struct rb_node *rb_node; + + rb_node = mm->mm_rb.rb_node; + vma = NULL; + + while (rb_node) { + struct vm_area_struct *vma_tmp; + + vma_tmp = rb_entry(rb_node, + struct vm_area_struct, vm_rb); + + dprintk(KERN_INFO + "vma_tmp->vm_start=0x%lX" + "vma_tmp->vm_end=0x%lX\n", + vma_tmp->vm_start, + vma_tmp->vm_end); + + if (vma_tmp->vm_end >= (addr+size)) { + vma = vma_tmp; + if (vma_tmp->vm_start <= addr) + break; + + rb_node = rb_node->rb_left; + } else { + rb_node = rb_node->rb_right; + } + } + + if (vma) + mm->mmap_cache = vma; + if (rb_node == NULL) + vma = NULL; + } + } + return vma; +} + +static int tf_validate_shmem_and_flags( + u32 shmem, + u32 shmem_size, + u32 flags) +{ + struct vm_area_struct *vma; + u32 chunk; + + if (shmem_size == 0) + /* This is always valid */ + return 0; + + if ((shmem + shmem_size) < shmem) + /* Overflow */ + return -EINVAL; + + down_read(¤t->mm->mmap_sem); + + /* + * When looking for a memory address, split buffer into chunks of + * size=PAGE_SIZE. + */ + chunk = PAGE_SIZE - (shmem & (PAGE_SIZE-1)); + if (chunk > shmem_size) + chunk = shmem_size; + + do { + vma = tf_find_vma(current->mm, shmem, chunk); + + if (vma == NULL) { + dprintk(KERN_ERR "%s: area not found\n", __func__); + goto error; + } + + if (flags & TF_SHMEM_TYPE_READ) + if (!(vma->vm_flags & VM_READ)) { + dprintk(KERN_ERR "%s: no read permission\n", + __func__); + goto error; + } + if (flags & TF_SHMEM_TYPE_WRITE) + if (!(vma->vm_flags & VM_WRITE)) { + dprintk(KERN_ERR "%s: no write permission\n", + __func__); + goto error; + } + + shmem_size -= chunk; + shmem += chunk; + chunk = (shmem_size <= PAGE_SIZE ? + shmem_size : PAGE_SIZE); + } while (shmem_size != 0); + + up_read(¤t->mm->mmap_sem); + return 0; + +error: + up_read(¤t->mm->mmap_sem); + return -EFAULT; +} + + +static int tf_map_temp_shmem(struct tf_connection *connection, + struct tf_command_param_temp_memref *temp_memref, + u32 param_type, + struct tf_shmem_desc **shmem_desc) +{ + u32 flags; + u32 error = S_SUCCESS; + bool in_user_space = connection->owner != TF_CONNECTION_OWNER_KERNEL; + + dprintk(KERN_INFO "tf_map_temp_shmem(%p, " + "0x%08x[size=0x%08x], offset=0x%08x)\n", + connection, + temp_memref->descriptor, + temp_memref->size, + temp_memref->offset); + + switch (param_type) { + case TF_PARAM_TYPE_MEMREF_TEMP_INPUT: + flags = TF_SHMEM_TYPE_READ; + break; + case TF_PARAM_TYPE_MEMREF_TEMP_OUTPUT: + flags = TF_SHMEM_TYPE_WRITE; + break; + case TF_PARAM_TYPE_MEMREF_TEMP_INOUT: + flags = TF_SHMEM_TYPE_WRITE | TF_SHMEM_TYPE_READ; + break; + default: + error = -EINVAL; + goto error; + } + + if (temp_memref->descriptor == 0) { + /* NULL tmpref */ + temp_memref->offset = 0; + *shmem_desc = NULL; + } else if ((temp_memref->descriptor != 0) && + (temp_memref->size == 0)) { + /* Empty tmpref */ + temp_memref->offset = temp_memref->descriptor; + temp_memref->descriptor = 0; + temp_memref->size = 0; + *shmem_desc = NULL; + } else { + /* Map the temp shmem block */ + + u32 shared_mem_descriptors[TF_MAX_COARSE_PAGES]; + u32 descriptorCount; + + if (in_user_space) { + error = tf_validate_shmem_and_flags( + temp_memref->descriptor, + temp_memref->size, + flags); + if (error != 0) + goto error; + } + + error = tf_map_shmem( + connection, + temp_memref->descriptor, + flags, + in_user_space, + shared_mem_descriptors, + &(temp_memref->offset), + temp_memref->size, + shmem_desc, + &descriptorCount); + temp_memref->descriptor = shared_mem_descriptors[0]; + } + +error: + return error; +} + +/* + * Clean up a list of shared memory descriptors. + */ +static void tf_shared_memory_cleanup_list( + struct tf_connection *connection, + struct list_head *shmem_desc_list) +{ + while (!list_empty(shmem_desc_list)) { + struct tf_shmem_desc *shmem_desc; + + shmem_desc = list_first_entry(shmem_desc_list, + struct tf_shmem_desc, list); + + tf_unmap_shmem(connection, shmem_desc, 1); + } +} + + +/* + * Clean up the shared memory information in the connection. + * Releases all allocated pages. + */ +static void tf_cleanup_shared_memories(struct tf_connection *connection) +{ + /* clean up the list of used and free descriptors. + * done outside the mutex, because tf_unmap_shmem already + * mutex()ed + */ + tf_shared_memory_cleanup_list(connection, + &connection->used_shmem_list); + tf_shared_memory_cleanup_list(connection, + &connection->free_shmem_list); + + mutex_lock(&(connection->shmem_mutex)); + + /* Free the Vmas page */ + if (connection->vmas) { + internal_free_page((unsigned long) connection->vmas); + connection->vmas = NULL; + } + + tf_release_coarse_page_table_allocator( + &(connection->cpt_alloc_context)); + + mutex_unlock(&(connection->shmem_mutex)); +} + + +/* + * Initialize the shared memory in a connection. + * Allocates the minimum memory to be provided + * for shared memory management + */ +int tf_init_shared_memory(struct tf_connection *connection) +{ + int error; + int i; + int coarse_page_index; + + /* + * We only need to initialize special elements and attempt to allocate + * the minimum shared memory descriptors we want to support + */ + + mutex_init(&(connection->shmem_mutex)); + INIT_LIST_HEAD(&(connection->free_shmem_list)); + INIT_LIST_HEAD(&(connection->used_shmem_list)); + atomic_set(&(connection->shmem_count), 0); + + tf_init_coarse_page_table_allocator( + &(connection->cpt_alloc_context)); + + + /* + * Preallocate 3 pages to increase the chances that a connection + * succeeds in allocating shared mem + */ + for (i = 0; + i < 3; + i++) { + struct tf_shmem_desc *shmem_desc = + (struct tf_shmem_desc *) internal_kmalloc( + sizeof(*shmem_desc), GFP_KERNEL); + + if (shmem_desc == NULL) { + printk(KERN_ERR "tf_init_shared_memory(%p):" + " failed to pre allocate descriptor %d\n", + connection, + i); + error = -ENOMEM; + goto error; + } + + for (coarse_page_index = 0; + coarse_page_index < TF_MAX_COARSE_PAGES; + coarse_page_index++) { + struct tf_coarse_page_table *coarse_pg_table; + + coarse_pg_table = tf_alloc_coarse_page_table( + &(connection->cpt_alloc_context), + TF_PAGE_DESCRIPTOR_TYPE_PREALLOCATED); + + if (coarse_pg_table == NULL) { + printk(KERN_ERR "tf_init_shared_memory(%p)" + ": descriptor %d coarse page %d - " + "tf_alloc_coarse_page_table() " + "failed\n", + connection, + i, + coarse_page_index); + error = -ENOMEM; + goto error; + } + + shmem_desc->coarse_pg_table[coarse_page_index] = + coarse_pg_table; + } + shmem_desc->coarse_pg_table_count = 0; + + shmem_desc->type = TF_SHMEM_TYPE_PREALLOC_REGISTERED_SHMEM; + atomic_set(&shmem_desc->ref_count, 1); + + /* + * add this preallocated descriptor to the list of free + * descriptors Keep the device context specific one at the + * beginning of the list + */ + INIT_LIST_HEAD(&(shmem_desc->list)); + list_add_tail(&(shmem_desc->list), + &(connection->free_shmem_list)); + } + + /* allocate memory for the vmas structure */ + connection->vmas = + (struct vm_area_struct **) internal_get_zeroed_page(GFP_KERNEL); + if (connection->vmas == NULL) { + printk(KERN_ERR "tf_init_shared_memory(%p):" + " vmas - failed to get_zeroed_page\n", + connection); + error = -ENOMEM; + goto error; + } + + return 0; + +error: + tf_cleanup_shared_memories(connection); + return error; +} + +/*---------------------------------------------------------------------------- + * Connection operations to the Secure World + *----------------------------------------------------------------------------*/ + +int tf_create_device_context( + struct tf_connection *connection) +{ + union tf_command command; + union tf_answer answer; + int error = 0; + + dprintk(KERN_INFO "tf_create_device_context(%p)\n", + connection); + + command.create_device_context.message_type = + TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT; + command.create_device_context.message_size = + (sizeof(struct tf_command_create_device_context) + - sizeof(struct tf_command_header))/sizeof(u32); + command.create_device_context.operation_id = (u32) &answer; + command.create_device_context.device_context_id = (u32) connection; + + error = tf_send_receive( + &connection->dev->sm, + &command, + &answer, + connection, + true); + + if ((error != 0) || + (answer.create_device_context.error_code != S_SUCCESS)) + goto error; + + /* + * CREATE_DEVICE_CONTEXT succeeded, + * store device context handler and update connection status + */ + connection->device_context = + answer.create_device_context.device_context; + spin_lock(&(connection->state_lock)); + connection->state = TF_CONN_STATE_VALID_DEVICE_CONTEXT; + spin_unlock(&(connection->state_lock)); + + /* successful completion */ + dprintk(KERN_INFO "tf_create_device_context(%p):" + " device_context=0x%08x\n", + connection, + answer.create_device_context.device_context); + return 0; + +error: + if (error != 0) { + dprintk(KERN_ERR "tf_create_device_context failed with " + "error %d\n", error); + } else { + /* + * We sent a DeviceCreateContext. The state is now + * TF_CONN_STATE_CREATE_DEVICE_CONTEXT_SENT It has to be + * reset if we ever want to send a DeviceCreateContext again + */ + spin_lock(&(connection->state_lock)); + connection->state = TF_CONN_STATE_NO_DEVICE_CONTEXT; + spin_unlock(&(connection->state_lock)); + dprintk(KERN_ERR "tf_create_device_context failed with " + "error_code 0x%08X\n", + answer.create_device_context.error_code); + if (answer.create_device_context.error_code == + S_ERROR_OUT_OF_MEMORY) + error = -ENOMEM; + else + error = -EFAULT; + } + + return error; +} + +/* Check that the current application belongs to the + * requested GID */ +static bool tf_check_gid(gid_t requested_gid) +{ + if (requested_gid == current_egid()) { + return true; + } else { + u32 size; + u32 i; + /* Look in the supplementary GIDs */ + get_group_info(GROUP_INFO); + size = GROUP_INFO->ngroups; + for (i = 0; i < size; i++) + if (requested_gid == GROUP_AT(GROUP_INFO , i)) + return true; + } + return false; +} + +/* + * Opens a client session to the Secure World + */ +int tf_open_client_session( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + struct tf_shmem_desc *shmem_desc[4] = {NULL}; + u32 i; + + dprintk(KERN_INFO "tf_open_client_session(%p)\n", connection); + + /* + * Initialize the message size with no login data. This will be later + * adjusted the the cases below + */ + command->open_client_session.message_size = + (sizeof(struct tf_command_open_client_session) - 20 + - sizeof(struct tf_command_header))/4; + + switch (command->open_client_session.login_type) { + case TF_LOGIN_PUBLIC: + /* Nothing to do */ + break; + + case TF_LOGIN_USER: + /* + * Send the EUID of the calling application in the login data. + * Update message size. + */ + *(u32 *) &command->open_client_session.login_data = + current_euid(); +#ifndef CONFIG_ANDROID + command->open_client_session.login_type = + (u32) TF_LOGIN_USER_LINUX_EUID; +#else + command->open_client_session.login_type = + (u32) TF_LOGIN_USER_ANDROID_EUID; +#endif + + /* Added one word */ + command->open_client_session.message_size += 1; + break; + + case TF_LOGIN_GROUP: { + /* Check requested GID */ + gid_t requested_gid = + *(u32 *) command->open_client_session.login_data; + + if (!tf_check_gid(requested_gid)) { + dprintk(KERN_ERR "tf_open_client_session(%p) " + "TF_LOGIN_GROUP: requested GID (0x%x) does " + "not match real eGID (0x%x)" + "or any of the supplementary GIDs\n", + connection, requested_gid, current_egid()); + error = -EACCES; + goto error; + } +#ifndef CONFIG_ANDROID + command->open_client_session.login_type = + TF_LOGIN_GROUP_LINUX_GID; +#else + command->open_client_session.login_type = + TF_LOGIN_GROUP_ANDROID_GID; +#endif + + command->open_client_session.message_size += 1; /* GID */ + break; + } + +#ifndef CONFIG_ANDROID + case TF_LOGIN_APPLICATION: { + /* + * Compute SHA-1 hash of the application fully-qualified path + * name. Truncate the hash to 16 bytes and send it as login + * data. Update message size. + */ + u8 pSHA1Hash[SHA1_DIGEST_SIZE]; + + error = tf_hash_application_path_and_data(pSHA1Hash, + NULL, 0); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "error in tf_hash_application_path_and_data\n"); + goto error; + } + memcpy(&command->open_client_session.login_data, + pSHA1Hash, 16); + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_LINUX_PATH_SHA1_HASH; + /* 16 bytes */ + command->open_client_session.message_size += 4; + break; + } +#else + case TF_LOGIN_APPLICATION: + /* + * Send the real UID of the calling application in the login + * data. Update message size. + */ + *(u32 *) &command->open_client_session.login_data = + current_uid(); + + command->open_client_session.login_type = + (u32) TF_LOGIN_APPLICATION_ANDROID_UID; + + /* Added one word */ + command->open_client_session.message_size += 1; + break; +#endif + +#ifndef CONFIG_ANDROID + case TF_LOGIN_APPLICATION_USER: { + /* + * Compute SHA-1 hash of the concatenation of the application + * fully-qualified path name and the EUID of the calling + * application. Truncate the hash to 16 bytes and send it as + * login data. Update message size. + */ + u8 pSHA1Hash[SHA1_DIGEST_SIZE]; + + error = tf_hash_application_path_and_data(pSHA1Hash, + (u8 *) &(current_euid()), sizeof(current_euid())); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "error in tf_hash_application_path_and_data\n"); + goto error; + } + memcpy(&command->open_client_session.login_data, + pSHA1Hash, 16); + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_USER_LINUX_PATH_EUID_SHA1_HASH; + + /* 16 bytes */ + command->open_client_session.message_size += 4; + + break; + } +#else + case TF_LOGIN_APPLICATION_USER: + /* + * Send the real UID and the EUID of the calling application in + * the login data. Update message size. + */ + *(u32 *) &command->open_client_session.login_data = + current_uid(); + *(u32 *) &command->open_client_session.login_data[4] = + current_euid(); + + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_USER_ANDROID_UID_EUID; + + /* Added two words */ + command->open_client_session.message_size += 2; + break; +#endif + +#ifndef CONFIG_ANDROID + case TF_LOGIN_APPLICATION_GROUP: { + /* + * Check requested GID. Compute SHA-1 hash of the concatenation + * of the application fully-qualified path name and the + * requested GID. Update message size + */ + gid_t requested_gid; + u8 pSHA1Hash[SHA1_DIGEST_SIZE]; + + requested_gid = *(u32 *) &command->open_client_session. + login_data; + + if (!tf_check_gid(requested_gid)) { + dprintk(KERN_ERR "tf_open_client_session(%p) " + "TF_LOGIN_APPLICATION_GROUP: requested GID (0x%x) " + "does not match real eGID (0x%x)" + "or any of the supplementary GIDs\n", + connection, requested_gid, current_egid()); + error = -EACCES; + goto error; + } + + error = tf_hash_application_path_and_data(pSHA1Hash, + &requested_gid, sizeof(u32)); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "error in tf_hash_application_path_and_data\n"); + goto error; + } + + memcpy(&command->open_client_session.login_data, + pSHA1Hash, 16); + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_GROUP_LINUX_PATH_GID_SHA1_HASH; + + /* 16 bytes */ + command->open_client_session.message_size += 4; + break; + } +#else + case TF_LOGIN_APPLICATION_GROUP: { + /* + * Check requested GID. Send the real UID and the requested GID + * in the login data. Update message size. + */ + gid_t requested_gid; + + requested_gid = *(u32 *) &command->open_client_session. + login_data; + + if (!tf_check_gid(requested_gid)) { + dprintk(KERN_ERR "tf_open_client_session(%p) " + "TF_LOGIN_APPLICATION_GROUP: requested GID (0x%x) " + "does not match real eGID (0x%x)" + "or any of the supplementary GIDs\n", + connection, requested_gid, current_egid()); + error = -EACCES; + goto error; + } + + *(u32 *) &command->open_client_session.login_data = + current_uid(); + *(u32 *) &command->open_client_session.login_data[4] = + requested_gid; + + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_GROUP_ANDROID_UID_GID; + + /* Added two words */ + command->open_client_session.message_size += 2; + + break; + } +#endif + + case TF_LOGIN_PRIVILEGED: + /* A privileged login may be performed only on behalf of the + kernel itself or on behalf of a process with euid=0 or + egid=0. */ + if (connection->owner == TF_CONNECTION_OWNER_KERNEL) { + dprintk(KERN_DEBUG "tf_open_client_session: " + "TF_LOGIN_PRIVILEGED for kernel API\n"); + command->open_client_session.login_type = + TF_LOGIN_PRIVILEGED_KERNEL; + } else { + dprintk(KERN_DEBUG "tf_open_client_session: " + "TF_LOGIN_PRIVILEGED for %u:%u\n", + current_euid(), current_egid()); + command->open_client_session.login_type = + TF_LOGIN_PRIVILEGED; + } + break; + + case TF_LOGIN_AUTHENTICATION: { + /* + * Compute SHA-1 hash of the application binary + * Send this hash as the login data (20 bytes) + */ + + u8 *hash; + hash = &(command->open_client_session.login_data[0]); + + error = tf_get_current_process_hash(hash); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "error in tf_get_current_process_hash\n"); + goto error; + } + command->open_client_session.login_type = + TF_LOGIN_AUTHENTICATION_BINARY_SHA1_HASH; + + /* 20 bytes */ + command->open_client_session.message_size += 5; + break; + } + + case TF_LOGIN_PRIVILEGED_KERNEL: + /* A kernel login may be performed only on behalf of the + kernel itself. */ + if (connection->owner == TF_CONNECTION_OWNER_KERNEL) { + dprintk(KERN_DEBUG "tf_open_client_session: " + "TF_LOGIN_PRIVILEGED_KERNEL for kernel API\n"); + command->open_client_session.login_type = + TF_LOGIN_PRIVILEGED_KERNEL; + } else { + dprintk(KERN_ERR "tf_open_client_session: " + " user %d, group %d not allowed to open " + "session with TF_LOGIN_PRIVILEGED_KERNEL\n", + current_euid(), current_egid()); + error = -EACCES; + goto error; + } + command->open_client_session.login_type = + TF_LOGIN_PRIVILEGED_KERNEL; + break; + + default: + dprintk(KERN_ERR "tf_open_client_session: " + "unknown login_type(%08X)\n", + command->open_client_session.login_type); + error = -EOPNOTSUPP; + goto error; + } + + /* Map the temporary memory references */ + for (i = 0; i < 4; i++) { + int param_type; + param_type = TF_GET_PARAM_TYPE( + command->open_client_session.param_types, i); + if ((param_type & (TF_PARAM_TYPE_MEMREF_FLAG | + TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG)) + == TF_PARAM_TYPE_MEMREF_FLAG) { + /* Map temp mem ref */ + error = tf_map_temp_shmem(connection, + &command->open_client_session. + params[i].temp_memref, + param_type, + &shmem_desc[i]); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "unable to map temporary memory block " + "(%08X)\n", error); + goto error; + } + } + } + + /* Fill the handle of the Device Context */ + command->open_client_session.device_context = + connection->device_context; + + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + +error: + /* Unmap the temporary memory references */ + for (i = 0; i < 4; i++) + if (shmem_desc[i] != NULL) + tf_unmap_shmem(connection, shmem_desc[i], 0); + + if (error != 0) + dprintk(KERN_ERR "tf_open_client_session returns %d\n", + error); + else + dprintk(KERN_ERR "tf_open_client_session returns " + "error_code 0x%08X\n", + answer->open_client_session.error_code); + + return error; +} + + +/* + * Closes a client session from the Secure World + */ +int tf_close_client_session( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + + dprintk(KERN_DEBUG "tf_close_client_session(%p)\n", connection); + + command->close_client_session.message_size = + (sizeof(struct tf_command_close_client_session) - + sizeof(struct tf_command_header)) / 4; + command->close_client_session.device_context = + connection->device_context; + + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + + if (error != 0) + dprintk(KERN_ERR "tf_close_client_session returns %d\n", + error); + else + dprintk(KERN_ERR "tf_close_client_session returns " + "error 0x%08X\n", + answer->close_client_session.error_code); + + return error; +} + + +/* + * Registers a shared memory to the Secure World + */ +int tf_register_shared_memory( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + struct tf_shmem_desc *shmem_desc = NULL; + bool in_user_space = connection->owner != TF_CONNECTION_OWNER_KERNEL; + struct tf_command_register_shared_memory *msg = + &command->register_shared_memory; + + dprintk(KERN_INFO "tf_register_shared_memory(%p) " + "%p[0x%08X][0x%08x]\n", + connection, + (void *)msg->shared_mem_descriptors[0], + msg->shared_mem_size, + (u32)msg->memory_flags); + + if (in_user_space) { + error = tf_validate_shmem_and_flags( + msg->shared_mem_descriptors[0], + msg->shared_mem_size, + (u32)msg->memory_flags); + if (error != 0) + goto error; + } + + /* Initialize message_size with no descriptors */ + msg->message_size + = (sizeof(struct tf_command_register_shared_memory) - + sizeof(struct tf_command_header)) / 4; + + /* Map the shmem block and update the message */ + if (msg->shared_mem_size == 0) { + /* Empty shared mem */ + msg->shared_mem_start_offset = msg->shared_mem_descriptors[0]; + } else { + u32 descriptorCount; + error = tf_map_shmem( + connection, + msg->shared_mem_descriptors[0], + msg->memory_flags, + in_user_space, + msg->shared_mem_descriptors, + &(msg->shared_mem_start_offset), + msg->shared_mem_size, + &shmem_desc, + &descriptorCount); + if (error != 0) { + dprintk(KERN_ERR "tf_register_shared_memory: " + "unable to map shared memory block\n"); + goto error; + } + msg->message_size += descriptorCount; + } + + /* + * write the correct device context handle and the address of the shared + * memory descriptor in the message + */ + msg->device_context = connection->device_context; + msg->block_id = (u32)shmem_desc; + + /* Send the updated message */ + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + + if ((error != 0) || + (answer->register_shared_memory.error_code + != S_SUCCESS)) { + dprintk(KERN_ERR "tf_register_shared_memory: " + "operation failed. Unmap block\n"); + goto error; + } + + /* Saves the block handle returned by the secure world */ + if (shmem_desc != NULL) + shmem_desc->block_identifier = + answer->register_shared_memory.block; + + /* successful completion */ + dprintk(KERN_INFO "tf_register_shared_memory(%p):" + " block_id=0x%08x block=0x%08x\n", + connection, msg->block_id, + answer->register_shared_memory.block); + return 0; + + /* error completion */ +error: + tf_unmap_shmem( + connection, + shmem_desc, + 0); + + if (error != 0) + dprintk(KERN_ERR "tf_register_shared_memory returns %d\n", + error); + else + dprintk(KERN_ERR "tf_register_shared_memory returns " + "error_code 0x%08X\n", + answer->register_shared_memory.error_code); + + return error; +} + + +/* + * Releases a shared memory from the Secure World + */ +int tf_release_shared_memory( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + + dprintk(KERN_DEBUG "tf_release_shared_memory(%p)\n", connection); + + command->release_shared_memory.message_size = + (sizeof(struct tf_command_release_shared_memory) - + sizeof(struct tf_command_header)) / 4; + command->release_shared_memory.device_context = + connection->device_context; + + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + + if ((error != 0) || + (answer->release_shared_memory.error_code != S_SUCCESS)) + goto error; + + /* Use block_id to get back the pointer to shmem_desc */ + tf_unmap_shmem( + connection, + (struct tf_shmem_desc *) + answer->release_shared_memory.block_id, + 0); + + /* successful completion */ + dprintk(KERN_INFO "tf_release_shared_memory(%p):" + " block_id=0x%08x block=0x%08x\n", + connection, answer->release_shared_memory.block_id, + command->release_shared_memory.block); + return 0; + + +error: + if (error != 0) + dprintk(KERN_ERR "tf_release_shared_memory returns %d\n", + error); + else + dprintk(KERN_ERR "tf_release_shared_memory returns " + "nChannelStatus 0x%08X\n", + answer->release_shared_memory.error_code); + + return error; + +} + + +#ifdef CONFIG_TF_ION +extern struct ion_device *omap_ion_device; +#endif /* CONFIG_TF_ION */ +/* + * Invokes a client command to the Secure World + */ +int tf_invoke_client_command( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + struct tf_shmem_desc *shmem_desc[4] = {NULL}; + int i; +#ifdef CONFIG_TF_ION + struct ion_handle *new_handle = NULL; +#endif /* CONFIG_TF_ION */ + + dprintk(KERN_INFO "tf_invoke_client_command(%p)\n", connection); + + command->release_shared_memory.message_size = + (sizeof(struct tf_command_invoke_client_command) - + sizeof(struct tf_command_header)) / 4; + +#ifdef CONFIG_TF_ZEBRA + error = tf_crypto_try_shortcuted_update(connection, + (struct tf_command_invoke_client_command *) command, + (struct tf_answer_invoke_client_command *) answer); + if (error == 0) + return error; +#endif + + /* Map the tmprefs */ + for (i = 0; i < 4; i++) { + int param_type = TF_GET_PARAM_TYPE( + command->invoke_client_command.param_types, i); + + if ((param_type & (TF_PARAM_TYPE_MEMREF_FLAG | + TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG)) + == TF_PARAM_TYPE_MEMREF_FLAG) { + /* A temporary memref: map it */ + error = tf_map_temp_shmem(connection, + &command->invoke_client_command. + params[i].temp_memref, + param_type, &shmem_desc[i]); + if (error != 0) { + dprintk(KERN_ERR + "tf_invoke_client_command: " + "unable to map temporary memory " + "block\n (%08X)", error); + goto error; + } + } +#ifdef CONFIG_TF_ION + else if (param_type == TF_PARAM_TYPE_MEMREF_ION_HANDLE) { + struct tf_command_invoke_client_command *invoke; + ion_phys_addr_t ion_addr; + size_t ion_len; + struct ion_buffer *buffer; + + if (connection->ion_client == NULL) { + connection->ion_client = ion_client_create( + omap_ion_device, + (1 << ION_HEAP_TYPE_CARVEOUT), + "smc"); + } + if (connection->ion_client == NULL) { + dprintk(KERN_ERR "%s(%p): " + "unable to create ion client\n", + __func__, connection); + error = -EFAULT; + goto error; + } + + invoke = &command->invoke_client_command; + + dprintk(KERN_INFO "ion_handle %x", + invoke->params[i].value.a); + buffer = ion_share(connection->ion_client, + (struct ion_handle *)invoke->params[i].value.a); + if (buffer == NULL) { + dprintk(KERN_ERR "%s(%p): " + "unable to share ion handle\n", + __func__, connection); + error = -EFAULT; + goto error; + } + + dprintk(KERN_INFO "ion_buffer %p", buffer); + new_handle = ion_import(connection->ion_client, buffer); + if (new_handle == NULL) { + dprintk(KERN_ERR "%s(%p): " + "unable to import ion buffer\n", + __func__, connection); + error = -EFAULT; + goto error; + } + + dprintk(KERN_INFO "new_handle %x", new_handle); + error = ion_phys(connection->ion_client, + new_handle, + &ion_addr, + &ion_len); + if (error) { + dprintk(KERN_ERR + "%s: unable to convert ion handle " + "0x%08X (error code 0x%08X)\n", + __func__, + new_handle, + error); + error = -EINVAL; + goto error; + } + dprintk(KERN_INFO + "%s: handle=0x%08x phys_add=0x%08x length=0x%08x\n", + __func__, invoke->params[i].value.a, ion_addr, ion_len); + + invoke->params[i].value.a = (u32) ion_addr; + invoke->params[i].value.b = (u32) ion_len; + + invoke->param_types &= ~((0xF) << (4*i)); + invoke->param_types |= + TF_PARAM_TYPE_VALUE_INPUT << (4*i); + } +#endif /* CONFIG_TF_ION */ + } + + command->invoke_client_command.device_context = + connection->device_context; + + error = tf_send_receive(&connection->dev->sm, command, + answer, connection, true); + +error: +#ifdef CONFIG_TF_ION + if (new_handle != NULL) + ion_free(connection->ion_client, new_handle); +#endif /* CONFIG_TF_ION */ + /* Unmap de temp mem refs */ + for (i = 0; i < 4; i++) { + if (shmem_desc[i] != NULL) { + dprintk(KERN_INFO "tf_invoke_client_command: " + "UnMatemp_memref %d\n ", i); + + tf_unmap_shmem(connection, shmem_desc[i], 0); + } + } + + if (error != 0) + dprintk(KERN_ERR "tf_invoke_client_command returns %d\n", + error); + else + dprintk(KERN_ERR "tf_invoke_client_command returns " + "error_code 0x%08X\n", + answer->invoke_client_command.error_code); + + return error; +} + + +/* + * Cancels a client command from the Secure World + */ +int tf_cancel_client_command( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + + dprintk(KERN_DEBUG "tf_cancel_client_command(%p)\n", connection); + + command->cancel_client_operation.device_context = + connection->device_context; + command->cancel_client_operation.message_size = + (sizeof(struct tf_command_cancel_client_operation) - + sizeof(struct tf_command_header)) / 4; + + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + + if ((error != 0) || + (answer->cancel_client_operation.error_code != S_SUCCESS)) + goto error; + + + /* successful completion */ + return 0; + +error: + if (error != 0) + dprintk(KERN_ERR "tf_cancel_client_command returns %d\n", + error); + else + dprintk(KERN_ERR "tf_cancel_client_command returns " + "nChannelStatus 0x%08X\n", + answer->cancel_client_operation.error_code); + + return error; +} + + + +/* + * Destroys a device context from the Secure World + */ +int tf_destroy_device_context( + struct tf_connection *connection) +{ + int error; + /* + * AFY: better use the specialized tf_command_destroy_device_context + * structure: this will save stack + */ + union tf_command command; + union tf_answer answer; + + dprintk(KERN_INFO "tf_destroy_device_context(%p)\n", connection); + + BUG_ON(connection == NULL); + + command.header.message_type = TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT; + command.header.message_size = + (sizeof(struct tf_command_destroy_device_context) - + sizeof(struct tf_command_header))/sizeof(u32); + + /* + * fill in the device context handler + * it is guarantied that the first shared memory descriptor describes + * the device context + */ + command.destroy_device_context.device_context = + connection->device_context; + + error = tf_send_receive( + &connection->dev->sm, + &command, + &answer, + connection, + false); + + if ((error != 0) || + (answer.destroy_device_context.error_code != S_SUCCESS)) + goto error; + + spin_lock(&(connection->state_lock)); + connection->state = TF_CONN_STATE_NO_DEVICE_CONTEXT; + spin_unlock(&(connection->state_lock)); + + /* successful completion */ + dprintk(KERN_INFO "tf_destroy_device_context(%p)\n", + connection); + return 0; + +error: + if (error != 0) { + dprintk(KERN_ERR "tf_destroy_device_context failed with " + "error %d\n", error); + } else { + dprintk(KERN_ERR "tf_destroy_device_context failed with " + "error_code 0x%08X\n", + answer.destroy_device_context.error_code); + if (answer.destroy_device_context.error_code == + S_ERROR_OUT_OF_MEMORY) + error = -ENOMEM; + else + error = -EFAULT; + } + + return error; +} + + +/*---------------------------------------------------------------------------- + * Connection initialization and cleanup operations + *----------------------------------------------------------------------------*/ + +/* + * Opens a connection to the specified device. + * + * The placeholder referenced by connection is set to the address of the + * new connection; it is set to NULL upon failure. + * + * Returns zero upon successful completion, or an appropriate error code upon + * failure. + */ +int tf_open(struct tf_device *dev, + struct file *file, + struct tf_connection **connection) +{ + int error; + struct tf_connection *conn = NULL; + + dprintk(KERN_INFO "tf_open(%p, %p)\n", file, connection); + + /* + * Allocate and initialize the conn. + * kmalloc only allocates sizeof(*conn) virtual memory + */ + conn = (struct tf_connection *) internal_kmalloc(sizeof(*conn), + GFP_KERNEL); + if (conn == NULL) { + printk(KERN_ERR "tf_open(): " + "Out of memory for conn!\n"); + error = -ENOMEM; + goto error; + } + + memset(conn, 0, sizeof(*conn)); + + conn->state = TF_CONN_STATE_NO_DEVICE_CONTEXT; + conn->dev = dev; + spin_lock_init(&(conn->state_lock)); + atomic_set(&(conn->pending_op_count), 0); + INIT_LIST_HEAD(&(conn->list)); + + /* + * Initialize the shared memory + */ + error = tf_init_shared_memory(conn); + if (error != 0) + goto error; + +#ifdef CONFIG_TF_ZEBRA + /* + * Initialize CUS specifics + */ + tf_crypto_init_cus(conn); +#endif + + /* + * Attach the conn to the device. + */ + spin_lock(&(dev->connection_list_lock)); + list_add(&(conn->list), &(dev->connection_list)); + spin_unlock(&(dev->connection_list_lock)); + + /* + * Successful completion. + */ + + *connection = conn; + + dprintk(KERN_INFO "tf_open(): Success (conn=%p)\n", conn); + return 0; + + /* + * Error handling. + */ + +error: + dprintk(KERN_ERR "tf_open(): Failure (error %d)\n", error); + /* Deallocate the descriptor pages if necessary */ + internal_kfree(conn); + *connection = NULL; + return error; +} + + +/* + * Closes the specified connection. + * + * Upon return, the connection has been destroyed and cannot be used anymore. + * + * This function does nothing if connection is set to NULL. + */ +void tf_close(struct tf_connection *connection) +{ + int error; + enum TF_CONN_STATE state; + + dprintk(KERN_DEBUG "tf_close(%p)\n", connection); + + if (connection == NULL) + return; + + /* + * Assumption: Linux guarantees that no other operation is in progress + * and that no other operation will be started when close is called + */ + BUG_ON(atomic_read(&(connection->pending_op_count)) != 0); + + /* + * Exchange a Destroy Device Context message if needed. + */ + spin_lock(&(connection->state_lock)); + state = connection->state; + spin_unlock(&(connection->state_lock)); + if (state == TF_CONN_STATE_VALID_DEVICE_CONTEXT) { + /* + * A DestroyDeviceContext operation was not performed. Do it + * now. + */ + error = tf_destroy_device_context(connection); + if (error != 0) + /* avoid cleanup if destroy device context fails */ + goto error; + } + + /* + * Clean up the shared memory + */ + tf_cleanup_shared_memories(connection); + +#ifdef CONFIG_TF_ION + if (connection->ion_client != NULL) + ion_client_destroy(connection->ion_client); +#endif + + spin_lock(&(connection->dev->connection_list_lock)); + list_del(&(connection->list)); + spin_unlock(&(connection->dev->connection_list_lock)); + + internal_kfree(connection); + + return; + +error: + dprintk(KERN_DEBUG "tf_close(%p) failed with error code %d\n", + connection, error); +} diff --git a/security/smc/tf_conn.h b/security/smc/tf_conn.h new file mode 100644 index 0000000..d2c8261 --- /dev/null +++ b/security/smc/tf_conn.h @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __TF_CONN_H__ +#define __TF_CONN_H__ + +#include "tf_defs.h" + +/* + * Returns a pointer to the connection referenced by the + * specified file. + */ +static inline struct tf_connection *tf_conn_from_file( + struct file *file) +{ + return file->private_data; +} + +/*---------------------------------------------------------------------------- + * Connection operations to the Secure World + *----------------------------------------------------------------------------*/ + +int tf_create_device_context( + struct tf_connection *connection); + +int tf_destroy_device_context( + struct tf_connection *connection); + +int tf_open_client_session( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer); + +int tf_close_client_session( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer); + +int tf_register_shared_memory( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer); + +int tf_release_shared_memory( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer); + +int tf_invoke_client_command( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer); + +int tf_cancel_client_command( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer); + +/*---------------------------------------------------------------------------- + * Connection initialization and cleanup operations + *----------------------------------------------------------------------------*/ + +int tf_open(struct tf_device *dev, + struct file *file, + struct tf_connection **connection); + +void tf_close( + struct tf_connection *connection); + + +#endif /* !defined(__TF_CONN_H__) */ diff --git a/security/smc/tf_crypto.c b/security/smc/tf_crypto.c new file mode 100644 index 0000000..7edca0f --- /dev/null +++ b/security/smc/tf_crypto.c @@ -0,0 +1,1278 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_zebra.h" +#include "tf_crypto.h" +#include "tf_dma.h" + +#define IO_ADDRESS OMAP2_L4_IO_ADDRESS + +#define S_SUCCESS 0x00000000 +#define S_ERROR_GENERIC 0xFFFF0000 +#define S_ERROR_ACCESS_DENIED 0xFFFF0001 +#define S_ERROR_BAD_FORMAT 0xFFFF0005 +#define S_ERROR_BAD_PARAMETERS 0xFFFF0006 +#define S_ERROR_OUT_OF_MEMORY 0xFFFF000C +#define S_ERROR_SHORT_BUFFER 0xFFFF0010 +#define S_ERROR_UNREACHABLE 0xFFFF3013 +#define S_ERROR_SERVICE 0xFFFF1000 + +#define CKR_OK 0x00000000 + +#define PUBLIC_CRYPTO_TIMEOUT_CONST 0x000FFFFF + +#define RPC_AES1_CODE PUBLIC_CRYPTO_HWA_AES1 +#define RPC_DES_CODE PUBLIC_CRYPTO_HWA_DES +#define RPC_SHA_CODE PUBLIC_CRYPTO_HWA_SHA + +#define RPC_CRYPTO_COMMAND_MASK 0x000003c0 + +#define RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR 0x200 +#define RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_UNLOCK 0x000 +#define RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_LOCK 0x001 + +#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT 0x240 +#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_AES1 RPC_AES1_CODE +#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_DES RPC_DES_CODE +#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_SHA RPC_SHA_CODE +#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_SUSPEND 0x010 +#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_UNINSTALL 0x020 + +#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS 0x280 +#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_AES1 RPC_AES1_CODE +#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_DES RPC_DES_CODE +#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_SHA RPC_SHA_CODE +#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_RESUME 0x010 + +#define RPC_CLEAR_GLOBAL_KEY_CONTEXT 0x2c0 +#define RPC_CLEAR_GLOBAL_KEY_CONTEXT_CLEARED_AES 0x001 +#define RPC_CLEAR_GLOBAL_KEY_CONTEXT_CLEARED_DES 0x002 + +#define ENABLE_CLOCK true +#define DISABLE_CLOCK false + +/*---------------------------------------------------------------------------*/ +/*RPC IN/OUT structures for CUS implementation */ +/*---------------------------------------------------------------------------*/ + +struct rpc_install_shortcut_lock_accelerator_out { + u32 shortcut_id; + u32 error; +}; + +struct rpc_install_shortcut_lock_accelerator_in { + u32 device_context_id; + u32 client_session; + u32 command_id; + u32 key_context; + /** + *The identifier of the HWA accelerator that this shortcut uses! + *Possible values are: + *- 1 (RPC_AES1_CODE) + *- 4 (RPC_DES_CODE) + *- 8 (RPC_SHA_CODE) + **/ + u32 hwa_id; + /** + *This field defines the algorithm, direction, mode, key size. + *It contains some of the bits of the corresponding "CTRL" register + *of the accelerator. + * + *More precisely: + *For AES1 accelerator, hwa_ctrl contains the following bits: + *- CTR (bit 6): + * when 1, selects CTR mode. + * when 0, selects CBC or ECB mode (according to CBC bit) + *- CBC (bit 5) + * when 1, selects CBC mode (but only if CTR=0) + * when 0, selects EBC mode (but only if CTR=0) + *- DIRECTION (bit 2) + * 0: decryption + * 1: encryption + * + *For the DES2 accelerator, hwa_ctrl contains the following bits: + *- CBC (bit 4): 1 for CBC, 0 for ECB + *- DIRECTION (bit 2): 0 for decryption, 1 for encryption + * + *For the SHA accelerator, hwa_ctrl contains the following bits: + *- ALGO (bit 2:1): + * 0x0: MD5 + * 0x1: SHA1 + * 0x2: SHA-224 + * 0x3: SHA-256 + **/ + u32 hwa_ctrl; + union tf_crypto_operation_state operation_state; +}; + +struct rpc_lock_hwa_suspend_shortcut_out { + union tf_crypto_operation_state operation_state; +}; + +struct rpc_lock_hwa_suspend_shortcut_in { + u32 shortcut_id; +}; + +struct rpc_resume_shortcut_unlock_hwa_in { + u32 shortcut_id; + u32 aes1_key_context; + u32 reserved; + u32 des_key_context; + union tf_crypto_operation_state operation_state; +}; + +/*------------------------------------------------------------------------- */ +/* + * tf_get_device_context(struct cus_context *cus) + * search in the all the device context (connection_list) if the CUS context + * specified by cus exist. + * + * If it is found, return the device context where the CUS context is. + * If is is not found, return NULL. + */ +static struct tf_connection *tf_get_device_context( + struct cus_context *cus) +{ + struct tf_connection *connection = NULL; + struct cus_context *cusFromList = NULL; + struct tf_device *dev = tf_get_device(); + + spin_lock(&(dev->connection_list_lock)); + list_for_each_entry(connection, &(dev->connection_list), + list) { + spin_lock(&(connection->shortcut_list_lock)); + list_for_each_entry(cusFromList, + &(connection->shortcut_list), list) { + if ((u32)cusFromList == (u32)cus) { + spin_unlock(&(connection-> + shortcut_list_lock)); + spin_unlock(&(dev-> + connection_list_lock)); + return connection; + } + } + spin_unlock(&(connection-> + shortcut_list_lock)); + } + spin_unlock(&(dev->connection_list_lock)); + + /*cus does not exist */ + return NULL; +} + +/*------------------------------------------------------------------------- */ +/* + * Get the shared memory from the memory block handle coming from secure. + * Return NULL if it does not exist. + */ +static struct tf_shmem_desc *tf_get_shmem_from_block_handle( + struct tf_connection *connection, u32 block) +{ + struct tf_shmem_desc *shmem_desc = NULL; + + mutex_lock(&(connection->shmem_mutex)); + + list_for_each_entry(shmem_desc, + &(connection->used_shmem_list), list) { + if ((u32) shmem_desc->block_identifier == + (u32) block) { + mutex_unlock(&(connection->shmem_mutex)); + return shmem_desc; + } + } + + /* block does not exist */ + mutex_unlock(&(connection->shmem_mutex)); + + return NULL; +} + +/*------------------------------------------------------------------------- */ +/* + * HWA public lock or unlock one HWA according algo specified by hwa_id + */ +void tf_crypto_lock_hwa(u32 hwa_id, bool do_lock) +{ + struct semaphore *s = NULL; + struct tf_device *dev = tf_get_device(); + + dprintk(KERN_INFO "[pid=%d] %s: hwa_id=0x%04X do_lock=%d\n", + current->pid, __func__, hwa_id, do_lock); + + switch (hwa_id) { + case RPC_AES1_CODE: + s = &dev->aes1_sema; + break; + case RPC_DES_CODE: + s = &dev->des_sema; + break; + default: + case RPC_SHA_CODE: + s = &dev->sha_sema; + break; + } + + if (do_lock == LOCK_HWA) { + dprintk(KERN_INFO "tf_crypto_lock_hwa: " + "Wait for HWAID=0x%04X\n", hwa_id); + while (down_trylock(s)) + cpu_relax(); + dprintk(KERN_INFO "tf_crypto_lock_hwa: " + "Locked on HWAID=0x%04X\n", hwa_id); + } else { + up(s); + dprintk(KERN_INFO "tf_crypto_lock_hwa: " + "Released for HWAID=0x%04X\n", hwa_id); + } +} + +/*------------------------------------------------------------------------- */ +/* + * HWAs public lock or unlock HWA's specified in the HWA H/A/D fields of RPC + * command rpc_command + */ +static void tf_crypto_lock_hwas(u32 rpc_command, bool do_lock) +{ + dprintk(KERN_INFO + "tf_crypto_lock_hwas: rpc_command=0x%08x do_lock=%d\n", + rpc_command, do_lock); + + /* perform the locks */ + if (rpc_command & RPC_AES1_CODE) + tf_crypto_lock_hwa(RPC_AES1_CODE, do_lock); + + if (rpc_command & RPC_DES_CODE) + tf_crypto_lock_hwa(RPC_DES_CODE, do_lock); + + if (rpc_command & RPC_SHA_CODE) + tf_crypto_lock_hwa(RPC_SHA_CODE, do_lock); +} + +/*------------------------------------------------------------------------- */ +/** + *Initialize the public crypto DMA channels, global HWA semaphores and handles + */ +u32 tf_crypto_init(void) +{ + struct tf_device *dev = tf_get_device(); + u32 error = PUBLIC_CRYPTO_OPERATION_SUCCESS; + + /* Initialize HWAs */ + tf_aes_init(); + tf_des_init(); + tf_digest_init(); + + /*initialize the HWA semaphores */ + sema_init(&dev->aes1_sema, 1); + sema_init(&dev->des_sema, 1); + sema_init(&dev->sha_sema, 1); + + /*initialize the current key handle loaded in the AESn/DES HWA */ + dev->aes1_key_context = 0; + dev->des_key_context = 0; + dev->sham1_is_public = false; + + /*initialize the DMA semaphores */ + mutex_init(&dev->sm.dma_mutex); + + /*allocate DMA buffer */ + dev->dma_buffer_length = PAGE_SIZE * 16; + dev->dma_buffer = dma_alloc_coherent(NULL, + dev->dma_buffer_length, + &(dev->dma_buffer_phys), + GFP_KERNEL); + if (dev->dma_buffer == NULL) { + printk(KERN_ERR + "tf_crypto_init: Out of memory for DMA buffer\n"); + error = S_ERROR_OUT_OF_MEMORY; + } + + return error; +} + +/*------------------------------------------------------------------------- */ +/* + *Initialize the device context CUS fields (shortcut semaphore and public CUS + *list) + */ +void tf_crypto_init_cus(struct tf_connection *connection) +{ + /*initialize the CUS list in the given device context */ + spin_lock_init(&(connection->shortcut_list_lock)); + INIT_LIST_HEAD(&(connection->shortcut_list)); +} + +/*------------------------------------------------------------------------- */ +/** + *Terminate the public crypto (including DMA) + */ +void tf_crypto_terminate(void) +{ + struct tf_device *dev = tf_get_device(); + + if (dev->dma_buffer != NULL) { + dma_free_coherent(NULL, dev->dma_buffer_length, + dev->dma_buffer, + dev->dma_buffer_phys); + dev->dma_buffer = NULL; + } + + tf_digest_exit(); + tf_des_exit(); + tf_aes_exit(); +} + +/*------------------------------------------------------------------------- */ +/* + *Perform a crypto update operation. + *THIS FUNCTION IS CALLED FROM THE IOCTL + */ +static bool tf_crypto_update( + struct cus_context *cus, + struct cus_params *params) +{ + bool status = true; + dprintk(KERN_INFO + "tf_crypto_update(%x): "\ + "HWAID=0x%x, In=%p, Out=%p, Len=%u\n", + (uint32_t) cus, cus->hwa_id, + params->input_data, + params->output_data, params->input_data_length); + + /* Enable the clock and Process Data */ + switch (cus->hwa_id) { + case RPC_AES1_CODE: + tf_crypto_enable_clock(PUBLIC_CRYPTO_AES1_CLOCK_REG); + cus->operation_state.aes.key_is_public = 0; + cus->operation_state.aes.CTRL = cus->hwa_ctrl; + status = tf_aes_update( + &cus->operation_state.aes, + params->input_data, + params->output_data, + params->input_data_length / AES_BLOCK_SIZE); + tf_crypto_disable_clock(PUBLIC_CRYPTO_AES1_CLOCK_REG); + break; + + case RPC_DES_CODE: + tf_crypto_enable_clock(PUBLIC_CRYPTO_DES3DES_CLOCK_REG); + status = tf_des_update( + cus->hwa_ctrl, + &cus->operation_state.des, + params->input_data, + params->output_data, + params->input_data_length / DES_BLOCK_SIZE); + tf_crypto_disable_clock(PUBLIC_CRYPTO_DES3DES_CLOCK_REG); + break; + + case RPC_SHA_CODE: + tf_crypto_enable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + cus->operation_state.sha.CTRL = cus->hwa_ctrl; + status = tf_digest_update( + &cus->operation_state.sha, + params->input_data, + params->input_data_length); + tf_crypto_disable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + break; + + default: + BUG_ON(1); + break; + } + + dprintk(KERN_INFO "tf_crypto_update: Done\n"); + return status; +} + +/*------------------------------------------------------------------------- */ + +/* + *Check if the command must be intercepted by a CUS or not. + *THIS FUNCTION IS CALLED FROM THE USER THREAD (ioctl). + * + *inputs: struct tf_connection *connection : current device context + * tf_command_invoke_client_command *command : the command + * bool incrementuse_count : specify if the use_count must be incremented + *output: + * struct cus_context **cus_ctx : the public CUS + * if it is shortcuted + *return: true or false + * + */ +static bool tf_crypto_is_shortcuted_command( + struct tf_connection *connection, + struct tf_command_invoke_client_command *command, + struct cus_context **cus_ctx, + bool incrementuse_count) +{ + struct tf_device *dev = tf_get_device(); + struct cus_context *cus = NULL; + *cus_ctx = NULL; + + dprintk(KERN_INFO "tf_crypto_is_shortcuted_command: "\ + "connection=0x%08x, command=0x%08x, "\ + "CltSession=0x%08x, CmdID=0x%08x\n", + (uint32_t) connection, (uint32_t) command, + (uint32_t) command->client_session, + command->client_command_identifier); + + /*take shortcut_list_lock for the device context + *in which the message is sent <=> make sure that nobody is + *going to change data while processing */ + spin_lock(&(connection->shortcut_list_lock)); + + /*lookup in the list of shortcuts attached to the device context for a + *shortcut context that contains the same client_session as the command + *and such that command_id is equal to client_command_identifier of the + *INVOKE_CLIENT_COMMAND message. If no such shortcut exists, take the + *standard path */ + list_for_each_entry( + cus, &(connection->shortcut_list), list) { + dprintk(KERN_INFO + "tf_crypto_is_shortcuted_command: "\ + "command_id = 0x%08x client_session = 0x%08x\n", + cus->command_id, cus->client_session); + + if ((cus->client_session == command->client_session) + && + (cus->command_id == command-> + client_command_identifier)) { + dprintk(KERN_INFO + "tf_crypto_is_shortcuted_command: "\ + "shortcut is identified\n"); + /*find a CUS : check if is suspended or not */ + if (cus->suspended) { + /* + * suspended of the shortcut context is set to + * true, it means that the secure world has + * suspended the shortcut to perform an update + * on its own. In this case, take the standard + * path. This should happen very rarely because + * the client and the service should generally + * communicate to avoid such a collision + */ + dprintk(KERN_INFO "shortcut exists but "\ + "suspended\n"); + goto command_not_shortcutable; + + } else { + dprintk(KERN_INFO "shortcut exists\n"); + /*For AES and DES/3DES operations, + *provisionally determine if the accelerator + *is loaded with the appropriate key before + *deciding to enter the accelerator critical + *section. In most cases, if some other thread + *or the secure world is currently using the + *accelerator, the key won't change. + *So, if the key doesn't match now, it is + *likely not to match later on, so we'd better + *not try to enter the critical section in this + *case: */ + + if (cus->hwa_id == RPC_AES1_CODE && + cus-> + key_context != dev-> + aes1_key_context) { + /*For AES operations, atomically read + *g_hAES1SSecureKeyContext and check if + *it is equal to key_context. If not, + *take the standard path <=> do not + *shortcut */ + dprintk(KERN_INFO + "shortcut exists but AES key "\ + "not correct\nkey_context="\ + "0x%08x vs 0x%08x\n", + cus->key_context, + dev-> + aes1_key_context); + goto command_not_shortcutable; + + } else if (cus->hwa_id == RPC_DES_CODE + && cus->key_context != + dev-> + des_key_context) { + /* + * For DES/3DES atomically read + * des_key_context and check if + * it is equal to key_context. If not, + * take the standard path <=> do not + * shortcut + */ + dprintk(KERN_INFO + "shortcut exists but DES key " + "not correct " + "des_key_context = 0x%08x" + " key_context0x%08x\n", + (u32)dev-> + des_key_context, + (u32)cus->key_context); + goto command_not_shortcutable; + } else if (cus->hwa_id == RPC_SHA_CODE + && !dev->sham1_is_public) { + /* + * For digest operations, atomically + * read sham1_is_public and check if it + * is true. If not, no shortcut. + */ + dprintk(KERN_INFO + "shortcut exists but SHAM1 " + "is not accessible in public"); + goto command_not_shortcutable; + } + } + + dprintk(KERN_INFO "shortcut exists and enable\n"); + + /*Shortcut has been found and context fits with + *thread => YES! the command can be shortcuted */ + + /* + *set the pointer on the corresponding session + *(eq CUS context) + */ + *cus_ctx = cus; + + /* + *increment use_count if required + */ + if (incrementuse_count) + cus->use_count++; + + /* + *release shortcut_list_lock + */ + spin_unlock(&(connection-> + shortcut_list_lock)); + return true; + } + } + + command_not_shortcutable: + /* + *release shortcut_list_lock + */ + spin_unlock(&(connection->shortcut_list_lock)); + *cus_ctx = NULL; + return false; +} + +/*------------------------------------------------------------------------- */ +/* + * Pre-process the client command (crypto update operation), i.e., parse the + * command message (decode buffers, etc.) THIS FUNCTION IS CALLED FROM THE USER + * THREAD (ioctl). + * + * For incorrect messages, an error is returned and the message will be sent to + * secure + */ +static bool tf_crypto_parse_command_message(struct tf_connection *connection, + struct cus_context *cus, + struct tf_command_invoke_client_command *command, + struct cus_params *params) +{ + u32 param_type; + u32 input_data_length; + u32 output_data_length; + u8 *input_data; + u8 *output_data; + struct tf_shmem_desc *input_shmem = NULL; + struct tf_shmem_desc *output_shmem = NULL; + + dprintk(KERN_INFO + "tf_crypto_parse_command_message(%p) : Session=0x%x\n", + cus, cus->client_session); + + if (command->params[0].temp_memref.size == 0) + return false; + + param_type = TF_GET_PARAM_TYPE(command->param_types, 0); + switch (param_type) { + case TF_PARAM_TYPE_MEMREF_TEMP_INPUT: + if (command->params[0].temp_memref.descriptor == 0) + return false; + + input_data = (u8 *) command->params[0].temp_memref. + descriptor; + input_data_length = command->params[0].temp_memref.size; + + break; + + case TF_PARAM_TYPE_MEMREF_INPUT: + input_shmem = tf_get_shmem_from_block_handle(connection, + command->params[0].memref.block); + + if (input_shmem == NULL) + return false; + atomic_inc(&input_shmem->ref_count); + + input_data = input_shmem->pBuffer + + command->params[0].memref.offset; + input_data_length = command->params[0].memref.size; + + break; + + default: + return false; + } + + if (cus->hwa_id != RPC_SHA_CODE) { + if (command->params[1].temp_memref.size == 0) + goto err0; + + /* We need an output buffer as well */ + param_type = TF_GET_PARAM_TYPE(command->param_types, 1); + switch (param_type) { + case TF_PARAM_TYPE_MEMREF_TEMP_OUTPUT: + output_data = + (u8 *) command->params[1].temp_memref. + descriptor; + output_data_length = + command->params[1].temp_memref.size; + + break; + + case TF_PARAM_TYPE_MEMREF_OUTPUT: + if (command->params[1].temp_memref.descriptor == 0) + return false; + + output_shmem = tf_get_shmem_from_block_handle( + connection, command->params[1].memref.block); + if (output_shmem == NULL) + goto err0; + atomic_inc(&output_shmem->ref_count); + + output_data = output_shmem->pBuffer + + command->params[1].memref.offset; + output_data_length = command->params[1].memref.size; + + break; + + default: + dprintk(KERN_ERR "tf_crypto_parse_command_message: " + "Encrypt/decrypt operations require an output " + "buffer\n"); + + goto err0; + } + + if (output_data_length < input_data_length) { + dprintk(KERN_ERR "tf_crypto_parse_command_message: " + "Short buffer: output_data_length = %d < " + "input_data_length = %d\n", + output_data_length, input_data_length); + goto err1; + } + } else { + output_data_length = 0; + output_data = NULL; + } + + /* + * Check if input length is compatible with the algorithm of the + * shortcut + */ + switch (cus->hwa_id) { + case RPC_AES1_CODE: + /* Must be multiple of the AES block size */ + if ((input_data_length % AES_BLOCK_SIZE) != 0) { + dprintk(KERN_ERR + "tf_crypto_parse_command_message(%p): "\ + "Input Data Length invalid [%d] for AES\n", + cus, input_data_length); + goto err1; + } + break; + case RPC_DES_CODE: + /* Must be multiple of the DES block size */ + if ((input_data_length % DES_BLOCK_SIZE) != 0) { + dprintk(KERN_ERR + "tf_crypto_parse_command_message(%p): "\ + "Input Data Length invalid [%d] for DES\n", + cus, input_data_length); + goto err1; + } + break; + default: + /* SHA operation: no constraint on data length */ + break; + } + + params->input_data = input_data; + params->input_data_length = input_data_length; + params->input_shmem = input_shmem; + params->output_data = output_data; + params->output_data_length = output_data_length; + params->output_shmem = output_shmem; + + return true; + +err1: + if (output_shmem) + atomic_dec(&output_shmem->ref_count); +err0: + if (input_shmem) + atomic_dec(&input_shmem->ref_count); + + return false; +} + +/*------------------------------------------------------------------------- */ + +/* + *Post-process the client command (crypto update operation), + *i.e. copy the result into the user output buffer and release the resources. + *THIS FUNCTION IS CALLED FROM THE USER THREAD (ioctl). + */ +static void tf_crypto_write_answer( + struct cus_context *cus, + struct cus_params *params, + struct tf_answer_invoke_client_command *answer) +{ + u32 error = S_SUCCESS; + + dprintk(KERN_INFO + "tf_crypto_write_answer(%p) : Session=0x%x\n", + cus, cus->client_session); + + /* Generate the answer */ + answer->message_size = + (sizeof(struct tf_answer_invoke_client_command) - + sizeof(struct tf_answer_header)) / 4; + answer->message_type = TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND; + answer->error_origin = TF_ORIGIN_TRUSTED_APP; + answer->operation_id = 0; + answer->error_code = error; + answer->answers[1].size.size = params->output_data_length; +} + +/*------------------------------------------------------------------------- */ + +int tf_crypto_try_shortcuted_update(struct tf_connection *connection, + struct tf_command_invoke_client_command *command, + struct tf_answer_invoke_client_command *answer) +{ + struct cus_context *cus = NULL; + + if (tf_crypto_is_shortcuted_command(connection, + (struct tf_command_invoke_client_command *) command, + &cus, false)) { + u32 hwa_id = cus->hwa_id; + + /* Lock HWA */ + tf_crypto_lock_hwa(hwa_id, LOCK_HWA); + + if (tf_crypto_is_shortcuted_command(connection, + command, + &cus, true)) { + struct cus_params cus_params; + + memset(&cus_params, 0, sizeof(cus_params)); + + if (!tf_crypto_parse_command_message( + connection, + cus, + command, + &cus_params)) { + /* Decrement CUS context use count */ + cus->use_count--; + + /* Release HWA lock */ + tf_crypto_lock_hwa(cus->hwa_id, + UNLOCK_HWA); + + return -1; + } + + /* Perform the update in public <=> THE shortcut */ + if (!tf_crypto_update(cus, &cus_params)) { + /* Decrement CUS context use count */ + cus->use_count--; + + /* Release HWA lock */ + tf_crypto_lock_hwa(cus->hwa_id, + UNLOCK_HWA); + + return -1; + } + + /* Write answer message */ + tf_crypto_write_answer(cus, + &cus_params, answer); + + /* Decrement registered shmems use count if needed */ + if (cus_params.input_shmem) + atomic_dec(&cus_params.input_shmem->ref_count); + if (cus_params.output_shmem) + atomic_dec(&cus_params.output_shmem->ref_count); + + /* Decrement CUS context use count */ + cus->use_count--; + + tf_crypto_lock_hwa(cus->hwa_id, + UNLOCK_HWA); + } else { + tf_crypto_lock_hwa(hwa_id, UNLOCK_HWA); + return -1; + } + } else { + return -1; + } + + return 0; +} + +/*------------------------------------------------------------------------- */ + +void tf_crypto_wait_for_ready_bit_infinitely(u32 *reg, u32 bit) +{ + while (!(INREG32(reg) & bit)) + ; +} + +/*------------------------------------------------------------------------- */ + +u32 tf_crypto_wait_for_ready_bit(u32 *reg, u32 bit) +{ + u32 timeoutCounter = PUBLIC_CRYPTO_TIMEOUT_CONST; + + while ((!(INREG32(reg) & bit)) && ((--timeoutCounter) != 0)) + ; + + if (timeoutCounter == 0) + return PUBLIC_CRYPTO_ERR_TIMEOUT; + + return PUBLIC_CRYPTO_OPERATION_SUCCESS; +} + +/*------------------------------------------------------------------------- */ + +static DEFINE_SPINLOCK(clk_lock); + +void tf_crypto_disable_clock(uint32_t clock_paddr) +{ + u32 *clock_reg; + u32 val; + unsigned long flags; + + dprintk(KERN_INFO "tf_crypto_disable_clock: " \ + "clock_paddr=0x%08X\n", + clock_paddr); + + /* Ensure none concurrent access when changing clock registers */ + spin_lock_irqsave(&clk_lock, flags); + + clock_reg = (u32 *)IO_ADDRESS(clock_paddr); + + val = __raw_readl(clock_reg); + val &= ~(0x3); + __raw_writel(val, clock_reg); + + /* Wait for clock to be fully disabled */ + while ((__raw_readl(clock_reg) & 0x30000) == 0) + ; + + spin_unlock_irqrestore(&clk_lock, flags); + + tf_l4sec_clkdm_allow_idle(true); +} + +/*------------------------------------------------------------------------- */ + +void tf_crypto_enable_clock(uint32_t clock_paddr) +{ + u32 *clock_reg; + u32 val; + unsigned long flags; + + dprintk(KERN_INFO "tf_crypto_enable_clock: " \ + "clock_paddr=0x%08X\n", + clock_paddr); + + tf_l4sec_clkdm_wakeup(true); + + /* Ensure none concurrent access when changing clock registers */ + spin_lock_irqsave(&clk_lock, flags); + + clock_reg = (u32 *)IO_ADDRESS(clock_paddr); + + val = __raw_readl(clock_reg); + val |= 0x2; + __raw_writel(val, clock_reg); + + /* Wait for clock to be fully enabled */ + while ((__raw_readl(clock_reg) & 0x30000) != 0) + ; + + spin_unlock_irqrestore(&clk_lock, flags); +} + +/*------------------------------------------------------------------------- */ +/* CUS RPCs */ +/*------------------------------------------------------------------------- */ +/* + * This RPC is used by the secure world to install a new shortcut. Optionally, + * for AES or DES/3DES operations, it can also lock the accelerator so that the + * secure world can install a new key in it. + */ +static int tf_crypto_install_shortcut_lock_hwa( + u32 rpc_command, void *rpc_shared_buffer) +{ + struct cus_context *cus = NULL; + struct tf_connection *connection = NULL; + + /* Reference the input/ouput data */ + struct rpc_install_shortcut_lock_accelerator_out *install_cus_out = + rpc_shared_buffer; + struct rpc_install_shortcut_lock_accelerator_in *install_cus_in = + rpc_shared_buffer; + + dprintk(KERN_INFO "tf_crypto_install_shortcut_lock_hwa: " + "rpc_command=0x%08x; hwa_id=0x%08x\n", + rpc_command, install_cus_in->hwa_id); + + connection = (struct tf_connection *) + install_cus_in->device_context_id; + + if (connection == NULL) { + dprintk(KERN_INFO + "tf_crypto_install_shortcut_lock_hwa: " + "DeviceContext 0x%08x does not exist, " + "cannot create Shortcut\n", + install_cus_in->device_context_id); + install_cus_out->error = -1; + return 0; + } + + /* + * Allocate a shortcut context. If the allocation fails, + * return S_ERROR_OUT_OF_MEMORY error code + */ + cus = (struct cus_context *) + internal_kmalloc(sizeof(*cus), GFP_KERNEL); + if (cus == NULL) { + dprintk(KERN_ERR + "tf_crypto_install_shortcut_lock_hwa: "\ + "Out of memory for public session\n"); + install_cus_out->error = S_ERROR_OUT_OF_MEMORY; + return 0; + } + + memset(cus, 0, sizeof(*cus)); + + /*setup the shortcut */ + cus->magic_number = CUS_CONTEXT_MAGIC; + cus->client_session = install_cus_in->client_session; + cus->command_id = install_cus_in->command_id; + cus->hwa_id = install_cus_in->hwa_id; + cus->hwa_ctrl = install_cus_in->hwa_ctrl; + cus->key_context = install_cus_in->key_context; + cus->use_count = 0; + cus->suspended = false; + + memcpy(&cus->operation_state, + &install_cus_in->operation_state, + sizeof(union tf_crypto_operation_state)); + + /*lock the shortcut_list_lock for this device context */ + spin_lock(&connection->shortcut_list_lock); + + /*Insert the shortcut in the list of shortcuts in the device context */ + list_add(&(cus->list), &(connection->shortcut_list)); + + /*release shortcut_list_lock */ + spin_unlock(&connection->shortcut_list_lock); + + /*fill the output structure */ + install_cus_out->shortcut_id = (u32) cus; + install_cus_out->error = S_SUCCESS; + + /*If the L bit is true, then: + * Enter the accelerator critical section. If an update is currently in + * progress on the accelerator (using g_hXXXKeyContext key), this will + * wait until the update has completed. This is call when secure wants + * to install a key in HWA, once it is done secure world will release + * the lock. For SHA (activate shortcut is always called without LOCK + * fag):do nothing + */ + if ((rpc_command & RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_LOCK) != 0) { + /*Lock the HWA */ + tf_crypto_lock_hwa(cus->hwa_id, LOCK_HWA); + } + + dprintk(KERN_INFO + "tf_crypto_install_shortcut_lock_hwa: Done\n"); + + return S_SUCCESS; +} + +/*------------------------------------------------------------------------- */ + +/* + * This RPC is used to perform one or several of the following operations + * - Lock one or several accelerators for the exclusive use by the secure world, + * either because it is going to be switched to secure or because a new key is + * going to be loaded in the accelerator + * - Suspend a shortcut, i.e., make it temporarily unavailable to the public + * world. This is used when a secure update is going to be performed on the + * operation. The answer to the RPC then contains the operation state + * necessary for the secure world to do the update. + * - Uninstall the shortcut + */ +static int tf_crypto_lock_hwas_suspend_shortcut( + u32 rpc_command, void *rpc_shared_buffer) +{ + u32 target_shortcut; + struct cus_context *cus = NULL; + struct tf_connection *connection = NULL; + + /*reference the input/ouput data */ + struct rpc_lock_hwa_suspend_shortcut_out *suspend_cus_out = + rpc_shared_buffer; + struct rpc_lock_hwa_suspend_shortcut_in *suspend_cus_in = + rpc_shared_buffer; + + dprintk(KERN_INFO + "tf_crypto_lock_hwas_suspend_shortcut: "\ + "suspend_cus_in=0x%08x; shortcut_id=0x%08x\n", + suspend_cus_in->shortcut_id, (u32)suspend_cus_in); + + target_shortcut = suspend_cus_in->shortcut_id; + + /*lock HWAs */ + tf_crypto_lock_hwas(rpc_command, LOCK_HWA); + + /*if suspend_cus_in->shortcut_id != 0 and if rpc_command.S != 0, + then, suspend shortcut */ + if ((target_shortcut != 0) && ((rpc_command & + RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_SUSPEND) != 0)) { + /*reference the CUSContext */ + cus = (struct cus_context *) + suspend_cus_in->shortcut_id; + + /*preventive check1: return if shortcut does not exist */ + connection = tf_get_device_context(cus); + if (connection == NULL) { + dprintk(KERN_INFO + "tf_crypto_lock_hwas_suspend_shortcut: "\ + "shortcut_id=0x%08x does not exist, cannot suspend "\ + "Shortcut\n", + suspend_cus_in->shortcut_id); + return -1; + } + +loop_on_suspend: + /*lock shortcut_list_lock associated with the + *device context */ + spin_lock(&connection->shortcut_list_lock); + + /*Suspend shortcut */ + cus->suspended = true; + + if (cus->use_count != 0) { + /*release shortcut_list_lock */ + spin_unlock(&connection-> + shortcut_list_lock); + schedule(); + goto loop_on_suspend; + } + + /*Copy the operation state data stored in CUS Context into the + *answer to the RPC output assuming that HWA register has been + *saved at update time */ + memcpy(&suspend_cus_out->operation_state, + &cus->operation_state, + sizeof(union tf_crypto_operation_state)); + + /*Uninstall shortcut if requiered */ + if ((rpc_command & + RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_UNINSTALL) != 0) { + dprintk(KERN_INFO + "tf_crypto_lock_hwas_suspend_shortcut:"\ + "Uninstall 0x%08x\n", + target_shortcut); + list_del(&(cus->list)); + /*list_del only remove the item in the list, the + *memory must be free afterward */ + /*release the lock before calling internal_kfree */ + spin_unlock(&connection-> + shortcut_list_lock); + if (cus != NULL) + internal_kfree(cus); + return 0; + } + + /*release shortcut_list_lock */ + spin_unlock(&connection->shortcut_list_lock); + } + + return 0; +} + +/*------------------------------------------------------------------------- */ + +/* + * This RPC is used to perform one or several of the following operations: + * - Resume a shortcut previously suspended + * - Inform the public driver of the new keys installed in the DES and AES + * accelerators + * - Unlock some of the accelerators + */ +static int tf_crypto_resume_shortcut_unlock_hwas( + u32 rpc_command, void *rpc_shared_buffer) +{ + struct tf_device *dev = tf_get_device(); + struct tf_connection *connection = NULL; + struct cus_context *cus = NULL; + + /*reference the input data */ + struct rpc_resume_shortcut_unlock_hwa_in *resume_cus_in = + rpc_shared_buffer; + + dprintk(KERN_INFO + "tf_crypto_resume_shortcut_unlock_hwas\n" + "rpc_command=0x%08x\nshortcut_id=0x%08x\n", + rpc_command, resume_cus_in->shortcut_id); + + /*if shortcut_id not 0 resume the shortcut and unlock HWA + else only unlock HWA */ + if (resume_cus_in->shortcut_id != 0) { + /*reference the CUSContext */ + cus = (struct cus_context *) + resume_cus_in->shortcut_id; + + /*preventive check1: return if shortcut does not exist + *else, points to the public crypto monitor (inside the device + *context) */ + connection = tf_get_device_context(cus); + if (connection == NULL) { + dprintk(KERN_INFO + "tf_crypto_resume_shortcut_unlock_hwas(...):"\ + "shortcut_id 0x%08x does not exist, cannot suspend "\ + "Shortcut\n", + resume_cus_in->shortcut_id); + return -1; + } + + /*if S set and shortcut not yet suspended */ + if ((cus->suspended) && + ((rpc_command & + RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_RESUME) != 0)){ + /*Write operation_stateData in the shortcut context */ + memcpy(&cus->operation_state, + &resume_cus_in->operation_state, + sizeof(union tf_crypto_operation_state)); + /*resume the shortcut */ + cus->suspended = false; + } + } + + /* + * If A is set: Atomically set aes1_key_context to + * aes1_key_context + */ + if ((rpc_command & + RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_AES1) != 0) { + dev->aes1_key_context = + resume_cus_in->aes1_key_context; + } + + /* + * If D is set: + * Atomically set des_key_context to des_key_context + */ + if ((rpc_command & + RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_DES) != 0) { + dev->des_key_context = + resume_cus_in->des_key_context; + } + + /* H is never set by the PA: Atomically set sham1_is_public to true */ + dev->sham1_is_public = true; + + /* Unlock HWAs according rpc_command */ + tf_crypto_lock_hwas(rpc_command, UNLOCK_HWA); + + return 0; +} + +/*------------------------------------------------------------------------- */ + +/* + * This RPC is used to notify the public driver that the key in the AES, DES + * accelerators has been cleared. This happens only when the key is no longer + * referenced by any shortcuts. So, it is guaranteed that no-one has entered the + * accelerators critical section and there is no need to enter it to implement + * this RPC. + */ +static int tf_crypto_clear_global_key_context( + u32 rpc_command, void *rpc_shared_buffer) +{ + struct tf_device *dev = tf_get_device(); + + /* + * If A is set: Atomically set aes1_key_context to 0 + */ + if ((rpc_command & + RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_AES1) != 0) { + dev->aes1_key_context = 0; + } + + /* + *If D is set: Atomically set des_key_context to 0 + */ + if ((rpc_command & + RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_DES) != 0) { + dev->des_key_context = 0; + } + + return 0; +} + +/*------------------------------------------------------------------------- */ +/* + * Execute a public crypto related RPC + */ + +int tf_crypto_execute_rpc(u32 rpc_command, void *rpc_shared_buffer) +{ + switch (rpc_command & RPC_CRYPTO_COMMAND_MASK) { + case RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR: + dprintk(KERN_INFO "RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR\n"); + return tf_crypto_install_shortcut_lock_hwa( + rpc_command, rpc_shared_buffer); + + case RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT: + dprintk(KERN_INFO "RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT\n"); + return tf_crypto_lock_hwas_suspend_shortcut( + rpc_command, rpc_shared_buffer); + + case RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS: + dprintk(KERN_INFO "RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS\n"); + return tf_crypto_resume_shortcut_unlock_hwas( + rpc_command, rpc_shared_buffer); + + case RPC_CLEAR_GLOBAL_KEY_CONTEXT: + dprintk(KERN_INFO "RPC_CLEAR_GLOBAL_KEY_CONTEXT\n"); + return tf_crypto_clear_global_key_context( + rpc_command, rpc_shared_buffer); + } + + return -1; +} diff --git a/security/smc/omap4/scx_public_crypto.h b/security/smc/tf_crypto.h index 984cb18..2291439 100644 --- a/security/smc/omap4/scx_public_crypto.h +++ b/security/smc/tf_crypto.h @@ -1,34 +1,39 @@ -/* - * Copyright (c)2006-2008 Trusted Logic S.A. +/** + * Copyright (c) 2011 Trusted Logic S.A. * 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 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. + * 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 + * 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 __SCX_PUBLIC_CRYPTO_H -#define __SCX_PUBLIC_CRYPTO_H +#ifndef __TF_PUBLIC_CRYPTO_H +#define __TF_PUBLIC_CRYPTO_H -#include "scxlnx_defs.h" +#include "tf_defs.h" #include <linux/io.h> #include <mach/io.h> #include <clockdomain.h> +#ifdef __ASM_ARM_ARCH_OMAP_CLOCKDOMAIN_H +#define clkdm_wakeup omap2_clkdm_wakeup +#define clkdm_allow_idle omap2_clkdm_allow_idle +#endif + /*-------------------------------------------------------------------------- */ #define PUBLIC_CRYPTO_HWA_AES1 0x1 -#define PUBLIC_CRYPTO_HWA_AES2 0x2 #define PUBLIC_CRYPTO_HWA_DES 0x4 #define PUBLIC_CRYPTO_HWA_SHA 0x8 @@ -39,7 +44,6 @@ #define PUBLIC_CRYPTO_CLKSTCTRL_CLOCK_REG 0x4A009580 #define PUBLIC_CRYPTO_AES1_CLOCK_REG 0x4A0095A0 -#define PUBLIC_CRYPTO_AES2_CLOCK_REG 0x4A0095A8 #define PUBLIC_CRYPTO_DES3DES_CLOCK_REG 0x4A0095B0 #define PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG 0x4A0095C8 @@ -110,14 +114,14 @@ /* *The magic word. */ -#define CRYPTOKI_UPDATE_SHORTCUT_CONTEXT_MAGIC 0x45EF683C +#define CUS_CONTEXT_MAGIC 0x45EF683C /*-------------------------------------------------------------------------- */ /* CUS context structure */ /*-------------------------------------------------------------------------- */ /* State of an AES operation */ -struct PUBLIC_CRYPTO_AES_OPERATION_STATE { +struct tf_crypto_aes_operation_state { u32 AES_IV_0; u32 AES_IV_1; u32 AES_IV_2; @@ -138,14 +142,14 @@ struct PUBLIC_CRYPTO_AES_OPERATION_STATE { u32 key_is_public; }; -struct PUBLIC_CRYPTO_DES_OPERATION_STATE { +struct tf_crypto_des_operation_state { u32 DES_IV_L; u32 DES_IV_H; }; #define HASH_BLOCK_BYTES_LENGTH 64 -struct PUBLIC_CRYPTO_SHA_OPERATION_STATE { +struct tf_crypto_sha_operation_state { /* Current digest */ u32 SHA_DIGEST_A; u32 SHA_DIGEST_B; @@ -157,63 +161,60 @@ struct PUBLIC_CRYPTO_SHA_OPERATION_STATE { u32 SHA_DIGEST_H; /* This buffer contains a partial chunk */ - u8 pChunkBuffer[HASH_BLOCK_BYTES_LENGTH]; + u8 chunk_buffer[HASH_BLOCK_BYTES_LENGTH]; - /* Number of bytes stored in pChunkBuffer (0..64) */ - u32 nChunkLength; + /* Number of bytes stored in chunk_buffer (0..64) */ + u32 chunk_length; /* * Total number of bytes processed so far * (not including the partial chunk) */ - u32 nBytesProcessed; + u32 bytes_processed; u32 CTRL; }; -union PUBLIC_CRYPTO_OPERATION_STATE { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE aes; - struct PUBLIC_CRYPTO_DES_OPERATION_STATE des; - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE sha; +union tf_crypto_operation_state { + struct tf_crypto_aes_operation_state aes; + struct tf_crypto_des_operation_state des; + struct tf_crypto_sha_operation_state sha; }; /* *Fully describes a public crypto operation *(i.e., an operation that has a shortcut attached). */ -struct CRYPTOKI_UPDATE_SHORTCUT_CONTEXT { +struct cus_context { /* *Identifies the public crypto operation in the list of all public *operations. */ struct list_head list; - u32 nMagicNumber; /*Must be set to - *{CRYPTOKI_UPDATE_SHORTCUT_CONTEXT_MAGIC} */ + u32 magic_number; /*Must be set to + *{CUS_CONTEXT_MAGIC} */ /*basic fields */ - u32 hClientSession; - u32 nCommandID; - u32 nHWAID; - u32 nHWA_CTRL; - u32 hKeyContext; - union PUBLIC_CRYPTO_OPERATION_STATE sOperationState; - u32 nUseCount; - bool bSuspended; + u32 client_session; + u32 command_id; + u32 hwa_id; + u32 hwa_ctrl; + u32 key_context; + union tf_crypto_operation_state operation_state; + u32 use_count; + bool suspended; }; -struct CRYPTOKI_UPDATE_PARAMS { +struct cus_params { /*fields for data processing of an update command */ - u32 nInputDataLength; - u8 *pInputData; - struct SCXLNX_SHMEM_DESC *pInputShmem; - - u32 nResultDataLength; - u8 *pResultData; - struct SCXLNX_SHMEM_DESC *pOutputShmem; + u32 input_data_length; + u8 *input_data; + struct tf_shmem_desc *input_shmem; - u8 *pS2CDataBuffer; - u32 nS2CDataBufferMaxLength; + u32 output_data_length; + u8 *output_data; + struct tf_shmem_desc *output_shmem; }; /*-------------------------------------------------------------------------- */ @@ -224,46 +225,46 @@ struct CRYPTOKI_UPDATE_PARAMS { /* *Initialize the public crypto DMA chanels and global HWA semaphores */ -u32 SCXPublicCryptoInit(void); +u32 tf_crypto_init(void); /* *Initialize the device context CUS fields *(shortcut semaphore and public CUS list) */ -void SCXPublicCryptoInitDeviceContext(struct SCXLNX_CONNECTION *pDeviceContext); +void tf_crypto_init_cus(struct tf_connection *connection); /** *Terminate the public crypto (including DMA) */ -void SCXPublicCryptoTerminate(void); +void tf_crypto_terminate(void); -int SCXPublicCryptoTryShortcutedUpdate(struct SCXLNX_CONNECTION *pConn, - struct SCX_COMMAND_INVOKE_CLIENT_COMMAND *pMessage, - struct SCX_ANSWER_INVOKE_CLIENT_COMMAND *pAnswer); +int tf_crypto_try_shortcuted_update(struct tf_connection *connection, + struct tf_command_invoke_client_command *command, + struct tf_answer_invoke_client_command *answer); -int SCXPublicCryptoExecuteRPCCommand(u32 nRPCCommand, void *pRPCSharedBuffer); +int tf_crypto_execute_rpc(u32 rpc_command, void *rpc_shared_buffer); /*-------------------------------------------------------------------------- */ /* *Helper methods */ -u32 SCXPublicCryptoWaitForReadyBit(u32 *pRegister, u32 vBit); -void SCXPublicCryptoWaitForReadyBitInfinitely(u32 *pRegister, u32 vBit); +u32 tf_crypto_wait_for_ready_bit(u32 *reg, u32 bit); +void tf_crypto_wait_for_ready_bit_infinitely(u32 *reg, u32 bit); -void SCXPublicCryptoEnableClock(uint32_t vClockPhysAddr); -void SCXPublicCryptoDisableClock(uint32_t vClockPhysAddr); +void tf_crypto_enable_clock(uint32_t clock_paddr); +void tf_crypto_disable_clock(uint32_t clock_paddr); #define LOCK_HWA true #define UNLOCK_HWA false -void PDrvCryptoLockUnlockHWA(u32 nHWAID, bool bDoLock); +void tf_crypto_lock_hwa(u32 hwa_id, bool do_lock); /*---------------------------------------------------------------------------*/ /* AES operations */ /*---------------------------------------------------------------------------*/ -void PDrvCryptoAESInit(void); -void PDrvCryptoAESExit(void); +void tf_aes_init(void); +void tf_aes_exit(void); #ifdef CONFIG_SMC_KERNEL_CRYPTO int register_smc_public_crypto_aes(void); @@ -283,21 +284,21 @@ static inline void unregister_smc_public_crypto_aes(void) {} *The AES1 accelerator is assumed loaded with the correct key * *AES_CTRL: defines the mode and direction - *pAESState: defines the operation IV - *pSrc: Input buffer to process. - *pDest: Output buffer containing the processed data. + *aes_state: defines the operation IV + *src: Input buffer to process. + *dest: Output buffer containing the processed data. * - *nbBlocks number of block(s)to process. + *nb_blocks number of block(s)to process. */ -bool PDrvCryptoUpdateAES(struct PUBLIC_CRYPTO_AES_OPERATION_STATE *pAESState, - u8 *pSrc, u8 *pDest, u32 nbBlocks); +bool tf_aes_update(struct tf_crypto_aes_operation_state *aes_state, + u8 *src, u8 *dest, u32 nb_blocks); /*---------------------------------------------------------------------------*/ /* DES/DES3 operations */ /*---------------------------------------------------------------------------*/ -void PDrvCryptoDESInit(void); -void PDrvCryptoDESExit(void); +void tf_des_init(void); +void tf_des_exit(void); /** *This function performs a DES update operation. @@ -305,21 +306,21 @@ void PDrvCryptoDESExit(void); *The DES accelerator is assumed loaded with the correct key * *DES_CTRL: defines the mode and direction - *pDESState: defines the operation IV - *pSrc: Input buffer to process. - *pDest: Output buffer containing the processed data. - *nbBlocks: Number of block(s)to process. + *des_state: defines the operation IV + *src: Input buffer to process. + *dest: Output buffer containing the processed data. + *nb_blocks: Number of block(s)to process. */ -bool PDrvCryptoUpdateDES(u32 DES_CTRL, - struct PUBLIC_CRYPTO_DES_OPERATION_STATE *pDESState, - u8 *pSrc, u8 *pDest, u32 nbBlocks); +bool tf_des_update(u32 DES_CTRL, + struct tf_crypto_des_operation_state *des_state, + u8 *src, u8 *dest, u32 nb_blocks); /*---------------------------------------------------------------------------*/ /* Digest operations */ /*---------------------------------------------------------------------------*/ -void PDrvCryptoDigestInit(void); -void PDrvCryptoDigestExit(void); +void tf_digest_init(void); +void tf_digest_exit(void); #ifdef CONFIG_SMC_KERNEL_CRYPTO int register_smc_public_crypto_digest(void); @@ -337,12 +338,12 @@ static inline void unregister_smc_public_crypto_digest(void) {} *This function performs a HASH update Operation. * *SHA_CTRL: defines the algorithm - *pSHAState: State of the operation - *pData: Input buffer to process - *dataLength: Length in bytes of the input buffer. + *sha_state: State of the operation + *data: Input buffer to process + *data_length: Length in bytes of the input buffer. */ -void PDrvCryptoUpdateHash( - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *pSHAState, - u8 *pData, u32 dataLength); +bool tf_digest_update( + struct tf_crypto_sha_operation_state *sha_state, + u8 *data, u32 data_length); -#endif /*__SCX_PUBLIC_CRYPTO_H */ +#endif /*__TF_PUBLIC_CRYPTO_H */ diff --git a/security/smc/omap4/scx_public_crypto_AES.c b/security/smc/tf_crypto_aes.c index 96b065f..36dc522 100644 --- a/security/smc/omap4/scx_public_crypto_AES.c +++ b/security/smc/tf_crypto_aes.c @@ -1,26 +1,27 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. +/** + * Copyright (c) 2011 Trusted Logic S.A. * 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 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. + * 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 + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA */ -#include "scxlnx_defs.h" -#include "scxlnx_util.h" -#include "scx_public_crypto.h" -#include "scx_public_dma.h" -#include "scxlnx_mshield.h" +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_crypto.h" +#include "tf_dma.h" +#include "tf_zebra.h" #include <linux/io.h> #include <linux/interrupt.h> @@ -35,7 +36,6 @@ *AES Hardware Accelerator: Base address */ #define AES1_REGS_HW_ADDR 0x4B501000 -#define AES2_REGS_HW_ADDR 0x4B701000 /* *CTRL register Masks @@ -80,7 +80,7 @@ /** *This structure contains the registers of the AES HW accelerator. */ -struct AESReg_t { +struct aes_reg { u32 AES_KEY2_6; /* 0x00 */ u32 AES_KEY2_7; /* 0xO4 */ u32 AES_KEY2_4; /* 0x08 */ @@ -126,7 +126,7 @@ struct AESReg_t { u32 AES_SYSSTATUS; /* 0x88 */ }; -static struct AESReg_t *pAESReg_t; +static struct aes_reg *paes_reg; #ifdef CONFIG_SMC_KERNEL_CRYPTO #define FLAGS_FAST BIT(7) @@ -158,7 +158,7 @@ struct aes_hwa_ctx { int dma_lch_out; dma_addr_t dma_addr_out; - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *ctx; + struct tf_crypto_aes_operation_state *ctx; }; static struct aes_hwa_ctx *aes_ctx; #endif @@ -167,69 +167,68 @@ static struct aes_hwa_ctx *aes_ctx; *Forward declarations *------------------------------------------------------------------------- */ -static void PDrvCryptoUpdateAESWithDMA(u8 *pSrc, u8 *pDest, - u32 nbBlocks); +static bool tf_aes_update_dma(u8 *src, u8 *dest, u32 nb_blocks, bool is_kernel); /*---------------------------------------------------------------------------- *Save HWA registers into the specified operation state structure *--------------------------------------------------------------------------*/ -static void PDrvCryptoSaveAESRegisters( - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *pAESState) +static void tf_aes_save_registers( + struct tf_crypto_aes_operation_state *aes_state) { - dprintk(KERN_INFO "PDrvCryptoSaveAESRegisters: \ - pAESState(%p) <- pAESReg_t(%p): CTRL=0x%08x\n", - pAESState, pAESReg_t, pAESState->CTRL); + dprintk(KERN_INFO "tf_aes_save_registers: " + "aes_state(%p) <- paes_reg(%p): CTRL=0x%08x\n", + aes_state, paes_reg, aes_state->CTRL); /*Save the IV if we are in CBC or CTR mode (not required for ECB) */ - if (!AES_CTRL_IS_MODE_ECB(pAESState->CTRL)) { - pAESState->AES_IV_0 = INREG32(&pAESReg_t->AES_IV_IN_0); - pAESState->AES_IV_1 = INREG32(&pAESReg_t->AES_IV_IN_1); - pAESState->AES_IV_2 = INREG32(&pAESReg_t->AES_IV_IN_2); - pAESState->AES_IV_3 = INREG32(&pAESReg_t->AES_IV_IN_3); + if (!AES_CTRL_IS_MODE_ECB(aes_state->CTRL)) { + aes_state->AES_IV_0 = INREG32(&paes_reg->AES_IV_IN_0); + aes_state->AES_IV_1 = INREG32(&paes_reg->AES_IV_IN_1); + aes_state->AES_IV_2 = INREG32(&paes_reg->AES_IV_IN_2); + aes_state->AES_IV_3 = INREG32(&paes_reg->AES_IV_IN_3); } } /*---------------------------------------------------------------------------- *Restore the HWA registers from the operation state structure *---------------------------------------------------------------------------*/ -static void PDrvCryptoRestoreAESRegisters( - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *pAESState) +static void tf_aes_restore_registers( + struct tf_crypto_aes_operation_state *aes_state) { - struct SCXLNX_DEVICE *pDevice = SCXLNXGetDevice(); - - dprintk(KERN_INFO "PDrvCryptoRestoreAESRegisters: \ - pAESReg_t(%p) <- pAESState(%p): CTRL=0x%08x\n", - pAESReg_t, pAESState, pAESState->CTRL); - - if (pAESState->key_is_public) { - OUTREG32(&pAESReg_t->AES_KEY1_0, pAESState->KEY1_0); - OUTREG32(&pAESReg_t->AES_KEY1_1, pAESState->KEY1_1); - OUTREG32(&pAESReg_t->AES_KEY1_2, pAESState->KEY1_2); - OUTREG32(&pAESReg_t->AES_KEY1_3, pAESState->KEY1_3); - OUTREG32(&pAESReg_t->AES_KEY1_4, pAESState->KEY1_4); - OUTREG32(&pAESReg_t->AES_KEY1_5, pAESState->KEY1_5); - OUTREG32(&pAESReg_t->AES_KEY1_6, pAESState->KEY1_6); - OUTREG32(&pAESReg_t->AES_KEY1_7, pAESState->KEY1_7); + struct tf_device *dev = tf_get_device(); + + dprintk(KERN_INFO "tf_aes_restore_registers: " + "paes_reg(%p) <- aes_state(%p): CTRL=0x%08x\n", + paes_reg, aes_state, aes_state->CTRL); + + if (aes_state->key_is_public) { + OUTREG32(&paes_reg->AES_KEY1_0, aes_state->KEY1_0); + OUTREG32(&paes_reg->AES_KEY1_1, aes_state->KEY1_1); + OUTREG32(&paes_reg->AES_KEY1_2, aes_state->KEY1_2); + OUTREG32(&paes_reg->AES_KEY1_3, aes_state->KEY1_3); + OUTREG32(&paes_reg->AES_KEY1_4, aes_state->KEY1_4); + OUTREG32(&paes_reg->AES_KEY1_5, aes_state->KEY1_5); + OUTREG32(&paes_reg->AES_KEY1_6, aes_state->KEY1_6); + OUTREG32(&paes_reg->AES_KEY1_7, aes_state->KEY1_7); /* * Make sure a potential secure key that has been overwritten by * the previous code is reinstalled before performing other * public crypto operations. */ - pDevice->hAES1SecureKeyContext = 0; + dev->aes1_key_context = 0; } else { - pAESState->CTRL |= INREG32(&pAESReg_t->AES_CTRL); + aes_state->CTRL |= INREG32(&paes_reg->AES_CTRL); } /* * Restore the IV first if we are in CBC or CTR mode * (not required for ECB) */ - if (!AES_CTRL_IS_MODE_ECB(pAESState->CTRL)) { - OUTREG32(&pAESReg_t->AES_IV_IN_0, pAESState->AES_IV_0); - OUTREG32(&pAESReg_t->AES_IV_IN_1, pAESState->AES_IV_1); - OUTREG32(&pAESReg_t->AES_IV_IN_2, pAESState->AES_IV_2); - OUTREG32(&pAESReg_t->AES_IV_IN_3, pAESState->AES_IV_3); + if (!AES_CTRL_IS_MODE_ECB(aes_state->CTRL)) { + OUTREG32(&paes_reg->AES_IV_IN_0, aes_state->AES_IV_0); + OUTREG32(&paes_reg->AES_IV_IN_1, aes_state->AES_IV_1); + OUTREG32(&paes_reg->AES_IV_IN_2, aes_state->AES_IV_2); + OUTREG32(&paes_reg->AES_IV_IN_3, aes_state->AES_IV_3); } /* Then set the CTRL register: @@ -237,126 +236,153 @@ static void PDrvCryptoRestoreAESRegisters( * it leads to break the HWA process (observed by experimentation) */ - pAESState->CTRL = (pAESState->CTRL & (3 << 3)) /* key size */ - | (pAESState->CTRL & ((1 << 2) | (1 << 5) | (1 << 6))) + aes_state->CTRL = (aes_state->CTRL & (3 << 3)) /* key size */ + | (aes_state->CTRL & ((1 << 2) | (1 << 5) | (1 << 6))) | (0x3 << 7) /* Always set CTR_WIDTH to 128-bit */; - if ((pAESState->CTRL & 0x1FC) != - (INREG32(&pAESReg_t->AES_CTRL) & 0x1FC)) - OUTREG32(&pAESReg_t->AES_CTRL, pAESState->CTRL & 0x1FC); + if ((aes_state->CTRL & 0x1FC) != + (INREG32(&paes_reg->AES_CTRL) & 0x1FC)) + OUTREG32(&paes_reg->AES_CTRL, aes_state->CTRL & 0x1FC); /* Set the SYSCONFIG register to 0 */ - OUTREG32(&pAESReg_t->AES_SYSCONFIG, 0); + OUTREG32(&paes_reg->AES_SYSCONFIG, 0); } /*-------------------------------------------------------------------------- */ -void PDrvCryptoAESInit(void) +void tf_aes_init(void) { - pAESReg_t = omap_ioremap(AES1_REGS_HW_ADDR, SZ_1M, MT_DEVICE); - if (pAESReg_t == NULL) + paes_reg = omap_ioremap(AES1_REGS_HW_ADDR, SZ_1M, MT_DEVICE); + if (paes_reg == NULL) panic("Unable to remap AES1 module"); } -void PDrvCryptoAESExit(void) +void tf_aes_exit(void) { - omap_iounmap(pAESReg_t); + omap_iounmap(paes_reg); } -bool PDrvCryptoUpdateAES(struct PUBLIC_CRYPTO_AES_OPERATION_STATE *pAESState, - u8 *pSrc, u8 *pDest, u32 nbBlocks) +bool tf_aes_update(struct tf_crypto_aes_operation_state *aes_state, + u8 *src, u8 *dest, u32 nb_blocks) { u32 nbr_of_blocks; - u32 vTemp; - u8 *pProcessSrc = pSrc; - u8 *pProcessDest = pDest; - u32 dmaUse = PUBLIC_CRYPTO_DMA_USE_NONE; + u32 temp; + u8 *process_src; + u8 *process_dest; + u32 dma_use = PUBLIC_CRYPTO_DMA_USE_NONE; + bool is_kernel = false; /* *Choice of the processing type */ - if (nbBlocks * AES_BLOCK_SIZE >= DMA_TRIGGER_IRQ_AES) - dmaUse = PUBLIC_CRYPTO_DMA_USE_IRQ; - - dprintk(KERN_INFO "PDrvCryptoUpdateAES: \ - pSrc=0x%08x, pDest=0x%08x, nbBlocks=0x%08x, dmaUse=0x%08x\n", - (unsigned int)pSrc, - (unsigned int)pDest, - (unsigned int)nbBlocks, - (unsigned int)dmaUse); - - if (nbBlocks == 0) { - dprintk(KERN_INFO "PDrvCryptoUpdateAES: Nothing to process\n"); + if (nb_blocks * AES_BLOCK_SIZE >= DMA_TRIGGER_IRQ_AES) + dma_use = PUBLIC_CRYPTO_DMA_USE_IRQ; + + dprintk(KERN_INFO "tf_aes_update: " + "src=0x%08x, dest=0x%08x, nb_blocks=0x%08x, dma_use=0x%08x\n", + (unsigned int)src, + (unsigned int)dest, + (unsigned int)nb_blocks, + (unsigned int)dma_use); + + if (aes_state->key_is_public) + is_kernel = true; + + if (nb_blocks == 0) { + dprintk(KERN_INFO "tf_aes_update: Nothing to process\n"); return true; } - if ((AES_CTRL_GET_DIRECTION(INREG32(&pAESReg_t->AES_CTRL)) != - AES_CTRL_GET_DIRECTION(pAESState->CTRL)) && - !pAESState->key_is_public) { + if ((AES_CTRL_GET_DIRECTION(INREG32(&paes_reg->AES_CTRL)) != + AES_CTRL_GET_DIRECTION(aes_state->CTRL)) && + !aes_state->key_is_public) { dprintk(KERN_WARNING "HWA configured for another direction\n"); return false; } /*Restore the registers of the accelerator from the operation state */ - PDrvCryptoRestoreAESRegisters(pAESState); + tf_aes_restore_registers(aes_state); - if (dmaUse == PUBLIC_CRYPTO_DMA_USE_IRQ) { + if (dma_use == PUBLIC_CRYPTO_DMA_USE_IRQ) { /* Perform the update with DMA */ - PDrvCryptoUpdateAESWithDMA(pProcessSrc, - pProcessDest, nbBlocks); + if (!tf_aes_update_dma(src, dest, nb_blocks, is_kernel)) + return false; } else { + u8 buf[DMA_TRIGGER_IRQ_AES]; + + /* + * Synchronous Linux crypto API buffers are mapped in kernel + * space + */ + + if (is_kernel) { + process_src = src; + process_dest = dest; + } else { + if (copy_from_user(buf, src, + nb_blocks * AES_BLOCK_SIZE)) + return false; + + process_src = process_dest = buf; + } + for (nbr_of_blocks = 0; - nbr_of_blocks < nbBlocks; nbr_of_blocks++) { + nbr_of_blocks < nb_blocks; nbr_of_blocks++) { /*We wait for the input ready */ /*Crash the system as this should never occur */ - if (SCXPublicCryptoWaitForReadyBit( - (u32 *)&pAESReg_t->AES_CTRL, + if (tf_crypto_wait_for_ready_bit( + (u32 *)&paes_reg->AES_CTRL, AES_CTRL_INPUT_READY_BIT) != PUBLIC_CRYPTO_OPERATION_SUCCESS) - panic("Wait too long for AES hardware \ - accelerator Input data to be ready\n"); + panic("Wait too long for AES hardware " + "accelerator Input data to be ready\n"); /* We copy the 16 bytes of data src->reg */ - vTemp = (u32) BYTES_TO_LONG(pProcessSrc); - OUTREG32(&pAESReg_t->AES_DATA_IN_0, vTemp); - pProcessSrc += 4; - vTemp = (u32) BYTES_TO_LONG(pProcessSrc); - OUTREG32(&pAESReg_t->AES_DATA_IN_1, vTemp); - pProcessSrc += 4; - vTemp = (u32) BYTES_TO_LONG(pProcessSrc); - OUTREG32(&pAESReg_t->AES_DATA_IN_2, vTemp); - pProcessSrc += 4; - vTemp = (u32) BYTES_TO_LONG(pProcessSrc); - OUTREG32(&pAESReg_t->AES_DATA_IN_3, vTemp); - pProcessSrc += 4; + temp = (u32) BYTES_TO_LONG(process_src); + OUTREG32(&paes_reg->AES_DATA_IN_0, temp); + process_src += 4; + temp = (u32) BYTES_TO_LONG(process_src); + OUTREG32(&paes_reg->AES_DATA_IN_1, temp); + process_src += 4; + temp = (u32) BYTES_TO_LONG(process_src); + OUTREG32(&paes_reg->AES_DATA_IN_2, temp); + process_src += 4; + temp = (u32) BYTES_TO_LONG(process_src); + OUTREG32(&paes_reg->AES_DATA_IN_3, temp); + process_src += 4; /* We wait for the output ready */ - SCXPublicCryptoWaitForReadyBitInfinitely( - (u32 *)&pAESReg_t->AES_CTRL, + tf_crypto_wait_for_ready_bit_infinitely( + (u32 *)&paes_reg->AES_CTRL, AES_CTRL_OUTPUT_READY_BIT); /* We copy the 16 bytes of data reg->dest */ - vTemp = INREG32(&pAESReg_t->AES_DATA_IN_0); - LONG_TO_BYTE(pProcessDest, vTemp); - pProcessDest += 4; - vTemp = INREG32(&pAESReg_t->AES_DATA_IN_1); - LONG_TO_BYTE(pProcessDest, vTemp); - pProcessDest += 4; - vTemp = INREG32(&pAESReg_t->AES_DATA_IN_2); - LONG_TO_BYTE(pProcessDest, vTemp); - pProcessDest += 4; - vTemp = INREG32(&pAESReg_t->AES_DATA_IN_3); - LONG_TO_BYTE(pProcessDest, vTemp); - pProcessDest += 4; + temp = INREG32(&paes_reg->AES_DATA_IN_0); + LONG_TO_BYTE(process_dest, temp); + process_dest += 4; + temp = INREG32(&paes_reg->AES_DATA_IN_1); + LONG_TO_BYTE(process_dest, temp); + process_dest += 4; + temp = INREG32(&paes_reg->AES_DATA_IN_2); + LONG_TO_BYTE(process_dest, temp); + process_dest += 4; + temp = INREG32(&paes_reg->AES_DATA_IN_3); + LONG_TO_BYTE(process_dest, temp); + process_dest += 4; } + + if (!is_kernel) + if (copy_to_user(dest, buf, + nb_blocks * AES_BLOCK_SIZE)) + return false; } /* Save the accelerator registers into the operation state */ - PDrvCryptoSaveAESRegisters(pAESState); + tf_aes_save_registers(aes_state); - dprintk(KERN_INFO "PDrvCryptoUpdateAES: Done\n"); + dprintk(KERN_INFO "tf_aes_update: Done\n"); return true; } @@ -366,13 +392,13 @@ bool PDrvCryptoUpdateAES(struct PUBLIC_CRYPTO_AES_OPERATION_STATE *pAESState, *Static function, perform AES encryption/decryption using the DMA for data *transfer. * - *inputs: pSrc : pointer of the input data to process - * nbBlocks : number of block to process - * dmaUse : PUBLIC_CRYPTO_DMA_USE_IRQ (use irq to monitor end of DMA) + *inputs: src : pointer of the input data to process + * nb_blocks : number of block to process + * dma_use : PUBLIC_CRYPTO_DMA_USE_IRQ (use irq to monitor end of DMA) * | PUBLIC_CRYPTO_DMA_USE_POLLING (poll the end of DMA) - *output: pDest : pointer of the output data (can be eq to pSrc) + *output: dest : pointer of the output data (can be eq to src) */ -static void PDrvCryptoUpdateAESWithDMA(u8 *pSrc, u8 *pDest, u32 nbBlocks) +static bool tf_aes_update_dma(u8 *src, u8 *dest, u32 nb_blocks, bool is_kernel) { /* *Note: The DMA only sees physical addresses ! @@ -382,31 +408,33 @@ static void PDrvCryptoUpdateAESWithDMA(u8 *pSrc, u8 *pDest, u32 nbBlocks) int dma_ch1; struct omap_dma_channel_params ch0_parameters; struct omap_dma_channel_params ch1_parameters; - u32 nLength = nbBlocks * AES_BLOCK_SIZE; - u32 nLengthLoop = 0; - u32 nbBlocksLoop = 0; - struct SCXLNX_DEVICE *pDevice = SCXLNXGetDevice(); + u32 length = nb_blocks * AES_BLOCK_SIZE; + u32 length_loop = 0; + u32 nb_blocksLoop = 0; + struct tf_device *dev = tf_get_device(); dprintk(KERN_INFO - "PDrvCryptoUpdateAESWithDMA: In=0x%08x, Out=0x%08x, Len=%u\n", - (unsigned int)pSrc, - (unsigned int)pDest, - (unsigned int)nLength); + "%s: In=0x%08x, Out=0x%08x, Len=%u\n", + __func__, + (unsigned int)src, + (unsigned int)dest, + (unsigned int)length); /*lock the DMA */ - mutex_lock(&pDevice->sm.sDMALock); + while (!mutex_trylock(&dev->sm.dma_mutex)) + cpu_relax(); - if (scxPublicDMARequest(&dma_ch0) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { - mutex_unlock(&pDevice->sm.sDMALock); - return; + if (tf_dma_request(&dma_ch0) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { + mutex_unlock(&dev->sm.dma_mutex); + return false; } - if (scxPublicDMARequest(&dma_ch1) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { - scxPublicDMARelease(dma_ch0); - mutex_unlock(&pDevice->sm.sDMALock); - return; + if (tf_dma_request(&dma_ch1) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { + omap_free_dma(dma_ch0); + mutex_unlock(&dev->sm.dma_mutex); + return false; } - while (nLength > 0) { + while (length > 0) { /* * At this time, we are sure that the DMAchannels @@ -414,52 +442,61 @@ static void PDrvCryptoUpdateAESWithDMA(u8 *pSrc, u8 *pDest, u32 nbBlocks) */ /*DMA used for Input and Output */ - OUTREG32(&pAESReg_t->AES_SYSCONFIG, - INREG32(&pAESReg_t->AES_SYSCONFIG) + OUTREG32(&paes_reg->AES_SYSCONFIG, + INREG32(&paes_reg->AES_SYSCONFIG) | AES_SYSCONFIG_DMA_REQ_OUT_EN_BIT | AES_SYSCONFIG_DMA_REQ_IN_EN_BIT); /*check length */ - if (nLength <= pDevice->nDMABufferLength) - nLengthLoop = nLength; + if (length <= dev->dma_buffer_length) + length_loop = length; else - nLengthLoop = pDevice->nDMABufferLength; + length_loop = dev->dma_buffer_length; /*The length is always a multiple of the block size */ - nbBlocksLoop = nLengthLoop / AES_BLOCK_SIZE; + nb_blocksLoop = length_loop / AES_BLOCK_SIZE; /* - *Copy the data from the input buffer into a preallocated - *buffer which is aligned on the beginning of a page. - *This may prevent potential issues when flushing/invalidating - *the buffer as the cache lines are 64 bytes long. + * Copy the data from the user input buffer into a preallocated + * buffer which has correct properties from efficient DMA + * transfers. */ - memcpy(pDevice->pDMABuffer, pSrc, nLengthLoop); + if (!is_kernel) { + if (copy_from_user( + dev->dma_buffer, src, length_loop)) { + omap_free_dma(dma_ch0); + omap_free_dma(dma_ch1); + mutex_unlock(&dev->sm.dma_mutex); + return false; + } + } else { + memcpy(dev->dma_buffer, src, length_loop); + } /*DMA1: Mem -> AES */ - scxPublicSetDMAChannelCommonParams(&ch0_parameters, - nbBlocksLoop, + tf_dma_set_channel_common_params(&ch0_parameters, + nb_blocksLoop, DMA_CEN_Elts_per_Frame_AES, AES1_REGS_HW_ADDR + 0x60, - (u32)pDevice->pDMABufferPhys, + (u32)dev->dma_buffer_phys, OMAP44XX_DMA_AES1_P_DATA_IN_REQ); ch0_parameters.src_amode = OMAP_DMA_AMODE_POST_INC; ch0_parameters.dst_amode = OMAP_DMA_AMODE_CONSTANT; ch0_parameters.src_or_dst_synch = OMAP_DMA_DST_SYNC; - dprintk(KERN_INFO "PDrvCryptoUpdateAESWithDMA: \ - scxPublicDMASetParams(ch0)\n"); - scxPublicDMASetParams(dma_ch0, &ch0_parameters); + dprintk(KERN_INFO "%s: omap_set_dma_params(ch0)\n", __func__); + omap_set_dma_params(dma_ch0, &ch0_parameters); - omap_set_dma_src_burst_mode(dma_ch0, OMAP_DMA_DATA_BURST_16); - omap_set_dma_dest_burst_mode(dma_ch0, OMAP_DMA_DATA_BURST_16); + omap_set_dma_src_burst_mode(dma_ch0, OMAP_DMA_DATA_BURST_8); + omap_set_dma_dest_burst_mode(dma_ch0, OMAP_DMA_DATA_BURST_8); + omap_set_dma_src_data_pack(dma_ch0, 1); /*DMA2: AES -> Mem */ - scxPublicSetDMAChannelCommonParams(&ch1_parameters, - nbBlocksLoop, + tf_dma_set_channel_common_params(&ch1_parameters, + nb_blocksLoop, DMA_CEN_Elts_per_Frame_AES, - (u32)pDevice->pDMABufferPhys, + (u32)dev->dma_buffer_phys, AES1_REGS_HW_ADDR + 0x60, OMAP44XX_DMA_AES1_P_DATA_OUT_REQ); @@ -467,36 +504,36 @@ static void PDrvCryptoUpdateAESWithDMA(u8 *pSrc, u8 *pDest, u32 nbBlocks) ch1_parameters.dst_amode = OMAP_DMA_AMODE_POST_INC; ch1_parameters.src_or_dst_synch = OMAP_DMA_SRC_SYNC; - dprintk(KERN_INFO "PDrvCryptoUpdateAESWithDMA: \ - scxPublicDMASetParams(ch1)\n"); - scxPublicDMASetParams(dma_ch1, &ch1_parameters); + dprintk(KERN_INFO "%s: omap_set_dma_params(ch1)\n", __func__); + omap_set_dma_params(dma_ch1, &ch1_parameters); - omap_set_dma_src_burst_mode(dma_ch1, OMAP_DMA_DATA_BURST_16); - omap_set_dma_dest_burst_mode(dma_ch1, OMAP_DMA_DATA_BURST_16); + omap_set_dma_src_burst_mode(dma_ch1, OMAP_DMA_DATA_BURST_8); + omap_set_dma_dest_burst_mode(dma_ch1, OMAP_DMA_DATA_BURST_8); + omap_set_dma_dest_data_pack(dma_ch1, 1); wmb(); dprintk(KERN_INFO - "PDrvCryptoUpdateAESWithDMA: Start DMA channel %d\n", - (unsigned int)dma_ch1); - scxPublicDMAStart(dma_ch1, OMAP_DMA_BLOCK_IRQ); + "%s: Start DMA channel %d\n", + __func__, (unsigned int)dma_ch1); + tf_dma_start(dma_ch1, OMAP_DMA_BLOCK_IRQ); dprintk(KERN_INFO - "PDrvCryptoUpdateAESWithDMA: Start DMA channel %d\n", - (unsigned int)dma_ch0); - scxPublicDMAStart(dma_ch0, OMAP_DMA_BLOCK_IRQ); + "%s: Start DMA channel %d\n", + __func__, (unsigned int)dma_ch0); + tf_dma_start(dma_ch0, OMAP_DMA_BLOCK_IRQ); dprintk(KERN_INFO - "PDrvCryptoUpdateAESWithDMA: Waiting for IRQ\n"); - scxPublicDMAWait(2); + "%s: Waiting for IRQ\n", __func__); + tf_dma_wait(2); /*Unset DMA synchronisation requests */ - OUTREG32(&pAESReg_t->AES_SYSCONFIG, - INREG32(&pAESReg_t->AES_SYSCONFIG) + OUTREG32(&paes_reg->AES_SYSCONFIG, + INREG32(&paes_reg->AES_SYSCONFIG) & (~AES_SYSCONFIG_DMA_REQ_OUT_EN_BIT) & (~AES_SYSCONFIG_DMA_REQ_IN_EN_BIT)); - scxPublicDMAClearChannel(dma_ch0); - scxPublicDMAClearChannel(dma_ch1); + omap_clear_dma(dma_ch0); + omap_clear_dma(dma_ch1); /* *The dma transfer is complete @@ -504,23 +541,35 @@ static void PDrvCryptoUpdateAESWithDMA(u8 *pSrc, u8 *pDest, u32 nbBlocks) /*The DMA output is in the preallocated aligned buffer *and needs to be copied to the output buffer.*/ - memcpy(pDest, pDevice->pDMABuffer, nLengthLoop); + if (!is_kernel) { + if (copy_to_user( + dest, dev->dma_buffer, length_loop)) { + omap_free_dma(dma_ch0); + omap_free_dma(dma_ch1); + mutex_unlock(&dev->sm.dma_mutex); + return false; + } + } else { + memcpy(dest, dev->dma_buffer, length_loop); + } - pSrc += nLengthLoop; - pDest += nLengthLoop; - nLength -= nLengthLoop; + src += length_loop; + dest += length_loop; + length -= length_loop; } /*For safety reasons, let's clean the working buffer */ - memset(pDevice->pDMABuffer, 0, nLengthLoop); + memset(dev->dma_buffer, 0, length_loop); /*release the DMA */ - scxPublicDMARelease(dma_ch0); - scxPublicDMARelease(dma_ch1); + omap_free_dma(dma_ch0); + omap_free_dma(dma_ch1); + + mutex_unlock(&dev->sm.dma_mutex); - mutex_unlock(&pDevice->sm.sDMALock); + dprintk(KERN_INFO "%s: Success\n", __func__); - dprintk(KERN_INFO "PDrvCryptoUpdateAESWithDMA: Success\n"); + return true; } #ifdef CONFIG_SMC_KERNEL_CRYPTO @@ -579,7 +628,7 @@ static int aes_dma_start(struct aes_hwa_ctx *ctx) size_t count; dma_addr_t addr_in, addr_out; struct omap_dma_channel_params dma_params; - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = + struct tf_crypto_aes_operation_state *state = crypto_ablkcipher_ctx(crypto_ablkcipher_reqtfm(ctx->req)); if (sg_is_last(ctx->in_sg) && sg_is_last(ctx->out_sg)) { @@ -622,14 +671,14 @@ static int aes_dma_start(struct aes_hwa_ctx *ctx) ctx->total -= count; - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_AES1, LOCK_HWA); + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_AES1, LOCK_HWA); /* Configure HWA */ - SCXPublicCryptoEnableClock(PUBLIC_CRYPTO_AES1_CLOCK_REG); + tf_crypto_enable_clock(PUBLIC_CRYPTO_AES1_CLOCK_REG); - PDrvCryptoRestoreAESRegisters(state); + tf_aes_restore_registers(state); - OUTREG32(&pAESReg_t->AES_SYSCONFIG, INREG32(&pAESReg_t->AES_SYSCONFIG) + OUTREG32(&paes_reg->AES_SYSCONFIG, INREG32(&paes_reg->AES_SYSCONFIG) | AES_SYSCONFIG_DMA_REQ_OUT_EN_BIT | AES_SYSCONFIG_DMA_REQ_IN_EN_BIT); @@ -657,8 +706,9 @@ static int aes_dma_start(struct aes_hwa_ctx *ctx) omap_set_dma_params(ctx->dma_lch_in, &dma_params); - omap_set_dma_dest_burst_mode(ctx->dma_lch_in, OMAP_DMA_DATA_BURST_16); - omap_set_dma_src_burst_mode(ctx->dma_lch_in, OMAP_DMA_DATA_BURST_16); + omap_set_dma_dest_burst_mode(ctx->dma_lch_in, OMAP_DMA_DATA_BURST_8); + omap_set_dma_src_burst_mode(ctx->dma_lch_in, OMAP_DMA_DATA_BURST_8); + omap_set_dma_src_data_pack(ctx->dma_lch_in, 1); /* OUT */ dma_params.trigger = ctx->dma_out; @@ -670,8 +720,9 @@ static int aes_dma_start(struct aes_hwa_ctx *ctx) omap_set_dma_params(ctx->dma_lch_out, &dma_params); - omap_set_dma_dest_burst_mode(ctx->dma_lch_out, OMAP_DMA_DATA_BURST_16); - omap_set_dma_src_burst_mode(ctx->dma_lch_out, OMAP_DMA_DATA_BURST_16); + omap_set_dma_dest_burst_mode(ctx->dma_lch_out, OMAP_DMA_DATA_BURST_8); + omap_set_dma_src_burst_mode(ctx->dma_lch_out, OMAP_DMA_DATA_BURST_8); + omap_set_dma_dest_data_pack(ctx->dma_lch_out, 1); /* Is this really needed? */ omap_disable_dma_irq(ctx->dma_lch_in, OMAP_DMA_DROP_IRQ); @@ -689,14 +740,14 @@ static int aes_dma_start(struct aes_hwa_ctx *ctx) static int aes_dma_stop(struct aes_hwa_ctx *ctx) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = + struct tf_crypto_aes_operation_state *state = crypto_ablkcipher_ctx(crypto_ablkcipher_reqtfm(ctx->req)); int err = 0; size_t count; dprintk(KERN_INFO "aes_dma_stop(%p)\n", ctx); - PDrvCryptoSaveAESRegisters(state); + tf_aes_save_registers(state); if (!AES_CTRL_IS_MODE_ECB(state->CTRL)) { u32 *ptr = (u32 *) ctx->req->info; @@ -707,11 +758,11 @@ static int aes_dma_stop(struct aes_hwa_ctx *ctx) ptr[3] = state->AES_IV_3; } - OUTREG32(&pAESReg_t->AES_SYSCONFIG, 0); + OUTREG32(&paes_reg->AES_SYSCONFIG, 0); - SCXPublicCryptoDisableClock(PUBLIC_CRYPTO_AES1_CLOCK_REG); + tf_crypto_disable_clock(PUBLIC_CRYPTO_AES1_CLOCK_REG); - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_AES1, UNLOCK_HWA); + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_AES1, UNLOCK_HWA); omap_stop_dma(ctx->dma_lch_in); omap_stop_dma(ctx->dma_lch_out); @@ -812,7 +863,7 @@ static void aes_dma_cleanup(struct aes_hwa_ctx *ctx) static int aes_handle_req(struct aes_hwa_ctx *ctx) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state; + struct tf_crypto_aes_operation_state *state; struct crypto_async_request *async_req, *backlog; struct ablkcipher_request *req; unsigned long flags; @@ -866,7 +917,7 @@ static void aes_tasklet(unsigned long data) } /* Generic */ -static int aes_setkey(struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state, +static int aes_setkey(struct tf_crypto_aes_operation_state *state, const u8 *key, unsigned int keylen) { u32 *ptr = (u32 *)key; @@ -908,6 +959,9 @@ static int aes_operate(struct ablkcipher_request *req) unsigned long flags; int err; + /* Make sure AES HWA is accessible */ + tf_delayed_secure_resume(); + spin_lock_irqsave(&aes_ctx->lock, flags); err = ablkcipher_enqueue_request(&aes_ctx->queue, req); spin_unlock_irqrestore(&aes_ctx->lock, flags); @@ -920,7 +974,7 @@ static int aes_operate(struct ablkcipher_request *req) static int aes_encrypt(struct ablkcipher_request *req) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = + struct tf_crypto_aes_operation_state *state = crypto_ablkcipher_ctx(crypto_ablkcipher_reqtfm(req)); state->CTRL |= AES_CTRL_DIRECTION_ENCRYPT; @@ -930,7 +984,7 @@ static int aes_encrypt(struct ablkcipher_request *req) static int aes_decrypt(struct ablkcipher_request *req) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = + struct tf_crypto_aes_operation_state *state = crypto_ablkcipher_ctx(crypto_ablkcipher_reqtfm(req)); state->CTRL &= ~(AES_CTRL_DIRECTION_ENCRYPT); @@ -939,53 +993,110 @@ static int aes_decrypt(struct ablkcipher_request *req) return aes_operate(req); } -static int aes_single_setkey(struct crypto_tfm *tfm, const u8 *key, - unsigned int keylen) +static int aes_sync_operate(struct blkcipher_desc *desc, + struct scatterlist *dst, struct scatterlist *src, + unsigned int nbytes) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = crypto_tfm_ctx(tfm); + struct crypto_tfm *tfm = crypto_blkcipher_tfm(desc->tfm); + struct tf_crypto_aes_operation_state *state = crypto_tfm_ctx(tfm); + struct blkcipher_walk walk; + int err; - state->CTRL = AES_CTRL_MODE_ECB_BIT; + if (nbytes % AES_BLOCK_SIZE) + return -EINVAL; - return aes_setkey(state, key, keylen); + /* Make sure AES HWA is accessible */ + tf_delayed_secure_resume(); + + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_AES1, LOCK_HWA); + tf_crypto_enable_clock(PUBLIC_CRYPTO_AES1_CLOCK_REG); + + blkcipher_walk_init(&walk, dst, src, nbytes); + err = blkcipher_walk_virt(desc, &walk); + + if (!AES_CTRL_IS_MODE_ECB(state->CTRL)) { + u32 *ptr = (u32 *) walk.iv; + + state->AES_IV_0 = ptr[0]; + state->AES_IV_1 = ptr[1]; + state->AES_IV_2 = ptr[2]; + state->AES_IV_3 = ptr[3]; + } + + while ((nbytes = walk.nbytes)) { + if (!tf_aes_update(state, walk.src.virt.addr, + walk.dst.virt.addr, nbytes / AES_BLOCK_SIZE)) { + err = -EINVAL; + break; + } + + /* tf_aes_update processes all the data */ + nbytes = 0; + + err = blkcipher_walk_done(desc, &walk, nbytes); + } + + if (!AES_CTRL_IS_MODE_ECB(state->CTRL)) { + u32 *ptr = (u32 *) walk.iv; + + ptr[0] = state->AES_IV_0; + ptr[1] = state->AES_IV_1; + ptr[2] = state->AES_IV_2; + ptr[3] = state->AES_IV_3; + } + + tf_crypto_disable_clock(PUBLIC_CRYPTO_AES1_CLOCK_REG); + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_AES1, UNLOCK_HWA); + + return err; } -static void aes_single_encrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in) +static int aes_sync_encrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, struct scatterlist *src, + unsigned int nbytes) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = crypto_tfm_ctx(tfm); + struct crypto_tfm *tfm = crypto_blkcipher_tfm(desc->tfm); + struct tf_crypto_aes_operation_state *state = crypto_tfm_ctx(tfm); state->CTRL |= AES_CTRL_DIRECTION_ENCRYPT; - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_AES1, LOCK_HWA); + dprintk(KERN_INFO "aes_sync_encrypt nbytes=0x%x\n", nbytes); - SCXPublicCryptoEnableClock(PUBLIC_CRYPTO_AES1_CLOCK_REG); - PDrvCryptoUpdateAES(state, (u8 *) in, out, 1); - SCXPublicCryptoDisableClock(PUBLIC_CRYPTO_AES1_CLOCK_REG); - - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_AES1, UNLOCK_HWA); + return aes_sync_operate(desc, dst, src, nbytes); } -static void aes_single_decrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in) +static int aes_sync_decrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, struct scatterlist *src, + unsigned int nbytes) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = - crypto_tfm_ctx(tfm); + struct crypto_tfm *tfm = crypto_blkcipher_tfm(desc->tfm); + struct tf_crypto_aes_operation_state *state = crypto_tfm_ctx(tfm); state->CTRL &= ~(AES_CTRL_DIRECTION_ENCRYPT); state->CTRL |= AES_CTRL_DIRECTION_DECRYPT; - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_AES1, LOCK_HWA); - - SCXPublicCryptoEnableClock(PUBLIC_CRYPTO_AES1_CLOCK_REG); - PDrvCryptoUpdateAES(state, (u8 *) in, out, 1); - SCXPublicCryptoDisableClock(PUBLIC_CRYPTO_AES1_CLOCK_REG); + dprintk(KERN_INFO "aes_sync_decrypt\n"); - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_AES1, UNLOCK_HWA); + return aes_sync_operate(desc, dst, src, nbytes); } /* AES ECB */ +static int aes_ecb_sync_setkey(struct crypto_tfm *tfm, const u8 *key, + unsigned int keylen) +{ + struct tf_crypto_aes_operation_state *state = crypto_tfm_ctx(tfm); + + state->CTRL = AES_CTRL_MODE_ECB_BIT; + + dprintk(KERN_INFO "aes_ecb_sync_setkey\n"); + + return aes_setkey(state, key, keylen); +} + static int aes_ecb_setkey(struct crypto_ablkcipher *tfm, const u8 *key, unsigned int keylen) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = + struct tf_crypto_aes_operation_state *state = crypto_ablkcipher_ctx(tfm); state->CTRL = AES_CTRL_MODE_ECB_BIT; @@ -994,10 +1105,22 @@ static int aes_ecb_setkey(struct crypto_ablkcipher *tfm, const u8 *key, } /* AES CBC */ +static int aes_cbc_sync_setkey(struct crypto_tfm *tfm, const u8 *key, + unsigned int keylen) +{ + struct tf_crypto_aes_operation_state *state = crypto_tfm_ctx(tfm); + + state->CTRL = AES_CTRL_MODE_CBC_BIT; + + dprintk(KERN_INFO "aes_cbc_sync_setkey\n"); + + return aes_setkey(state, key, keylen); +} + static int aes_cbc_setkey(struct crypto_ablkcipher *tfm, const u8 *key, unsigned int keylen) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = + struct tf_crypto_aes_operation_state *state = crypto_ablkcipher_ctx(tfm); state->CTRL = AES_CTRL_MODE_CBC_BIT; @@ -1006,10 +1129,22 @@ static int aes_cbc_setkey(struct crypto_ablkcipher *tfm, const u8 *key, } /* AES CTR */ +static int aes_ctr_sync_setkey(struct crypto_tfm *tfm, const u8 *key, + unsigned int keylen) +{ + struct tf_crypto_aes_operation_state *state = crypto_tfm_ctx(tfm); + + state->CTRL = AES_CTRL_MODE_CTR_BIT; + + dprintk(KERN_INFO "aes_cbc_sync_setkey\n"); + + return aes_setkey(state, key, keylen); +} + static int aes_ctr_setkey(struct crypto_ablkcipher *tfm, const u8 *key, unsigned int keylen) { - struct PUBLIC_CRYPTO_AES_OPERATION_STATE *state = + struct tf_crypto_aes_operation_state *state = crypto_ablkcipher_ctx(tfm); /* Always defaults to 128-bit counter */ @@ -1018,24 +1153,73 @@ static int aes_ctr_setkey(struct crypto_ablkcipher *tfm, const u8 *key, return aes_setkey(state, key, keylen); } -static struct crypto_alg smc_aes_alg = { - .cra_flags = CRYPTO_ALG_TYPE_CIPHER, +static struct crypto_alg smc_aes_ecb_sync_alg = { + .cra_flags = CRYPTO_ALG_TYPE_BLKCIPHER, .cra_priority = 999, - .cra_name = "aes", - .cra_driver_name = "aes-smc", + .cra_name = "ecb(aes)", + .cra_driver_name = "aes-ecb-smc", + .cra_type = &crypto_blkcipher_type, .cra_module = THIS_MODULE, .cra_blocksize = AES_BLOCK_SIZE, .cra_ctxsize = - sizeof(struct PUBLIC_CRYPTO_AES_OPERATION_STATE), + sizeof(struct tf_crypto_aes_operation_state), .cra_alignmask = 3, - .cra_list = LIST_HEAD_INIT(smc_aes_alg.cra_list), + .cra_list = LIST_HEAD_INIT(smc_aes_ecb_sync_alg.cra_list), .cra_u = { - .cipher = { - .cia_min_keysize = AES_MIN_KEY_SIZE, - .cia_max_keysize = AES_MAX_KEY_SIZE, - .cia_setkey = aes_single_setkey, - .cia_encrypt = aes_single_encrypt, - .cia_decrypt = aes_single_decrypt, + .blkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = aes_ecb_sync_setkey, + .encrypt = aes_sync_encrypt, + .decrypt = aes_sync_decrypt, + } + }, +}; + +static struct crypto_alg smc_aes_cbc_sync_alg = { + .cra_flags = CRYPTO_ALG_TYPE_BLKCIPHER, + .cra_priority = 999, + .cra_name = "cbc(aes)", + .cra_driver_name = "aes-cbc-smc", + .cra_type = &crypto_blkcipher_type, + .cra_module = THIS_MODULE, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = + sizeof(struct tf_crypto_aes_operation_state), + .cra_alignmask = 3, + .cra_list = LIST_HEAD_INIT(smc_aes_cbc_sync_alg.cra_list), + .cra_u = { + .blkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .ivsize = PUBLIC_CRYPTO_IV_MAX_SIZE, + .setkey = aes_cbc_sync_setkey, + .encrypt = aes_sync_encrypt, + .decrypt = aes_sync_decrypt, + } + }, +}; + +static struct crypto_alg smc_aes_ctr_sync_alg = { + .cra_flags = CRYPTO_ALG_TYPE_BLKCIPHER, + .cra_priority = 999, + .cra_name = "ctr(aes)", + .cra_driver_name = "aes-ctr-smc", + .cra_type = &crypto_blkcipher_type, + .cra_module = THIS_MODULE, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = + sizeof(struct tf_crypto_aes_operation_state), + .cra_alignmask = 3, + .cra_list = LIST_HEAD_INIT(smc_aes_ctr_sync_alg.cra_list), + .cra_u = { + .blkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .ivsize = PUBLIC_CRYPTO_IV_MAX_SIZE, + .setkey = aes_ctr_sync_setkey, + .encrypt = aes_sync_encrypt, + .decrypt = aes_sync_decrypt, } }, }; @@ -1049,7 +1233,7 @@ static struct crypto_alg smc_aes_ecb_alg = { .cra_module = THIS_MODULE, .cra_blocksize = AES_BLOCK_SIZE, .cra_ctxsize = - sizeof(struct PUBLIC_CRYPTO_AES_OPERATION_STATE), + sizeof(struct tf_crypto_aes_operation_state), .cra_alignmask = 3, .cra_list = LIST_HEAD_INIT(smc_aes_ecb_alg.cra_list), .cra_u = { @@ -1072,7 +1256,7 @@ static struct crypto_alg smc_aes_cbc_alg = { .cra_type = &crypto_ablkcipher_type, .cra_blocksize = AES_BLOCK_SIZE, .cra_ctxsize = - sizeof(struct PUBLIC_CRYPTO_AES_OPERATION_STATE), + sizeof(struct tf_crypto_aes_operation_state), .cra_alignmask = 3, .cra_list = LIST_HEAD_INIT(smc_aes_cbc_alg.cra_list), .cra_u = { @@ -1096,7 +1280,7 @@ static struct crypto_alg smc_aes_ctr_alg = { .cra_type = &crypto_ablkcipher_type, .cra_blocksize = AES_BLOCK_SIZE, .cra_ctxsize = - sizeof(struct PUBLIC_CRYPTO_AES_OPERATION_STATE), + sizeof(struct tf_crypto_aes_operation_state), .cra_alignmask = 3, .cra_list = LIST_HEAD_INIT(smc_aes_ctr_alg.cra_list), .cra_u = { @@ -1122,7 +1306,7 @@ int register_smc_public_crypto_aes(void) crypto_init_queue(&aes_ctx->queue, 1); tasklet_init(&aes_ctx->task, aes_tasklet, (unsigned long)aes_ctx); - spin_lock_init(&aes_ctx->lock); + spin_lock_init(&aes_ctx->lock); aes_ctx->dma_in = OMAP44XX_DMA_AES1_P_DATA_IN_REQ; aes_ctx->dma_out = OMAP44XX_DMA_AES1_P_DATA_OUT_REQ; @@ -1131,9 +1315,17 @@ int register_smc_public_crypto_aes(void) if (ret) goto err_dma; - ret = crypto_register_alg(&smc_aes_alg); + ret = crypto_register_alg(&smc_aes_ecb_sync_alg); if (ret) - goto err_dma; + goto err_ecb_sync; + + ret = crypto_register_alg(&smc_aes_cbc_sync_alg); + if (ret) + goto err_cbc_sync; + + ret = crypto_register_alg(&smc_aes_ctr_sync_alg); + if (ret) + goto err_ctr_sync; ret = crypto_register_alg(&smc_aes_ecb_alg); if (ret) @@ -1154,7 +1346,13 @@ err_ctr: err_cbc: crypto_unregister_alg(&smc_aes_ecb_alg); err_ecb: - crypto_unregister_alg(&smc_aes_alg); + crypto_unregister_alg(&smc_aes_ctr_sync_alg); +err_ctr_sync: + crypto_unregister_alg(&smc_aes_cbc_sync_alg); +err_cbc_sync: + crypto_unregister_alg(&smc_aes_ecb_sync_alg); +err_ecb_sync: + aes_dma_cleanup(aes_ctx); err_dma: tasklet_kill(&aes_ctx->task); kfree(aes_ctx); @@ -1166,15 +1364,17 @@ void unregister_smc_public_crypto_aes(void) if (aes_ctx == NULL) return; - crypto_unregister_alg(&smc_aes_alg); + crypto_unregister_alg(&smc_aes_ecb_sync_alg); + crypto_unregister_alg(&smc_aes_cbc_sync_alg); + crypto_unregister_alg(&smc_aes_ctr_sync_alg); + crypto_unregister_alg(&smc_aes_ecb_alg); crypto_unregister_alg(&smc_aes_cbc_alg); crypto_unregister_alg(&smc_aes_ctr_alg); - tasklet_kill(&aes_ctx->task); - aes_dma_cleanup(aes_ctx); + tasklet_kill(&aes_ctx->task); kfree(aes_ctx); } #endif diff --git a/security/smc/tf_crypto_des.c b/security/smc/tf_crypto_des.c new file mode 100644 index 0000000..716a60f --- /dev/null +++ b/security/smc/tf_crypto_des.c @@ -0,0 +1,404 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_crypto.h" +#include "tf_dma.h" + +#include <linux/io.h> +#include <mach/io.h> + +/* + * DES Hardware Accelerator: Base address + */ +#define DES_REGS_HW_ADDR 0x480A5000 + +/* + * CTRL register Masks + */ +#define DES_CTRL_OUTPUT_READY_BIT (1<<0) +#define DES_CTRL_INPUT_READY_BIT (1<<1) + +#define DES_CTRL_GET_DIRECTION(x) (x&4) +#define DES_CTRL_DIRECTION_DECRYPT 0 +#define DES_CTRL_DIRECTION_ENCRYPT (1<<2) + +#define DES_CTRL_GET_TDES(x) (x&8) +#define DES_CTRL_TDES_DES 0 +#define DES_CTRL_TDES_TRIPLE_DES (1<<3) + +#define DES_CTRL_GET_MODE(x) (x&0x10) +#define DES_CTRL_MODE_ECB 0 +#define DES_CTRL_MODE_CBC (1<<4) + +/* + * SYSCONFIG register masks + */ +#define DES_SYSCONFIG_DMA_REQ_IN_EN_BIT (1<<5) +#define DES_SYSCONFIG_DMA_REQ_OUT_EN_BIT (1<<6) + +/*------------------------------------------------------------------------*/ +/* DES/DES3 Context */ +/*------------------------------------------------------------------------*/ +/** + * This structure contains the registers of the DES HW accelerator. + */ +struct des3_des_reg { + u32 DES_KEY3_L; /* DES Key 3 Low Register */ + u32 DES_KEY3_H; /* DES Key 3 High Register */ + u32 DES_KEY2_L; /* DES Key 2 Low Register */ + u32 DES_KEY2_H; /* DES Key 2 High Register */ + u32 DES_KEY1_L; /* DES Key 1 Low Register */ + u32 DES_KEY1_H; /* DES Key 1 High Register */ + u32 DES_IV_L; /* DES Initialization Vector Low Reg */ + u32 DES_IV_H; /* DES Initialization Vector High Reg */ + u32 DES_CTRL; /* DES Control Register */ + u32 DES_LENGTH; /* DES Length Register */ + u32 DES_DATA_L; /* DES Data Input/Output Low Register */ + u32 DES_DATA_H; /* DES Data Input/Output High Register */ + u32 DES_REV; /* DES Revision Register */ + u32 DES_SYSCONFIG; /* DES Mask and Reset Register */ + u32 DES_SYSSTATUS; /* DES System Status Register */ +}; + +static struct des3_des_reg *des_reg; + +/*------------------------------------------------------------------------ + *Forward declarations + *------------------------------------------------------------------------ */ + +static bool tf_des_update_dma(u8 *src, u8 *dest, u32 nb_blocks); + +/*------------------------------------------------------------------------- + *Save HWA registers into the specified operation state structure + *-------------------------------------------------------------------------*/ +static void tf_des_save_registers(u32 DES_CTRL, + struct tf_crypto_des_operation_state *des_state) +{ + dprintk(KERN_INFO + "tf_des_save_registers in des_state=%p CTRL=0x%08x\n", + des_state, DES_CTRL); + + /*Save the IV if we are in CBC mode */ + if (DES_CTRL_GET_MODE(DES_CTRL) == DES_CTRL_MODE_CBC) { + des_state->DES_IV_L = INREG32(&des_reg->DES_IV_L); + des_state->DES_IV_H = INREG32(&des_reg->DES_IV_H); + } +} + +/*------------------------------------------------------------------------- + *Restore the HWA registers from the operation state structure + *-------------------------------------------------------------------------*/ +static void tf_des_restore_registers(u32 DES_CTRL, + struct tf_crypto_des_operation_state *des_state) +{ + dprintk(KERN_INFO "tf_des_restore_registers from " + "des_state=%p CTRL=0x%08x\n", + des_state, DES_CTRL); + + /*Write the IV ctx->reg */ + if (DES_CTRL_GET_MODE(DES_CTRL) == DES_CTRL_MODE_CBC) { + OUTREG32(&des_reg->DES_IV_L, des_state->DES_IV_L); + OUTREG32(&des_reg->DES_IV_H, des_state->DES_IV_H); + } + + /*Set the DIRECTION and CBC bits in the CTRL register. + *Keep the TDES from the accelerator */ + OUTREG32(&des_reg->DES_CTRL, + (INREG32(&des_reg->DES_CTRL) & (1 << 3)) | + (DES_CTRL & ((1 << 2) | (1 << 4)))); + + /*Set the SYSCONFIG register to 0 */ + OUTREG32(&des_reg->DES_SYSCONFIG, 0); +} + +/*------------------------------------------------------------------------- */ + +void tf_des_init(void) +{ + des_reg = omap_ioremap(DES_REGS_HW_ADDR, SZ_1M, MT_DEVICE); + if (des_reg == NULL) + panic("Unable to remap DES/3DES module"); +} + +void tf_des_exit(void) +{ + omap_iounmap(des_reg); +} + +bool tf_des_update(u32 DES_CTRL, + struct tf_crypto_des_operation_state *des_state, + u8 *src, u8 *dest, u32 nb_blocks) +{ + u32 nbr_of_blocks; + u32 temp; + u8 *process_src; + u8 *process_dest; + u32 dma_use = PUBLIC_CRYPTO_DMA_USE_NONE; + + /* + *Choice of the processing type + */ + if (nb_blocks * DES_BLOCK_SIZE >= DMA_TRIGGER_IRQ_DES) + dma_use = PUBLIC_CRYPTO_DMA_USE_IRQ; + + dprintk(KERN_INFO "tf_des_update: " + "src=0x%08x, dest=0x%08x, nb_blocks=0x%08x, dma_use=0x%08x\n", + (unsigned int)src, (unsigned int)dest, + (unsigned int)nb_blocks, (unsigned int)dma_use); + + if (nb_blocks == 0) { + dprintk(KERN_INFO "tf_des_update: Nothing to process\n"); + return true; + } + + if (DES_CTRL_GET_DIRECTION(INREG32(&des_reg->DES_CTRL)) != + DES_CTRL_GET_DIRECTION(DES_CTRL)) { + dprintk(KERN_WARNING "HWA configured for another direction\n"); + return false; + } + + /*Restore the registers of the accelerator from the operation state */ + tf_des_restore_registers(DES_CTRL, des_state); + + OUTREG32(&des_reg->DES_LENGTH, nb_blocks * DES_BLOCK_SIZE); + + if (dma_use == PUBLIC_CRYPTO_DMA_USE_IRQ) { + + /*perform the update with DMA */ + if (!tf_des_update_dma(src, dest, nb_blocks)) + return false; + + } else { + u8 buf[DMA_TRIGGER_IRQ_DES]; + + process_src = process_dest = buf; + + if (copy_from_user(buf, src, nb_blocks * DES_BLOCK_SIZE)) + return false; + + for (nbr_of_blocks = 0; + nbr_of_blocks < nb_blocks; nbr_of_blocks++) { + + /*We wait for the input ready */ + /*Crash the system as this should never occur */ + if (tf_crypto_wait_for_ready_bit( + (u32 *)&des_reg->DES_CTRL, + DES_CTRL_INPUT_READY_BIT) != + PUBLIC_CRYPTO_OPERATION_SUCCESS) { + panic("Wait too long for DES HW " + "accelerator Input data to be ready\n"); + } + + /*We copy the 8 bytes of data src->reg */ + temp = (u32) BYTES_TO_LONG(process_src); + OUTREG32(&des_reg->DES_DATA_L, temp); + process_src += 4; + temp = (u32) BYTES_TO_LONG(process_src); + OUTREG32(&des_reg->DES_DATA_H, temp); + process_src += 4; + + /*We wait for the output ready */ + tf_crypto_wait_for_ready_bit_infinitely( + (u32 *)&des_reg->DES_CTRL, + DES_CTRL_OUTPUT_READY_BIT); + + /*We copy the 8 bytes of data reg->dest */ + temp = INREG32(&des_reg->DES_DATA_L); + LONG_TO_BYTE(process_dest, temp); + process_dest += 4; + temp = INREG32(&des_reg->DES_DATA_H); + LONG_TO_BYTE(process_dest, temp); + process_dest += 4; + } + + if (copy_to_user(dest, buf, nb_blocks * DES_BLOCK_SIZE)) + return false; + } + + /*Save the accelerator registers into the operation state */ + tf_des_save_registers(DES_CTRL, des_state); + + dprintk(KERN_INFO "tf_des_update: Done\n"); + return true; +} + +/*------------------------------------------------------------------------- */ +/* + *Static function, perform DES encryption/decryption using the DMA for data + *transfer. + * + *inputs: src : pointer of the input data to process + * nb_blocks : number of block to process + * dma_use : PUBLIC_CRYPTO_DMA_USE_IRQ (use irq to monitor end of DMA) + *output: dest : pointer of the output data (can be eq to src) + */ +static bool tf_des_update_dma(u8 *src, u8 *dest, u32 nb_blocks) +{ + /* + *Note: The DMA only sees physical addresses ! + */ + + int dma_ch0; + int dma_ch1; + struct omap_dma_channel_params ch0_parameters; + struct omap_dma_channel_params ch1_parameters; + u32 length = nb_blocks * DES_BLOCK_SIZE; + u32 length_loop = 0; + u32 nb_blocksLoop = 0; + struct tf_device *dev = tf_get_device(); + + dprintk(KERN_INFO + "tf_des_update_dma: In=0x%08x, Out=0x%08x, Len=%u\n", + (unsigned int)src, (unsigned int)dest, + (unsigned int)length); + + /*lock the DMA */ + mutex_lock(&dev->sm.dma_mutex); + + if (tf_dma_request(&dma_ch0) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { + mutex_unlock(&dev->sm.dma_mutex); + return false; + } + if (tf_dma_request(&dma_ch1) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { + omap_free_dma(dma_ch0); + mutex_unlock(&dev->sm.dma_mutex); + return false; + } + + while (length > 0) { + + /* + * At this time, we are sure that the DMAchannels are available + * and not used by other public crypto operation + */ + + /*DMA used for Input and Output */ + OUTREG32(&des_reg->DES_SYSCONFIG, + INREG32(&des_reg->DES_SYSCONFIG) + | DES_SYSCONFIG_DMA_REQ_OUT_EN_BIT + | DES_SYSCONFIG_DMA_REQ_IN_EN_BIT); + + /* Check length */ + if (length <= dev->dma_buffer_length) + length_loop = length; + else + length_loop = dev->dma_buffer_length; + + /* The length is always a multiple of the block size */ + nb_blocksLoop = length_loop / DES_BLOCK_SIZE; + + /* + * Copy the data from the user input buffer into a preallocated + * buffer which has correct properties from efficient DMA + * transfers. + */ + if (copy_from_user(dev->dma_buffer, src, length_loop)) { + omap_free_dma(dma_ch0); + omap_free_dma(dma_ch1); + mutex_unlock(&dev->sm.dma_mutex); + return false; + } + + /* DMA1: Mem -> DES */ + tf_dma_set_channel_common_params(&ch0_parameters, + nb_blocksLoop, + DMA_CEN_Elts_per_Frame_DES, + DES_REGS_HW_ADDR + 0x28, + dev->dma_buffer_phys, + OMAP44XX_DMA_DES_P_DATA_IN_REQ); + + ch0_parameters.src_amode = OMAP_DMA_AMODE_POST_INC; + ch0_parameters.dst_amode = OMAP_DMA_AMODE_CONSTANT; + ch0_parameters.src_or_dst_synch = OMAP_DMA_DST_SYNC; + + dprintk(KERN_INFO + "tf_des_update_dma: omap_set_dma_params(ch0)\n"); + omap_set_dma_params(dma_ch0, &ch0_parameters); + + /* DMA2: DES -> Mem */ + tf_dma_set_channel_common_params(&ch1_parameters, + nb_blocksLoop, + DMA_CEN_Elts_per_Frame_DES, + dev->dma_buffer_phys, + DES_REGS_HW_ADDR + 0x28, + OMAP44XX_DMA_DES_P_DATA_OUT_REQ); + + ch1_parameters.src_amode = OMAP_DMA_AMODE_CONSTANT; + ch1_parameters.dst_amode = OMAP_DMA_AMODE_POST_INC; + ch1_parameters.src_or_dst_synch = OMAP_DMA_SRC_SYNC; + + dprintk(KERN_INFO "tf_des_update_dma: " + "omap_set_dma_params(ch1)\n"); + omap_set_dma_params(dma_ch1, &ch1_parameters); + + wmb(); + + dprintk(KERN_INFO + "tf_des_update_dma: Start DMA channel %d\n", + (unsigned int)dma_ch0); + tf_dma_start(dma_ch0, OMAP_DMA_BLOCK_IRQ); + + dprintk(KERN_INFO + "tf_des_update_dma: Start DMA channel %d\n", + (unsigned int)dma_ch1); + tf_dma_start(dma_ch1, OMAP_DMA_BLOCK_IRQ); + tf_dma_wait(2); + + /* Unset DMA synchronisation requests */ + OUTREG32(&des_reg->DES_SYSCONFIG, + INREG32(&des_reg->DES_SYSCONFIG) + & (~DES_SYSCONFIG_DMA_REQ_OUT_EN_BIT) + & (~DES_SYSCONFIG_DMA_REQ_IN_EN_BIT)); + + omap_clear_dma(dma_ch0); + omap_clear_dma(dma_ch1); + + /* + * The dma transfer is complete + */ + + /*The DMA output is in the preallocated aligned buffer + *and needs to be copied to the output buffer.*/ + if (copy_to_user(dest, dev->dma_buffer, length_loop)) { + omap_free_dma(dma_ch0); + omap_free_dma(dma_ch1); + mutex_unlock(&dev->sm.dma_mutex); + return false; + } + + src += length_loop; + dest += length_loop; + length -= length_loop; + } + + /* For safety reasons, let's clean the working buffer */ + memset(dev->dma_buffer, 0, length_loop); + + /* Release the DMA */ + omap_free_dma(dma_ch0); + omap_free_dma(dma_ch1); + + mutex_unlock(&dev->sm.dma_mutex); + + dprintk(KERN_INFO "tf_des_update_dma: Success\n"); + + return true; +} diff --git a/security/smc/omap4/scx_public_crypto_Digest.c b/security/smc/tf_crypto_digest.c index 7a40089..d69a97f 100644 --- a/security/smc/omap4/scx_public_crypto_Digest.c +++ b/security/smc/tf_crypto_digest.c @@ -1,5 +1,5 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. +/** + * Copyright (c) 2011 Trusted Logic S.A. * All Rights Reserved. * * This program is free software; you can redistribute it and/or @@ -17,11 +17,11 @@ * MA 02111-1307 USA */ -#include "scxlnx_defs.h" -#include "scxlnx_util.h" -#include "scx_public_crypto.h" -#include "scx_public_dma.h" -#include "scxlnx_mshield.h" +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_crypto.h" +#include "tf_dma.h" +#include "tf_zebra.h" #include <linux/io.h> #include <mach/io.h> @@ -65,7 +65,7 @@ /** * This structure contains the registers of the SHA1/MD5 HW accelerator. */ -struct Sha1Md5Reg_t { +struct sha1_md5_reg { u32 ODIGEST_A; /* 0x00 Outer Digest A */ u32 ODIGEST_B; /* 0x04 Outer Digest B */ u32 ODIGEST_C; /* 0x08 Outer Digest C */ @@ -117,7 +117,7 @@ struct Sha1Md5Reg_t { u32 IRQENABLE; /* 0x11C IRQ Enable */ }; -static struct Sha1Md5Reg_t *pSha1Md5Reg_t; +static struct sha1_md5_reg *sha1_md5_reg; static const u8 md5OverEmptyString[] = { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, @@ -148,111 +148,112 @@ static const u8 sha256OverEmptyString[] = { *Forward declarations *------------------------------------------------------------------------- */ -static void static_Hash_HwPerform64bDigest(u32 *pData, - u32 nAlgo, u32 nBytesProcessed); -static void static_Hash_HwPerformDmaDigest(u8 *pData, u32 nDataLength, - u32 nAlgo, u32 nBytesProcessed); +static void tf_digest_hw_perform_64b(u32 *data, + u32 algo, u32 bytes_processed); +static bool tf_digest_hw_perform_dma(u8 *data, u32 nDataLength, + u32 algo, u32 bytes_processed); -static void PDrvCryptoUpdateHashWithDMA( - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *pSHAState, - u8 *pData, u32 dataLength); +static bool tf_digest_update_dma( + struct tf_crypto_sha_operation_state *sha_state, + u8 *data, u32 data_length); /*------------------------------------------------------------------------- *Save HWA registers into the specified operation state structure *------------------------------------------------------------------------*/ -static void PDrvCryptoSaveHashRegisters( - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *pSHAState) +static void tf_digest_save_registers( + struct tf_crypto_sha_operation_state *sha_state) { - dprintk(KERN_INFO "PDrvCryptoSaveHashRegisters: State=%p\n", - pSHAState); - - pSHAState->SHA_DIGEST_A = INREG32(&pSha1Md5Reg_t->IDIGEST_A); - pSHAState->SHA_DIGEST_B = INREG32(&pSha1Md5Reg_t->IDIGEST_B); - pSHAState->SHA_DIGEST_C = INREG32(&pSha1Md5Reg_t->IDIGEST_C); - pSHAState->SHA_DIGEST_D = INREG32(&pSha1Md5Reg_t->IDIGEST_D); - pSHAState->SHA_DIGEST_E = INREG32(&pSha1Md5Reg_t->IDIGEST_E); - pSHAState->SHA_DIGEST_F = INREG32(&pSha1Md5Reg_t->IDIGEST_F); - pSHAState->SHA_DIGEST_G = INREG32(&pSha1Md5Reg_t->IDIGEST_G); - pSHAState->SHA_DIGEST_H = INREG32(&pSha1Md5Reg_t->IDIGEST_H); + dprintk(KERN_INFO "tf_digest_save_registers: State=%p\n", + sha_state); + + sha_state->SHA_DIGEST_A = INREG32(&sha1_md5_reg->IDIGEST_A); + sha_state->SHA_DIGEST_B = INREG32(&sha1_md5_reg->IDIGEST_B); + sha_state->SHA_DIGEST_C = INREG32(&sha1_md5_reg->IDIGEST_C); + sha_state->SHA_DIGEST_D = INREG32(&sha1_md5_reg->IDIGEST_D); + sha_state->SHA_DIGEST_E = INREG32(&sha1_md5_reg->IDIGEST_E); + sha_state->SHA_DIGEST_F = INREG32(&sha1_md5_reg->IDIGEST_F); + sha_state->SHA_DIGEST_G = INREG32(&sha1_md5_reg->IDIGEST_G); + sha_state->SHA_DIGEST_H = INREG32(&sha1_md5_reg->IDIGEST_H); } /*------------------------------------------------------------------------- *Restore the HWA registers from the operation state structure *-------------------------------------------------------------------------*/ -static void PDrvCryptoRestoreHashRegisters( - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *pSHAState) +static void tf_digest_restore_registers( + struct tf_crypto_sha_operation_state *sha_state) { - dprintk(KERN_INFO "PDrvCryptoRestoreHashRegisters: State=%p\n", - pSHAState); + dprintk(KERN_INFO "tf_digest_restore_registers: State=%p\n", + sha_state); - if (pSHAState->nBytesProcessed != 0) { + if (sha_state->bytes_processed != 0) { /* * Some bytes were already processed. Initialize * previous digest */ - OUTREG32(&pSha1Md5Reg_t->IDIGEST_A, pSHAState->SHA_DIGEST_A); - OUTREG32(&pSha1Md5Reg_t->IDIGEST_B, pSHAState->SHA_DIGEST_B); - OUTREG32(&pSha1Md5Reg_t->IDIGEST_C, pSHAState->SHA_DIGEST_C); - OUTREG32(&pSha1Md5Reg_t->IDIGEST_D, pSHAState->SHA_DIGEST_D); - OUTREG32(&pSha1Md5Reg_t->IDIGEST_E, pSHAState->SHA_DIGEST_E); - OUTREG32(&pSha1Md5Reg_t->IDIGEST_F, pSHAState->SHA_DIGEST_F); - OUTREG32(&pSha1Md5Reg_t->IDIGEST_G, pSHAState->SHA_DIGEST_G); - OUTREG32(&pSha1Md5Reg_t->IDIGEST_H, pSHAState->SHA_DIGEST_H); + OUTREG32(&sha1_md5_reg->IDIGEST_A, sha_state->SHA_DIGEST_A); + OUTREG32(&sha1_md5_reg->IDIGEST_B, sha_state->SHA_DIGEST_B); + OUTREG32(&sha1_md5_reg->IDIGEST_C, sha_state->SHA_DIGEST_C); + OUTREG32(&sha1_md5_reg->IDIGEST_D, sha_state->SHA_DIGEST_D); + OUTREG32(&sha1_md5_reg->IDIGEST_E, sha_state->SHA_DIGEST_E); + OUTREG32(&sha1_md5_reg->IDIGEST_F, sha_state->SHA_DIGEST_F); + OUTREG32(&sha1_md5_reg->IDIGEST_G, sha_state->SHA_DIGEST_G); + OUTREG32(&sha1_md5_reg->IDIGEST_H, sha_state->SHA_DIGEST_H); } - OUTREG32(&pSha1Md5Reg_t->SYSCONFIG, 0); + OUTREG32(&sha1_md5_reg->SYSCONFIG, 0); } /*------------------------------------------------------------------------- */ -void PDrvCryptoDigestInit(void) +void tf_digest_init(void) { - pSha1Md5Reg_t = omap_ioremap(DIGEST1_REGS_HW_ADDR, SZ_1M, MT_DEVICE); - if (pSha1Md5Reg_t == NULL) + sha1_md5_reg = omap_ioremap(DIGEST1_REGS_HW_ADDR, SZ_1M, MT_DEVICE); + if (sha1_md5_reg == NULL) panic("Unable to remap SHA2/MD5 module"); } -void PDrvCryptoDigestExit(void) +void tf_digest_exit(void) { - omap_iounmap(pSha1Md5Reg_t); + omap_iounmap(sha1_md5_reg); } -void PDrvCryptoUpdateHash(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *pSHAState, - u8 *pData, u32 dataLength) +bool tf_digest_update(struct tf_crypto_sha_operation_state *sha_state, + u8 *data, u32 data_length) { - u32 dmaUse = PUBLIC_CRYPTO_DMA_USE_NONE; + u32 dma_use = PUBLIC_CRYPTO_DMA_USE_NONE; /* *Choice of the processing type */ - if (dataLength >= DMA_TRIGGER_IRQ_DIGEST) - dmaUse = PUBLIC_CRYPTO_DMA_USE_IRQ; + if (data_length >= DMA_TRIGGER_IRQ_DIGEST) + dma_use = PUBLIC_CRYPTO_DMA_USE_IRQ; - dprintk(KERN_INFO "PDrvCryptoUpdateHash : "\ - "Data=0x%08x/%u, Chunk=%u, Processed=%u, dmaUse=0x%08x\n", - (u32)pData, (u32)dataLength, - pSHAState->nChunkLength, pSHAState->nBytesProcessed, - dmaUse); + dprintk(KERN_INFO "tf_digest_update : "\ + "Data=0x%08x/%u, Chunk=%u, Processed=%u, dma_use=0x%08x\n", + (u32)data, (u32)data_length, + sha_state->chunk_length, sha_state->bytes_processed, + dma_use); - if (dataLength == 0) { - dprintk(KERN_INFO "PDrvCryptoUpdateHash: "\ + if (data_length == 0) { + dprintk(KERN_INFO "tf_digest_update: "\ "Nothing to process\n"); - return; + return true; } - if (dmaUse != PUBLIC_CRYPTO_DMA_USE_NONE) { + if (dma_use != PUBLIC_CRYPTO_DMA_USE_NONE) { /* * Restore the registers of the accelerator from the operation * state */ - PDrvCryptoRestoreHashRegisters(pSHAState); + tf_digest_restore_registers(sha_state); /*perform the updates with DMA */ - PDrvCryptoUpdateHashWithDMA(pSHAState, pData, dataLength); + if (!tf_digest_update_dma(sha_state, data, data_length)) + return false; /* Save the accelerator registers into the operation state */ - PDrvCryptoSaveHashRegisters(pSHAState); + tf_digest_save_registers(sha_state); } else { /*Non-DMA transfer */ @@ -264,63 +265,66 @@ void PDrvCryptoUpdateHash(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *pSHAState, /*Is there any data in the chunk? If yes is it possible to *make a 64B buffer with the new data passed ? */ - if ((pSHAState->nChunkLength != 0) - && (pSHAState->nChunkLength + dataLength >= + if ((sha_state->chunk_length != 0) + && (sha_state->chunk_length + data_length >= HASH_BLOCK_BYTES_LENGTH)) { u8 vLengthToComplete = - HASH_BLOCK_BYTES_LENGTH - pSHAState->nChunkLength; + HASH_BLOCK_BYTES_LENGTH - sha_state->chunk_length; /*So we fill the chunk buffer with the new data to *complete to 64B */ - memcpy(pSHAState->pChunkBuffer + pSHAState-> - nChunkLength, pData, vLengthToComplete); + if (copy_from_user( + sha_state->chunk_buffer+sha_state->chunk_length, + data, + vLengthToComplete)) + return false; - if (pSHAState->nChunkLength + dataLength == + if (sha_state->chunk_length + data_length == HASH_BLOCK_BYTES_LENGTH) { /*We'll keep some data for the final */ - pSHAState->nChunkLength = + sha_state->chunk_length = HASH_BLOCK_BYTES_LENGTH; - dprintk(KERN_INFO "PDrvCryptoUpdateHash: "\ + dprintk(KERN_INFO "tf_digest_update: "\ "Done: Chunk=%u; Processed=%u\n", - pSHAState->nChunkLength, - pSHAState->nBytesProcessed); - return; + sha_state->chunk_length, + sha_state->bytes_processed); + return true; } /* * Restore the registers of the accelerator from the * operation state */ - PDrvCryptoRestoreHashRegisters(pSHAState); + tf_digest_restore_registers(sha_state); /*Then we send this buffer to the HWA */ - static_Hash_HwPerform64bDigest( - (u32 *)pSHAState->pChunkBuffer, pSHAState->CTRL, - pSHAState->nBytesProcessed); + tf_digest_hw_perform_64b( + (u32 *)sha_state->chunk_buffer, sha_state->CTRL, + sha_state->bytes_processed); /* * Save the accelerator registers into the operation * state */ - PDrvCryptoSaveHashRegisters(pSHAState); + tf_digest_save_registers(sha_state); - pSHAState->nBytesProcessed = - INREG32(&pSha1Md5Reg_t->DIGEST_COUNT); + sha_state->bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); /*We have flushed the chunk so it is empty now */ - pSHAState->nChunkLength = 0; + sha_state->chunk_length = 0; /*Then we have less data to process */ - pData += vLengthToComplete; - dataLength -= vLengthToComplete; + data += vLengthToComplete; + data_length -= vLengthToComplete; } /*(2)We process all the 64B buffer that we can */ - if (pSHAState->nChunkLength + dataLength >= + if (sha_state->chunk_length + data_length >= HASH_BLOCK_BYTES_LENGTH) { - while (dataLength > HASH_BLOCK_BYTES_LENGTH) { + while (data_length > HASH_BLOCK_BYTES_LENGTH) { u8 pTempAlignedBuffer[HASH_BLOCK_BYTES_LENGTH]; /* @@ -328,71 +332,79 @@ void PDrvCryptoUpdateHash(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *pSHAState, */ /*We copy the data to process to an aligned *buffer */ - memcpy(pTempAlignedBuffer, pData, - HASH_BLOCK_BYTES_LENGTH); + if (copy_from_user( + pTempAlignedBuffer, + data, + HASH_BLOCK_BYTES_LENGTH)) + return false; /*Then we send this buffer to the hash *hardware */ - PDrvCryptoRestoreHashRegisters(pSHAState); - static_Hash_HwPerform64bDigest( + tf_digest_restore_registers(sha_state); + tf_digest_hw_perform_64b( (u32 *) pTempAlignedBuffer, - pSHAState->CTRL, - pSHAState->nBytesProcessed); - PDrvCryptoSaveHashRegisters(pSHAState); + sha_state->CTRL, + sha_state->bytes_processed); + tf_digest_save_registers(sha_state); - pSHAState->nBytesProcessed = - INREG32(&pSha1Md5Reg_t->DIGEST_COUNT); + sha_state->bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); /*Then we decrease the remaining data of 64B */ - pData += HASH_BLOCK_BYTES_LENGTH; - dataLength -= HASH_BLOCK_BYTES_LENGTH; + data += HASH_BLOCK_BYTES_LENGTH; + data_length -= HASH_BLOCK_BYTES_LENGTH; } } /*(3)We look if we have some data that could not be processed *yet because it is not large enough to fill a buffer of 64B */ - if (dataLength > 0) { - if (pSHAState->nChunkLength + dataLength > + if (data_length > 0) { + if (sha_state->chunk_length + data_length > HASH_BLOCK_BYTES_LENGTH) { /*Should never be in this case !!! */ - panic("PDrvCryptoUpdateHash: nChunkLength + \ - dataLength > HASH_BLOCK_BYTES_LENGTH\n"); + panic("tf_digest_update: chunk_length data_length > " + "HASH_BLOCK_BYTES_LENGTH\n"); } /*So we fill the chunk buffer with the new data to *complete to 64B */ - memcpy(pSHAState->pChunkBuffer + pSHAState-> - nChunkLength, pData, dataLength); - pSHAState->nChunkLength += dataLength; + if (copy_from_user( + sha_state->chunk_buffer+sha_state->chunk_length, + data, + data_length)) + return false; + sha_state->chunk_length += data_length; } } - dprintk(KERN_INFO "PDrvCryptoUpdateHash: Done: "\ + dprintk(KERN_INFO "tf_digest_update: Done: "\ "Chunk=%u; Processed=%u\n", - pSHAState->nChunkLength, pSHAState->nBytesProcessed); + sha_state->chunk_length, sha_state->bytes_processed); + + return true; } /*------------------------------------------------------------------------- */ -static void static_Hash_HwPerform64bDigest(u32 *pData, - u32 nAlgo, u32 nBytesProcessed) +static void tf_digest_hw_perform_64b(u32 *data, + u32 algo, u32 bytes_processed) { - u32 nAlgoConstant = 0; + u32 algo_constant = 0; - OUTREG32(&pSha1Md5Reg_t->DIGEST_COUNT, nBytesProcessed); + OUTREG32(&sha1_md5_reg->DIGEST_COUNT, bytes_processed); - if (nBytesProcessed == 0) { + if (bytes_processed == 0) { /* No bytes processed so far. Will use the algo constant instead of previous digest */ - nAlgoConstant = 1 << 3; + algo_constant = 1 << 3; } - OUTREG32(&pSha1Md5Reg_t->MODE, - nAlgoConstant | (nAlgo & 0x6)); - OUTREG32(&pSha1Md5Reg_t->LENGTH, HASH_BLOCK_BYTES_LENGTH); + OUTREG32(&sha1_md5_reg->MODE, + algo_constant | (algo & 0x6)); + OUTREG32(&sha1_md5_reg->LENGTH, HASH_BLOCK_BYTES_LENGTH); - if (SCXPublicCryptoWaitForReadyBit( - (u32 *)&pSha1Md5Reg_t->IRQSTATUS, + if (tf_crypto_wait_for_ready_bit( + (u32 *)&sha1_md5_reg->IRQSTATUS, DIGEST_IRQSTATUS_INPUT_READY_BIT) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { /* Crash the system as this should never occur */ @@ -401,37 +413,37 @@ static void static_Hash_HwPerform64bDigest(u32 *pData, } /* - *The pData buffer is a buffer of 64 bytes. + *The data buffer is a buffer of 64 bytes. */ - OUTREG32(&pSha1Md5Reg_t->DIN_0, pData[0]); - OUTREG32(&pSha1Md5Reg_t->DIN_1, pData[1]); - OUTREG32(&pSha1Md5Reg_t->DIN_2, pData[2]); - OUTREG32(&pSha1Md5Reg_t->DIN_3, pData[3]); - OUTREG32(&pSha1Md5Reg_t->DIN_4, pData[4]); - OUTREG32(&pSha1Md5Reg_t->DIN_5, pData[5]); - OUTREG32(&pSha1Md5Reg_t->DIN_6, pData[6]); - OUTREG32(&pSha1Md5Reg_t->DIN_7, pData[7]); - OUTREG32(&pSha1Md5Reg_t->DIN_8, pData[8]); - OUTREG32(&pSha1Md5Reg_t->DIN_9, pData[9]); - OUTREG32(&pSha1Md5Reg_t->DIN_10, pData[10]); - OUTREG32(&pSha1Md5Reg_t->DIN_11, pData[11]); - OUTREG32(&pSha1Md5Reg_t->DIN_12, pData[12]); - OUTREG32(&pSha1Md5Reg_t->DIN_13, pData[13]); - OUTREG32(&pSha1Md5Reg_t->DIN_14, pData[14]); - OUTREG32(&pSha1Md5Reg_t->DIN_15, pData[15]); + OUTREG32(&sha1_md5_reg->DIN_0, data[0]); + OUTREG32(&sha1_md5_reg->DIN_1, data[1]); + OUTREG32(&sha1_md5_reg->DIN_2, data[2]); + OUTREG32(&sha1_md5_reg->DIN_3, data[3]); + OUTREG32(&sha1_md5_reg->DIN_4, data[4]); + OUTREG32(&sha1_md5_reg->DIN_5, data[5]); + OUTREG32(&sha1_md5_reg->DIN_6, data[6]); + OUTREG32(&sha1_md5_reg->DIN_7, data[7]); + OUTREG32(&sha1_md5_reg->DIN_8, data[8]); + OUTREG32(&sha1_md5_reg->DIN_9, data[9]); + OUTREG32(&sha1_md5_reg->DIN_10, data[10]); + OUTREG32(&sha1_md5_reg->DIN_11, data[11]); + OUTREG32(&sha1_md5_reg->DIN_12, data[12]); + OUTREG32(&sha1_md5_reg->DIN_13, data[13]); + OUTREG32(&sha1_md5_reg->DIN_14, data[14]); + OUTREG32(&sha1_md5_reg->DIN_15, data[15]); /* *Wait until the hash operation is finished. */ - SCXPublicCryptoWaitForReadyBitInfinitely( - (u32 *)&pSha1Md5Reg_t->IRQSTATUS, + tf_crypto_wait_for_ready_bit_infinitely( + (u32 *)&sha1_md5_reg->IRQSTATUS, DIGEST_IRQSTATUS_OUTPUT_READY_BIT); } /*------------------------------------------------------------------------- */ -static void static_Hash_HwPerformDmaDigest(u8 *pData, u32 nDataLength, - u32 nAlgo, u32 nBytesProcessed) +static bool tf_digest_hw_perform_dma(u8 *data, u32 nDataLength, + u32 algo, u32 bytes_processed) { /* *Note: The DMA only sees physical addresses ! @@ -439,50 +451,53 @@ static void static_Hash_HwPerformDmaDigest(u8 *pData, u32 nDataLength, int dma_ch0; struct omap_dma_channel_params ch0_parameters; - u32 nLengthLoop = 0; - u32 nAlgoConstant; - struct SCXLNX_DEVICE *pDevice = SCXLNXGetDevice(); + u32 length_loop = 0; + u32 algo_constant; + struct tf_device *dev = tf_get_device(); dprintk(KERN_INFO - "static_Hash_HwPerformDmaDigest: Buffer=0x%08x/%u\n", - (u32)pData, (u32)nDataLength); + "tf_digest_hw_perform_dma: Buffer=0x%08x/%u\n", + (u32)data, (u32)nDataLength); /*lock the DMA */ - mutex_lock(&pDevice->sm.sDMALock); - if (scxPublicDMARequest(&dma_ch0) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { - mutex_unlock(&pDevice->sm.sDMALock); - return; + mutex_lock(&dev->sm.dma_mutex); + if (tf_dma_request(&dma_ch0) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { + mutex_unlock(&dev->sm.dma_mutex); + return false; } while (nDataLength > 0) { - nAlgoConstant = 0; - if (nBytesProcessed == 0) { + algo_constant = 0; + if (bytes_processed == 0) { /*No bytes processed so far. Will use the algo *constant instead of previous digest */ - nAlgoConstant = 1 << 3; + algo_constant = 1 << 3; } /*check length */ - if (nDataLength <= pDevice->nDMABufferLength) - nLengthLoop = nDataLength; + if (nDataLength <= dev->dma_buffer_length) + length_loop = nDataLength; else - nLengthLoop = pDevice->nDMABufferLength; + length_loop = dev->dma_buffer_length; /* - *Copy the data from the input buffer into a preallocated - *buffer which is aligned on the beginning of a page. - *This may prevent potential issues when flushing/invalidating - *the buffer as the cache lines are 64 bytes long. + * Copy the data from the user input buffer into a preallocated + * buffer which has correct properties from efficient DMA + * transfers. */ - memcpy(pDevice->pDMABuffer, pData, nLengthLoop); + if (copy_from_user(dev->dma_buffer, data, length_loop)) { + omap_free_dma(dma_ch0); + mutex_unlock(&dev->sm.dma_mutex); + return false; + } /*DMA1: Mem -> HASH */ - scxPublicSetDMAChannelCommonParams(&ch0_parameters, - nLengthLoop / HASH_BLOCK_BYTES_LENGTH, + tf_dma_set_channel_common_params(&ch0_parameters, + length_loop / HASH_BLOCK_BYTES_LENGTH, DMA_CEN_Elts_per_Frame_SHA, DIGEST1_REGS_HW_ADDR + 0x80, - pDevice->pDMABufferPhys, + dev->dma_buffer_phys, OMAP44XX_DMA_SHA2_DIN_P); /*specific for Mem -> HWA */ @@ -490,55 +505,57 @@ static void static_Hash_HwPerformDmaDigest(u8 *pData, u32 nDataLength, ch0_parameters.dst_amode = OMAP_DMA_AMODE_CONSTANT; ch0_parameters.src_or_dst_synch = OMAP_DMA_DST_SYNC; - scxPublicDMASetParams(dma_ch0, &ch0_parameters); + omap_set_dma_params(dma_ch0, &ch0_parameters); omap_set_dma_src_burst_mode(dma_ch0, OMAP_DMA_DATA_BURST_16); omap_set_dma_dest_burst_mode(dma_ch0, OMAP_DMA_DATA_BURST_16); - OUTREG32(&pSha1Md5Reg_t->DIGEST_COUNT, nBytesProcessed); - OUTREG32(&pSha1Md5Reg_t->MODE, - nAlgoConstant | (nAlgo & 0x6)); + OUTREG32(&sha1_md5_reg->DIGEST_COUNT, bytes_processed); + OUTREG32(&sha1_md5_reg->MODE, + algo_constant | (algo & 0x6)); /* * Triggers operation * Interrupt, Free Running + GO (DMA on) */ - OUTREG32(&pSha1Md5Reg_t->SYSCONFIG, - INREG32(&pSha1Md5Reg_t->SYSCONFIG) | + OUTREG32(&sha1_md5_reg->SYSCONFIG, + INREG32(&sha1_md5_reg->SYSCONFIG) | DIGEST_SYSCONFIG_PDMA_EN_BIT); - OUTREG32(&pSha1Md5Reg_t->LENGTH, nLengthLoop); + OUTREG32(&sha1_md5_reg->LENGTH, length_loop); wmb(); - scxPublicDMAStart(dma_ch0, OMAP_DMA_BLOCK_IRQ); + tf_dma_start(dma_ch0, OMAP_DMA_BLOCK_IRQ); - scxPublicDMAWait(1); + tf_dma_wait(1); - OUTREG32(&pSha1Md5Reg_t->SYSCONFIG, 0); + OUTREG32(&sha1_md5_reg->SYSCONFIG, 0); - scxPublicDMAClearChannel(dma_ch0); + omap_clear_dma(dma_ch0); - pData += nLengthLoop; - nDataLength -= nLengthLoop; - nBytesProcessed = - INREG32(&pSha1Md5Reg_t->DIGEST_COUNT); + data += length_loop; + nDataLength -= length_loop; + bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); } /*For safety reasons, let's clean the working buffer */ - memset(pDevice->pDMABuffer, 0, nLengthLoop); + memset(dev->dma_buffer, 0, length_loop); /*release the DMA */ - scxPublicDMARelease(dma_ch0); + omap_free_dma(dma_ch0); - mutex_unlock(&pDevice->sm.sDMALock); + mutex_unlock(&dev->sm.dma_mutex); /* * The dma transfert is finished, now wait until the hash * operation is finished. */ - SCXPublicCryptoWaitForReadyBitInfinitely( - (u32 *)&pSha1Md5Reg_t->IRQSTATUS, + tf_crypto_wait_for_ready_bit_infinitely( + (u32 *)&sha1_md5_reg->IRQSTATUS, DIGEST_IRQSTATUS_CONTEXT_READY_BIT); + + return true; } /*------------------------------------------------------------------------- */ @@ -546,96 +563,101 @@ static void static_Hash_HwPerformDmaDigest(u8 *pData, u32 nDataLength, *Static function, perform data digest using the DMA for data transfer. * *inputs: - * pData : pointer of the input data to process - * dataLength : number of byte to process + * data : pointer of the input data to process + * data_length : number of byte to process */ -static void PDrvCryptoUpdateHashWithDMA( - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *pSHAState, - u8 *pData, u32 dataLength) +static bool tf_digest_update_dma( + struct tf_crypto_sha_operation_state *sha_state, + u8 *data, u32 data_length) { - dprintk(KERN_INFO "PDrvCryptoUpdateHashWithDMA\n"); + dprintk(KERN_INFO "tf_digest_update_dma\n"); - if (pSHAState->nChunkLength != 0) { + if (sha_state->chunk_length != 0) { u32 vLengthToComplete; /*Fill the chunk first */ - if (pSHAState-> - nChunkLength + dataLength <= HASH_BLOCK_BYTES_LENGTH) { + if (sha_state-> + chunk_length + data_length <= HASH_BLOCK_BYTES_LENGTH) { /*So we fill the chunk buffer with the new data */ - memcpy(pSHAState-> - pChunkBuffer + pSHAState->nChunkLength, - pData, dataLength); - pSHAState->nChunkLength += dataLength; + if (copy_from_user(sha_state->chunk_buffer + + sha_state->chunk_length, data, + data_length)) + return false; + sha_state->chunk_length += data_length; /*We'll keep some data for the final */ - return; + return true; } - vLengthToComplete = HASH_BLOCK_BYTES_LENGTH - pSHAState-> - nChunkLength; + vLengthToComplete = HASH_BLOCK_BYTES_LENGTH - sha_state-> + chunk_length; if (vLengthToComplete != 0) { /*So we fill the chunk buffer with the new data to *complete to 64B */ - memcpy(pSHAState->pChunkBuffer + pSHAState-> - nChunkLength, pData, vLengthToComplete); + if (copy_from_user(sha_state->chunk_buffer + + sha_state->chunk_length, data, + vLengthToComplete)) + return false; } /*Then we send this buffer to the HWA (no DMA) */ - static_Hash_HwPerform64bDigest( - (u32 *)pSHAState->pChunkBuffer, pSHAState->CTRL, - pSHAState->nBytesProcessed); + tf_digest_hw_perform_64b( + (u32 *)sha_state->chunk_buffer, sha_state->CTRL, + sha_state->bytes_processed); - pSHAState->nBytesProcessed = - INREG32(&pSha1Md5Reg_t->DIGEST_COUNT); + sha_state->bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); /*We have flushed the chunk so it is empty now */ - pSHAState->nChunkLength = 0; + sha_state->chunk_length = 0; /*Update the data buffer depending of the data already *processed */ - pData += vLengthToComplete; - dataLength -= vLengthToComplete; + data += vLengthToComplete; + data_length -= vLengthToComplete; } - if (dataLength > HASH_BLOCK_BYTES_LENGTH) { + if (data_length > HASH_BLOCK_BYTES_LENGTH) { /*DMA only manages data length that is multiple of 64b */ - u32 vDmaProcessSize = dataLength & 0xFFFFFFC0; + u32 vDmaProcessize = data_length & 0xFFFFFFC0; - if (vDmaProcessSize == dataLength) { + if (vDmaProcessize == data_length) { /*We keep one block for the final */ - vDmaProcessSize -= HASH_BLOCK_BYTES_LENGTH; + vDmaProcessize -= HASH_BLOCK_BYTES_LENGTH; } - static_Hash_HwPerformDmaDigest(pData, vDmaProcessSize, - pSHAState->CTRL, pSHAState->nBytesProcessed); + if (!tf_digest_hw_perform_dma(data, vDmaProcessize, + sha_state->CTRL, sha_state->bytes_processed)) + return false; - pSHAState->nBytesProcessed = - INREG32(&pSha1Md5Reg_t->DIGEST_COUNT); - pData += vDmaProcessSize; - dataLength -= vDmaProcessSize; + sha_state->bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); + data += vDmaProcessize; + data_length -= vDmaProcessize; } /*At that point, there is less than 64b left to process*/ - if ((dataLength == 0) || (dataLength > HASH_BLOCK_BYTES_LENGTH)) { + if ((data_length == 0) || (data_length > HASH_BLOCK_BYTES_LENGTH)) /*Should never be in this case !!! */ - panic("PDrvCryptoUpdateHASHWithDMA: \ - Remaining dataLength=%u\n", dataLength); - } + return false; /*We now fill the chunk buffer with the remaining data */ - memcpy(pSHAState->pChunkBuffer, pData, dataLength); - pSHAState->nChunkLength = dataLength; + if (copy_from_user(sha_state->chunk_buffer, data, data_length)) + return false; + sha_state->chunk_length = data_length; + + return true; } #ifdef CONFIG_SMC_KERNEL_CRYPTO -static void PDrvCryptoInitHash(u32 alg, - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state) +static void tf_digest_init_operation(u32 alg, + struct tf_crypto_sha_operation_state *state) { - memset(state, 0, sizeof(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE)); + memset(state, 0, sizeof(struct tf_crypto_sha_operation_state)); state->CTRL = alg << 1; } @@ -663,7 +685,7 @@ static int static_Hash_HwReadDigest(u32 algo, u8 *out) } for (i = 0; i < regs; i++) { - tmp = INREG32(&pSha1Md5Reg_t->IDIGEST_A + i); + tmp = INREG32(&sha1_md5_reg->IDIGEST_A + i); out[idx++] = (u8) ((tmp >> 0) & 0xff); out[idx++] = (u8) ((tmp >> 8) & 0xff); @@ -674,13 +696,13 @@ static int static_Hash_HwReadDigest(u32 algo, u8 *out) return 0; } -static int PDrvCryptoFinalHash(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state, +static int tf_digest_final(struct tf_crypto_sha_operation_state *state, u8 *out) { - u32 *data = (u32 *) state->pChunkBuffer; + u32 *data = (u32 *) state->chunk_buffer; /* Hashing an empty string? */ - if (state->nBytesProcessed + state->nChunkLength == 0) { + if (state->bytes_processed + state->chunk_length == 0) { switch (DIGEST_MODE_GET_ALGO(state->CTRL)) { case DIGEST_CTRL_ALGO_MD5: memcpy(out, md5OverEmptyString, HASH_MD5_LENGTH); @@ -701,20 +723,20 @@ static int PDrvCryptoFinalHash(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state, return 0; } - PDrvCryptoRestoreHashRegisters(state); + tf_digest_restore_registers(state); /* * At this point, the chunk buffer should contain the last block of data * needed for the final. */ - OUTREG32(&pSha1Md5Reg_t->DIGEST_COUNT, state->nBytesProcessed); - OUTREG32(&pSha1Md5Reg_t->MODE, + OUTREG32(&sha1_md5_reg->DIGEST_COUNT, state->bytes_processed); + OUTREG32(&sha1_md5_reg->MODE, (state->CTRL & 0x6) | 0x10 | - (state->nBytesProcessed == 0) << 3); - OUTREG32(&pSha1Md5Reg_t->LENGTH, state->nChunkLength); + (state->bytes_processed == 0) << 3); + OUTREG32(&sha1_md5_reg->LENGTH, state->chunk_length); - if (SCXPublicCryptoWaitForReadyBit( - (u32 *) &pSha1Md5Reg_t->IRQSTATUS, + if (tf_crypto_wait_for_ready_bit( + (u32 *) &sha1_md5_reg->IRQSTATUS, DIGEST_IRQSTATUS_INPUT_READY_BIT) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { /* Crash the system as this should never occur */ @@ -722,26 +744,26 @@ static int PDrvCryptoFinalHash(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state, "Input data to be ready\n"); } - OUTREG32(&pSha1Md5Reg_t->DIN_0, data[0]); - OUTREG32(&pSha1Md5Reg_t->DIN_1, data[1]); - OUTREG32(&pSha1Md5Reg_t->DIN_2, data[2]); - OUTREG32(&pSha1Md5Reg_t->DIN_3, data[3]); - OUTREG32(&pSha1Md5Reg_t->DIN_4, data[4]); - OUTREG32(&pSha1Md5Reg_t->DIN_5, data[5]); - OUTREG32(&pSha1Md5Reg_t->DIN_6, data[6]); - OUTREG32(&pSha1Md5Reg_t->DIN_7, data[7]); - OUTREG32(&pSha1Md5Reg_t->DIN_8, data[8]); - OUTREG32(&pSha1Md5Reg_t->DIN_9, data[9]); - OUTREG32(&pSha1Md5Reg_t->DIN_10, data[10]); - OUTREG32(&pSha1Md5Reg_t->DIN_11, data[11]); - OUTREG32(&pSha1Md5Reg_t->DIN_12, data[12]); - OUTREG32(&pSha1Md5Reg_t->DIN_13, data[13]); - OUTREG32(&pSha1Md5Reg_t->DIN_14, data[14]); - OUTREG32(&pSha1Md5Reg_t->DIN_15, data[15]); + OUTREG32(&sha1_md5_reg->DIN_0, data[0]); + OUTREG32(&sha1_md5_reg->DIN_1, data[1]); + OUTREG32(&sha1_md5_reg->DIN_2, data[2]); + OUTREG32(&sha1_md5_reg->DIN_3, data[3]); + OUTREG32(&sha1_md5_reg->DIN_4, data[4]); + OUTREG32(&sha1_md5_reg->DIN_5, data[5]); + OUTREG32(&sha1_md5_reg->DIN_6, data[6]); + OUTREG32(&sha1_md5_reg->DIN_7, data[7]); + OUTREG32(&sha1_md5_reg->DIN_8, data[8]); + OUTREG32(&sha1_md5_reg->DIN_9, data[9]); + OUTREG32(&sha1_md5_reg->DIN_10, data[10]); + OUTREG32(&sha1_md5_reg->DIN_11, data[11]); + OUTREG32(&sha1_md5_reg->DIN_12, data[12]); + OUTREG32(&sha1_md5_reg->DIN_13, data[13]); + OUTREG32(&sha1_md5_reg->DIN_14, data[14]); + OUTREG32(&sha1_md5_reg->DIN_15, data[15]); /* Wait till the hash operation is finished */ - SCXPublicCryptoWaitForReadyBitInfinitely( - (u32 *) &pSha1Md5Reg_t->IRQSTATUS, + tf_crypto_wait_for_ready_bit_infinitely( + (u32 *) &sha1_md5_reg->IRQSTATUS, DIGEST_IRQSTATUS_OUTPUT_READY_BIT); return static_Hash_HwReadDigest(DIGEST_MODE_GET_ALGO(state->CTRL), out); @@ -754,17 +776,20 @@ static int PDrvCryptoFinalHash(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state, static int digest_update(struct shash_desc *desc, const u8 *data, unsigned int len) { - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state = shash_desc_ctx(desc); + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_SHA, LOCK_HWA); + /* Make sure SHA/MD5 HWA is accessible */ + tf_delayed_secure_resume(); - SCXPublicCryptoEnableClock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_SHA, LOCK_HWA); - PDrvCryptoUpdateHash(state, (u8 *) data, len); + tf_crypto_enable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); - SCXPublicCryptoDisableClock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + tf_digest_update(state, (u8 *) data, len); - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_SHA, UNLOCK_HWA); + tf_crypto_disable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_SHA, UNLOCK_HWA); return 0; } @@ -772,24 +797,27 @@ static int digest_update(struct shash_desc *desc, const u8 *data, static int digest_final(struct shash_desc *desc, u8 *out) { int ret; - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state = shash_desc_ctx(desc); + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); + + /* Make sure SHA/MD5 HWA is accessible */ + tf_delayed_secure_resume(); - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_SHA, LOCK_HWA); + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_SHA, LOCK_HWA); - SCXPublicCryptoEnableClock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + tf_crypto_enable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); - ret = PDrvCryptoFinalHash(state, out); + ret = tf_digest_final(state, out); - SCXPublicCryptoDisableClock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + tf_crypto_disable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); - PDrvCryptoLockUnlockHWA(PUBLIC_CRYPTO_HWA_SHA, UNLOCK_HWA); + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_SHA, UNLOCK_HWA); return ret; } static int digest_import(struct shash_desc *desc, const void *in) { - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state = shash_desc_ctx(desc); + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); memcpy(state, in, sizeof(*state)); return 0; @@ -797,7 +825,7 @@ static int digest_import(struct shash_desc *desc, const void *in) static int digest_export(struct shash_desc *desc, void *out) { - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state = shash_desc_ctx(desc); + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); memcpy(out, state, sizeof(*state)); return 0; @@ -806,9 +834,9 @@ static int digest_export(struct shash_desc *desc, void *out) /* MD5 */ static int md5_init(struct shash_desc *desc) { - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state = shash_desc_ctx(desc); + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); - PDrvCryptoInitHash(DIGEST_CTRL_ALGO_MD5, state); + tf_digest_init_operation(DIGEST_CTRL_ALGO_MD5, state); return 0; } @@ -820,8 +848,8 @@ static struct shash_alg smc_md5_alg = { .final = digest_final, .export = digest_export, .import = digest_import, - .descsize = sizeof(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE), - .statesize = sizeof(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE), + .descsize = sizeof(struct tf_crypto_sha_operation_state), + .statesize = sizeof(struct tf_crypto_sha_operation_state), .base = { .cra_name = "md5", .cra_driver_name = "md5-smc", @@ -835,9 +863,9 @@ static struct shash_alg smc_md5_alg = { /* SHA1 */ static int sha1_init(struct shash_desc *desc) { - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state = shash_desc_ctx(desc); + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); - PDrvCryptoInitHash(DIGEST_CTRL_ALGO_SHA1, state); + tf_digest_init_operation(DIGEST_CTRL_ALGO_SHA1, state); return 0; } @@ -849,8 +877,8 @@ static struct shash_alg smc_sha1_alg = { .final = digest_final, .export = digest_export, .import = digest_import, - .descsize = sizeof(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE), - .statesize = sizeof(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE), + .descsize = sizeof(struct tf_crypto_sha_operation_state), + .statesize = sizeof(struct tf_crypto_sha_operation_state), .base = { .cra_name = "sha1", .cra_driver_name = "sha1-smc", @@ -864,9 +892,9 @@ static struct shash_alg smc_sha1_alg = { /* SHA224 */ static int sha224_init(struct shash_desc *desc) { - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state = shash_desc_ctx(desc); + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); - PDrvCryptoInitHash(DIGEST_CTRL_ALGO_SHA224, state); + tf_digest_init_operation(DIGEST_CTRL_ALGO_SHA224, state); return 0; } @@ -878,8 +906,8 @@ static struct shash_alg smc_sha224_alg = { .final = digest_final, .export = digest_export, .import = digest_import, - .descsize = sizeof(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE), - .statesize = sizeof(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE), + .descsize = sizeof(struct tf_crypto_sha_operation_state), + .statesize = sizeof(struct tf_crypto_sha_operation_state), .base = { .cra_name = "sha224", .cra_driver_name = "sha224-smc", @@ -893,9 +921,9 @@ static struct shash_alg smc_sha224_alg = { /* SHA256 */ static int sha256_init(struct shash_desc *desc) { - struct PUBLIC_CRYPTO_SHA_OPERATION_STATE *state = shash_desc_ctx(desc); + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); - PDrvCryptoInitHash(DIGEST_CTRL_ALGO_SHA256, state); + tf_digest_init_operation(DIGEST_CTRL_ALGO_SHA256, state); return 0; } @@ -907,8 +935,8 @@ static struct shash_alg smc_sha256_alg = { .final = digest_final, .export = digest_export, .import = digest_import, - .descsize = sizeof(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE), - .statesize = sizeof(struct PUBLIC_CRYPTO_SHA_OPERATION_STATE), + .descsize = sizeof(struct tf_crypto_sha_operation_state), + .statesize = sizeof(struct tf_crypto_sha_operation_state), .base = { .cra_name = "sha256", .cra_driver_name = "sha256-smc", diff --git a/security/smc/omap4/scxlnx_defs.h b/security/smc/tf_defs.h index a6dcb9c..23dc7ca 100644 --- a/security/smc/omap4/scxlnx_defs.h +++ b/security/smc/tf_defs.h @@ -1,5 +1,5 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. +/** + * Copyright (c) 2011 Trusted Logic S.A. * All Rights Reserved. * * This program is free software; you can redistribute it and/or @@ -17,8 +17,8 @@ * MA 02111-1307 USA */ -#ifndef __SCXLNX_DEFS_H__ -#define __SCXLNX_DEFS_H__ +#ifndef __TF_DEFS_H__ +#define __TF_DEFS_H__ #include <asm/atomic.h> #include <linux/version.h> @@ -27,7 +27,6 @@ #include <linux/completion.h> #include <linux/list.h> #include <linux/spinlock.h> -#include <linux/sysdev.h> #include <linux/sysfs.h> #include <linux/sched.h> #include <linux/semaphore.h> @@ -35,7 +34,12 @@ #include <linux/wakelock.h> #endif -#include "scx_protocol.h" +#include "tf_protocol.h" + +#ifdef CONFIG_TF_ION +#include <linux/ion.h> +#include <linux/omap_ion.h> +#endif /*----------------------------------------------------------------------------*/ @@ -44,77 +48,77 @@ /* * Maximum number of shared memory blocks that can be reigsters in a connection */ -#define SCXLNX_SHMEM_MAX_COUNT (64) +#define TF_SHMEM_MAX_COUNT (64) /* * Describes the possible types of shared memories * - * SCXLNX_SHMEM_TYPE_PREALLOC_REGISTERED_SHMEM : + * TF_SHMEM_TYPE_PREALLOC_REGISTERED_SHMEM : * The descriptor describes a registered shared memory. * Its coarse pages are preallocated when initializing the * connection - * SCXLNX_SHMEM_TYPE_REGISTERED_SHMEM : + * TF_SHMEM_TYPE_REGISTERED_SHMEM : * The descriptor describes a registered shared memory. * Its coarse pages are not preallocated - * SCXLNX_SHMEM_TYPE_PM_HIBERNATE : + * TF_SHMEM_TYPE_PM_HIBERNATE : * The descriptor describes a power management shared memory. */ -enum SCXLNX_SHMEM_TYPE { - SCXLNX_SHMEM_TYPE_PREALLOC_REGISTERED_SHMEM = 0, - SCXLNX_SHMEM_TYPE_REGISTERED_SHMEM, - SCXLNX_SHMEM_TYPE_PM_HIBERNATE, +enum TF_SHMEM_TYPE { + TF_SHMEM_TYPE_PREALLOC_REGISTERED_SHMEM = 0, + TF_SHMEM_TYPE_REGISTERED_SHMEM, + TF_SHMEM_TYPE_PM_HIBERNATE, }; /* * This structure contains a pointer on a coarse page table */ -struct SCXLNX_COARSE_PAGE_TABLE { +struct tf_coarse_page_table { /* * Identifies the coarse page table descriptor in - * sFreeCoarsePageTables list + * free_coarse_page_tables list */ struct list_head list; /* * The address of the coarse page table */ - u32 *pDescriptors; + u32 *descriptors; /* * The address of the array containing this coarse page table */ - struct SCXLNX_COARSE_PAGE_TABLE_ARRAY *pParent; + struct tf_coarse_page_table_array *parent; }; -#define SCXLNX_PAGE_DESCRIPTOR_TYPE_NORMAL 0 -#define SCXLNX_PAGE_DESCRIPTOR_TYPE_PREALLOCATED 1 +#define TF_PAGE_DESCRIPTOR_TYPE_NORMAL 0 +#define TF_PAGE_DESCRIPTOR_TYPE_PREALLOCATED 1 /* * This structure describes an array of up to 4 coarse page tables * allocated within a single 4KB page. */ -struct SCXLNX_COARSE_PAGE_TABLE_ARRAY { +struct tf_coarse_page_table_array { /* - * identifies the element in the sCoarsePageTableArrays list + * identifies the element in the coarse_page_table_arrays list */ struct list_head list; /* * Type of page descriptor - * can take any of SCXLNX_PAGE_DESCRIPTOR_TYPE_XXX value + * can take any of TF_PAGE_DESCRIPTOR_TYPE_XXX value */ - u32 nType; + u32 type; - struct SCXLNX_COARSE_PAGE_TABLE sCoarsePageTables[4]; + struct tf_coarse_page_table coarse_page_tables[4]; /* * A counter of the number of coarse pages currently used * the max value should be 4 (one coarse page table is 1KB while one * page is 4KB) */ - u8 nReferenceCount; + u8 ref_count; }; @@ -124,7 +128,7 @@ struct SCXLNX_COARSE_PAGE_TABLE_ARRAY { * when the driver needs to allocate a new coarse page * table. */ -struct SCXLNX_COARSE_PAGE_TABLE_ALLOCATION_CONTEXT { +struct tf_coarse_page_table_allocation_context { /* * The spin lock protecting concurrent access to the structure. */ @@ -133,19 +137,19 @@ struct SCXLNX_COARSE_PAGE_TABLE_ALLOCATION_CONTEXT { /* * The list of allocated coarse page table arrays */ - struct list_head sCoarsePageTableArrays; + struct list_head coarse_page_table_arrays; /* * The list of free coarse page tables */ - struct list_head sFreeCoarsePageTables; + struct list_head free_coarse_page_tables; }; /* * Fully describes a shared memory block */ -struct SCXLNX_SHMEM_DESC { +struct tf_shmem_desc { /* * Identifies the shared memory descriptor in the list of free shared * memory descriptors @@ -155,25 +159,25 @@ struct SCXLNX_SHMEM_DESC { /* * Identifies the type of shared memory descriptor */ - enum SCXLNX_SHMEM_TYPE nType; + enum TF_SHMEM_TYPE type; /* * The identifier of the block of shared memory, as returned by the * Secure World. - * This identifier is hBlock field of a REGISTER_SHARED_MEMORY answer + * This identifier is block field of a REGISTER_SHARED_MEMORY answer */ - u32 hIdentifier; + u32 block_identifier; /* Client buffer */ u8 *pBuffer; /* Up to eight coarse page table context */ - struct SCXLNX_COARSE_PAGE_TABLE *pCoarsePageTable[SCX_MAX_COARSE_PAGES]; + struct tf_coarse_page_table *coarse_pg_table[TF_MAX_COARSE_PAGES]; - u32 nNumberOfCoarsePageTables; + u32 coarse_pg_table_count; /* Reference counter */ - atomic_t nRefCnt; + atomic_t ref_count; }; @@ -184,7 +188,7 @@ struct SCXLNX_SHMEM_DESC { * * Note that this driver supports only one instance of the Secure World */ -struct SCXLNX_COMM { +struct tf_comm { /* * The spin lock protecting concurrent access to the structure. */ @@ -192,81 +196,81 @@ struct SCXLNX_COMM { /* * Bit vector with the following possible flags: - * - SCXLNX_COMM_FLAG_IRQ_REQUESTED: If set, indicates that + * - TF_COMM_FLAG_IRQ_REQUESTED: If set, indicates that * the IRQ has been successfuly requested. - * - SCXLNX_COMM_FLAG_TERMINATING: If set, indicates that the + * - TF_COMM_FLAG_TERMINATING: If set, indicates that the * communication with the Secure World is being terminated. * Transmissions to the Secure World are not permitted - * - SCXLNX_COMM_FLAG_W3B_ALLOCATED: If set, indicates that the + * - TF_COMM_FLAG_W3B_ALLOCATED: If set, indicates that the * W3B buffer has been allocated. * * This bit vector must be accessed with the kernel's atomic bitwise * operations. */ - unsigned long nFlags; + unsigned long flags; /* * The virtual address of the L1 shared buffer. */ - struct SCHANNEL_C1S_BUFFER *pBuffer; + struct tf_l1_shared_buffer *pBuffer; /* * The wait queue the client threads are waiting on. */ - wait_queue_head_t waitQueue; + wait_queue_head_t wait_queue; #ifdef CONFIG_TF_TRUSTZONE /* * The interrupt line used by the Secure World. */ - int nSoftIntIrq; + int soft_int_irq; /* ----- W3B ----- */ /* shared memory descriptor to identify the W3B */ - struct SCXLNX_SHMEM_DESC sW3BShmemDesc; + struct tf_shmem_desc w3b_shmem_desc; /* Virtual address of the kernel allocated shared memory */ - u32 nW3BShmemVAddr; + u32 w3b; /* offset of data in shared memory coarse pages */ - u32 nW3BShmemOffset; + u32 w3b_shmem_offset; - u32 nW3BShmemSize; + u32 w3b_shmem_size; - struct SCXLNX_COARSE_PAGE_TABLE_ALLOCATION_CONTEXT - sW3BAllocationContext; + struct tf_coarse_page_table_allocation_context + w3b_cpt_alloc_context; #endif -#ifdef CONFIG_TF_MSHIELD +#ifdef CONFIG_TF_ZEBRA /* * The SE SDP can only be initialized once... */ - int bSEInitialized; + int se_initialized; /* Virtual address of the L0 communication buffer */ - void *pInitSharedBuffer; + void *init_shared_buffer; /* * Lock to be held by a client when executing an RPC */ - struct mutex sRPCLock; + struct mutex rpc_mutex; /* * Lock to protect concurrent accesses to DMA channels */ - struct mutex sDMALock; + struct mutex dma_mutex; #endif }; -#define SCXLNX_COMM_FLAG_IRQ_REQUESTED (0) -#define SCXLNX_COMM_FLAG_PA_AVAILABLE (1) -#define SCXLNX_COMM_FLAG_TERMINATING (2) -#define SCXLNX_COMM_FLAG_W3B_ALLOCATED (3) -#define SCXLNX_COMM_FLAG_L1_SHARED_ALLOCATED (4) +#define TF_COMM_FLAG_IRQ_REQUESTED (0) +#define TF_COMM_FLAG_PA_AVAILABLE (1) +#define TF_COMM_FLAG_TERMINATING (2) +#define TF_COMM_FLAG_W3B_ALLOCATED (3) +#define TF_COMM_FLAG_L1_SHARED_ALLOCATED (4) /*----------------------------------------------------------------------------*/ -struct SCXLNX_DEVICE_STATS { +struct tf_device_stats { struct kobject kobj; struct kobj_type kobj_type; @@ -283,89 +287,79 @@ struct SCXLNX_DEVICE_STATS { /* * This structure describes the information about one device handled by the * driver. Note that the driver supports only a single device. see the global - * variable g_SCXLNXDevice + * variable g_tf_dev + */ -struct SCXLNX_DEVICE { +struct tf_device { /* * The device number for the device. */ - dev_t nDevNum; - - /* - * Interfaces the system device with the kernel. - */ - struct sys_device sysdev; + dev_t dev_number; /* * Interfaces the char device with the kernel. */ struct cdev cdev; -#ifdef CONFIG_TF_MSHIELD +#ifdef CONFIG_TF_TEEC + struct cdev cdev_teec; +#endif + +#ifdef CONFIG_TF_ZEBRA struct cdev cdev_ctrl; /* * Globals for CUS */ /* Current key handles loaded in HWAs */ - u32 hAES1SecureKeyContext; - u32 hAES2SecureKeyContext; - u32 hDESSecureKeyContext; - bool bSHAM1IsPublic; + u32 aes1_key_context; + u32 des_key_context; + bool sham1_is_public; - /* Semaphores used to serialize HWA accesses */ - struct semaphore sAES1CriticalSection; - struct semaphore sAES2CriticalSection; - struct mutex sDESCriticalSection; - struct mutex sSHACriticalSection; + /* Object used to serialize HWA accesses */ + struct semaphore aes1_sema; + struct semaphore des_sema; + struct semaphore sha_sema; /* * An aligned and correctly shaped pre-allocated buffer used for DMA * transfers */ - u32 nDMABufferLength; - u8 *pDMABuffer; - dma_addr_t pDMABufferPhys; + u32 dma_buffer_length; + u8 *dma_buffer; + dma_addr_t dma_buffer_phys; /* Workspace allocated at boot time and reserved to the Secure World */ - u32 nWorkspaceAddr; - u32 nWorkspaceSize; + u32 workspace_addr; + u32 workspace_size; + + /* + * A Mutex to provide exclusive locking of the ioctl() + */ + struct mutex dev_mutex; #endif /* * Communications with the SM. */ - struct SCXLNX_COMM sm; + struct tf_comm sm; /* * Lists the connections attached to this device. A connection is * created each time a user space application "opens" a file descriptor * on the driver */ - struct list_head conns; + struct list_head connection_list; /* * The spin lock used to protect concurrent access to the connection * list. */ - spinlock_t connsLock; - - struct SCXLNX_DEVICE_STATS sDeviceStats; + spinlock_t connection_list_lock; - /* - * A Mutex to provide exlusive locking of the ioctl() - */ - struct mutex dev_mutex; + struct tf_device_stats stats; }; -/* the bits of the nFlags field of the SCXLNX_DEVICE structure */ -#define SCXLNX_DEVICE_FLAG_CDEV_INITIALIZED (0) -#define SCXLNX_DEVICE_FLAG_SYSDEV_CLASS_REGISTERED (1) -#define SCXLNX_DEVICE_FLAG_SYSDEV_REGISTERED (2) -#define SCXLNX_DEVICE_FLAG_CDEV_REGISTERED (3) -#define SCXLNX_DEVICE_FLAG_CDEV_ADDED (4) -#define SCXLNX_DEVICE_SYSFS_REGISTERED (5) - /*----------------------------------------------------------------------------*/ /* * This type describes a connection state. @@ -375,24 +369,24 @@ struct SCXLNX_DEVICE { * Messages may be invalidated between the start of the ioctl call and the * moment the message is sent to the Secure World. * - * SCXLNX_CONN_STATE_NO_DEVICE_CONTEXT : + * TF_CONN_STATE_NO_DEVICE_CONTEXT : * The connection has no DEVICE_CONTEXT created and no * CREATE_DEVICE_CONTEXT being processed by the Secure World - * SCXLNX_CONN_STATE_CREATE_DEVICE_CONTEXT_SENT : + * TF_CONN_STATE_CREATE_DEVICE_CONTEXT_SENT : * The connection has a CREATE_DEVICE_CONTEXT being processed by the Secure * World - * SCXLNX_CONN_STATE_VALID_DEVICE_CONTEXT : + * TF_CONN_STATE_VALID_DEVICE_CONTEXT : * The connection has a DEVICE_CONTEXT created and no * DESTROY_DEVICE_CONTEXT is being processed by the Secure World - * SCXLNX_CONN_STATE_DESTROY_DEVICE_CONTEXT_SENT : + * TF_CONN_STATE_DESTROY_DEVICE_CONTEXT_SENT : * The connection has a DESTROY_DEVICE_CONTEXT being processed by the Secure * World */ -enum SCXLNX_CONN_STATE { - SCXLNX_CONN_STATE_NO_DEVICE_CONTEXT = 0, - SCXLNX_CONN_STATE_CREATE_DEVICE_CONTEXT_SENT, - SCXLNX_CONN_STATE_VALID_DEVICE_CONTEXT, - SCXLNX_CONN_STATE_DESTROY_DEVICE_CONTEXT_SENT +enum TF_CONN_STATE { + TF_CONN_STATE_NO_DEVICE_CONTEXT = 0, + TF_CONN_STATE_CREATE_DEVICE_CONTEXT_SENT, + TF_CONN_STATE_VALID_DEVICE_CONTEXT, + TF_CONN_STATE_DESTROY_DEVICE_CONTEXT_SENT }; @@ -408,10 +402,22 @@ enum SCXLNX_CONN_STATE { * Note that this only covers the case where some other thread * sent a DESTROY_DEVICE_CONTEXT command. */ -enum SCXLNX_COMMAND_STATE { - SCXLNX_COMMAND_STATE_PENDING = 0, - SCXLNX_COMMAND_STATE_SENT, - SCXLNX_COMMAND_STATE_ABORTED +enum TF_COMMAND_STATE { + TF_COMMAND_STATE_PENDING = 0, + TF_COMMAND_STATE_SENT, + TF_COMMAND_STATE_ABORTED +}; + +/* + * The origin of connection parameters such as login data and + * memory reference pointers. + * + * PROCESS: the calling process. All arguments must be validated. + * KERNEL: kernel code. All arguments can be trusted by this driver. + */ +enum TF_CONNECTION_OWNER { + TF_CONNECTION_OWNER_PROCESS = 0, + TF_CONNECTION_OWNER_KERNEL, }; @@ -420,7 +426,7 @@ enum SCXLNX_COMMAND_STATE { * A connection is created each time an application opens a file descriptor on * the driver */ -struct SCXLNX_CONNECTION { +struct tf_connection { /* * Identifies the connection in the list of the connections attached to * the same device. @@ -430,80 +436,88 @@ struct SCXLNX_CONNECTION { /* * State of the connection. */ - enum SCXLNX_CONN_STATE nState; + enum TF_CONN_STATE state; /* * A pointer to the corresponding device structure */ - struct SCXLNX_DEVICE *pDevice; + struct tf_device *dev; /* - * A spinlock to use to access nState + * A spinlock to use to access state */ - spinlock_t stateLock; + spinlock_t state_lock; /* * Counts the number of operations currently pending on the connection. * (for debug only) */ - atomic_t nPendingOpCounter; + atomic_t pending_op_count; /* * A handle for the device context */ - u32 hDeviceContext; + u32 device_context; /* * Lists the used shared memory descriptors */ - struct list_head sUsedSharedMemoryList; + struct list_head used_shmem_list; /* * Lists the free shared memory descriptors */ - struct list_head sFreeSharedMemoryList; + struct list_head free_shmem_list; /* * A mutex to use to access this structure */ - struct mutex sharedMemoriesMutex; + struct mutex shmem_mutex; /* * Counts the number of shared memories registered. */ - atomic_t nShmemAllocated; + atomic_t shmem_count; /* * Page to retrieve memory properties when * registering shared memory through REGISTER_SHARED_MEMORY * messages */ - struct vm_area_struct **ppVmas; + struct vm_area_struct **vmas; /* * coarse page table allocation context */ - struct SCXLNX_COARSE_PAGE_TABLE_ALLOCATION_CONTEXT sAllocationContext; + struct tf_coarse_page_table_allocation_context cpt_alloc_context; + + /* The origin of connection parameters such as login data and + memory reference pointers. */ + enum TF_CONNECTION_OWNER owner; -#ifdef CONFIG_TF_MSHIELD +#ifdef CONFIG_TF_ZEBRA /* Lists all the Cryptoki Update Shortcuts */ - struct list_head ShortcutList; + struct list_head shortcut_list; + + /* Lock to protect concurrent accesses to shortcut_list */ + spinlock_t shortcut_list_lock; +#endif - /* Lock to protect concurrent accesses to ShortcutList */ - spinlock_t shortcutListCriticalSectionLock; +#ifdef CONFIG_TF_ION + struct ion_client *ion_client; #endif }; /*----------------------------------------------------------------------------*/ /* - * The nOperationID field of a message points to this structure. + * The operation_id field of a message points to this structure. * It is used to identify the thread that triggered the message transmission * Whoever reads an answer can wake up that thread using the completion event */ -struct SCXLNX_ANSWER_STRUCT { - bool bAnswerCopied; - union SCX_ANSWER_MESSAGE *pAnswer; +struct tf_answer_struct { + bool answer_copied; + union tf_answer *answer; }; /*----------------------------------------------------------------------------*/ @@ -512,16 +526,16 @@ struct SCXLNX_ANSWER_STRUCT { * The ASCII-C string representation of the base name of the devices managed by * this driver. */ -#define SCXLNX_DEVICE_BASE_NAME "tf_driver" +#define TF_DEVICE_BASE_NAME "tf_driver" /** * The major and minor numbers of the registered character device driver. * Only 1 instance of the driver is supported. */ -#define SCXLNX_DEVICE_MINOR_NUMBER (0) +#define TF_DEVICE_MINOR_NUMBER (0) -struct SCXLNX_DEVICE *SCXLNXGetDevice(void); +struct tf_device *tf_get_device(void); #define CLEAN_CACHE_CFG_MASK (~0xC) /* 1111 0011 */ @@ -536,4 +550,4 @@ struct SCXLNX_DEVICE *SCXLNXGetDevice(void); #define GROUP_INFO (current->group_info) #endif -#endif /* !defined(__SCXLNX_DEFS_H__) */ +#endif /* !defined(__TF_DEFS_H__) */ diff --git a/security/smc/tf_device.c b/security/smc/tf_device.c new file mode 100644 index 0000000..7c2c623 --- /dev/null +++ b/security/smc/tf_device.c @@ -0,0 +1,728 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <asm/atomic.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/page-flags.h> +#include <linux/pm.h> +#include <linux/syscore_ops.h> +#include <linux/vmalloc.h> +#include <linux/signal.h> +#ifdef CONFIG_ANDROID +#include <linux/device.h> +#endif + +#include "tf_protocol.h" +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_conn.h" +#include "tf_comm.h" +#ifdef CONFIG_TF_ZEBRA +#include <plat/cpu.h> +#include "tf_zebra.h" +#endif + +#include "s_version.h" + +#ifdef CONFIG_TF_ION +extern struct ion_device *omap_ion_device; +#endif + +/*---------------------------------------------------------------------------- + * Forward Declarations + *----------------------------------------------------------------------------*/ + +/* + * Creates and registers the device to be managed by the specified driver. + * + * Returns zero upon successful completion, or an appropriate error code upon + * failure. + */ +static int tf_device_register(void); + + +/* + * Implements the device Open callback. + */ +static int tf_device_open( + struct inode *inode, + struct file *file); + + +/* + * Implements the device Release callback. + */ +static int tf_device_release( + struct inode *inode, + struct file *file); + + +/* + * Implements the device ioctl callback. + */ +static long tf_device_ioctl( + struct file *file, + unsigned int ioctl_num, + unsigned long ioctl_param); + + +/* + * Implements the device shutdown callback. + */ +static void tf_device_shutdown(void); + + +/* + * Implements the device suspend callback. + */ +static int tf_device_suspend(void); + + +/* + * Implements the device resume callback. + */ +static void tf_device_resume(void); + + +/*--------------------------------------------------------------------------- + * Module Parameters + *---------------------------------------------------------------------------*/ + +/* + * The device major number used to register a unique character device driver. + * Let the default value be 122 + */ +static int device_major_number = 122; + +module_param(device_major_number, int, 0000); +MODULE_PARM_DESC(device_major_number, + "The device major number used to register a unique character " + "device driver"); + +#ifdef CONFIG_TF_TRUSTZONE +/** + * The softint interrupt line used by the Secure World. + */ +static int soft_interrupt = -1; + +module_param(soft_interrupt, int, 0000); +MODULE_PARM_DESC(soft_interrupt, + "The softint interrupt line used by the Secure world"); +#endif + +#ifdef CONFIG_ANDROID +static struct class *tf_class; +#endif + +/*---------------------------------------------------------------------------- + * Global Variables + *----------------------------------------------------------------------------*/ + +/* + * tf_driver character device definitions. + * read and write methods are not defined + * and will return an error if used by user space + */ +static const struct file_operations g_tf_device_file_ops = { + .owner = THIS_MODULE, + .open = tf_device_open, + .release = tf_device_release, + .unlocked_ioctl = tf_device_ioctl, + .llseek = no_llseek, +}; + +/* The single device supported by this driver */ +static struct tf_device g_tf_dev = {0, }; + +/*---------------------------------------------------------------------------- + * Implementations + *----------------------------------------------------------------------------*/ + +struct tf_device *tf_get_device(void) +{ + return &g_tf_dev; +} + +/* + * displays the driver stats + */ +static ssize_t kobject_show(struct kobject *kobj, + struct attribute *attribute, char *buf) +{ + struct tf_device_stats *dev_stats = &g_tf_dev.stats; + u32 pages_allocated; + u32 pages_locked; + u32 memories_allocated; + + memories_allocated = + atomic_read(&(dev_stats->stat_memories_allocated)); + pages_allocated = + atomic_read(&(dev_stats->stat_pages_allocated)); + pages_locked = atomic_read(&(dev_stats->stat_pages_locked)); + + /* + * AFY: could we add the number of context switches (call to the SMI + * instruction) + */ + + return snprintf(buf, PAGE_SIZE, + "stat.memories.allocated: %d\n" + "stat.pages.allocated: %d\n" + "stat.pages.locked: %d\n", + memories_allocated, + pages_allocated, + pages_locked); +} + +static const struct sysfs_ops kobj_sysfs_operations = { + .show = kobject_show, +}; + +/*----------------------------------------------------------------------------*/ + +static const struct syscore_ops g_tf_syscore_ops = { + .shutdown = tf_device_shutdown, + .suspend = tf_device_suspend, + .resume = tf_device_resume, +}; + +/* + * First routine called when the kernel module is loaded + */ +static int __init tf_device_register(void) +{ + int error; + struct tf_device *dev = &g_tf_dev; + struct tf_device_stats *dev_stats = &dev->stats; + + dprintk(KERN_INFO "tf_device_register()\n"); + + /* + * Initialize the device + */ + dev->dev_number = MKDEV(device_major_number, + TF_DEVICE_MINOR_NUMBER); + cdev_init(&dev->cdev, &g_tf_device_file_ops); + dev->cdev.owner = THIS_MODULE; + + INIT_LIST_HEAD(&dev->connection_list); + spin_lock_init(&dev->connection_list_lock); + + /* register the sysfs object driver stats */ + dev_stats->kobj_type.sysfs_ops = &kobj_sysfs_operations; + + dev_stats->kobj_stat_attribute.name = "info"; + dev_stats->kobj_stat_attribute.mode = S_IRUGO; + dev_stats->kobj_attribute_list[0] = + &dev_stats->kobj_stat_attribute; + + dev_stats->kobj_type.default_attrs = + dev_stats->kobj_attribute_list, + error = kobject_init_and_add(&(dev_stats->kobj), + &(dev_stats->kobj_type), NULL, "%s", + TF_DEVICE_BASE_NAME); + if (error) { + kobject_put(&dev_stats->kobj); + goto kobject_init_and_add_failed; + } + + register_syscore_ops((struct syscore_ops *)&g_tf_syscore_ops); + + /* + * Register the char device. + */ + printk(KERN_INFO "Registering char device %s (%u:%u)\n", + TF_DEVICE_BASE_NAME, + MAJOR(dev->dev_number), + MINOR(dev->dev_number)); + error = register_chrdev_region(dev->dev_number, 1, + TF_DEVICE_BASE_NAME); + if (error != 0) { + printk(KERN_ERR "tf_device_register():" + " register_chrdev_region failed (error %d)!\n", + error); + goto register_chrdev_region_failed; + } + + error = cdev_add(&dev->cdev, dev->dev_number, 1); + if (error != 0) { + printk(KERN_ERR "tf_device_register(): " + "cdev_add failed (error %d)!\n", + error); + goto cdev_add_failed; + } + + /* + * Initialize the communication with the Secure World. + */ +#ifdef CONFIG_TF_TRUSTZONE + dev->sm.soft_int_irq = soft_interrupt; +#endif + error = tf_init(&g_tf_dev.sm); + if (error != S_SUCCESS) { + dprintk(KERN_ERR "tf_device_register(): " + "tf_init failed (error %d)!\n", + error); + goto init_failed; + } + +#ifdef CONFIG_ANDROID + tf_class = class_create(THIS_MODULE, TF_DEVICE_BASE_NAME); + device_create(tf_class, NULL, + dev->dev_number, + NULL, TF_DEVICE_BASE_NAME); +#endif + +#ifdef CONFIG_TF_ZEBRA + /* + * Initializes the /dev/tf_ctrl device node. + */ + error = tf_ctrl_device_register(); + if (error) + goto init_failed; +#endif + +#ifdef CONFIG_BENCH_SECURE_CYCLE + run_bogo_mips(); + address_cache_property((unsigned long) &tf_device_register); +#endif + /* + * Successful completion. + */ + + dprintk(KERN_INFO "tf_device_register(): Success\n"); + return 0; + + /* + * Error: undo all operations in the reverse order + */ +init_failed: + cdev_del(&dev->cdev); +cdev_add_failed: + unregister_chrdev_region(dev->dev_number, 1); +register_chrdev_region_failed: + unregister_syscore_ops((struct syscore_ops *)&g_tf_syscore_ops); +kobject_init_and_add_failed: + kobject_del(&g_tf_dev.stats.kobj); + + dprintk(KERN_INFO "tf_device_register(): Failure (error %d)\n", + error); + return error; +} + +/*----------------------------------------------------------------------------*/ + +static int tf_device_open(struct inode *inode, struct file *file) +{ + int error; + struct tf_device *dev = &g_tf_dev; + struct tf_connection *connection = NULL; + + dprintk(KERN_INFO "tf_device_open(%u:%u, %p)\n", + imajor(inode), iminor(inode), file); + + /* Dummy lseek for non-seekable driver */ + error = nonseekable_open(inode, file); + if (error != 0) { + dprintk(KERN_ERR "tf_device_open(%p): " + "nonseekable_open failed (error %d)!\n", + file, error); + goto error; + } + +#ifndef CONFIG_ANDROID + /* + * Check file flags. We only autthorize the O_RDWR access + */ + if (file->f_flags != O_RDWR) { + dprintk(KERN_ERR "tf_device_open(%p): " + "Invalid access mode %u\n", + file, file->f_flags); + error = -EACCES; + goto error; + } +#endif + + /* + * Open a new connection. + */ + + error = tf_open(dev, file, &connection); + if (error != 0) { + dprintk(KERN_ERR "tf_device_open(%p): " + "tf_open failed (error %d)!\n", + file, error); + goto error; + } + + file->private_data = connection; + + /* + * Send the CreateDeviceContext command to the secure + */ + error = tf_create_device_context(connection); + if (error != 0) { + dprintk(KERN_ERR "tf_device_open(%p): " + "tf_create_device_context failed (error %d)!\n", + file, error); + goto error1; + } + + /* + * Successful completion. + */ + + dprintk(KERN_INFO "tf_device_open(%p): Success (connection=%p)\n", + file, connection); + return 0; + + /* + * Error handling. + */ + +error1: + tf_close(connection); +error: + dprintk(KERN_INFO "tf_device_open(%p): Failure (error %d)\n", + file, error); + return error; +} + +/*----------------------------------------------------------------------------*/ + +static int tf_device_release(struct inode *inode, struct file *file) +{ + struct tf_connection *connection; + + dprintk(KERN_INFO "tf_device_release(%u:%u, %p)\n", + imajor(inode), iminor(inode), file); + + connection = tf_conn_from_file(file); + tf_close(connection); + + dprintk(KERN_INFO "tf_device_release(%p): Success\n", file); + return 0; +} + +/*----------------------------------------------------------------------------*/ + +static long tf_device_ioctl(struct file *file, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + int result = S_SUCCESS; + struct tf_connection *connection; + union tf_command command; + struct tf_command_header header; + union tf_answer answer; + u32 command_size; + u32 answer_size; + void *user_answer; + + dprintk(KERN_INFO "tf_device_ioctl(%p, %u, %p)\n", + file, ioctl_num, (void *) ioctl_param); + + switch (ioctl_num) { + case IOCTL_TF_GET_VERSION: + /* ioctl is asking for the driver interface version */ + result = TF_DRIVER_INTERFACE_VERSION; + goto exit; + +#ifdef CONFIG_TF_ION + case IOCTL_TF_ION_REGISTER: { + int ion_register; + /* ioctl is asking to register an ion handle */ + if (copy_from_user(&ion_register, + (int *) ioctl_param, + sizeof(int))) { + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "copy_from_user failed\n", + file); + result = -EFAULT; + goto exit; + } + + connection = tf_conn_from_file(file); + BUG_ON(connection == NULL); + + /* Initialize ION connection */ + if (connection->ion_client == NULL) { + connection->ion_client = ion_client_create( + omap_ion_device, + (1 << ION_HEAP_TYPE_CARVEOUT), + "smc"); + } + + if (connection->ion_client == NULL) { + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "unable to create ion client\n", + file); + result = -EFAULT; + goto exit; + } + + /* + * TODO: We should use a reference count on this handle in order + * to not unregistered it while using it. + */ + return (long)ion_import_fd(connection->ion_client, ion_register); + } + + case IOCTL_TF_ION_UNREGISTER: { + int ion_register; + /* ioctl is asking to unregister an ion handle */ + + if (copy_from_user(&ion_register, + (int *) ioctl_param, + sizeof(int))) { + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "copy_from_user failed\n", + file); + result = -EFAULT; + goto exit; + } + + connection = tf_conn_from_file(file); + BUG_ON(connection == NULL); + + if (connection->ion_client == NULL) { + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "ion client does not exist\n", + file); + result = -EFAULT; + goto exit; + } + + ion_free(connection->ion_client, + (struct ion_handle *) ion_register); + + return S_SUCCESS; + } +#endif + + case IOCTL_TF_EXCHANGE: + /* + * ioctl is asking to perform a message exchange with the Secure + * Module + */ + + /* + * Make a local copy of the data from the user application + * This routine checks the data is readable + * + * Get the header first. + */ + if (copy_from_user(&header, + (struct tf_command_header *)ioctl_param, + sizeof(struct tf_command_header))) { + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "Cannot access ioctl parameter %p\n", + file, (void *) ioctl_param); + result = -EFAULT; + goto exit; + } + + /* size in words of u32 */ + command_size = header.message_size + + sizeof(struct tf_command_header)/sizeof(u32); + if (command_size > sizeof(command)/sizeof(u32)) { + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "Buffer overflow: too many bytes to copy %d\n", + file, command_size); + result = -EFAULT; + goto exit; + } + + if (copy_from_user(&command, + (union tf_command *)ioctl_param, + command_size * sizeof(u32))) { + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "Cannot access ioctl parameter %p\n", + file, (void *) ioctl_param); + result = -EFAULT; + goto exit; + } + + connection = tf_conn_from_file(file); + BUG_ON(connection == NULL); + + /* + * The answer memory space address is in the operation_id field + */ + user_answer = (void *) command.header.operation_id; + + atomic_inc(&(connection->pending_op_count)); + + dprintk(KERN_WARNING "tf_device_ioctl(%p): " + "Sending message type 0x%08x\n", + file, command.header.message_type); + + switch (command.header.message_type) { + case TF_MESSAGE_TYPE_OPEN_CLIENT_SESSION: + result = tf_open_client_session(connection, + &command, &answer); + break; + + case TF_MESSAGE_TYPE_CLOSE_CLIENT_SESSION: + result = tf_close_client_session(connection, + &command, &answer); + break; + + case TF_MESSAGE_TYPE_REGISTER_SHARED_MEMORY: + result = tf_register_shared_memory(connection, + &command, &answer); + break; + + case TF_MESSAGE_TYPE_RELEASE_SHARED_MEMORY: + result = tf_release_shared_memory(connection, + &command, &answer); + break; + + case TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND: + result = tf_invoke_client_command(connection, + &command, &answer); + break; + + case TF_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND: + result = tf_cancel_client_command(connection, + &command, &answer); + break; + + default: + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "Incorrect message type (0x%08x)!\n", + connection, command.header.message_type); + result = -EOPNOTSUPP; + break; + } + + atomic_dec(&(connection->pending_op_count)); + + if (result != 0) { + dprintk(KERN_WARNING "tf_device_ioctl(%p): " + "Operation returning error code 0x%08x)!\n", + file, result); + goto exit; + } + + /* + * Copy the answer back to the user space application. + * The driver does not check this field, only copy back to user + * space the data handed over by Secure World + */ + answer_size = answer.header.message_size + + sizeof(struct tf_answer_header)/sizeof(u32); + if (copy_to_user(user_answer, + &answer, answer_size * sizeof(u32))) { + dprintk(KERN_WARNING "tf_device_ioctl(%p): " + "Failed to copy back the full command " + "answer to %p\n", file, user_answer); + result = -EFAULT; + goto exit; + } + + /* successful completion */ + dprintk(KERN_INFO "tf_device_ioctl(%p): Success\n", file); + break; + + case IOCTL_TF_GET_DESCRIPTION: { + /* ioctl asking for the version information buffer */ + struct tf_version_information_buffer *pInfoBuffer; + + dprintk(KERN_INFO "IOCTL_TF_GET_DESCRIPTION:(%p, %u, %p)\n", + file, ioctl_num, (void *) ioctl_param); + + pInfoBuffer = + ((struct tf_version_information_buffer *) ioctl_param); + + dprintk(KERN_INFO "IOCTL_TF_GET_DESCRIPTION1: " + "driver_description=\"%64s\"\n", S_VERSION_STRING); + + if (copy_to_user(pInfoBuffer->driver_description, + S_VERSION_STRING, + strlen(S_VERSION_STRING) + 1)) { + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "Fail to copy back the driver description " + "to %p\n", + file, pInfoBuffer->driver_description); + result = -EFAULT; + goto exit; + } + + dprintk(KERN_INFO "IOCTL_TF_GET_DESCRIPTION2: " + "secure_world_description=\"%64s\"\n", + tf_get_description(&g_tf_dev.sm)); + + if (copy_to_user(pInfoBuffer->secure_world_description, + tf_get_description(&g_tf_dev.sm), + TF_DESCRIPTION_BUFFER_LENGTH)) { + dprintk(KERN_WARNING "tf_device_ioctl(%p): " + "Failed to copy back the secure world " + "description to %p\n", + file, pInfoBuffer->secure_world_description); + result = -EFAULT; + goto exit; + } + break; + } + + default: + dprintk(KERN_ERR "tf_device_ioctl(%p): " + "Unknown IOCTL code 0x%08x!\n", + file, ioctl_num); + result = -EOPNOTSUPP; + goto exit; + } + +exit: + return result; +} + +/*----------------------------------------------------------------------------*/ + +static void tf_device_shutdown(void) +{ + tf_power_management(&g_tf_dev.sm, TF_POWER_OPERATION_SHUTDOWN); +} + +/*----------------------------------------------------------------------------*/ + +static int tf_device_suspend(void) +{ + dprintk(KERN_INFO "%s\n", __func__); + return tf_power_management(&g_tf_dev.sm, TF_POWER_OPERATION_HIBERNATE); +} + + +/*----------------------------------------------------------------------------*/ + +static void tf_device_resume(void) +{ + tf_power_management(&g_tf_dev.sm, TF_POWER_OPERATION_RESUME); +} + + +/*----------------------------------------------------------------------------*/ + +module_init(tf_device_register); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Trusted Logic S.A."); diff --git a/security/smc/tf_device_mshield.c b/security/smc/tf_device_mshield.c new file mode 100644 index 0000000..17f1451 --- /dev/null +++ b/security/smc/tf_device_mshield.c @@ -0,0 +1,351 @@ +/** + * Copyright (c) 2010 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <asm/atomic.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/page-flags.h> +#include <linux/pm.h> +#include <linux/sysdev.h> +#include <linux/vmalloc.h> +#include <linux/signal.h> +#ifdef CONFIG_ANDROID +#include <linux/device.h> +#endif +#include <linux/init.h> +#include <linux/bootmem.h> + +#include "tf_protocol.h" +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_conn.h" +#include "tf_comm.h" +#include "tf_zebra.h" + +#include "s_version.h" + +#define TF_PA_CTRL_START 0x1 +#define TF_PA_CTRL_STOP 0x2 + +#ifdef CONFIG_ANDROID +static struct class *tf_ctrl_class; +#endif + +#define TF_DEVICE_CTRL_BASE_NAME "tf_ctrl" + +struct tf_pa_ctrl { + u32 nPACommand; + + u32 pa_size; + u8 *pa_buffer; + + u32 conf_size; + u8 *conf_buffer; +}; + +static int tf_ctrl_check_omap_type(void) +{ + /* No need to do anything on a GP device */ + switch (omap_type()) { + case OMAP2_DEVICE_TYPE_GP: + dprintk(KERN_INFO "SMC: Running on a GP device\n"); + return 0; + + case OMAP2_DEVICE_TYPE_EMU: + case OMAP2_DEVICE_TYPE_SEC: + /*case OMAP2_DEVICE_TYPE_TEST:*/ + dprintk(KERN_INFO "SMC: Running on a EMU or HS device\n"); + return 1; + + default: + printk(KERN_ERR "SMC: unknown omap type %x\n", omap_type()); + return -EFAULT; + } +} + +#define IOCTL_TF_PA_CTRL _IOWR('z', 0xFF, struct tf_pa_ctrl) + +static long tf_ctrl_device_ioctl(struct file *file, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + int result = S_SUCCESS; + struct tf_pa_ctrl pa_ctrl; + u8 *pa_buffer = NULL; + u8 *conf_buffer = NULL; + struct tf_device *dev = tf_get_device(); + + dprintk(KERN_INFO "tf_ctrl_device_ioctl(%p, %u, %p)\n", + file, ioctl_num, (void *) ioctl_param); + + mutex_lock(&dev->dev_mutex); + + if (ioctl_num != IOCTL_TF_PA_CTRL) { + dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " + "ioctl number is invalid (%p)\n", + file, (void *)ioctl_num); + + result = -EFAULT; + goto exit; + } + + if ((ioctl_param & 0x3) != 0) { + dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " + "ioctl command message pointer is not word " + "aligned (%p)\n", + file, (void *)ioctl_param); + + result = -EFAULT; + goto exit; + } + + if (copy_from_user(&pa_ctrl, (struct tf_pa_ctrl *)ioctl_param, + sizeof(struct tf_pa_ctrl))) { + dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " + "cannot access ioctl parameter (%p)\n", + file, (void *)ioctl_param); + + result = -EFAULT; + goto exit; + } + + switch (pa_ctrl.nPACommand) { + case TF_PA_CTRL_START: + dprintk(KERN_INFO "tf_ctrl_device_ioctl(%p): " + "Start the SMC PA (%d bytes) with conf (%d bytes)\n", + file, pa_ctrl.pa_size, pa_ctrl.conf_size); + + pa_buffer = (u8 *) internal_kmalloc(pa_ctrl.pa_size, + GFP_KERNEL); + if (pa_buffer == NULL) { + dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " + "Out of memory for PA buffer\n", file); + + result = -ENOMEM; + goto exit; + } + + if (copy_from_user( + pa_buffer, pa_ctrl.pa_buffer, pa_ctrl.pa_size)) { + dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " + "Cannot access PA buffer (%p)\n", + file, (void *) pa_ctrl.pa_buffer); + + internal_kfree(pa_buffer); + + result = -EFAULT; + goto exit; + } + + if (pa_ctrl.conf_size > 0) { + conf_buffer = (u8 *) internal_kmalloc( + pa_ctrl.conf_size, GFP_KERNEL); + if (conf_buffer == NULL) { + internal_kfree(pa_buffer); + + result = -ENOMEM; + goto exit; + } + + if (copy_from_user(conf_buffer, + pa_ctrl.conf_buffer, pa_ctrl.conf_size)) { + internal_kfree(pa_buffer); + internal_kfree(conf_buffer); + + result = -EFAULT; + goto exit; + } + } + + if (dev->workspace_addr == 0) { + result = -ENOMEM; + goto exit; + } + + result = tf_start(&dev->sm, + dev->workspace_addr, + dev->workspace_size, + pa_buffer, + pa_ctrl.pa_size, + conf_buffer, + pa_ctrl.conf_size); + if (result) + dprintk(KERN_ERR "SMC: start failed\n"); + else + dprintk(KERN_INFO "SMC: started\n"); + + internal_kfree(pa_buffer); + internal_kfree(conf_buffer); + break; + + case TF_PA_CTRL_STOP: + dprintk(KERN_INFO "tf_ctrl_device_ioctl(%p): " + "Stop the SMC PA\n", file); + + result = tf_power_management(&dev->sm, + TF_POWER_OPERATION_SHUTDOWN); + if (result) + dprintk(KERN_WARNING "SMC: stop failed [0x%x]\n", + result); + else + dprintk(KERN_INFO "SMC: stopped\n"); + break; + + default: + result = -EOPNOTSUPP; + break; + } + +exit: + mutex_unlock(&dev->dev_mutex); + return result; +} + +/*----------------------------------------------------------------------------*/ + +static int tf_ctrl_device_open(struct inode *inode, struct file *file) +{ + int error; + + dprintk(KERN_INFO "tf_ctrl_device_open(%u:%u, %p)\n", + imajor(inode), iminor(inode), file); + + /* Dummy lseek for non-seekable driver */ + error = nonseekable_open(inode, file); + if (error != 0) { + dprintk(KERN_ERR "tf_ctrl_device_open(%p): " + "nonseekable_open failed (error %d)!\n", + file, error); + goto error; + } + +#ifndef CONFIG_ANDROID + /* + * Check file flags. We only autthorize the O_RDWR access + */ + if (file->f_flags != O_RDWR) { + dprintk(KERN_ERR "tf_ctrl_device_open(%p): " + "Invalid access mode %u\n", + file, file->f_flags); + error = -EACCES; + goto error; + } +#endif + + error = tf_ctrl_check_omap_type(); + if (error <= 0) + return error; + + /* + * Successful completion. + */ + + dprintk(KERN_INFO "tf_ctrl_device_open(%p): Success\n", file); + return 0; + + /* + * Error handling. + */ +error: + dprintk(KERN_INFO "tf_ctrl_device_open(%p): Failure (error %d)\n", + file, error); + return error; +} + +static const struct file_operations g_tf_ctrl_device_file_ops = { + .owner = THIS_MODULE, + .open = tf_ctrl_device_open, + .unlocked_ioctl = tf_ctrl_device_ioctl, + .llseek = no_llseek, +}; + +int __init tf_ctrl_device_register(void) +{ + int error; + struct tf_device *dev = tf_get_device(); + + cdev_init(&dev->cdev_ctrl, &g_tf_ctrl_device_file_ops); + dev->cdev_ctrl.owner = THIS_MODULE; + + error = register_chrdev_region(dev->dev_number + 1, 1, + TF_DEVICE_CTRL_BASE_NAME); + if (error) + return error; + + error = cdev_add(&dev->cdev_ctrl, + dev->dev_number + 1, 1); + if (error) { + cdev_del(&(dev->cdev_ctrl)); + unregister_chrdev_region(dev->dev_number + 1, 1); + return error; + } + +#ifdef CONFIG_ANDROID + tf_ctrl_class = class_create(THIS_MODULE, TF_DEVICE_CTRL_BASE_NAME); + device_create(tf_ctrl_class, NULL, + dev->dev_number + 1, + NULL, TF_DEVICE_CTRL_BASE_NAME); +#endif + + mutex_init(&dev->dev_mutex); + + return error; +} + +static int __initdata smc_mem; +static int __initdata smc_address; + +void __init tf_allocate_workspace(void) +{ + struct tf_device *dev = tf_get_device(); + + if (tf_ctrl_check_omap_type() <= 0) + return; + + dev->workspace_size = smc_mem; + if (dev->workspace_size < 3*SZ_1M) + dev->workspace_size = 3*SZ_1M; + + if (smc_address == 0) +#if 0 + dev->workspace_addr = (u32) __pa(__alloc_bootmem( + dev->workspace_size, SZ_1M, __pa(MAX_DMA_ADDRESS))); +#else + dev->workspace_addr = (u32) 0xBFD00000; +#endif + else + dev->workspace_addr = smc_address; + + pr_info("SMC: Allocated workspace of 0x%x Bytes at (0x%x)\n", + dev->workspace_size, + dev->workspace_addr); +} + +static int __init tf_mem_setup(char *str) +{ + smc_mem = memparse(str, &str); + if (*str == '@') { + str += 1; + get_option(&str, &smc_address); + } + return 0; +} + +early_param("smc_mem", tf_mem_setup); diff --git a/security/smc/tf_dma.c b/security/smc/tf_dma.c new file mode 100644 index 0000000..a424dbb --- /dev/null +++ b/security/smc/tf_dma.c @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_dma.h" + +#include <asm/atomic.h> + +static atomic_t g_dmaEventFlag = ATOMIC_INIT(0); + +/*------------------------------------------------------------------------ */ +/* + * Internal functions + */ + +static void tf_dma_callback(int lch, u16 ch_status, void *data) +{ + atomic_inc(&g_dmaEventFlag); +} + +/*------------------------------------------------------------------------ */ +/* + * Public DMA API + */ + +u32 tf_dma_request(int *lch) +{ + int dma_ch_out = 0; + + if (lch == NULL) + return PUBLIC_CRYPTO_ERR_BAD_PARAMETERS; + + if (omap_request_dma(0, "SMC Public Crypto", + tf_dma_callback, NULL, &dma_ch_out) != 0) + return PUBLIC_CRYPTO_ERR_OUT_OF_MEMORY; + + omap_disable_dma_irq(dma_ch_out, OMAP_DMA_DROP_IRQ | + OMAP_DMA_BLOCK_IRQ); + + *lch = dma_ch_out; + + return PUBLIC_CRYPTO_OPERATION_SUCCESS; +} + +/*------------------------------------------------------------------------ */ + +void tf_dma_start(int lch, int interrupt_mask) +{ + atomic_set(&g_dmaEventFlag, 0); + omap_enable_dma_irq(lch, interrupt_mask); + omap_start_dma(lch); +} + +/*------------------------------------------------------------------------ */ + +void tf_dma_wait(int nr_of_cb) +{ + while (atomic_read(&g_dmaEventFlag) < nr_of_cb) + cpu_relax(); +} + +/*------------------------------------------------------------------------ */ +/* + * Perform common DMA channel setup, used to factorize the code + * + * Output: struct omap_dma_channel_params *dma_channel + * Inputs: u32 nb_blocks Number of block of the transfer + * u32 nb_elements Number of elements of the transfer + * u32 dst_start Destination address + * u32 src_start Source address + * u32 trigger_id Trigger ID + */ +void tf_dma_set_channel_common_params( + struct omap_dma_channel_params *dma_channel, + u32 nb_blocks, u32 nb_elements, + u32 dst_start, u32 src_start, u32 trigger_id) +{ + dma_channel->data_type = OMAP_DMA_DATA_TYPE_S32; + dma_channel->elem_count = nb_elements; + dma_channel->frame_count = nb_blocks; + dma_channel->src_ei = 0; + dma_channel->src_fi = 0; + dma_channel->dst_ei = 0; + dma_channel->dst_fi = 0; + dma_channel->sync_mode = OMAP_DMA_SYNC_FRAME; + dma_channel->src_start = src_start; + dma_channel->dst_start = dst_start; + dma_channel->trigger = trigger_id; +} diff --git a/security/smc/tf_dma.h b/security/smc/tf_dma.h new file mode 100644 index 0000000..3492241 --- /dev/null +++ b/security/smc/tf_dma.h @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __TF_PUBLIC_DMA_H +#define __TF_PUBLIC_DMA_H + +#include <linux/dma-mapping.h> +#include <plat/dma.h> +#include <plat/dma-44xx.h> + +#include "tf_crypto.h" + +/*-------------------------------------------------------------------------- */ +/* + * Public DMA API + */ + +/* + * CEN Masks + */ +#define DMA_CEN_Elts_per_Frame_AES 4 +#define DMA_CEN_Elts_per_Frame_DES 2 +#define DMA_CEN_Elts_per_Frame_SHA 16 + +/* + * Request a DMA channel + */ +u32 tf_dma_request(int *lch); + +/** + * This function waits for the DMA IRQ. + */ +void tf_dma_wait(int nr_of_cb); + +/* + * This function starts a DMA operation. + * + * lch DMA channel ID. + * interrupt_mask Configures the Channel Interrupt Control Register. + */ +void tf_dma_start(int lch, int interrupt_mask); + +void tf_dma_set_channel_common_params( + struct omap_dma_channel_params *dma_channel, + u32 nb_blocks, u32 nb_elements, u32 dst_start, + u32 src_start, u32 trigger_id); + +#endif /*__TF_PUBLIC_DMA_H */ diff --git a/security/smc/tf_protocol.h b/security/smc/tf_protocol.h new file mode 100644 index 0000000..e3e6485 --- /dev/null +++ b/security/smc/tf_protocol.h @@ -0,0 +1,674 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __TF_PROTOCOL_H__ +#define __TF_PROTOCOL_H__ + +/*---------------------------------------------------------------------------- + * + * This header file defines the structure used in the SChannel Protocol. + * See your Product Reference Manual for a specification of the SChannel + * protocol. + *---------------------------------------------------------------------------*/ + +/* + * The driver interface version returned by the version ioctl + */ +#define TF_DRIVER_INTERFACE_VERSION 0x04000000 + +/* + * Protocol version handling + */ +#define TF_S_PROTOCOL_MAJOR_VERSION (0x06) +#define GET_PROTOCOL_MAJOR_VERSION(a) (a >> 24) +#define GET_PROTOCOL_MINOR_VERSION(a) ((a >> 16) & 0xFF) + +/* + * The S flag of the config_flag_s register. + */ +#define TF_CONFIG_FLAG_S (1 << 3) + +/* + * The TimeSlot field of the sync_serial_n register. + */ +#define TF_SYNC_SERIAL_TIMESLOT_N (1) + +/* + * status_s related defines. + */ +#define TF_STATUS_P_MASK (0X00000001) +#define TF_STATUS_POWER_STATE_SHIFT (3) +#define TF_STATUS_POWER_STATE_MASK (0x1F << TF_STATUS_POWER_STATE_SHIFT) + +/* + * Possible power states of the POWER_STATE field of the status_s register + */ +#define TF_POWER_MODE_COLD_BOOT (0) +#define TF_POWER_MODE_WARM_BOOT (1) +#define TF_POWER_MODE_ACTIVE (3) +#define TF_POWER_MODE_READY_TO_SHUTDOWN (5) +#define TF_POWER_MODE_READY_TO_HIBERNATE (7) +#define TF_POWER_MODE_WAKEUP (8) +#define TF_POWER_MODE_PANIC (15) + +/* + * Possible command values for MANAGEMENT commands + */ +#define TF_MANAGEMENT_HIBERNATE (1) +#define TF_MANAGEMENT_SHUTDOWN (2) +#define TF_MANAGEMENT_PREPARE_FOR_CORE_OFF (3) +#define TF_MANAGEMENT_RESUME_FROM_CORE_OFF (4) + +/* + * The capacity of the Normal Word message queue, in number of slots. + */ +#define TF_N_MESSAGE_QUEUE_CAPACITY (512) + +/* + * The capacity of the Secure World message answer queue, in number of slots. + */ +#define TF_S_ANSWER_QUEUE_CAPACITY (256) + +/* + * The value of the S-timeout register indicating an infinite timeout. + */ +#define TF_S_TIMEOUT_0_INFINITE (0xFFFFFFFF) +#define TF_S_TIMEOUT_1_INFINITE (0xFFFFFFFF) + +/* + * The value of the S-timeout register indicating an immediate timeout. + */ +#define TF_S_TIMEOUT_0_IMMEDIATE (0x0) +#define TF_S_TIMEOUT_1_IMMEDIATE (0x0) + +/* + * Identifies the get protocol version SMC. + */ +#define TF_SMC_GET_PROTOCOL_VERSION (0XFFFFFFFB) + +/* + * Identifies the init SMC. + */ +#define TF_SMC_INIT (0XFFFFFFFF) + +/* + * Identifies the reset irq SMC. + */ +#define TF_SMC_RESET_IRQ (0xFFFFFFFE) + +/* + * Identifies the SET_W3B SMC. + */ +#define TF_SMC_WAKE_UP (0xFFFFFFFD) + +/* + * Identifies the STOP SMC. + */ +#define TF_SMC_STOP (0xFFFFFFFC) + +/* + * Identifies the n-yield SMC. + */ +#define TF_SMC_N_YIELD (0X00000003) + + +/* Possible stop commands for SMC_STOP */ +#define SCSTOP_HIBERNATE (0xFFFFFFE1) +#define SCSTOP_SHUTDOWN (0xFFFFFFE2) + +/* + * representation of an UUID. + */ +struct tf_uuid { + u32 time_low; + u16 time_mid; + u16 time_hi_and_version; + u8 clock_seq_and_node[8]; +}; + + +/** + * Command parameters. + */ +struct tf_command_param_value { + u32 a; + u32 b; +}; + +struct tf_command_param_temp_memref { + u32 descriptor; /* data pointer for exchange message.*/ + u32 size; + u32 offset; +}; + +struct tf_command_param_memref { + u32 block; + u32 size; + u32 offset; +}; + +union tf_command_param { + struct tf_command_param_value value; + struct tf_command_param_temp_memref temp_memref; + struct tf_command_param_memref memref; +}; + +/** + * Answer parameters. + */ +struct tf_answer_param_value { + u32 a; + u32 b; +}; + +struct tf_answer_param_size { + u32 _ignored; + u32 size; +}; + +union tf_answer_param { + struct tf_answer_param_size size; + struct tf_answer_param_value value; +}; + +/* + * Descriptor tables capacity + */ +#define TF_MAX_W3B_COARSE_PAGES (2) +#define TF_MAX_COARSE_PAGES (8) +#define TF_DESCRIPTOR_TABLE_CAPACITY_BIT_SHIFT (8) +#define TF_DESCRIPTOR_TABLE_CAPACITY \ + (1 << TF_DESCRIPTOR_TABLE_CAPACITY_BIT_SHIFT) +#define TF_DESCRIPTOR_TABLE_CAPACITY_MASK \ + (TF_DESCRIPTOR_TABLE_CAPACITY - 1) +/* Shared memories coarse pages can map up to 1MB */ +#define TF_MAX_COARSE_PAGE_MAPPED_SIZE \ + (PAGE_SIZE * TF_DESCRIPTOR_TABLE_CAPACITY) +/* Shared memories cannot exceed 8MB */ +#define TF_MAX_SHMEM_SIZE \ + (TF_MAX_COARSE_PAGE_MAPPED_SIZE << 3) + +/* + * Buffer size for version description fields + */ +#define TF_DESCRIPTION_BUFFER_LENGTH 64 + +/* + * Shared memory type flags. + */ +#define TF_SHMEM_TYPE_READ (0x00000001) +#define TF_SHMEM_TYPE_WRITE (0x00000002) + +/* + * Shared mem flags + */ +#define TF_SHARED_MEM_FLAG_INPUT 1 +#define TF_SHARED_MEM_FLAG_OUTPUT 2 +#define TF_SHARED_MEM_FLAG_INOUT 3 + + +/* + * Parameter types + */ +#define TF_PARAM_TYPE_NONE 0x0 +#define TF_PARAM_TYPE_VALUE_INPUT 0x1 +#define TF_PARAM_TYPE_VALUE_OUTPUT 0x2 +#define TF_PARAM_TYPE_VALUE_INOUT 0x3 +#define TF_PARAM_TYPE_MEMREF_TEMP_INPUT 0x5 +#define TF_PARAM_TYPE_MEMREF_TEMP_OUTPUT 0x6 +#define TF_PARAM_TYPE_MEMREF_TEMP_INOUT 0x7 +#define TF_PARAM_TYPE_MEMREF_ION_HANDLE 0xB +#define TF_PARAM_TYPE_MEMREF_INPUT 0xD +#define TF_PARAM_TYPE_MEMREF_OUTPUT 0xE +#define TF_PARAM_TYPE_MEMREF_INOUT 0xF + +#define TF_PARAM_TYPE_MEMREF_FLAG 0x4 +#define TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG 0x8 + + +#define TF_MAKE_PARAM_TYPES(t0, t1, t2, t3) \ + ((t0) | ((t1) << 4) | ((t2) << 8) | ((t3) << 12)) +#define TF_GET_PARAM_TYPE(t, i) (((t) >> (4 * i)) & 0xF) + +/* + * Login types. + */ +#define TF_LOGIN_PUBLIC 0x00000000 +#define TF_LOGIN_USER 0x00000001 +#define TF_LOGIN_GROUP 0x00000002 +#define TF_LOGIN_APPLICATION 0x00000004 +#define TF_LOGIN_APPLICATION_USER 0x00000005 +#define TF_LOGIN_APPLICATION_GROUP 0x00000006 +#define TF_LOGIN_AUTHENTICATION 0x80000000 +#define TF_LOGIN_PRIVILEGED 0x80000002 + +/* Login variants */ + +#define TF_LOGIN_VARIANT(main_type, os, variant) \ + ((main_type) | (1 << 27) | ((os) << 16) | ((variant) << 8)) + +#define TF_LOGIN_GET_MAIN_TYPE(type) \ + ((type) & ~TF_LOGIN_VARIANT(0, 0xFF, 0xFF)) + +#define TF_LOGIN_OS_ANY 0x00 +#define TF_LOGIN_OS_LINUX 0x01 +#define TF_LOGIN_OS_ANDROID 0x04 + +/* OS-independent variants */ +#define TF_LOGIN_USER_NONE \ + TF_LOGIN_VARIANT(TF_LOGIN_USER, TF_LOGIN_OS_ANY, 0xFF) +#define TF_LOGIN_GROUP_NONE \ + TF_LOGIN_VARIANT(TF_LOGIN_GROUP, TF_LOGIN_OS_ANY, 0xFF) +#define TF_LOGIN_APPLICATION_USER_NONE \ + TF_LOGIN_VARIANT(TF_LOGIN_APPLICATION_USER, TF_LOGIN_OS_ANY, 0xFF) +#define TF_LOGIN_AUTHENTICATION_BINARY_SHA1_HASH \ + TF_LOGIN_VARIANT(TF_LOGIN_AUTHENTICATION, TF_LOGIN_OS_ANY, 0x01) +#define TF_LOGIN_PRIVILEGED_KERNEL \ + TF_LOGIN_VARIANT(TF_LOGIN_PRIVILEGED, TF_LOGIN_OS_ANY, 0x01) + +/* Linux variants */ +#define TF_LOGIN_USER_LINUX_EUID \ + TF_LOGIN_VARIANT(TF_LOGIN_USER, TF_LOGIN_OS_LINUX, 0x01) +#define TF_LOGIN_GROUP_LINUX_GID \ + TF_LOGIN_VARIANT(TF_LOGIN_GROUP, TF_LOGIN_OS_LINUX, 0x01) +#define TF_LOGIN_APPLICATION_LINUX_PATH_SHA1_HASH \ + TF_LOGIN_VARIANT(TF_LOGIN_APPLICATION, TF_LOGIN_OS_LINUX, 0x01) +#define TF_LOGIN_APPLICATION_USER_LINUX_PATH_EUID_SHA1_HASH \ + TF_LOGIN_VARIANT(TF_LOGIN_APPLICATION_USER, TF_LOGIN_OS_LINUX, 0x01) +#define TF_LOGIN_APPLICATION_GROUP_LINUX_PATH_GID_SHA1_HASH \ + TF_LOGIN_VARIANT(TF_LOGIN_APPLICATION_GROUP, TF_LOGIN_OS_LINUX, 0x01) + +/* Android variants */ +#define TF_LOGIN_USER_ANDROID_EUID \ + TF_LOGIN_VARIANT(TF_LOGIN_USER, TF_LOGIN_OS_ANDROID, 0x01) +#define TF_LOGIN_GROUP_ANDROID_GID \ + TF_LOGIN_VARIANT(TF_LOGIN_GROUP, TF_LOGIN_OS_ANDROID, 0x01) +#define TF_LOGIN_APPLICATION_ANDROID_UID \ + TF_LOGIN_VARIANT(TF_LOGIN_APPLICATION, TF_LOGIN_OS_ANDROID, 0x01) +#define TF_LOGIN_APPLICATION_USER_ANDROID_UID_EUID \ + TF_LOGIN_VARIANT(TF_LOGIN_APPLICATION_USER, TF_LOGIN_OS_ANDROID, \ + 0x01) +#define TF_LOGIN_APPLICATION_GROUP_ANDROID_UID_GID \ + TF_LOGIN_VARIANT(TF_LOGIN_APPLICATION_GROUP, TF_LOGIN_OS_ANDROID, \ + 0x01) + +/* + * return origins + */ +#define TF_ORIGIN_COMMS 2 +#define TF_ORIGIN_TEE 3 +#define TF_ORIGIN_TRUSTED_APP 4 +/* + * The message types. + */ +#define TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT 0x02 +#define TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT 0xFD +#define TF_MESSAGE_TYPE_REGISTER_SHARED_MEMORY 0xF7 +#define TF_MESSAGE_TYPE_RELEASE_SHARED_MEMORY 0xF9 +#define TF_MESSAGE_TYPE_OPEN_CLIENT_SESSION 0xF0 +#define TF_MESSAGE_TYPE_CLOSE_CLIENT_SESSION 0xF2 +#define TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND 0xF5 +#define TF_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND 0xF4 +#define TF_MESSAGE_TYPE_MANAGEMENT 0xFE + + +/* + * The SChannel error codes. + */ +#define S_SUCCESS 0x00000000 +#define S_ERROR_OUT_OF_MEMORY 0xFFFF000C + + +struct tf_command_header { + u8 message_size; + u8 message_type; + u16 message_info; + u32 operation_id; +}; + +struct tf_answer_header { + u8 message_size; + u8 message_type; + u16 message_info; + u32 operation_id; + u32 error_code; +}; + +/* + * CREATE_DEVICE_CONTEXT command message. + */ +struct tf_command_create_device_context { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + u32 operation_id; + u32 device_context_id; +}; + +/* + * CREATE_DEVICE_CONTEXT answer message. + */ +struct tf_answer_create_device_context { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + /* an opaque Normal World identifier for the operation */ + u32 operation_id; + u32 error_code; + /* an opaque Normal World identifier for the device context */ + u32 device_context; +}; + +/* + * DESTROY_DEVICE_CONTEXT command message. + */ +struct tf_command_destroy_device_context { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + u32 operation_id; + u32 device_context; +}; + +/* + * DESTROY_DEVICE_CONTEXT answer message. + */ +struct tf_answer_destroy_device_context { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + /* an opaque Normal World identifier for the operation */ + u32 operation_id; + u32 error_code; + u32 device_context_id; +}; + +/* + * OPEN_CLIENT_SESSION command message. + */ +struct tf_command_open_client_session { + u8 message_size; + u8 message_type; + u16 param_types; + /* an opaque Normal World identifier for the operation */ + u32 operation_id; + u32 device_context; + u32 cancellation_id; + u64 timeout; + struct tf_uuid destination_uuid; + union tf_command_param params[4]; + u32 login_type; + /* + * Size = 0 for public, [16] for group identification, [20] for + * authentication + */ + u8 login_data[20]; +}; + +/* + * OPEN_CLIENT_SESSION answer message. + */ +struct tf_answer_open_client_session { + u8 message_size; + u8 message_type; + u8 error_origin; + u8 __reserved; + /* an opaque Normal World identifier for the operation */ + u32 operation_id; + u32 error_code; + u32 client_session; + union tf_answer_param answers[4]; +}; + +/* + * CLOSE_CLIENT_SESSION command message. + */ +struct tf_command_close_client_session { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + /* an opaque Normal World identifier for the operation */ + u32 operation_id; + u32 device_context; + u32 client_session; +}; + +/* + * CLOSE_CLIENT_SESSION answer message. + */ +struct tf_answer_close_client_session { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + /* an opaque Normal World identifier for the operation */ + u32 operation_id; + u32 error_code; +}; + + +/* + * REGISTER_SHARED_MEMORY command message + */ +struct tf_command_register_shared_memory { + u8 message_size; + u8 message_type; + u16 memory_flags; + u32 operation_id; + u32 device_context; + u32 block_id; + u32 shared_mem_size; + u32 shared_mem_start_offset; + u32 shared_mem_descriptors[TF_MAX_COARSE_PAGES]; +}; + +/* + * REGISTER_SHARED_MEMORY answer message. + */ +struct tf_answer_register_shared_memory { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + /* an opaque Normal World identifier for the operation */ + u32 operation_id; + u32 error_code; + u32 block; +}; + +/* + * RELEASE_SHARED_MEMORY command message. + */ +struct tf_command_release_shared_memory { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + /* an opaque Normal World identifier for the operation */ + u32 operation_id; + u32 device_context; + u32 block; +}; + +/* + * RELEASE_SHARED_MEMORY answer message. + */ +struct tf_answer_release_shared_memory { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + u32 operation_id; + u32 error_code; + u32 block_id; +}; + +/* + * INVOKE_CLIENT_COMMAND command message. + */ +struct tf_command_invoke_client_command { + u8 message_size; + u8 message_type; + u16 param_types; + u32 operation_id; + u32 device_context; + u32 client_session; + u64 timeout; + u32 cancellation_id; + u32 client_command_identifier; + union tf_command_param params[4]; +}; + +/* + * INVOKE_CLIENT_COMMAND command answer. + */ +struct tf_answer_invoke_client_command { + u8 message_size; + u8 message_type; + u8 error_origin; + u8 __reserved; + u32 operation_id; + u32 error_code; + union tf_answer_param answers[4]; +}; + +/* + * CANCEL_CLIENT_OPERATION command message. + */ +struct tf_command_cancel_client_operation { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + /* an opaque Normal World identifier for the operation */ + u32 operation_id; + u32 device_context; + u32 client_session; + u32 cancellation_id; +}; + +struct tf_answer_cancel_client_operation { + u8 message_size; + u8 message_type; + u16 message_info_rfu; + u32 operation_id; + u32 error_code; +}; + +/* + * MANAGEMENT command message. + */ +struct tf_command_management { + u8 message_size; + u8 message_type; + u16 command; + u32 operation_id; + u32 w3b_size; + u32 w3b_start_offset; + u32 shared_mem_descriptors[1]; +}; + +/* + * POWER_MANAGEMENT answer message. + * The message does not provide message specific parameters. + * Therefore no need to define a specific answer structure + */ + +/* + * Structure for L2 messages + */ +union tf_command { + struct tf_command_header header; + struct tf_command_create_device_context create_device_context; + struct tf_command_destroy_device_context destroy_device_context; + struct tf_command_open_client_session open_client_session; + struct tf_command_close_client_session close_client_session; + struct tf_command_register_shared_memory register_shared_memory; + struct tf_command_release_shared_memory release_shared_memory; + struct tf_command_invoke_client_command invoke_client_command; + struct tf_command_cancel_client_operation cancel_client_operation; + struct tf_command_management management; +}; + +/* + * Structure for any L2 answer + */ + +union tf_answer { + struct tf_answer_header header; + struct tf_answer_create_device_context create_device_context; + struct tf_answer_open_client_session open_client_session; + struct tf_answer_close_client_session close_client_session; + struct tf_answer_register_shared_memory register_shared_memory; + struct tf_answer_release_shared_memory release_shared_memory; + struct tf_answer_invoke_client_command invoke_client_command; + struct tf_answer_destroy_device_context destroy_device_context; + struct tf_answer_cancel_client_operation cancel_client_operation; +}; + +/* Structure of the Communication Buffer */ +struct tf_l1_shared_buffer { + u32 config_flag_s; + u32 w3b_size_max_s; + u32 reserved0; + u32 w3b_size_current_s; + u8 reserved1[48]; + u8 version_description[TF_DESCRIPTION_BUFFER_LENGTH]; + u32 status_s; + u32 reserved2; + u32 sync_serial_n; + u32 sync_serial_s; + u64 time_n[2]; + u64 timeout_s[2]; + u32 first_command; + u32 first_free_command; + u32 first_answer; + u32 first_free_answer; + u32 w3b_descriptors[128]; + #ifdef CONFIG_TF_ZEBRA + u8 rpc_trace_buffer[140]; + u8 rpc_cus_buffer[180]; + #else + u8 reserved3[320]; + #endif + u32 command_queue[TF_N_MESSAGE_QUEUE_CAPACITY]; + u32 answer_queue[TF_S_ANSWER_QUEUE_CAPACITY]; +}; + + +/* + * tf_version_information_buffer structure description + * Description of the sVersionBuffer handed over from user space to kernel space + * This field is filled by the driver during a CREATE_DEVICE_CONTEXT ioctl + * and handed back to user space + */ +struct tf_version_information_buffer { + u8 driver_description[65]; + u8 secure_world_description[65]; +}; + + +/* The IOCTLs the driver supports */ +#include <linux/ioctl.h> + +#define IOCTL_TF_GET_VERSION _IO('z', 0) +#define IOCTL_TF_EXCHANGE _IOWR('z', 1, union tf_command) +#define IOCTL_TF_GET_DESCRIPTION _IOR('z', 2, \ + struct tf_version_information_buffer) +#ifdef CONFIG_TF_ION +#define IOCTL_TF_ION_REGISTER _IOR('z', 254, int) +#define IOCTL_TF_ION_UNREGISTER _IOR('z', 255, int) +#endif + +#endif /* !defined(__TF_PROTOCOL_H__) */ diff --git a/security/smc/tf_teec.c b/security/smc/tf_teec.c new file mode 100644 index 0000000..6e6d5b2 --- /dev/null +++ b/security/smc/tf_teec.c @@ -0,0 +1,624 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifdef CONFIG_TF_TEEC + +#include <asm/atomic.h> +#include <asm/system.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/pagemap.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/vmalloc.h> + +#include "tf_protocol.h" +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_comm.h" +#include "tf_conn.h" +#include "tf_teec.h" + +#include "tee_client_api.h" + +#define TF_COMMAND_BYTES(cmd) \ + (sizeof(cmd) - sizeof(struct tf_command_header)) +#define TF_COMMAND_SIZE(cmd) \ + (TF_COMMAND_BYTES(cmd) / sizeof(u32)) + +/* Associate TEEC errors to POSIX/Linux errors. The matching is somewhat + arbitrary but one-to-one for supported error codes. */ +int TEEC_decode_error(TEEC_Result ret) +{ + switch (ret) + { + case TEEC_SUCCESS: return 0; + case TEEC_ERROR_GENERIC: return -EIO; + case TEEC_ERROR_ACCESS_DENIED: return -EPERM; + case TEEC_ERROR_CANCEL: return -ECANCELED; + case TEEC_ERROR_ACCESS_CONFLICT: return -EBUSY; + case TEEC_ERROR_EXCESS_DATA: return -E2BIG; + case TEEC_ERROR_BAD_FORMAT: return -EDOM; + case TEEC_ERROR_BAD_PARAMETERS: return -EINVAL; + case TEEC_ERROR_BAD_STATE: return -EBADFD; + case TEEC_ERROR_ITEM_NOT_FOUND: return -ENOENT; + case TEEC_ERROR_NOT_IMPLEMENTED: return -EPROTONOSUPPORT; + case TEEC_ERROR_NOT_SUPPORTED: return -ENOSYS; + case TEEC_ERROR_NO_DATA: return -ENODATA; + case TEEC_ERROR_OUT_OF_MEMORY: return -ENOMEM; + case TEEC_ERROR_BUSY: return -EAGAIN; + case TEEC_ERROR_COMMUNICATION: return -EPIPE; + case TEEC_ERROR_SECURITY: return -ECONNABORTED; + case TEEC_ERROR_SHORT_BUFFER: return -EFBIG; + default: return -EIO; + } +} + +/* Associate POSIX/Linux errors to TEEC errors. The matching is somewhat + arbitrary, but TEEC_encode_error(TEEC_decode_error(x))==x for supported + error codes. */ +TEEC_Result TEEC_encode_error(int err) +{ + if (err >= 0) { + return S_SUCCESS; + } + switch (err) { + case 0: return TEEC_SUCCESS; + case -EIO: return TEEC_ERROR_GENERIC; + case -EPERM: return TEEC_ERROR_ACCESS_DENIED; + case -ECANCELED: return TEEC_ERROR_CANCEL; + case -EBUSY: return TEEC_ERROR_ACCESS_CONFLICT; + case -E2BIG: return TEEC_ERROR_EXCESS_DATA; + case -EDOM: return TEEC_ERROR_BAD_FORMAT; + case -EINVAL: return TEEC_ERROR_BAD_PARAMETERS; + case -EBADFD: return TEEC_ERROR_BAD_STATE; + case -ENOENT: return TEEC_ERROR_ITEM_NOT_FOUND; + case -EPROTONOSUPPORT: return TEEC_ERROR_NOT_IMPLEMENTED; + case -ENOSYS: return TEEC_ERROR_NOT_SUPPORTED; + case -ENODATA: return TEEC_ERROR_NO_DATA; + case -ENOMEM: return TEEC_ERROR_OUT_OF_MEMORY; + case -EAGAIN: return TEEC_ERROR_BUSY; + case -EPIPE: return TEEC_ERROR_COMMUNICATION; + case -ECONNABORTED: return TEEC_ERROR_SECURITY; + case -EFBIG: return TEEC_ERROR_SHORT_BUFFER; + default: return TEEC_ERROR_GENERIC; + } +} + +/* Encode a TEEC time limit into an SChannel time limit. */ +static u64 TEEC_encode_timeout(const TEEC_TimeLimit *timeLimit) +{ + if (timeLimit == NULL) { + return (u64)-1; + } else { + return *timeLimit; + } +} + +/* Convert a timeout into a time limit in our internal format. */ +void TEEC_GetTimeLimit(TEEC_Context* sContext, + uint32_t nTimeout, /*ms from now*/ + TEEC_TimeLimit* sTimeLimit) +{ + /*Use the kernel time as the TEE time*/ + struct timeval now; + do_gettimeofday(&now); + *sTimeLimit = + ((TEEC_TimeLimit)now.tv_sec * 1000 + + now.tv_usec / 1000 + + nTimeout); +} + +#define TF_PARAM_TYPE_INPUT_FLAG 0x1 +#define TF_PARAM_TYPE_OUTPUT_FLAG 0x2 +#define TF_PARAM_TYPE_MEMREF_FLAG 0x4 +#define TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG 0x8 + +/* Update the type of a whole memref with the direction deduced from + the INPUT and OUTPUT flags of the memref. */ +static void TEEC_encode_whole_memref_flags(u16 *param_types, + unsigned i, + u32 flags) +{ + if (flags & TEEC_MEM_INPUT) + *param_types |= TF_PARAM_TYPE_INPUT_FLAG << (4*i); + if (flags & TEEC_MEM_OUTPUT) + *param_types |= TF_PARAM_TYPE_OUTPUT_FLAG << (4*i); +} + +/* Encode the parameters and type of an operation from the TEE API format + into an SChannel message. */ +void TEEC_encode_parameters(u16 *param_types, + union tf_command_param *params, + TEEC_Operation *operation) +{ + unsigned i; + if (operation == NULL) { + *param_types = 0; + return; + } + *param_types = operation->paramTypes; + for (i = 0; i < 4; i++) { + unsigned ty = TF_GET_PARAM_TYPE(operation->paramTypes, i); + TEEC_Parameter *op = operation->params + i; + if (ty & TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG) { + TEEC_SharedMemory *sm = op->memref.parent; + params[i].memref.block = sm->imp._block; + if (ty == TEEC_MEMREF_WHOLE) { + TEEC_encode_whole_memref_flags(param_types, i, + sm->flags); + params[i].memref.size = sm->size; + params[i].memref.offset = 0; + } else { + params[i].memref.size = op->memref.size; + params[i].memref.offset = op->memref.offset; + } + } else if (ty & TF_PARAM_TYPE_MEMREF_FLAG) { + /* Set up what tf_map_temp_shmem (called by + tf_open_client_session and + tf_invoke_client_command) expects: + .descriptor and .offset to both be set to the + address of the buffer. */ + u32 address = (u32)op->tmpref.buffer; + params[i].temp_memref.descriptor = address; + params[i].temp_memref.size = op->tmpref.size; + params[i].temp_memref.offset = address; + } else if (ty & TF_PARAM_TYPE_INPUT_FLAG) { + params[i].value.a = op->value.a; + params[i].value.b = op->value.b; + } else { + /* output-only value or none, so nothing to do */ + } + } +} + +/* Decode updated parameters from an SChannel answer into the TEE API format. */ +void TEEC_decode_parameters(union tf_answer_param *params, + TEEC_Operation *operation) +{ + unsigned i; + if (operation == NULL) { + return; + } + for (i = 0; i < 4; i++) { + unsigned ty = TF_GET_PARAM_TYPE(operation->paramTypes, i); + TEEC_Parameter *op = operation->params + i; + if (!(ty & TF_PARAM_TYPE_OUTPUT_FLAG)) { + /* input-only or none, so nothing to do */ + } else if (ty & TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG) { + op->memref.size = params[i].size.size; + } else if (ty & TF_PARAM_TYPE_MEMREF_FLAG) { + op->tmpref.size = params[i].size.size; + } else { + op->value.a = params[i].value.a; + op->value.b = params[i].value.b; + } + } +} + +/* Start a potentially-cancellable operation. */ +void TEEC_start_operation(TEEC_Context *context, + TEEC_Session *session, + TEEC_Operation *operation) +{ + if (operation != NULL) { + operation->imp._pSession = session; + /* Flush the assignment to imp._pSession, so that + RequestCancellation can read that field if started==1. */ + barrier(); + operation->started = 1; + } +} + +/* Mark a potentially-cancellable operation as finished. */ +void TEEC_finish_operation(TEEC_Operation *operation) +{ + if (operation != NULL) { + operation->started = 2; + barrier(); + } +} + + + +TEEC_Result TEEC_InitializeContext(const char *name, + TEEC_Context *context) +{ + int error; + struct tf_connection *connection = NULL; + + error = tf_open(tf_get_device(), NULL, &connection); + if (error != 0) { + dprintk(KERN_ERR "TEEC_InitializeContext(%s): " + "tf_open failed (error %d)!\n", + (name == NULL ? "(null)" : name), error); + goto error; + } + BUG_ON(connection == NULL); + connection->owner = TF_CONNECTION_OWNER_KERNEL; + + error = tf_create_device_context(connection); + if (error != 0) { + dprintk(KERN_ERR "TEEC_InitializeContext(%s): " + "tf_create_device_context failed (error %d)!\n", + (name == NULL ? "(null)" : name), error); + goto error; + } + + context->imp._connection = connection; + /*spin_lock_init(&context->imp._operations_lock);*/ + return S_SUCCESS; + +error: + tf_close(connection); + return TEEC_encode_error(error); +} + +void TEEC_FinalizeContext(TEEC_Context *context) +{ + struct tf_connection *connection = context->imp._connection; + dprintk(KERN_DEBUG "TEEC_FinalizeContext: connection=%p", connection); + tf_close(connection); + context->imp._connection = NULL; +} + +TEEC_Result TEEC_RegisterSharedMemory(TEEC_Context* context, + TEEC_SharedMemory* sharedMem) +{ + union tf_command command_message = {{0}}; + struct tf_command_register_shared_memory *cmd = + &command_message.register_shared_memory; + union tf_answer answer_message; + struct tf_answer_register_shared_memory *ans = + &answer_message.register_shared_memory; + TEEC_Result ret; + memset(&sharedMem->imp, 0, sizeof(sharedMem->imp)); + + cmd->message_size = TF_COMMAND_SIZE(*cmd); + cmd->message_type = TF_MESSAGE_TYPE_REGISTER_SHARED_MEMORY; + cmd->memory_flags = sharedMem->flags; + cmd->operation_id = (u32)&answer_message; + cmd->device_context = (u32)context; + /*cmd->block_id will be set by tf_register_shared_memory*/ + cmd->shared_mem_size = sharedMem->size; + cmd->shared_mem_start_offset = 0; + cmd->shared_mem_descriptors[0] = (u32)sharedMem->buffer; + + ret = TEEC_encode_error( + tf_register_shared_memory(context->imp._connection, + &command_message, + &answer_message)); + if (ret == TEEC_SUCCESS) { + ret = ans->error_code; + } + if (ret == S_SUCCESS) { + sharedMem->imp._context = context; + sharedMem->imp._block = ans->block; + } + return ret; +} + +#define TEEC_POINTER_TO_ZERO_SIZED_BUFFER ((void*)0x010) + +TEEC_Result TEEC_AllocateSharedMemory(TEEC_Context* context, + TEEC_SharedMemory* sharedMem) +{ + TEEC_Result ret; + dprintk(KERN_DEBUG "TEEC_AllocateSharedMemory: requested=%lu", + (unsigned long)sharedMem->size); + if (sharedMem->size == 0) { + /* Allocating 0 bytes must return a non-NULL pointer, but the + pointer doesn't need to be to memory that is mapped + anywhere. So we return a pointer into an unmapped page. */ + sharedMem->buffer = TEEC_POINTER_TO_ZERO_SIZED_BUFFER; + } else { + sharedMem->buffer = internal_vmalloc(sharedMem->size); + if (sharedMem->buffer == NULL) + { + dprintk(KERN_INFO "TEEC_AllocateSharedMemory: could not allocate %lu bytes", + (unsigned long)sharedMem->size); + return TEEC_ERROR_OUT_OF_MEMORY; + } + } + + ret = TEEC_RegisterSharedMemory(context, sharedMem); + if (ret == TEEC_SUCCESS) + { + sharedMem->imp._allocated = 1; + } + else + { + internal_vfree(sharedMem->buffer); + sharedMem->buffer = NULL; + memset(&sharedMem->imp, 0, sizeof(sharedMem->imp)); + } + return ret; +} + +void TEEC_ReleaseSharedMemory(TEEC_SharedMemory* sharedMem) +{ + TEEC_Context *context = sharedMem->imp._context; + union tf_command command_message = {{0}}; + struct tf_command_release_shared_memory *cmd = + &command_message.release_shared_memory; + union tf_answer answer_message; + + cmd->message_size = TF_COMMAND_SIZE(*cmd); + cmd->message_type = TF_MESSAGE_TYPE_RELEASE_SHARED_MEMORY; + cmd->operation_id = (u32)&answer_message; + cmd->device_context = (u32)context; + cmd->block = sharedMem->imp._block; + + tf_release_shared_memory(context->imp._connection, + &command_message, + &answer_message); + if (sharedMem->imp._allocated) { + if (sharedMem->buffer != TEEC_POINTER_TO_ZERO_SIZED_BUFFER) { + internal_vfree(sharedMem->buffer); + } + sharedMem->buffer = NULL; + sharedMem->size = 0; + } + memset(&sharedMem->imp, 0, sizeof(sharedMem->imp)); +} + +TEEC_Result TEEC_OpenSessionEx(TEEC_Context* context, + TEEC_Session* session, + const TEEC_TimeLimit* timeLimit, + const TEEC_UUID* destination, + u32 connectionMethod, + void* connectionData, + TEEC_Operation* operation, + u32* errorOrigin) +{ + union tf_command command_message = {{0}}; + struct tf_command_open_client_session *cmd = + &command_message.open_client_session; + union tf_answer answer_message = {{0}}; + struct tf_answer_open_client_session *ans = + &answer_message.open_client_session; + TEEC_Result ret; + + /* Note that we set the message size to the whole size of the + structure. tf_open_client_session will adjust it down + to trim the unnecessary portion of the login_data field. */ + cmd->message_size = TF_COMMAND_SIZE(*cmd); + cmd->message_type = TF_MESSAGE_TYPE_OPEN_CLIENT_SESSION; + cmd->operation_id = (u32)&answer_message; + cmd->device_context = (u32)context; + cmd->cancellation_id = (u32)operation; + cmd->timeout = TEEC_encode_timeout(timeLimit); + memcpy(&cmd->destination_uuid, destination, + sizeof(cmd->destination_uuid)); + cmd->login_type = connectionMethod; + TEEC_encode_parameters(&cmd->param_types, cmd->params, operation); + + switch (connectionMethod) + { + case TEEC_LOGIN_PRIVILEGED: + case TEEC_LOGIN_PUBLIC: + break; + case TEEC_LOGIN_APPLICATION: + case TEEC_LOGIN_USER: + case TEEC_LOGIN_USER_APPLICATION: + case TEEC_LOGIN_GROUP: + case TEEC_LOGIN_GROUP_APPLICATION: + default: + return TEEC_ERROR_NOT_IMPLEMENTED; + } + + TEEC_start_operation(context, session, operation); + + ret = TEEC_encode_error( + tf_open_client_session(context->imp._connection, + &command_message, + &answer_message)); + + TEEC_finish_operation(operation); + TEEC_decode_parameters(ans->answers, operation); + if (errorOrigin != NULL) { + *errorOrigin = (ret == TEEC_SUCCESS ? + ans->error_origin : + TEEC_ORIGIN_COMMS); + } + + if (ret == TEEC_SUCCESS) { + ret = ans->error_code; + } + if (ret == S_SUCCESS) { + session->imp._client_session = ans->client_session; + session->imp._context = context; + } + return ret; +} + +TEEC_Result TEEC_OpenSession(TEEC_Context* context, + TEEC_Session* session, + const TEEC_UUID* destination, + u32 connectionMethod, + void* connectionData, + TEEC_Operation* operation, + u32* errorOrigin) +{ + return TEEC_OpenSessionEx(context, session, + NULL, /*timeLimit*/ + destination, + connectionMethod, connectionData, + operation, errorOrigin); +} + +void TEEC_CloseSession(TEEC_Session* session) +{ + if (session != NULL) { + TEEC_Context *context = session->imp._context; + union tf_command command_message = {{0}}; + struct tf_command_close_client_session *cmd = + &command_message.close_client_session; + union tf_answer answer_message; + + cmd->message_size = TF_COMMAND_SIZE(*cmd); + cmd->message_type = TF_MESSAGE_TYPE_CLOSE_CLIENT_SESSION; + cmd->operation_id = (u32)&answer_message; + cmd->device_context = (u32)context; + cmd->client_session = session->imp._client_session; + + tf_close_client_session(context->imp._connection, + &command_message, + &answer_message); + + session->imp._client_session = 0; + session->imp._context = NULL; + } +} + +TEEC_Result TEEC_InvokeCommandEx(TEEC_Session* session, + const TEEC_TimeLimit* timeLimit, + u32 commandID, + TEEC_Operation* operation, + u32* errorOrigin) +{ + TEEC_Context *context = session->imp._context; + union tf_command command_message = {{0}}; + struct tf_command_invoke_client_command *cmd = + &command_message.invoke_client_command; + union tf_answer answer_message = {{0}}; + struct tf_answer_invoke_client_command *ans = + &answer_message.invoke_client_command; + TEEC_Result ret; + + cmd->message_size = TF_COMMAND_SIZE(*cmd); + cmd->message_type = TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND; + cmd->operation_id = (u32)&answer_message; + cmd->device_context = (u32)context; + cmd->client_session = session->imp._client_session; + cmd->timeout = TEEC_encode_timeout(timeLimit); + cmd->cancellation_id = (u32)operation; + cmd->client_command_identifier = commandID; + TEEC_encode_parameters(&cmd->param_types, cmd->params, operation); + + TEEC_start_operation(context, session, operation); + + ret = TEEC_encode_error( + tf_invoke_client_command(context->imp._connection, + &command_message, + &answer_message)); + + TEEC_finish_operation(operation); + TEEC_decode_parameters(ans->answers, operation); + if (errorOrigin != NULL) { + *errorOrigin = (ret == TEEC_SUCCESS ? + ans->error_origin : + TEEC_ORIGIN_COMMS); + } + + if (ret == TEEC_SUCCESS) { + ret = ans->error_code; + } + return ret; +} + +TEEC_Result TEEC_InvokeCommand(TEEC_Session* session, + u32 commandID, + TEEC_Operation* operation, + u32* errorOrigin) +{ + return TEEC_InvokeCommandEx(session, + NULL, /*timeLimit*/ + commandID, + operation, errorOrigin); +} + +TEEC_Result TEEC_send_cancellation_message(TEEC_Context *context, + u32 client_session, + u32 cancellation_id) +{ + union tf_command command_message = {{0}}; + struct tf_command_cancel_client_operation *cmd = + &command_message.cancel_client_operation; + union tf_answer answer_message = {{0}}; + struct tf_answer_cancel_client_operation *ans = + &answer_message.cancel_client_operation; + TEEC_Result ret; + + cmd->message_size = TF_COMMAND_SIZE(*cmd); + cmd->message_type = TF_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND; + cmd->operation_id = (u32)&answer_message; + cmd->device_context = (u32)context; + cmd->client_session = client_session; + cmd->cancellation_id = cancellation_id; + + ret = TEEC_encode_error( + tf_cancel_client_command(context->imp._connection, + &command_message, + &answer_message)); + + if (ret == TEEC_SUCCESS) { + ret = ans->error_code; + } + return ret; +} + +void TEEC_RequestCancellation(TEEC_Operation* operation) +{ + TEEC_Result ret; + while (1) { + u32 state = operation->started; + switch (state) { + case 0: /*The operation data structure isn't initialized yet*/ + break; + + case 1: /*operation is in progress in the client*/ + ret = TEEC_send_cancellation_message( + operation->imp._pSession->imp._context, + operation->imp._pSession->imp._client_session, + (u32)operation); + if (ret == TEEC_SUCCESS) { + /*The cancellation was successful*/ + return; + } + /* The command has either not reached the secure world + yet or has completed already. Either way, retry. */ + break; + + case 2: /*operation has completed already*/ + return; + } + /* Since we're busy-waiting for the operation to be started + or finished, yield. */ + schedule(); + } +} + +EXPORT_SYMBOL(TEEC_encode_error); +EXPORT_SYMBOL(TEEC_decode_error); +EXPORT_SYMBOL(TEEC_InitializeContext); +EXPORT_SYMBOL(TEEC_FinalizeContext); +EXPORT_SYMBOL(TEEC_RegisterSharedMemory); +EXPORT_SYMBOL(TEEC_AllocateSharedMemory); +EXPORT_SYMBOL(TEEC_ReleaseSharedMemory); +EXPORT_SYMBOL(TEEC_OpenSession); +EXPORT_SYMBOL(TEEC_CloseSession); +EXPORT_SYMBOL(TEEC_InvokeCommand); +EXPORT_SYMBOL(TEEC_RequestCancellation); + +#endif /* defined(CONFIG_TF_TEEC) */ diff --git a/security/smc/omap4/scxlnx_util.c b/security/smc/tf_teec.h index 90cd831..28b3287 100644 --- a/security/smc/omap4/scxlnx_util.c +++ b/security/smc/tf_teec.h @@ -1,5 +1,5 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. +/** + * Copyright (c) 2011 Trusted Logic S.A. * All Rights Reserved. * * This program is free software; you can redistribute it and/or @@ -16,30 +16,18 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ -#include <linux/mman.h> -#include "scxlnx_util.h" -void *internal_kmalloc(size_t nSize, int nPriority) -{ - void *pResult; - struct SCXLNX_DEVICE *pDevice = SCXLNXGetDevice(); +#ifndef __TF_TEEC_H__ +#define __TF_TEEC_H__ - pResult = kmalloc(nSize, nPriority); +#ifdef CONFIG_TF_TEEC - if (pResult != NULL) - atomic_inc( - &pDevice->sDeviceStats.stat_memories_allocated); +#include "tf_defs.h" +#include "tee_client_api.h" - return pResult; -} +TEEC_Result TEEC_encode_error(int err); +int TEEC_decode_error(TEEC_Result ret); -void internal_kfree(void *pMemory) -{ - struct SCXLNX_DEVICE *pDevice = SCXLNXGetDevice(); - - if (pMemory != NULL) - atomic_dec( - &pDevice->sDeviceStats.stat_memories_allocated); - return kfree(pMemory); -} +#endif /* defined(CONFIG_TF_TEEC) */ +#endif /* !defined(__TF_TEEC_H__) */ diff --git a/security/smc/tf_util.c b/security/smc/tf_util.c new file mode 100644 index 0000000..ec9941b --- /dev/null +++ b/security/smc/tf_util.c @@ -0,0 +1,1145 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <linux/mman.h> +#include "tf_util.h" + +/*---------------------------------------------------------------------------- + * Debug printing routines + *----------------------------------------------------------------------------*/ +#ifdef CONFIG_TF_DRIVER_DEBUG_SUPPORT + +void address_cache_property(unsigned long va) +{ + unsigned long pa; + unsigned long inner; + unsigned long outer; + + asm volatile ("mcr p15, 0, %0, c7, c8, 0" : : "r" (va)); + asm volatile ("mrc p15, 0, %0, c7, c4, 0" : "=r" (pa)); + + dprintk(KERN_INFO "VA:%x, PA:%x\n", + (unsigned int) va, + (unsigned int) pa); + + if (pa & 1) { + dprintk(KERN_INFO "Prop Error\n"); + return; + } + + outer = (pa >> 2) & 3; + dprintk(KERN_INFO "\touter : %x", (unsigned int) outer); + + switch (outer) { + case 3: + dprintk(KERN_INFO "Write-Back, no Write-Allocate\n"); + break; + case 2: + dprintk(KERN_INFO "Write-Through, no Write-Allocate.\n"); + break; + case 1: + dprintk(KERN_INFO "Write-Back, Write-Allocate.\n"); + break; + case 0: + dprintk(KERN_INFO "Non-cacheable.\n"); + break; + } + + inner = (pa >> 4) & 7; + dprintk(KERN_INFO "\tinner : %x", (unsigned int)inner); + + switch (inner) { + case 7: + dprintk(KERN_INFO "Write-Back, no Write-Allocate\n"); + break; + case 6: + dprintk(KERN_INFO "Write-Through.\n"); + break; + case 5: + dprintk(KERN_INFO "Write-Back, Write-Allocate.\n"); + break; + case 3: + dprintk(KERN_INFO "Device.\n"); + break; + case 1: + dprintk(KERN_INFO "Strongly-ordered.\n"); + break; + case 0: + dprintk(KERN_INFO "Non-cacheable.\n"); + break; + } + + if (pa & 0x00000002) + dprintk(KERN_INFO "SuperSection.\n"); + if (pa & 0x00000080) + dprintk(KERN_INFO "Memory is shareable.\n"); + else + dprintk(KERN_INFO "Memory is non-shareable.\n"); + + if (pa & 0x00000200) + dprintk(KERN_INFO "Non-secure.\n"); +} + +#ifdef CONFIG_BENCH_SECURE_CYCLE + +#define LOOP_SIZE (100000) + +void run_bogo_mips(void) +{ + uint32_t cycles; + void *address = &run_bogo_mips; + + dprintk(KERN_INFO "BogoMIPS:\n"); + + setup_counters(); + cycles = run_code_speed(LOOP_SIZE); + dprintk(KERN_INFO "%u cycles with code access\n", cycles); + cycles = run_data_speed(LOOP_SIZE, (unsigned long)address); + dprintk(KERN_INFO "%u cycles to access %x\n", cycles, + (unsigned int) address); +} + +#endif /* CONFIG_BENCH_SECURE_CYCLE */ + +/* + * Dump the L1 shared buffer. + */ +void tf_dump_l1_shared_buffer(struct tf_l1_shared_buffer *buffer) +{ + dprintk(KERN_INFO + "buffer@%p:\n" + " config_flag_s=%08X\n" + " version_description=%64s\n" + " status_s=%08X\n" + " sync_serial_n=%08X\n" + " sync_serial_s=%08X\n" + " time_n[0]=%016llX\n" + " time_n[1]=%016llX\n" + " timeout_s[0]=%016llX\n" + " timeout_s[1]=%016llX\n" + " first_command=%08X\n" + " first_free_command=%08X\n" + " first_answer=%08X\n" + " first_free_answer=%08X\n\n", + buffer, + buffer->config_flag_s, + buffer->version_description, + buffer->status_s, + buffer->sync_serial_n, + buffer->sync_serial_s, + buffer->time_n[0], + buffer->time_n[1], + buffer->timeout_s[0], + buffer->timeout_s[1], + buffer->first_command, + buffer->first_free_command, + buffer->first_answer, + buffer->first_free_answer); +} + + +/* + * Dump the specified SChannel message using dprintk. + */ +void tf_dump_command(union tf_command *command) +{ + u32 i; + + dprintk(KERN_INFO "message@%p:\n", command); + + switch (command->header.message_type) { + case TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT\n" + " operation_id = 0x%08X\n" + " device_context_id = 0x%08X\n", + command->header.message_size, + command->header.message_type, + command->header.operation_id, + command->create_device_context.device_context_id + ); + break; + + case TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT\n" + " operation_id = 0x%08X\n" + " device_context = 0x%08X\n", + command->header.message_size, + command->header.message_type, + command->header.operation_id, + command->destroy_device_context.device_context); + break; + + case TF_MESSAGE_TYPE_OPEN_CLIENT_SESSION: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_OPEN_CLIENT_SESSION\n" + " param_types = 0x%04X\n" + " operation_id = 0x%08X\n" + " device_context = 0x%08X\n" + " cancellation_id = 0x%08X\n" + " timeout = 0x%016llX\n" + " destination_uuid = " + "%08X-%04X-%04X-%02X%02X-" + "%02X%02X%02X%02X%02X%02X\n", + command->header.message_size, + command->header.message_type, + command->open_client_session.param_types, + command->header.operation_id, + command->open_client_session.device_context, + command->open_client_session.cancellation_id, + command->open_client_session.timeout, + command->open_client_session.destination_uuid. + time_low, + command->open_client_session.destination_uuid. + time_mid, + command->open_client_session.destination_uuid. + time_hi_and_version, + command->open_client_session.destination_uuid. + clock_seq_and_node[0], + command->open_client_session.destination_uuid. + clock_seq_and_node[1], + command->open_client_session.destination_uuid. + clock_seq_and_node[2], + command->open_client_session.destination_uuid. + clock_seq_and_node[3], + command->open_client_session.destination_uuid. + clock_seq_and_node[4], + command->open_client_session.destination_uuid. + clock_seq_and_node[5], + command->open_client_session.destination_uuid. + clock_seq_and_node[6], + command->open_client_session.destination_uuid. + clock_seq_and_node[7] + ); + + for (i = 0; i < 4; i++) { + uint32_t *param = (uint32_t *) &command-> + open_client_session.params[i]; + dprintk(KERN_INFO " params[%d] = " + "0x%08X:0x%08X:0x%08X\n", + i, param[0], param[1], param[2]); + } + + switch (TF_LOGIN_GET_MAIN_TYPE( + command->open_client_session.login_type)) { + case TF_LOGIN_PUBLIC: + dprintk( + KERN_INFO " login_type = " + "TF_LOGIN_PUBLIC\n"); + break; + case TF_LOGIN_USER: + dprintk( + KERN_INFO " login_type = " + "TF_LOGIN_USER\n"); + break; + case TF_LOGIN_GROUP: + dprintk( + KERN_INFO " login_type = " + "TF_LOGIN_GROUP\n"); + break; + case TF_LOGIN_APPLICATION: + dprintk( + KERN_INFO " login_type = " + "TF_LOGIN_APPLICATION\n"); + break; + case TF_LOGIN_APPLICATION_USER: + dprintk( + KERN_INFO " login_type = " + "TF_LOGIN_APPLICATION_USER\n"); + break; + case TF_LOGIN_APPLICATION_GROUP: + dprintk( + KERN_INFO " login_type = " + "TF_LOGIN_APPLICATION_GROUP\n"); + break; + case TF_LOGIN_AUTHENTICATION: + dprintk( + KERN_INFO " login_type = " + "TF_LOGIN_AUTHENTICATION\n"); + break; + case TF_LOGIN_PRIVILEGED: + dprintk( + KERN_INFO " login_type = " + "TF_LOGIN_PRIVILEGED\n"); + break; + case TF_LOGIN_PRIVILEGED_KERNEL: + dprintk( + KERN_INFO " login_type = " + "TF_LOGIN_PRIVILEGED_KERNEL\n"); + break; + default: + dprintk( + KERN_ERR " login_type = " + "0x%08X (Unknown login type)\n", + command->open_client_session.login_type); + break; + } + + dprintk( + KERN_INFO " login_data = "); + for (i = 0; i < 20; i++) + dprintk( + KERN_INFO "%d", + command->open_client_session. + login_data[i]); + dprintk("\n"); + break; + + case TF_MESSAGE_TYPE_CLOSE_CLIENT_SESSION: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_CLOSE_CLIENT_SESSION\n" + " operation_id = 0x%08X\n" + " device_context = 0x%08X\n" + " client_session = 0x%08X\n", + command->header.message_size, + command->header.message_type, + command->header.operation_id, + command->close_client_session.device_context, + command->close_client_session.client_session + ); + break; + + case TF_MESSAGE_TYPE_REGISTER_SHARED_MEMORY: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_REGISTER_SHARED_MEMORY\n" + " memory_flags = 0x%04X\n" + " operation_id = 0x%08X\n" + " device_context = 0x%08X\n" + " block_id = 0x%08X\n" + " shared_mem_size = 0x%08X\n" + " shared_mem_start_offset = 0x%08X\n" + " shared_mem_descriptors[0] = 0x%08X\n" + " shared_mem_descriptors[1] = 0x%08X\n" + " shared_mem_descriptors[2] = 0x%08X\n" + " shared_mem_descriptors[3] = 0x%08X\n" + " shared_mem_descriptors[4] = 0x%08X\n" + " shared_mem_descriptors[5] = 0x%08X\n" + " shared_mem_descriptors[6] = 0x%08X\n" + " shared_mem_descriptors[7] = 0x%08X\n", + command->header.message_size, + command->header.message_type, + command->register_shared_memory.memory_flags, + command->header.operation_id, + command->register_shared_memory.device_context, + command->register_shared_memory.block_id, + command->register_shared_memory.shared_mem_size, + command->register_shared_memory. + shared_mem_start_offset, + command->register_shared_memory. + shared_mem_descriptors[0], + command->register_shared_memory. + shared_mem_descriptors[1], + command->register_shared_memory. + shared_mem_descriptors[2], + command->register_shared_memory. + shared_mem_descriptors[3], + command->register_shared_memory. + shared_mem_descriptors[4], + command->register_shared_memory. + shared_mem_descriptors[5], + command->register_shared_memory. + shared_mem_descriptors[6], + command->register_shared_memory. + shared_mem_descriptors[7]); + break; + + case TF_MESSAGE_TYPE_RELEASE_SHARED_MEMORY: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_RELEASE_SHARED_MEMORY\n" + " operation_id = 0x%08X\n" + " device_context = 0x%08X\n" + " block = 0x%08X\n", + command->header.message_size, + command->header.message_type, + command->header.operation_id, + command->release_shared_memory.device_context, + command->release_shared_memory.block); + break; + + case TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND\n" + " param_types = 0x%04X\n" + " operation_id = 0x%08X\n" + " device_context = 0x%08X\n" + " client_session = 0x%08X\n" + " timeout = 0x%016llX\n" + " cancellation_id = 0x%08X\n" + " client_command_identifier = 0x%08X\n", + command->header.message_size, + command->header.message_type, + command->invoke_client_command.param_types, + command->header.operation_id, + command->invoke_client_command.device_context, + command->invoke_client_command.client_session, + command->invoke_client_command.timeout, + command->invoke_client_command.cancellation_id, + command->invoke_client_command. + client_command_identifier + ); + + for (i = 0; i < 4; i++) { + uint32_t *param = (uint32_t *) &command-> + open_client_session.params[i]; + dprintk(KERN_INFO " params[%d] = " + "0x%08X:0x%08X:0x%08X\n", i, + param[0], param[1], param[2]); + } + break; + + case TF_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND\n" + " operation_id = 0x%08X\n" + " device_context = 0x%08X\n" + " client_session = 0x%08X\n", + command->header.message_size, + command->header.message_type, + command->header.operation_id, + command->cancel_client_operation.device_context, + command->cancel_client_operation.client_session); + break; + + case TF_MESSAGE_TYPE_MANAGEMENT: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_MANAGEMENT\n" + " operation_id = 0x%08X\n" + " command = 0x%08X\n" + " w3b_size = 0x%08X\n" + " w3b_start_offset = 0x%08X\n", + command->header.message_size, + command->header.message_type, + command->header.operation_id, + command->management.command, + command->management.w3b_size, + command->management.w3b_start_offset); + break; + + default: + dprintk( + KERN_ERR " message_type = 0x%08X " + "(Unknown message type)\n", + command->header.message_type); + break; + } +} + + +/* + * Dump the specified SChannel answer using dprintk. + */ +void tf_dump_answer(union tf_answer *answer) +{ + u32 i; + dprintk( + KERN_INFO "answer@%p:\n", + answer); + + switch (answer->header.message_type) { + case TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "tf_answer_create_device_context\n" + " operation_id = 0x%08X\n" + " error_code = 0x%08X\n" + " device_context = 0x%08X\n", + answer->header.message_size, + answer->header.message_type, + answer->header.operation_id, + answer->create_device_context.error_code, + answer->create_device_context.device_context); + break; + + case TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "ANSWER_DESTROY_DEVICE_CONTEXT\n" + " operation_id = 0x%08X\n" + " error_code = 0x%08X\n" + " device_context_id = 0x%08X\n", + answer->header.message_size, + answer->header.message_type, + answer->header.operation_id, + answer->destroy_device_context.error_code, + answer->destroy_device_context.device_context_id); + break; + + + case TF_MESSAGE_TYPE_OPEN_CLIENT_SESSION: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "tf_answer_open_client_session\n" + " error_origin = 0x%02X\n" + " operation_id = 0x%08X\n" + " error_code = 0x%08X\n" + " client_session = 0x%08X\n", + answer->header.message_size, + answer->header.message_type, + answer->open_client_session.error_origin, + answer->header.operation_id, + answer->open_client_session.error_code, + answer->open_client_session.client_session); + for (i = 0; i < 4; i++) { + dprintk(KERN_INFO " answers[%d]=0x%08X:0x%08X\n", + i, + answer->open_client_session.answers[i]. + value.a, + answer->open_client_session.answers[i]. + value.b); + } + break; + + case TF_MESSAGE_TYPE_CLOSE_CLIENT_SESSION: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "ANSWER_CLOSE_CLIENT_SESSION\n" + " operation_id = 0x%08X\n" + " error_code = 0x%08X\n", + answer->header.message_size, + answer->header.message_type, + answer->header.operation_id, + answer->close_client_session.error_code); + break; + + case TF_MESSAGE_TYPE_REGISTER_SHARED_MEMORY: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "tf_answer_register_shared_memory\n" + " operation_id = 0x%08X\n" + " error_code = 0x%08X\n" + " block = 0x%08X\n", + answer->header.message_size, + answer->header.message_type, + answer->header.operation_id, + answer->register_shared_memory.error_code, + answer->register_shared_memory.block); + break; + + case TF_MESSAGE_TYPE_RELEASE_SHARED_MEMORY: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "ANSWER_RELEASE_SHARED_MEMORY\n" + " operation_id = 0x%08X\n" + " error_code = 0x%08X\n" + " block_id = 0x%08X\n", + answer->header.message_size, + answer->header.message_type, + answer->header.operation_id, + answer->release_shared_memory.error_code, + answer->release_shared_memory.block_id); + break; + + case TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "tf_answer_invoke_client_command\n" + " error_origin = 0x%02X\n" + " operation_id = 0x%08X\n" + " error_code = 0x%08X\n", + answer->header.message_size, + answer->header.message_type, + answer->invoke_client_command.error_origin, + answer->header.operation_id, + answer->invoke_client_command.error_code + ); + for (i = 0; i < 4; i++) { + dprintk(KERN_INFO " answers[%d]=0x%08X:0x%08X\n", + i, + answer->invoke_client_command.answers[i]. + value.a, + answer->invoke_client_command.answers[i]. + value.b); + } + break; + + case TF_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_ANSWER_CANCEL_CLIENT_COMMAND\n" + " operation_id = 0x%08X\n" + " error_code = 0x%08X\n", + answer->header.message_size, + answer->header.message_type, + answer->header.operation_id, + answer->cancel_client_operation.error_code); + break; + + case TF_MESSAGE_TYPE_MANAGEMENT: + dprintk(KERN_INFO + " message_size = 0x%02X\n" + " message_type = 0x%02X " + "TF_MESSAGE_TYPE_MANAGEMENT\n" + " operation_id = 0x%08X\n" + " error_code = 0x%08X\n", + answer->header.message_size, + answer->header.message_type, + answer->header.operation_id, + answer->header.error_code); + break; + + default: + dprintk( + KERN_ERR " message_type = 0x%02X " + "(Unknown message type)\n", + answer->header.message_type); + break; + + } +} + +#endif /* defined(TF_DRIVER_DEBUG_SUPPORT) */ + +/*---------------------------------------------------------------------------- + * SHA-1 implementation + * This is taken from the Linux kernel source crypto/sha1.c + *----------------------------------------------------------------------------*/ + +struct sha1_ctx { + u64 count; + u32 state[5]; + u8 buffer[64]; +}; + +static inline u32 rol(u32 value, u32 bits) +{ + return ((value) << (bits)) | ((value) >> (32 - (bits))); +} + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#define blk0(i) block32[i] + +#define blk(i) (block32[i & 15] = rol( \ + block32[(i + 13) & 15] ^ block32[(i + 8) & 15] ^ \ + block32[(i + 2) & 15] ^ block32[i & 15], 1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v, w, x, y, z, i) do { \ + z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); } while (0) + +#define R1(v, w, x, y, z, i) do { \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); } while (0) + +#define R2(v, w, x, y, z, i) do { \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ + w = rol(w, 30); } while (0) + +#define R3(v, w, x, y, z, i) do { \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); } while (0) + +#define R4(v, w, x, y, z, i) do { \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); } while (0) + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ +static void sha1_transform(u32 *state, const u8 *in) +{ + u32 a, b, c, d, e; + u32 block32[16]; + + /* convert/copy data to workspace */ + for (a = 0; a < sizeof(block32)/sizeof(u32); a++) + block32[a] = ((u32) in[4 * a]) << 24 | + ((u32) in[4 * a + 1]) << 16 | + ((u32) in[4 * a + 2]) << 8 | + ((u32) in[4 * a + 3]); + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); R0(a, b, c, d, e, 15); + + R1(e, a, b, c, d, 16); R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); R1(b, c, d, e, a, 19); + + R2(a, b, c, d, e, 20); R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); R2(b, c, d, e, a, 39); + + R3(a, b, c, d, e, 40); R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); R3(b, c, d, e, a, 59); + + R4(a, b, c, d, e, 60); R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); R4(b, c, d, e, a, 79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; + memset(block32, 0x00, sizeof(block32)); +} + + +static void sha1_init(void *ctx) +{ + struct sha1_ctx *sctx = ctx; + static const struct sha1_ctx initstate = { + 0, + { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 }, + { 0, } + }; + + *sctx = initstate; +} + + +static void sha1_update(void *ctx, const u8 *data, unsigned int len) +{ + struct sha1_ctx *sctx = ctx; + unsigned int i, j; + + j = (sctx->count >> 3) & 0x3f; + sctx->count += len << 3; + + if ((j + len) > 63) { + memcpy(&sctx->buffer[j], data, (i = 64 - j)); + sha1_transform(sctx->state, sctx->buffer); + for ( ; i + 63 < len; i += 64) + sha1_transform(sctx->state, &data[i]); + j = 0; + } else + i = 0; + memcpy(&sctx->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ +static void sha1_final(void *ctx, u8 *out) +{ + struct sha1_ctx *sctx = ctx; + u32 i, j, index, padlen; + u64 t; + u8 bits[8] = { 0, }; + static const u8 padding[64] = { 0x80, }; + + t = sctx->count; + bits[7] = 0xff & t; t >>= 8; + bits[6] = 0xff & t; t >>= 8; + bits[5] = 0xff & t; t >>= 8; + bits[4] = 0xff & t; t >>= 8; + bits[3] = 0xff & t; t >>= 8; + bits[2] = 0xff & t; t >>= 8; + bits[1] = 0xff & t; t >>= 8; + bits[0] = 0xff & t; + + /* Pad out to 56 mod 64 */ + index = (sctx->count >> 3) & 0x3f; + padlen = (index < 56) ? (56 - index) : ((64+56) - index); + sha1_update(sctx, padding, padlen); + + /* Append length */ + sha1_update(sctx, bits, sizeof(bits)); + + /* Store state in digest */ + for (i = j = 0; i < 5; i++, j += 4) { + u32 t2 = sctx->state[i]; + out[j+3] = t2 & 0xff; t2 >>= 8; + out[j+2] = t2 & 0xff; t2 >>= 8; + out[j+1] = t2 & 0xff; t2 >>= 8; + out[j] = t2 & 0xff; + } + + /* Wipe context */ + memset(sctx, 0, sizeof(*sctx)); +} + + + + +/*---------------------------------------------------------------------------- + * Process identification + *----------------------------------------------------------------------------*/ + +/* This function generates a processes hash table for authentication */ +int tf_get_current_process_hash(void *hash) +{ + int result = 0; + void *buffer; + struct mm_struct *mm; + struct vm_area_struct *vma; + + buffer = internal_kmalloc(PAGE_SIZE, GFP_KERNEL); + if (buffer == NULL) { + dprintk( + KERN_ERR "tf_get_current_process_hash:" + " Out of memory for buffer!\n"); + return -ENOMEM; + } + + mm = current->mm; + + down_read(&(mm->mmap_sem)); + for (vma = mm->mmap; vma != NULL; vma = vma->vm_next) { + if ((vma->vm_flags & VM_EXECUTABLE) != 0 && vma->vm_file + != NULL) { + struct dentry *dentry; + unsigned long start; + unsigned long cur; + unsigned long end; + struct sha1_ctx sha1; + + dentry = dget(vma->vm_file->f_dentry); + + dprintk( + KERN_DEBUG "tf_get_current_process_hash: " + "Found executable VMA for inode %lu " + "(%lu bytes).\n", + dentry->d_inode->i_ino, + (unsigned long) (dentry->d_inode-> + i_size)); + + start = do_mmap(vma->vm_file, 0, + dentry->d_inode->i_size, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE, 0); + if (start < 0) { + dprintk( + KERN_ERR "tf_get_current_process_hash" + "Hash: do_mmap failed (error %d)!\n", + (int) start); + dput(dentry); + result = -EFAULT; + goto vma_out; + } + + end = start + dentry->d_inode->i_size; + + sha1_init(&sha1); + cur = start; + while (cur < end) { + unsigned long chunk; + + chunk = end - cur; + if (chunk > PAGE_SIZE) + chunk = PAGE_SIZE; + if (copy_from_user(buffer, (const void *) cur, + chunk) != 0) { + dprintk( + KERN_ERR "tf_get_current_" + "process_hash: copy_from_user " + "failed!\n"); + result = -EINVAL; + (void) do_munmap(mm, start, + dentry->d_inode->i_size); + dput(dentry); + goto vma_out; + } + sha1_update(&sha1, buffer, chunk); + cur += chunk; + } + sha1_final(&sha1, hash); + result = 0; + + (void) do_munmap(mm, start, dentry->d_inode->i_size); + dput(dentry); + break; + } + } +vma_out: + up_read(&(mm->mmap_sem)); + + internal_kfree(buffer); + + if (result == -ENOENT) + dprintk( + KERN_ERR "tf_get_current_process_hash: " + "No executable VMA found for process!\n"); + return result; +} + +#ifndef CONFIG_ANDROID +/* This function hashes the path of the current application. + * If data = NULL ,nothing else is added to the hash + else add data to the hash + */ +int tf_hash_application_path_and_data(char *buffer, void *data, + u32 data_len) +{ + int result = -ENOENT; + char *buffer = NULL; + struct mm_struct *mm; + struct vm_area_struct *vma; + + buffer = internal_kmalloc(PAGE_SIZE, GFP_KERNEL); + if (buffer == NULL) { + result = -ENOMEM; + goto end; + } + + mm = current->mm; + + down_read(&(mm->mmap_sem)); + for (vma = mm->mmap; vma != NULL; vma = vma->vm_next) { + if ((vma->vm_flags & VM_EXECUTABLE) != 0 + && vma->vm_file != NULL) { + struct path *path; + char *endpath; + size_t pathlen; + struct sha1_ctx sha1; + u8 hash[SHA1_DIGEST_SIZE]; + + path = &vma->vm_file->f_path; + + endpath = d_path(path, buffer, PAGE_SIZE); + if (IS_ERR(path)) { + result = PTR_ERR(endpath); + up_read(&(mm->mmap_sem)); + goto end; + } + pathlen = (buffer + PAGE_SIZE) - endpath; + +#ifdef CONFIG_TF_DRIVER_DEBUG_SUPPORT + { + char *c; + dprintk(KERN_DEBUG "current process path = "); + for (c = endpath; + c < buffer + PAGE_SIZE; + c++) + dprintk("%c", *c); + + dprintk(", uid=%d, euid=%d\n", current_uid(), + current_euid()); + } +#endif /* defined(CONFIG_TF_DRIVER_DEBUG_SUPPORT) */ + + sha1_init(&sha1); + sha1_update(&sha1, endpath, pathlen); + if (data != NULL) { + dprintk(KERN_INFO "current process path: " + "Hashing additional data\n"); + sha1_update(&sha1, data, data_len); + } + sha1_final(&sha1, hash); + memcpy(buffer, hash, sizeof(hash)); + + result = 0; + + break; + } + } + up_read(&(mm->mmap_sem)); + +end: + if (buffer != NULL) + internal_kfree(buffer); + + return result; +} +#endif /* !CONFIG_ANDROID */ + +void *internal_kmalloc(size_t size, int priority) +{ + void *ptr; + struct tf_device *dev = tf_get_device(); + + ptr = kmalloc(size, priority); + + if (ptr != NULL) + atomic_inc( + &dev->stats.stat_memories_allocated); + + return ptr; +} + +void internal_kfree(void *ptr) +{ + struct tf_device *dev = tf_get_device(); + + if (ptr != NULL) + atomic_dec( + &dev->stats.stat_memories_allocated); + return kfree(ptr); +} + +void internal_vunmap(void *ptr) +{ + struct tf_device *dev = tf_get_device(); + + if (ptr != NULL) + atomic_dec( + &dev->stats.stat_memories_allocated); + + vunmap((void *) (((unsigned int)ptr) & 0xFFFFF000)); +} + +void *internal_vmalloc(size_t size) +{ + void *ptr; + struct tf_device *dev = tf_get_device(); + + ptr = vmalloc(size); + + if (ptr != NULL) + atomic_inc( + &dev->stats.stat_memories_allocated); + + return ptr; +} + +void internal_vfree(void *ptr) +{ + struct tf_device *dev = tf_get_device(); + + if (ptr != NULL) + atomic_dec( + &dev->stats.stat_memories_allocated); + return vfree(ptr); +} + +unsigned long internal_get_zeroed_page(int priority) +{ + unsigned long result; + struct tf_device *dev = tf_get_device(); + + result = get_zeroed_page(priority); + + if (result != 0) + atomic_inc(&dev->stats. + stat_pages_allocated); + + return result; +} + +void internal_free_page(unsigned long addr) +{ + struct tf_device *dev = tf_get_device(); + + if (addr != 0) + atomic_dec( + &dev->stats.stat_pages_allocated); + return free_page(addr); +} + +int internal_get_user_pages( + struct task_struct *tsk, + struct mm_struct *mm, + unsigned long start, + int len, + int write, + int force, + struct page **pages, + struct vm_area_struct **vmas) +{ + int result; + struct tf_device *dev = tf_get_device(); + + result = get_user_pages( + tsk, + mm, + start, + len, + write, + force, + pages, + vmas); + + if (result > 0) + atomic_add(result, + &dev->stats.stat_pages_locked); + + return result; +} + +void internal_get_page(struct page *page) +{ + struct tf_device *dev = tf_get_device(); + + atomic_inc(&dev->stats.stat_pages_locked); + + get_page(page); +} + +void internal_page_cache_release(struct page *page) +{ + struct tf_device *dev = tf_get_device(); + + atomic_dec(&dev->stats.stat_pages_locked); + + page_cache_release(page); +} diff --git a/security/smc/omap4/scxlnx_util.h b/security/smc/tf_util.h index 4569ec2..43a05da 100644 --- a/security/smc/omap4/scxlnx_util.h +++ b/security/smc/tf_util.h @@ -1,5 +1,5 @@ -/* - * Copyright (c) 2006-2010 Trusted Logic S.A. +/** + * Copyright (c) 2011 Trusted Logic S.A. * All Rights Reserved. * * This program is free software; you can redistribute it and/or @@ -16,8 +16,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ -#ifndef __SCXLNX_UTIL_H__ -#define __SCXLNX_UTIL_H__ + +#ifndef __TF_UTIL_H__ +#define __TF_UTIL_H__ #include <linux/spinlock.h> #include <linux/errno.h> @@ -30,8 +31,8 @@ #include <linux/vmalloc.h> #include <asm/byteorder.h> -#include "scx_protocol.h" -#include "scxlnx_defs.h" +#include "tf_protocol.h" +#include "tf_defs.h" /*---------------------------------------------------------------------------- * Debug printing routines @@ -39,29 +40,29 @@ #ifdef CONFIG_TF_DRIVER_DEBUG_SUPPORT -void addressCacheProperty(unsigned long va); +void address_cache_property(unsigned long va); #define dprintk printk -void SCXLNXDumpL1SharedBuffer(struct SCHANNEL_C1S_BUFFER *pBuf); +void tf_dump_l1_shared_buffer(struct tf_l1_shared_buffer *buffer); -void SCXLNXDumpMessage(union SCX_COMMAND_MESSAGE *pMessage); +void tf_dump_command(union tf_command *command); -void SCXLNXDumpAnswer(union SCX_ANSWER_MESSAGE *pAnswer); +void tf_dump_answer(union tf_answer *answer); -#ifdef CONFIG_SMC_BENCH_SECURE_CYCLE -void setupCounters(void); -void runBogoMIPS(void); -int runCodeSpeed(unsigned int nLoop); -int runDataSpeed(unsigned int nLoop, unsigned long nVA); -#endif /* CONFIG_SMC_BENCH_SECURE_CYCLE */ +#ifdef CONFIG_BENCH_SECURE_CYCLE +void setup_counters(void); +void run_bogo_mips(void); +int run_code_speed(unsigned int loop); +int run_data_speed(unsigned int loop, unsigned long va); +#endif /* CONFIG_BENCH_SECURE_CYCLE */ #else /* defined(CONFIG_TF_DRIVER_DEBUG_SUPPORT) */ #define dprintk(args...) do { ; } while (0) -#define SCXLNXDumpL1SharedBuffer(pBuf) ((void) 0) -#define SCXLNXDumpMessage(pMessage) ((void) 0) -#define SCXLNXDumpAnswer(pAnswer) ((void) 0) +#define tf_dump_l1_shared_buffer(buffer) ((void) 0) +#define tf_dump_command(command) ((void) 0) +#define tf_dump_answer(answer) ((void) 0) #endif /* defined(CONFIG_TF_DRIVER_DEBUG_SUPPORT) */ @@ -71,22 +72,23 @@ int runDataSpeed(unsigned int nLoop, unsigned long nVA); * Process identification *----------------------------------------------------------------------------*/ -int SCXLNXConnGetCurrentProcessHash(void *pHash); +int tf_get_current_process_hash(void *hash); -int SCXLNXConnHashApplicationPathAndData(char *pBuffer, void *pData, - u32 nDataLen); +#ifndef CONFIG_ANDROID +int tf_hash_application_path_and_data(char *buffer, void *data, u32 data_len); +#endif /* !CONFIG_ANDROID */ /*---------------------------------------------------------------------------- * Statistic computation *----------------------------------------------------------------------------*/ -void *internal_kmalloc(size_t nSize, int nPriority); -void internal_kfree(void *pMemory); -void internal_vunmap(void *pMemory); -void *internal_vmalloc(size_t nSize); -void internal_vfree(void *pMemory); -unsigned long internal_get_zeroed_page(int nPriority); -void internal_free_page(unsigned long pPage); +void *internal_kmalloc(size_t size, int priority); +void internal_kfree(void *ptr); +void internal_vunmap(void *ptr); +void *internal_vmalloc(size_t size); +void internal_vfree(void *ptr); +unsigned long internal_get_zeroed_page(int priority); +void internal_free_page(unsigned long addr); int internal_get_user_pages( struct task_struct *tsk, struct mm_struct *mm, @@ -98,5 +100,4 @@ int internal_get_user_pages( struct vm_area_struct **vmas); void internal_get_page(struct page *page); void internal_page_cache_release(struct page *page); -#endif /* __SCXLNX_UTIL_H__ */ - +#endif /* __TF_UTIL_H__ */ diff --git a/security/smc/omap4/scxlnx_mshield.h b/security/smc/tf_zebra.h index 9457ca9..b30fe6f 100644 --- a/security/smc/omap4/scxlnx_mshield.h +++ b/security/smc/tf_zebra.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010 Trusted Logic S.A. + * Copyright (c) 2011 Trusted Logic S.A. * All Rights Reserved. * * This program is free software; you can redistribute it and/or @@ -17,17 +17,17 @@ * MA 02111-1307 USA */ -#ifndef __SCXLNX_MSHIELD_H__ -#define __SCXLNX_MSHIELD_H__ +#ifndef __TF_ZEBRA_H__ +#define __TF_ZEBRA_H__ -#include "scxlnx_defs.h" +#include "tf_defs.h" -int SCXLNXCtrlDeviceRegister(void); +int tf_ctrl_device_register(void); -int SCXLNXCommStart(struct SCXLNX_COMM *pComm, - u32 nWorkspaceAddr, u32 nWorkspaceSize, - u8 *pPABufferVAddr, u32 nPABufferSize, - u8 *pPropertiesBuffer, u32 nPropertiesBufferLength); +int tf_start(struct tf_comm *comm, + u32 workspace_addr, u32 workspace_size, + u8 *pa_buffer, u32 pa_size, + u8 *properties_buffer, u32 properties_length); /* Assembler entry points to/from secure */ u32 schedule_secure_world(u32 app_id, u32 proc_id, u32 flags, u32 args); @@ -35,10 +35,10 @@ u32 rpc_handler(u32 p1, u32 p2, u32 p3, u32 p4); u32 read_mpidr(void); /* L4 SEC clockdomain enabling/disabling */ -void tf_l4sec_clkdm_wakeup(bool use_spin_lock, bool wakelock); -void tf_l4sec_clkdm_allow_idle(bool use_spin_lock, bool wakeunlock); +void tf_l4sec_clkdm_wakeup(bool wakelock); +void tf_l4sec_clkdm_allow_idle(bool wakeunlock); /* Delayed secure resume */ int tf_delayed_secure_resume(void); -#endif +#endif /* __TF_ZEBRA_H__ */ diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index ffcfeee..caeccc7 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -115,6 +115,8 @@ config SND_OMAP_SOC_SDP4430 select SND_SOC_DMIC select SND_OMAP_SOC_DMIC select SND_OMAP_SOC_ABE_DSP + select SND_OMAP_SOC_MCASP + select SND_OMAP_SOC_SPDIF help Say Y if you want to add support for SoC audio on Texas Instruments SDP4430 or PandaBoard. diff --git a/sound/soc/omap/omap-abe.c b/sound/soc/omap/omap-abe.c index da5ba44..b05dfe8 100644 --- a/sound/soc/omap/omap-abe.c +++ b/sound/soc/omap/omap-abe.c @@ -342,7 +342,7 @@ static void enable_be_port(struct snd_soc_pcm_runtime *be, /* BT_DL connection to McBSP 1 ports */ format.f = 8000; - format.samp_format = MONO_RSHIFTED_16; + format.samp_format = STEREO_RSHIFTED_16; abe_connect_serial_port(BT_VX_DL_PORT, &format, MCBSP1_TX); omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_DL]); @@ -355,7 +355,7 @@ static void enable_be_port(struct snd_soc_pcm_runtime *be, /* BT_UL connection to McBSP 1 ports */ format.f = 8000; - format.samp_format = MONO_RSHIFTED_16; + format.samp_format = STEREO_RSHIFTED_16; abe_connect_serial_port(BT_VX_UL_PORT, &format, MCBSP1_RX); omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_UL]); diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c index 9fc42e7..ece0d24 100755 --- a/sound/soc/omap/sdp4430.c +++ b/sound/soc/omap/sdp4430.c @@ -35,6 +35,7 @@ #include <plat/mux.h> #include <plat/mcbsp.h> +#include <linux/gpio.h> #include "omap-mcpdm.h" #include "omap-abe.h" #include "omap-abe-dsp.h" @@ -42,10 +43,31 @@ #include "omap-mcbsp.h" #include "../codecs/twl6040.h" +#include "../../../arch/arm/mach-omap2/board-tuna.h" + +#define TUNA_MAIN_MIC_GPIO 48 +#define TUNA_SUB_MIC_GPIO 171 + static int twl6040_power_mode; static int mcbsp_cfg; static struct snd_soc_codec *twl6040_codec; +int omap4_tuna_get_type(void); + +static int main_mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + gpio_set_value(TUNA_MAIN_MIC_GPIO, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +static int sub_mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + gpio_set_value(TUNA_SUB_MIC_GPIO, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + static int sdp4430_modem_mcbsp_configure(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, int flag) { @@ -67,18 +89,46 @@ static int sdp4430_modem_mcbsp_configure(struct snd_pcm_substream *substream, modem_substream[substream->stream]->private_data; if (!mcbsp_cfg) { - /* Set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(modem_rtd->cpu_dai, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); - - if (unlikely(ret < 0)) { - printk(KERN_ERR "can't set Modem cpu DAI configuration\n"); - goto exit; + if (omap4_tuna_get_type() == TUNA_TYPE_TORO) { + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(modem_rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (unlikely(ret < 0)) { + printk(KERN_ERR "can't set Modem cpu DAI format\n"); + goto exit; + } + + /* McBSP2 fclk reparented to ABE_24M_FCLK */ + ret = snd_soc_dai_set_sysclk(modem_rtd->cpu_dai, + OMAP_MCBSP_SYSCLK_CLKS_FCLK, + 32 * 96 * params_rate(params), + SND_SOC_CLOCK_IN); + if (unlikely(ret < 0)) { + printk(KERN_ERR "can't set Modem cpu DAI sysclk\n"); + goto exit; + } + + /* assuming McBSP2 is S16_LE stereo */ + ret = snd_soc_dai_set_clkdiv(modem_rtd->cpu_dai, 0, 96); + if (unlikely(ret < 0)) { + printk(KERN_ERR "can't set Modem cpu DAI clkdiv\n"); + goto exit; + } } else { - mcbsp_cfg = 1; + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(modem_rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + + if (unlikely(ret < 0)) { + printk(KERN_ERR "can't set Modem cpu DAI configuration\n"); + goto exit; + } } + mcbsp_cfg = 1; } if (params != NULL) { @@ -186,25 +236,28 @@ static int sdp4430_mcbsp_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - int ret = 0; - unsigned int be_id; + int ret = 0, channels = 0; + unsigned int be_id, fmt; be_id = rtd->dai_link->be_id; - if (be_id == OMAP_ABE_DAI_MM_FM) { - /* Set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); - } else if (be_id == OMAP_ABE_DAI_BT_VX) { - ret = snd_soc_dai_set_fmt(cpu_dai, - SND_SOC_DAIFMT_DSP_B | - SND_SOC_DAIFMT_NB_IF | - SND_SOC_DAIFMT_CBM_CFM); + if (be_id == OMAP_ABE_DAI_BT_VX) { + if (machine_is_tuna()) + fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + fmt = SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM; + } else { + fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; } + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set cpu DAI configuration\n"); return ret; @@ -216,11 +269,25 @@ static int sdp4430_mcbsp_hw_params(struct snd_pcm_substream *substream, */ /* Set McBSP clock to external */ ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_FCLK, - 64 * params_rate(params), + 32 * 96 * params_rate(params), SND_SOC_CLOCK_IN); if (ret < 0) printk(KERN_ERR "can't set cpu system clock\n"); + ret = snd_soc_dai_set_clkdiv(cpu_dai, 0, 96); + if (ret < 0) + printk(KERN_ERR "can't set McBSP cpu DAI clkdiv\n"); + + /* + * Configure McBSP internal buffer threshold + * for playback/record + */ + channels = params_channels(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_mcbsp_set_tx_threshold(cpu_dai->id, channels); + else + omap_mcbsp_set_rx_threshold(cpu_dai->id, channels); + return ret; } @@ -238,7 +305,7 @@ static int mcbsp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, if (be_id == OMAP_ABE_DAI_MM_FM) channels->min = 2; else if (be_id == OMAP_ABE_DAI_BT_VX) - channels->min = 1; + channels->min = 2; snd_mask_set(¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK], SNDRV_PCM_FORMAT_S16_LE); @@ -292,7 +359,15 @@ static const struct snd_kcontrol_new sdp4430_controls[] = { /* SDP4430 machine DAPM */ static const struct snd_soc_dapm_widget sdp4430_twl6040_dapm_widgets[] = { - SND_SOC_DAPM_MIC("Ext Mic", NULL), + + SND_SOC_DAPM_MIC("Ext Main Mic", NULL), + SND_SOC_DAPM_MIC("Ext Sub Mic", NULL), + SND_SOC_DAPM_MICBIAS_E("Ext Main Mic Bias", SND_SOC_NOPM, 0, 0, + main_mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MICBIAS_E("Ext Sub Mic Bias", SND_SOC_NOPM, 0, 0, + sub_mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_SPK("Ext Spk", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), SND_SOC_DAPM_HP("Headset Stereophone", NULL), @@ -302,9 +377,10 @@ static const struct snd_soc_dapm_widget sdp4430_twl6040_dapm_widgets[] = { static const struct snd_soc_dapm_route audio_map[] = { /* External Mics: MAINMIC, SUBMIC with bias*/ - {"MAINMIC", NULL, "Main Mic Bias"}, - {"SUBMIC", NULL, "Main Mic Bias"}, - {"Main Mic Bias", NULL, "Ext Mic"}, + {"MAINMIC", NULL, "Ext Main Mic Bias"}, + {"SUBMIC", NULL, "Ext Sub Mic Bias"}, + {"Ext Main Mic Bias" , NULL, "Ext Main Mic"}, + {"Ext Sub Mic Bias" , NULL, "Ext Sub Mic"}, /* External Speakers: HFL, HFR */ {"Ext Spk", NULL, "HFL"}, @@ -371,7 +447,10 @@ static int sdp4430_twl6040_init(struct snd_soc_pcm_runtime *rtd) snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); /* SDP4430 connected pins */ - snd_soc_dapm_enable_pin(dapm, "Ext Mic"); + if (machine_is_tuna()) { + snd_soc_dapm_enable_pin(dapm, "Ext Main Mic"); + snd_soc_dapm_enable_pin(dapm, "Ext Sub Mic"); + } snd_soc_dapm_enable_pin(dapm, "Ext Spk"); snd_soc_dapm_enable_pin(dapm, "AFML"); snd_soc_dapm_enable_pin(dapm, "AFMR"); @@ -379,7 +458,10 @@ static int sdp4430_twl6040_init(struct snd_soc_pcm_runtime *rtd) snd_soc_dapm_enable_pin(dapm, "Headset Stereophone"); /* allow audio paths from the audio modem to run during suspend */ - snd_soc_dapm_ignore_suspend(dapm, "Ext Mic"); + if (machine_is_tuna()) { + snd_soc_dapm_ignore_suspend(dapm, "Ext Main Mic"); + snd_soc_dapm_ignore_suspend(dapm, "Ext Sub Mic"); + } snd_soc_dapm_ignore_suspend(dapm, "Ext Spk"); snd_soc_dapm_ignore_suspend(dapm, "AFML"); snd_soc_dapm_ignore_suspend(dapm, "AFMR"); @@ -460,6 +542,12 @@ static int sdp4430_bt_init(struct snd_soc_pcm_runtime *rtd) return 0; } +static int sdp4430_spdif_init(struct snd_soc_pcm_runtime *rtd) +{ + rtd->pmdown_time = 0; + return 0; +} + static int sdp4430_stream_event(struct snd_soc_dapm_context *dapm) { /* @@ -678,6 +766,17 @@ static struct snd_soc_dai_link sdp4430_dai[] = { .ops = &sdp4430_mcpdm_ops, .ignore_suspend = 1, }, + { + .name = "SPDIF", + .stream_name = "SPDIF", + .cpu_dai_name = "omap-mcasp-dai", + .codec_dai_name = "dit-hifi", /* dummy s/pdif transciever + * driver */ + .platform_name = "omap-pcm-audio", + .ignore_suspend = 1, + .no_codec = 1, + .init = sdp4430_spdif_init, + }, /* * Backend DAIs - i.e. dynamically matched interfaces, invisible to userspace. @@ -843,20 +942,38 @@ static int __init sdp4430_soc_init(void) { int ret; - if (!machine_is_omap_4430sdp() && !machine_is_omap4_panda()) { - pr_debug("Not SDP4430 or PandaBoard!\n"); + if (!machine_is_omap_4430sdp() && !machine_is_omap4_panda() && + !machine_is_tuna()) { + pr_debug("Not SDP4430, PandaBoard or Tuna!\n"); return -ENODEV; } printk(KERN_INFO "SDP4430 SoC init\n"); + + if (machine_is_tuna()) { + ret = gpio_request(TUNA_MAIN_MIC_GPIO, "MAIN_MICBIAS_EN"); + if (ret) + goto mainmic_gpio_err; + + gpio_direction_output(TUNA_MAIN_MIC_GPIO, 0); + + ret = gpio_request(TUNA_SUB_MIC_GPIO, "SUB_MICBIAS_EN"); + if (ret) + goto submic_gpio_err; + gpio_direction_output(TUNA_SUB_MIC_GPIO, 0); + } + if (machine_is_omap_4430sdp()) snd_soc_sdp4430.name = "SDP4430"; else if (machine_is_omap4_panda()) snd_soc_sdp4430.name = "Panda"; + else if (machine_is_tuna()) + snd_soc_sdp4430.name = "Tuna"; sdp4430_snd_device = platform_device_alloc("soc-audio", -1); if (!sdp4430_snd_device) { printk(KERN_ERR "Platform device allocation failed\n"); - return -ENOMEM; + ret = -ENOMEM; + goto device_err; } ret = snd_soc_register_dais(&sdp4430_snd_device->dev, dai, ARRAY_SIZE(dai)); @@ -876,12 +993,23 @@ static int __init sdp4430_soc_init(void) err: printk(KERN_ERR "Unable to add platform device\n"); platform_device_put(sdp4430_snd_device); +device_err: + if (machine_is_tuna()) + gpio_free(TUNA_SUB_MIC_GPIO); +submic_gpio_err: + if (machine_is_tuna()) + gpio_free(TUNA_MAIN_MIC_GPIO); +mainmic_gpio_err: return ret; } module_init(sdp4430_soc_init); static void __exit sdp4430_soc_exit(void) { + if (machine_is_tuna()) { + gpio_free(TUNA_SUB_MIC_GPIO); + gpio_free(TUNA_MAIN_MIC_GPIO); + } platform_device_unregister(sdp4430_snd_device); } module_exit(sdp4430_soc_exit); |