diff options
135 files changed, 41672 insertions, 33 deletions
diff --git a/arch/arm/configs/tuna_defconfig b/arch/arm/configs/tuna_defconfig new file mode 100644 index 0000000..af50ec1 --- /dev/null +++ b/arch/arm/configs/tuna_defconfig @@ -0,0 +1,324 @@ +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_SLUB_DEBUG is not set +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_RESET_CLOCKS=y +# CONFIG_ARCH_OMAP2 is not set +# CONFIG_ARCH_OMAP3 is not set +# CONFIG_MACH_OMAP_4430SDP is not set +CONFIG_ARM_THUMBEE=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +CONFIG_NR_CPUS=2 +CONFIG_PREEMPT=y +CONFIG_HIGHMEM=y +CONFIG_CMDLINE="console=ttyO2,115200n8 mem=512M androidboot.console=ttyO2 omap_wdt.timer_margin=30" +CONFIG_CMDLINE_EXTEND=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_DEFAULT_GOV_HOTPLUG=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_INTERACTIVE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=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_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_NET_KEY=y +CONFIG_INET=y +CONFIG_INET_ESP=y +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# 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_SIP=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_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_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_RFKILL=y +CONFIG_RFKILL_INPUT=y +CONFIG_MTD=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=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_ANDROID_PMEM is not set +CONFIG_KERNEL_DEBUGGER_CORE=y +CONFIG_UID_STAT=y +CONFIG_BMP180=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_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_WIFI_CONTROL_FUNC=y +CONFIG_BCMDHD=m +CONFIG_BCMDHD_FW_PATH="/system/vendor/firmware/fw_bcmdhd.bin" +CONFIG_BCMDHD_WEXT=y +CONFIG_PPP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_MPPE=y +CONFIG_PPPOLAC=y +CONFIG_PPPOPNS=y +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_KEYRESET=y +CONFIG_KEYBOARD_OMAP4=y +# CONFIG_INPUT_MOUSE is not set +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_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_GPIO=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_PWM=y +CONFIG_TWL6030_POWEROFF=y +CONFIG_TWL6030_MADC=y +CONFIG_REGULATOR_TWL4030=y +CONFIG_MEDIA_SUPPORT=y +# CONFIG_RC_CORE is not set +CONFIG_PVR_SGX=y +CONFIG_ION=y +CONFIG_ION_OMAP=y +CONFIG_FB=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_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_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_DEVICEFS=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_MUSB_HDRC=y +CONFIG_USB_MUSB_OMAP2PLUS=y +CONFIG_USB_MUSB_PERIPHERAL=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_BLOCK_DEFERRED_RESUME=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_FS=y +CONFIG_DEBUG_KERNEL=y +CONFIG_DETECT_HUNG_TASK=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_RT_MUTEXES=y +CONFIG_DEBUG_SPINLOCK=y +CONFIG_DEBUG_MUTEXES=y +CONFIG_DEBUG_SPINLOCK_SLEEP=y +CONFIG_DEBUG_INFO=y +CONFIG_SYSCTL_SYSCALL_CHECK=y +# CONFIG_ARM_UNWIND is not set +CONFIG_DEBUG_USER=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRC_CCITT=y diff --git a/arch/arm/mach-omap2/Kconfig b/arch/arm/mach-omap2/Kconfig index 177771b..05b559f 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 7ef7745..be6213e 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -249,6 +249,17 @@ 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-nfc.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-power.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-sensors.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-wifi.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-bluetooth.o \ + board-tuna-emif.o obj-$(CONFIG_MACH_OMAP3517EVM) += board-am3517evm.o \ omap_phy_internal.o \ @@ -286,3 +297,5 @@ obj-y += $(disp-m) $(disp-y) 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 diff --git a/arch/arm/mach-omap2/board-4430sdp.c b/arch/arm/mach-omap2/board-4430sdp.c index eadd153..76adaa7 100644 --- a/arch/arm/mach-omap2/board-4430sdp.c +++ b/arch/arm/mach-omap2/board-4430sdp.c @@ -843,7 +843,7 @@ static __initdata struct emif_device_details emif_devices = { .cs1_device = &lpddr2_elpida_2G_S4_dev }; -static inline void board_serial_init(void) +static inline void __init board_serial_init(void) { struct omap_board_data bdata; bdata.flags = 0; diff --git a/arch/arm/mach-omap2/board-omap4panda.c b/arch/arm/mach-omap2/board-omap4panda.c index d236f82..ff80bd4 100644 --- a/arch/arm/mach-omap2/board-omap4panda.c +++ b/arch/arm/mach-omap2/board-omap4panda.c @@ -572,7 +572,7 @@ static struct omap_board_data serial4_data __initdata = { .pads_cnt = ARRAY_SIZE(serial4_pads), }; -static inline void board_serial_init(void) +static inline void __init board_serial_init(void) { struct omap_board_data bdata; bdata.flags = 0; @@ -589,7 +589,7 @@ static inline void board_serial_init(void) #else #define board_mux NULL -static inline void board_serial_init(void) +static inline void __init board_serial_init(void) { omap_serial_init(); } @@ -726,8 +726,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; @@ -757,7 +755,6 @@ static void __init omap4_panda_init(void) usb_musb_init(&musb_board_data); 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..ec58454 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-bluetooth.c @@ -0,0 +1,295 @@ +/* + * 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/serial_core.h> +#include <linux/wakelock.h> +#include <asm/mach-types.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; + +struct bcm_bt_lpm { + int wake; + int host_wake; + bool rx_wake_lock_taken; + + 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) { + 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); + } + + 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); + + gpio_set_value(BT_WAKE_GPIO, 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); + +void bcm_bt_rx_done_locked(struct uart_port *uport) { + if (bt_lpm.host_wake) { + // Release wake in 500 ms so that higher layers can take it. + wake_lock_timeout(&bt_lpm.wake_lock, HZ/2); + bt_lpm.rx_wake_lock_taken = true; + } +} +EXPORT_SYMBOL(bcm_bt_rx_done_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) { + bt_lpm.rx_wake_lock_taken = false; + wake_lock(&bt_lpm.wake_lock); + } else if (!bt_lpm.rx_wake_lock_taken) { + // Failsafe timeout of wakelock. + // If the host wake pin is asserted and no data is sent, + // when its deasserted we will enter this path + wake_lock_timeout(&bt_lpm.wake_lock, HZ/2); + } + +} + +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; + } + + 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); + + wake_lock_destroy(&bt_lpm.wake_lock); + return 0; +} + +static struct platform_driver bcm4330_bluetooth_platform_driver = { + .probe = bcm4330_bluetooth_probe, + .remove = bcm4330_bluetooth_remove, + .driver = { + .name = "bcm4330_bluetooth", + .owner = THIS_MODULE, + }, +}; + +static int __init bcm4330_bluetooth_init(void) +{ + 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-display.c b/arch/arm/mach-omap2/board-tuna-display.c new file mode 100644 index 0000000..3baf2c4 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-display.c @@ -0,0 +1,304 @@ +/* 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_LUNCHBOX 35 +#define TUNA_GPIO_MLCD_RST 23 + +static struct panel_generic_dpi_data tuna_lcd_panel = { + .name = "samsung_ams452gn05", + .platform_enable = NULL, + .platform_disable = NULL, +}; + +static struct omap_dss_device tuna_lcd_device = { + .name = "lcd", + .driver_name = "generic_dpi_panel", + .type = OMAP_DISPLAY_TYPE_DPI, + .channel = OMAP_DSS_CHANNEL_LCD2, + .data = &tuna_lcd_panel, + .phy.dpi.data_lines = 24, +}; + +struct regulator *tuna_oled_reg; + +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 (enable) + regulator_enable(tuna_oled_reg); + else + regulator_disable(tuna_oled_reg); +} + +static const struct s6e8aa0_gamma_entry tuna_oled_gamma_table[] = { + { BV_0, { 4500000, 4500000, 4500000, }, }, + { 1, { 4294200, 4407600, 4210200, }, }, + { 0x00000400, { 3969486, 4038030, 3955093, }, }, + { 0x000004C2, { 3964456, 4032059, 3949872, }, }, + { 0x000005A8, { 3959356, 4026019, 3944574, }, }, + { 0x000006BA, { 3954160, 4019879, 3939171, }, }, + { 0x00000800, { 3948872, 4013646, 3933668, }, }, + { 0x00000983, { 3943502, 4007331, 3928075, }, }, + { 0x00000B50, { 3938029, 4000909, 3922368, }, }, + { 0x00000D74, { 3932461, 3994392, 3916558, }, }, + { 0x00001000, { 3926792, 3987772, 3910636, }, }, + { 0x00001307, { 3921022, 3981052, 3904605, }, }, + { 0x000016A1, { 3915146, 3974224, 3898455, }, }, + { 0x00001AE9, { 3909163, 3967289, 3892189, }, }, + { 0x00002000, { 3903070, 3960245, 3885801, }, }, + { 0x0000260E, { 3896860, 3953083, 3879284, }, }, + { 0x00002D41, { 3890532, 3945805, 3872637, }, }, + { 0x000035D1, { 3884081, 3938403, 3865854, }, }, + { 0x00004000, { 3877504, 3930876, 3858930, }, }, + { 0x00004C1C, { 3870797, 3923221, 3851863, }, }, + { 0x00005A82, { 3863956, 3915434, 3844649, }, }, + { 0x00006BA2, { 3856976, 3907510, 3837279, }, }, + { 0x00008000, { 3849853, 3899444, 3829750, }, }, + { 0x00009838, { 3842582, 3891234, 3822056, }, }, + { 0x0000B505, { 3835159, 3882874, 3814193, }, }, + { 0x0000D745, { 3827577, 3874360, 3806153, }, }, + { 0x00010000, { 3819832, 3865687, 3797931, }, }, + { 0x00013070, { 3811918, 3856849, 3789519, }, }, + { 0x00016A0A, { 3803829, 3847842, 3780912, }, }, + { 0x0001AE8A, { 3795559, 3838659, 3772102, }, }, + { 0x00020000, { 3787101, 3829295, 3763080, }, }, + { 0x000260E0, { 3778447, 3819742, 3753839, }, }, + { 0x0002D414, { 3769592, 3809996, 3744372, }, }, + { 0x00035D14, { 3760527, 3800049, 3734667, }, }, + { 0x00040000, { 3751244, 3789893, 3724717, }, }, + { 0x0004C1C0, { 3741734, 3779522, 3714512, }, }, + { 0x0005A828, { 3731990, 3768927, 3704040, }, }, + { 0x0006BA28, { 3722000, 3758099, 3693292, }, }, + { 0x00080000, { 3711756, 3747030, 3682254, }, }, + { 0x0009837F, { 3701247, 3735711, 3670915, }, }, + { 0x000B504F, { 3690462, 3724131, 3659262, }, }, + { 0x000D7450, { 3679388, 3712280, 3647281, }, }, + { 0x00100000, { 3668014, 3700147, 3634957, }, }, + { 0x001306FE, { 3656325, 3687721, 3622274, }, }, + { 0x0016A09E, { 3644309, 3674988, 3609216, }, }, + { 0x001AE8A0, { 3631950, 3661936, 3595765, }, }, + { 0x00200000, { 3619231, 3648550, 3581902, }, }, + { 0x00260DFC, { 3606137, 3634817, 3567607, }, }, + { 0x002D413D, { 3592649, 3620719, 3552859, }, }, + { 0x0035D13F, { 3578748, 3606240, 3537634, }, }, + { 0x00400000, { 3564413, 3591361, 3521908, }, }, + { 0x004C1BF8, { 3549622, 3576065, 3505654, }, }, + { 0x005A827A, { 3534351, 3560329, 3488845, }, }, + { 0x006BA27E, { 3518576, 3544131, 3471449, }, }, + { 0x00800000, { 3502268, 3527448, 3453434, }, }, + { 0x009837F0, { 3485399, 3510255, 3434765, }, }, + { 0x00B504F3, { 3467936, 3492523, 3415404, }, }, + { 0x00D744FD, { 3449847, 3474223, 3395308, }, }, + { 0x01000000, { 3431093, 3455322, 3374435, }, }, + { 0x01306FE1, { 3411635, 3435786, 3352735, }, }, + { 0x016A09E6, { 3391431, 3415578, 3330156, }, }, + { 0x01AE89FA, { 3370432, 3394655, 3306641, }, }, + { 0x02000000, { 3348587, 3372974, 3282127, }, }, + { 0x0260DFC1, { 3325842, 3350485, 3256547, }, }, + { 0x02D413CD, { 3302134, 3327135, 3229824, }, }, + { 0x035D13F3, { 3277397, 3302865, 3201879, }, }, + { 0x04000000, { 3251558, 3277611, 3172620, }, }, + { 0x04C1BF83, { 3224535, 3251302, 3141948, }, }, + { 0x05A8279A, { 3196240, 3223858, 3109753, }, }, + { 0x06BA27E6, { 3166574, 3195192, 3075914, }, }, + { 0x08000000, { 3135426, 3165207, 3040295, }, }, + { 0x09837F05, { 3102676, 3133793, 3002744, }, }, + { 0x0B504F33, { 3068187, 3100829, 2963094, }, }, + { 0x0D744FCD, { 3031806, 3066175, 2921155, }, }, + { 0x10000000, { 2993361, 3029675, 2876712, }, }, + { 0x1306FE0A, { 2952659, 2991153, 2829527, }, }, + { 0x16A09E66, { 2909480, 2950402, 2779324, }, }, + { 0x1AE89F99, { 2863575, 2907191, 2725793, }, }, + { 0x20000000, { 2814655, 2861246, 2668579, }, }, + { 0x260DFC14, { 2762394, 2812251, 2607272, }, }, + { 0x2D413CCD, { 2706412, 2759834, 2541403, }, }, + { 0x35D13F32, { 2646266, 2703554, 2470425, }, }, + { 0x40000000, { 2581441, 2642883, 2393706, }, }, + { 0x4C1BF828, { 2511332, 2577183, 2310504, }, }, + { 0x5A82799A, { 2435220, 2505675, 2219951, }, }, + { 0x6BA27E65, { 2352250, 2427391, 2121028, }, }, + { 0x80000000, { 2261395, 2341114, 2012536, }, }, + { 0x9837F051, { 2161415, 2245288, 1893066, }, }, + { 0xB504F333, { 2050800, 2137874, 1760986, }, }, + { 0xD744FCCA, { 1927706, 2016150, 1614437, }, }, + { 0xFFFFFFFF, { 1789879, 1876363, 1451415, }, }, +}; + +static struct panel_s6e8aa0_data tuna_oled_data = { + .reset_gpio = TUNA_GPIO_MLCD_RST, + .set_power = tuna_oled_set_power, + .gamma_table = tuna_oled_gamma_table, + .gamma_table_size = ARRAY_SIZE(tuna_oled_gamma_table), + .factory_v255_regs = { + 0x084, + 0x083, + 0x0b7, + }, +}; + +/* width: 58mm */ +/* height: 102mm */ +static struct omap_dss_device tuna_oled_device = { + .name = "lcd", + .driver_name = "s6e8aa0", + .type = OMAP_DISPLAY_TYPE_DSI, + .data = &tuna_oled_data, + .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, + }, + .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 = 240, /* DSI_PLL_REGM */ + + .regm_dispc = 6, /* PLL_CLK1 (M4) */ + .regm_dsi = 6, /* PLL_CLK2 (M5) */ + .lp_clk_div = 14, /* LPDIV */ + + .dsi_fclk_src = OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI, + }, + }, + + .channel = OMAP_DSS_CHANNEL_LCD, +}; + + +static struct omap_dss_device *tuna_dss_devices[] = { + &tuna_oled_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 omap_dss_device *prelunchbox_dss_devices[] = { + &tuna_lcd_device, +}; + +static struct omap_dss_board_info prelunchbox_dss_data = { + .num_devices = ARRAY_SIZE(prelunchbox_dss_devices), + .devices = prelunchbox_dss_devices, + .default_device = &tuna_lcd_device, +}; + +static struct omapfb_platform_data tuna_fb_pdata = { + .mem_desc = { + .region_cnt = 1, + .region = { + [0] = { + .size = TUNA_FB_RAM_SIZE, + }, + }, + }, +}; + +#define MUX_DISPLAY_OUT OMAP_PIN_OUTPUT | OMAP_MUX_MODE5 +void __init omap4_tuna_display_init(void) +{ + struct omap_dss_board_info *dss_data; + + if (omap4_tuna_get_revision() == TUNA_REV_PRE_LUNCHBOX) { + /* dispc2_data23 - dispc2_data0 */ + omap_mux_init_signal("usbb2_ulpitll_stp", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_dir", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_nxt", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_dat0", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_dat1", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_dat2", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu6", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu5", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_dat3", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_dat4", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_dat5", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_dat6", MUX_DISPLAY_OUT); + omap_mux_init_signal("usbb2_ulpitll_dat7", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu3", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu4", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu11", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu12", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu13", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu14", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu15", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu16", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu17", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu18", MUX_DISPLAY_OUT); + omap_mux_init_signal("dpm_emu19", MUX_DISPLAY_OUT); + /* dispc2_hsync */ + omap_mux_init_signal("dpm_emu7", MUX_DISPLAY_OUT); + /* dispc2_pclk */ + omap_mux_init_signal("dpm_emu8", MUX_DISPLAY_OUT); + /* dispc2_vsync */ + omap_mux_init_signal("dpm_emu9", MUX_DISPLAY_OUT); + /* dispc2_de */ + omap_mux_init_signal("dpm_emu10", MUX_DISPLAY_OUT); + + dss_data = &prelunchbox_dss_data; + } else { + omap4_ctrl_pad_writel(0x1F000000, OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_DSIPHY); + if (!omap4_tuna_final_gpios()) + tuna_oled_data.reset_gpio = TUNA_GPIO_MLCD_RST_LUNCHBOX; + omap_mux_init_gpio(tuna_oled_data.reset_gpio, OMAP_PIN_OUTPUT); + dss_data = &tuna_dss_data; + } + + omap_vram_set_sdram_vram(TUNA_FB_RAM_SIZE, 0); + omapfb_set_platform_data(&tuna_fb_pdata); + omap_display_init(dss_data); +} 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..f7050ae --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-emif.c @@ -0,0 +1,107 @@ +/* + * 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 +}; + +/* + * 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..9ee5834 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-input.c @@ -0,0 +1,210 @@ +/* + * 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 const int tuna_keymap[] = { + KEY(1, 1, KEY_VOLUMEDOWN), + KEY(2, 1, KEY_VOLUMEUP), +}; + +static struct matrix_keymap_data tuna_keymap_data = { + .keymap = tuna_keymap, + .keymap_size = ARRAY_SIZE(tuna_keymap), +}; + +static struct omap4_keypad_platform_data tuna_keypad_data = { + .keymap_data = &tuna_keymap_data, + .rows = 3, + .cols = 2, +}; + +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, + .type = EV_KEY, + .keymap = tuna_gpio_keypad_keys_map_high, + .keymap_size = ARRAY_SIZE(tuna_gpio_keypad_keys_map_high), + .flags = GPIOEDF_ACTIVE_HIGH, +}; + +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, + .type = EV_KEY, + .keymap = tuna_gpio_keypad_keys_map_low, + .keymap_size = ARRAY_SIZE(tuna_gpio_keypad_keys_map_low), +}; + +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 struct mxt_platform_data atmel_mxt_ts_pdata = { + .x_line = 19, + .y_line = 11, + .x_size = 1024, + .y_size = 1024, + .blen = 0x21, + .threshold = 0x28, + .voltage = 2800000, /* 2.8V */ + .orient = MXT_DIAGONAL, + .irqflags = IRQF_TRIGGER_FALLING, +}; + +static struct i2c_board_info __initdata tuna_i2c3_boardinfo_pre_lunchbox[] = { + { + I2C_BOARD_INFO("atmel_mxt_ts", 0x4a), + .platform_data = &atmel_mxt_ts_pdata, + .irq = OMAP_GPIO_IRQ(GPIO_TOUCH_IRQ), + }, +}; + +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 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); + + if (omap4_tuna_final_gpios()) { + 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"); + + i2c_register_board_info(3, tuna_i2c3_boardinfo_final, + ARRAY_SIZE(tuna_i2c3_boardinfo_final)); + } + + if (omap4_tuna_get_revision() == TUNA_REV_PRE_LUNCHBOX) { + i2c_register_board_info(3, tuna_i2c3_boardinfo_pre_lunchbox, + ARRAY_SIZE(tuna_i2c3_boardinfo_pre_lunchbox)); + + omap_mux_init_signal("kpd_row1.kpd_row1", OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("kpd_row2.kpd_row2", OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("kpd_col1.kpd_col1", OMAP_PIN_OUTPUT); + omap4_keyboard_init(&tuna_keypad_data); + tuna_gpio_keypad_data.info_count = 1; + } else { + 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-modems.c b/arch/arm/mach-omap2/board-tuna-modems.c new file mode 100755 index 0000000..d9d6374 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-modems.c @@ -0,0 +1,631 @@ +/* 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> + +/* inlcude platform specific file */ +#include <mach/omap4-common.h> +#include <linux/platform_data/modem.h> +#include "mux.h" +#include "board-tuna.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_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 + +/* PROXIMA 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, + }, +}; + +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) +{ + int err = 0; + + 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_mux_init_signal("gpmc_ncs3.gpio_53", OMAP_PIN_OUTPUT); + omap_mux_init_signal("dpm_emu4.gpio_15", OMAP_PIN_OUTPUT); + omap_mux_init_signal("abe_dmic_clk1.gpio_119", OMAP_PIN_OUTPUT); + omap_mux_init_signal("abe_dmic_din1.gpio_120", OMAP_PIN_INPUT); + 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_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) { + err = gpio_request(gpio_reset_req_n, "RESET_REQ_N"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "RESET_REQ_N", err); + } + gpio_direction_output(gpio_reset_req_n, 0); + } + + if (gpio_cp_on) { + err = gpio_request(gpio_cp_on, "CP_ON"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "CP_ON", err); + } + gpio_direction_output(gpio_cp_on, 0); + } + + if (gpio_cp_rst) { + err = gpio_request(gpio_cp_rst, "CP_RST"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "CP_RST", err); + } + gpio_direction_output(gpio_cp_rst, 0); + } + + if (gpio_pda_active) { + err = gpio_request(gpio_pda_active, "PDA_ACTIVE"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "PDA_ACTIVE", err); + } + gpio_direction_output(gpio_pda_active, 0); + } + + if (gpio_phone_active) { + err = gpio_request(gpio_phone_active, "PHONE_ACTIVE"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "PHONE_ACTIVE", err); + } + gpio_direction_input(gpio_phone_active); + } + + if (gpio_cp_dump_int) { + err = gpio_request(gpio_cp_dump_int, "CP_DUMP_INT"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "CP_DUMP_INT", err); + } + gpio_direction_input(gpio_cp_dump_int); + } + + if (gpio_flm_uart_sel) { + err = gpio_request(gpio_flm_uart_sel, "GPS_UART_SEL"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "GPS_UART_SEL", err); + } + gpio_direction_output(gpio_reset_req_n, 1); + } + + if (gpio_phone_active) + irq_set_irq_type( + OMAP_GPIO_IRQ(OMAP_GPIO_MIPI_HSI_PHONE_ACTIVE), + IRQ_TYPE_LEVEL_HIGH); + + printk(KERN_INFO "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_rmnet0", + .id = 0x27, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_DPRAM, + }, + [3] = { + .name = "cdma_rmnet1", + .id = 0x31, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_DPRAM, + }, + [4] = { + .name = "cdma_rmnet2", + .id = 0x33, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_DPRAM, + }, + + [5] = { + .name = "cdma_rmnet3", + .id = 0x34, + .format = IPC_RAW, + .io_type = IODEV_NET, + .link = LINKDEV_DPRAM, + }, +}; + + +/* PROXIMA 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, + + .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 cdma_modem_cfg_gpio(void) +{ + int err = 0; + + 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; + + /*TODO*/ + /* gpio mux setting */ + + if (gpio_cp_rst) { + err = gpio_request(gpio_cp_rst, "CP_RST"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "CP_RST", err); + } else + gpio_direction_output(gpio_cp_rst, 0); + } + + if (gpio_pda_active) { + err = gpio_request(gpio_pda_active, "PDA_ACTIVE"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "PDA_ACTIVE", err); + } else + gpio_direction_output(gpio_pda_active, 0); +} + + if (gpio_phone_active) { + err = gpio_request(gpio_phone_active, "PHONE_ACTIVE"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "PHONE_ACTIVE", err); + } else + gpio_direction_input(gpio_phone_active); + } + + 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 = 0, /* dpram int */ + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; +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, + }, +}; + + +/* PROXIMA 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, + }, +}; + +/* +Proxima 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_info("[MODEM_IF] %s IN!\n", __func__); + + omap_mux_init_gpio(OMAP_GPIO_221_PMIC_PWRON, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(OMAP_GPIO_221_PMIC_PWRHOLD_OFF , OMAP_PIN_OUTPUT); + omap_mux_init_gpio(OMAP_GPIO_CMC_RST, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(OMAP_GPIO_AP2CMC_INT1, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(OMAP_GPIO_CMC2AP_INT2, OMAP_PIN_INPUT); + omap_mux_init_gpio(OMAP_GPIO_AP2CMC_INT2, OMAP_PIN_OUTPUT); + omap_mux_init_gpio(OMAP_GPIO_LTE_ACTIVE, OMAP_PIN_INPUT); +} + +static void lte_modem_cfg_gpio(void) +{ + + int err = 0; + + unsigned gpio_cp_on = lte_modem_data.gpio_cp_on; + unsigned gpio_cp_rst = lte_modem_data.gpio_cp_reset; + /*unsigned gpio_pda_active = lte_modem_data.gpio_pda_active;*/ + 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) { + err = gpio_request(gpio_cp_on, "LTE_ON"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "LTE_ON", err); + } else + gpio_direction_output(gpio_cp_on, 0); +} + + + if (gpio_cp_rst) { + err = gpio_request(gpio_cp_rst, "LTE_RST"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "LTE_RST", err); + } else + gpio_direction_output(gpio_cp_rst, 0); + } +/* + if (gpio_pda_active) { + err = gpio_request(gpio_pda_active, "PDA_ACTIVE"); + if (err) { + printk("fail to request gpio %s : %d\n", + "PDA_ACTIVE", err); + } else + gpio_direction_output(gpio_pda_active, 0); + } +*/ + if (gpio_phone_active) { + err = gpio_request(gpio_phone_active, "LTE_ACTIVE"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "LTE_ACTIVE", err); + } else + gpio_direction_input(gpio_phone_active); + } + +#ifdef CONFIG_LTE_MODEM_CMC221 + if (gpio_cp_off) { + err = gpio_request(gpio_cp_off, "LTE_OFF"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "LTE_OFF", err); + } else + gpio_direction_output(gpio_cp_off, 0); +} + if (gpio_slave_wakeup) { + err = gpio_request(gpio_slave_wakeup, "LTE_SLAVE_WAKEUP"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "LTE_SLAVE_WAKEUP", err); + } else + gpio_direction_input(gpio_slave_wakeup); + } + + if (gpio_host_wakeup) { + err = gpio_request(gpio_host_wakeup, "LTE_HOST_WAKEUP"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "LTE_HOST_WAKEUP", err); + } else + gpio_direction_input(gpio_host_wakeup); + } + + if (gpio_host_active) { + err = gpio_request(gpio_host_active, "LTE_HOST_ACTIVE"); + if (err) { + printk(KERN_ERR "fail to request gpio %s : %d\n", + "LTE_HOST_ACTIVE", err); + } else + gpio_direction_input(gpio_host_active); + } +#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 = { + .name = "modem_if", + .id = 2, + .num_resources = ARRAY_SIZE(lte_modem_res), + .resource = lte_modem_res, + .dev = { + .platform_data = <e_modem_data, + }, +}; + +static int __init init_modem(void) +{ + printk(KERN_INFO "[MODEM_IF] init_modem\n"); + + switch (omap4_tuna_get_type()) { + case TUNA_TYPE_MAGURO: /* Proxima_HSPA */ + /* umts gpios configuration */ + umts_modem_cfg_gpio(); + platform_device_register(&umts_modem); + break; + + case TUNA_TYPE_TORO: /* Proxima_LTE */ + /* cdma gpios configuration */ + /* TODO not supported yet + cdma_modem_cfg_gpio(); + platform_device_register(&cdma_modem); + */ + + /* lte gpios configuration */ + /* TODO not supported yet + 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..6b7895e --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-nfc.c @@ -0,0 +1,112 @@ +/* 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 "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 + +static unsigned int nfc_power; + +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); + +void __init omap4_tuna_nfc_init(void) +{ + struct platform_device *pdev; + + 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); + + 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-power.c b/arch/arm/mach-omap2/board-tuna-power.c new file mode 100644 index 0000000..cccfe99 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-power.c @@ -0,0 +1,158 @@ +/* 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/pda_power.h> +#include <linux/platform_device.h> + +#include <plat/cpu.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 CHG_CUR_ADJ 102 + +#define TPS62361_GPIO 7 + +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 = CHG_CUR_ADJ, .flags = GPIOF_OUT_INIT_LOW, .label = "charge_cur_adj" }, +}; + +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 charger_set_charge(int state) +{ + gpio_set_value(GPIO_CHARGE_N, !state); +} + +static int charger_is_ac_online(void) +{ + return !gpio_get_value(GPIO_TA_NCONNECTED); +} + +static int charger_is_charging(void) +{ + return !gpio_get_value(GPIO_CHARGING_N); +} + +static const __initdata struct resource charger_resources[] = { + { + .name = "ac", + .start = OMAP_GPIO_IRQ(GPIO_TA_NCONNECTED), + .end = OMAP_GPIO_IRQ(GPIO_TA_NCONNECTED), + .flags = IORESOURCE_IRQ | + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + }, + { + .name = "usb", + .start = OMAP_GPIO_IRQ(GPIO_TA_NCONNECTED), + .end = OMAP_GPIO_IRQ(GPIO_TA_NCONNECTED), + .flags = IORESOURCE_IRQ | + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + } +}; + +static char *tuna_charger_supplied_to[] = { + "battery", +}; + +static const __initdata struct pda_power_pdata charger_pdata = { + .init = charger_init, + .exit = charger_exit, + .is_ac_online = charger_is_ac_online, + .is_usb_online = charger_is_ac_online, + .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), +}; + +static struct max17040_platform_data max17043_pdata = { + .charger_online = charger_is_ac_online, + .charger_enable = charger_is_charging, +}; + +static const __initdata struct i2c_board_info max17043_i2c[] = { + { + I2C_BOARD_INFO("max17040", (0x6C >> 1)), + .platform_data = &max17043_pdata, + } +}; + +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); + } + + if (omap4_tuna_get_revision() == TUNA_REV_PRE_LUNCHBOX) { + charger_gpios[0].gpio = 11; + charger_gpios[1].gpio = 12; + } else if (!omap4_tuna_final_gpios()) { + charger_gpios[0].gpio = 159; + charger_gpios[1].gpio = 160; + } + + 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); + + pdev = platform_device_register_resndata(NULL, "pda-power", -1, + charger_resources, ARRAY_SIZE(charger_resources), + &charger_pdata, sizeof(charger_pdata)); + + i2c_register_board_info(4, max17043_i2c, ARRAY_SIZE(max17043_i2c)); + + omap_enable_smartreflex_on_init(); +} 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..dc59124 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-sensors.c @@ -0,0 +1,171 @@ +/* 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 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_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, +}; + +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 = { 0, 1, 0, + 1, 0, 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), + }, +}; + +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_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); + /* optical sensor */ + gp2a_gpio_init(); + + if (omap4_tuna_get_type() == TUNA_TYPE_MAGURO && + omap4_tuna_get_revision() >= 2) { + rotcpy(mpu_data.orientation, orientation_back_right_90); + rotcpy(mpu_data.accel.orientation, orientation_back_180); + } + if (omap4_tuna_get_type() == TUNA_TYPE_TORO && + omap4_tuna_get_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); + } + + i2c_register_board_info(4, tuna_sensors_i2c4_boardinfo, + ARRAY_SIZE(tuna_sensors_i2c4_boardinfo)); +} 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..67010b1 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-wifi.c @@ -0,0 +1,367 @@ +/* + * 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 */ + +#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; +} + +int __init tuna_init_wifi_mem(void) +{ + 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; + } + 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 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) +{ + pr_debug("%s: %d\n", __func__, on); + mdelay(100); + gpio_set_value(GPIO_WLAN_PMENA, on); + mdelay(200); + + tuna_wifi_power_state = on; + return 0; +} + +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 }; + +#if 0 +static int __init parse_tag_wlan_mac(const struct tag *tag) +{ + unsigned char *dptr = (unsigned char *)(&tag->u); + unsigned size; +#ifdef ATAG_TUNA_MAC_DEBUG + unsigned i; +#endif + + size = min((tag->hdr.size - 2) * sizeof(__u32), (unsigned)IFHWADDRLEN); +#ifdef ATAG_TUNA_MAC_DEBUG + printk("WiFi MAC Addr [%d] = 0x%x\n", tag->hdr.size, tag->hdr.tag); + for(i=0;(i < size);i++) { + printk(" %02x", dptr[i]); + } + printk("\n"); +#endif + memcpy(tuna_mac_addr, dptr, size); + return 0; +} + +__tagtable(ATAG_TUNA_MAC, parse_tag_wlan_mac); +#endif + +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; +} + +#if 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 */ + {"US", "US", 69}, /* input ISO "US" to : US regrev 69 */ + {"CA", "US", 69}, /* input ISO "CA" to : US regrev 69 */ + {"EU", "EU", 5}, /* input ISO "EU" to : EU regrev 05 */ + {"FR", "EU", 5}, + {"DE", "EU", 5}, + {"GB", "EU", 5}, /* input ISO "UK" to : EU regrev 05 */ + {"KR", "XY", 3}, + {"AU", "XY", 3}, + {"CN", "XY", 3}, /* input ISO "CN" to : XY regrev 03 */ + {"TW", "XY", 3}, + {"AR", "XY", 3}, +}; + +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 NULL; +} +#endif + +static struct wifi_platform_data tuna_wifi_control = { + .set_power = tuna_wifi_power, + .set_reset = tuna_wifi_reset, + .set_carddetect = tuna_wifi_set_carddetect, + .mem_prealloc = tuna_wifi_mem_prealloc, + .get_mac_addr = tuna_wifi_get_mac_addr, + .get_country_code = NULL, /* 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 100755 index 0000000..b5f20e8 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna.c @@ -0,0 +1,889 @@ +/* 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 <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.h> +#include <plat/common.h> +#include <plat/cpu.h> +#include <plat/usb.h> +#include <plat/mmc.h> +#include "timer-gp.h" + +#include "omap4-sar-layout.h" +#include "hsmmc.h" +#include "control.h" +#include "mux.h" +#include "board-tuna.h" + +#define TUNA_RAMCONSOLE_START (PLAT_PHYS_OFFSET + SZ_512M) +#define TUNA_RAMCONSOLE_SIZE SZ_2M + +struct class *sec_class; +EXPORT_SYMBOL(sec_class); + +#define GPIO_AUD_PWRON 127 +#define GPIO_AUD_PWRON_TORO_V1 20 +#define GPIO_MICBIAS_EN 48 + +/* 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 REBOOT_FLAG_RECOVERY 0x52564352 +#define REBOOT_FLAG_FASTBOOT 0x54534146 +#define REBOOT_FLAG_NORMAL 0x4D524F4E + +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", + [0x05] = "Toro Pre-Lunchbox", +}; + +static const char const *omap4_tuna_hw_name_toro[] = { + [0x00] = "Toro Lunchbox #2", + [0x01] = "Toro 1st Sample", + [0x02] = "Toro 2nd 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]; +} + +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; + + pr_info("Tuna HW revision: %02x (%s), cpu %s\n", tuna_hw_rev, + omap4_tuna_hw_rev_name(), + cpu_is_omap443x() ? "OMAP4430" : "OMAP4460"); +} + +bool omap4_tuna_final_gpios(void) +{ + int type = omap4_tuna_get_type(); + int rev = omap4_tuna_get_revision(); + + if (type == TUNA_TYPE_TORO || + (rev != TUNA_REV_PRE_LUNCHBOX && rev != TUNA_REV_LUNCHBOX)) + return true; + + return false; +} + +/* 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 platform_device ramconsole_device = { + .name = "ram_console", + .id = -1, + .num_resources = ARRAY_SIZE(ramconsole_resources), + .resource = ramconsole_resources, +}; + +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_PIN_INPUT); +} + +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, + }, +}; + +#define PHYS_ADDR_DUCATI_MEM (0x80000000 + SZ_1G - (SZ_1M * 104)) + +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_DUCATI_MEM - SZ_256M - SZ_64M, + .size = SZ_64M, + }, + { .type = OMAP_ION_HEAP_TYPE_TILER, + .id = OMAP_ION_HEAP_TILER, + .name = "tiler", + .base = PHYS_ADDR_DUCATI_MEM - SZ_256M, + .size = SZ_256M, + }, + { + .type = ION_HEAP_TYPE_CARVEOUT, + .id = OMAP_ION_HEAP_LARGE_SURFACES, + .name = "large_surfaces", + .base = 0x80000000 + SZ_512M + SZ_2M, + .size = SZ_32M, + }, + }, +}; + +static struct platform_device tuna_ion_device = { + .name = "ion-omap4", + .id = -1, + .dev = { + .platform_data = &tuna_ion_data, + }, +}; + +static struct platform_device *tuna_devices[] __initdata = { + &ramconsole_device, + &wl1271_device, + &bcm4330_bluetooth_device, + &twl6030_madc_device, + &tuna_ion_device, +}; + +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); + +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_GADGET_MUSB_HDRC + .mode = MUSB_PERIPHERAL, +#else + .mode = MUSB_OTG, +#endif + .power = 100, +}; + +static struct twl4030_usb_data omap4_usbphy_data = { + .phy_init = omap4430_phy_init, + .phy_exit = omap4430_phy_exit, + .phy_power = omap4430_phy_power, + .phy_set_clock = omap4430_phy_set_clk, + .phy_suspend = omap4430_phy_suspend, +}; + +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_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, + }, +}; + +static struct regulator_consumer_supply tuna_vaux3_supplies[] = { + { + .supply = "vlcd", + }, +}; + +static struct regulator_init_data tuna_vaux3 = { + .constraints = { + .min_uV = 3300000, + .max_uV = 3300000, + .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, + }, +}; + +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, + }, +}; + +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_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, + }, +}; + +static struct regulator_init_data tuna_vusb = { + .constraints = { + .min_uV = 3300000, + .max_uV = 3300000, + .apply_uV = true, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, +}; + +static struct regulator_init_data tuna_clk32kg = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, +}; + +static struct twl4030_codec_audio_data twl6040_audio = { + /* Add audio only data */ +}; + +static struct twl4030_codec_data twl6040_codec = { + .audio = &twl6040_audio, + .naudint_irq = OMAP44XX_IRQ_SYS_2N, + .irq_base = TWL6040_CODEC_IRQ_BASE, +}; + +static struct twl4030_platform_data tuna_twldata = { + .irq_base = TWL6030_IRQ_BASE, + .irq_end = TWL6030_IRQ_END, + + /* Regulators */ + .vmmc = &tuna_vmmc, + .vpp = &tuna_vpp, + .vana = &tuna_vana, + .vcxio = &tuna_vcxio, + .vdac = &tuna_vdac, + .vusb = &tuna_vusb, + .vaux2 = &tuna_vaux2, + .vaux3 = &tuna_vaux3, + .clk32kg = &tuna_clk32kg, + .usb = &omap4_usbphy_data, + + /* children */ + .codec = &twl6040_codec, + .madc = &twl6030_madc, +}; + +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_gpio(GPIO_MICBIAS_EN, OMAP_PIN_OUTPUT); + gpio_request(GPIO_MICBIAS_EN, "MICBIAS_EN"); + gpio_direction_output(GPIO_MICBIAS_EN, 1); +} + +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 int __init tuna_i2c_init(void) +{ + omap_mux_init_signal("sys_nirq1", OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("i2c1_scl.i2c1_scl", OMAP_PIN_INPUT_PULLUP); + omap_mux_init_signal("i2c1_sda.i2c1_sda", OMAP_PIN_INPUT_PULLUP); + + /* + * 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, NULL, 0); + omap_register_i2c_bus(3, 400, NULL, 0); + omap_register_i2c_bus(4, 400, NULL, 0); + 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), /* gpio_135 */ + OMAP4_MUX(KPD_COL0, OMAP_MUX_MODE3 | OMAP_PIN_INPUT), /* gpio_173 */ + OMAP4_MUX(GPMC_A19, OMAP_MUX_MODE3 | OMAP_PIN_INPUT), /* 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), + { .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 }, +}; + +static struct omap_device_pad serial2_pads[] __initdata = { + OMAP_MUX_STATIC("uart2_cts.uart2_cts", + OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0), + OMAP_MUX_STATIC("uart2_rts.uart2_rts", + OMAP_PIN_OUTPUT | OMAP_MUX_MODE0), + OMAP_MUX_STATIC("uart2_rx.uart2_rx", + OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0), + OMAP_MUX_STATIC("uart2_tx.uart2_tx", + OMAP_PIN_OUTPUT | OMAP_MUX_MODE0), +}; + +static struct omap_device_pad serial3_pads[] __initdata = { + OMAP_MUX_STATIC("uart3_cts_rctx.uart3_cts_rctx", + OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0), + OMAP_MUX_STATIC("uart3_rts_sd.uart3_rts_sd", + OMAP_PIN_OUTPUT | OMAP_MUX_MODE0), + OMAP_MUX_STATIC("uart3_rx_irrx.uart3_rx_irrx", + OMAP_PIN_INPUT | OMAP_MUX_MODE0), + OMAP_MUX_STATIC("uart3_tx_irtx.uart3_tx_irtx", + OMAP_PIN_OUTPUT | OMAP_MUX_MODE0), +}; + +static struct omap_device_pad serial4_pads[] __initdata = { + OMAP_MUX_STATIC("uart4_rx.uart4_rx", + OMAP_PIN_INPUT | OMAP_MUX_MODE0), + OMAP_MUX_STATIC("uart4_tx.uart4_tx", + OMAP_PIN_OUTPUT | OMAP_MUX_MODE0), +}; + +static struct omap_board_data serial2_data __initdata = { + .id = 1, + .pads = serial2_pads, + .pads_cnt = ARRAY_SIZE(serial2_pads), +}; + +static struct omap_board_data serial3_data __initdata = { + .id = 2, + .pads = serial3_pads, + .pads_cnt = ARRAY_SIZE(serial3_pads), +}; + +static struct omap_board_data serial4_data __initdata = { + .id = 3, + .pads = serial4_pads, + .pads_cnt = ARRAY_SIZE(serial4_pads), +}; + +static inline void __init board_serial_init(void) +{ + struct omap_board_data bdata; + bdata.flags = 0; + bdata.pads = NULL; + bdata.pads_cnt = 0; + bdata.id = 0; + /* pass dummy data for UART1 */ + omap_serial_init_port(&bdata); + + omap_serial_init_port(&serial2_data); + omap_serial_init_port(&serial3_data); + omap_serial_init_port(&serial4_data); +} +#else +#define board_mux NULL +#define board_wkup_mux NULL + +static inline void __init board_serial_init(void) +{ + omap_serial_init(); +} +#endif + +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; + } + } + + /* 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 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_SOC_ATTR_RO(_name, _show) \ + struct kobj_attribute tuna_soc_prop_attr_##_name = \ + __ATTR(_name, S_IRUGO, _show, NULL) + +static TUNA_SOC_ATTR_RO(family, tuna_soc_family_show); +static TUNA_SOC_ATTR_RO(revision, tuna_soc_revision_show); +static TUNA_SOC_ATTR_RO(type, tuna_soc_type_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, + NULL, +}; + +static struct attribute_group tuna_soc_prop_attr_group = { + .attrs = tuna_soc_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(soc_kobj, &tuna_soc_prop_attr_group); + if (ret) + goto err_sysfs_create; + return; + +err_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 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(); + + register_reboot_notifier(&tuna_reboot_notifier); + + if (omap4_tuna_final_gpios()) { + /* 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); + } else { + /* hsmmc d0-d7 */ + omap_mux_init_signal("gpmc_ad0", HSMMC2_MUX); + omap_mux_init_signal("gpmc_ad1", HSMMC2_MUX); + omap_mux_init_signal("gpmc_ad2", HSMMC2_MUX); + omap_mux_init_signal("gpmc_ad3", HSMMC2_MUX); + omap_mux_init_signal("gpmc_ad4", HSMMC2_MUX); + omap_mux_init_signal("gpmc_ad5", HSMMC2_MUX); + omap_mux_init_signal("gpmc_ad6", HSMMC2_MUX); + omap_mux_init_signal("gpmc_ad7", HSMMC2_MUX); + /* hsmmc cmd */ + omap_mux_init_signal("gpmc_nwe", HSMMC2_MUX); + /* hsmmc clk */ + omap_mux_init_signal("gpmc_noe", HSMMC2_MUX); + + mmc[0].mmc = 2; + } + + if (omap4_tuna_get_revision() != TUNA_REV_PRE_LUNCHBOX) { + gpio_request(158, "emmc_en"); + gpio_direction_output(158, 1); + omap_mux_init_gpio(158, OMAP_PIN_INPUT_PULLUP); + } + + sec_common_init(); + tuna_wlan_init(); + tuna_audio_init(); + tuna_i2c_init(); + tuna_bt_init(); + tuna_gsd4t_gps_init(); + platform_add_devices(tuna_devices, ARRAY_SIZE(tuna_devices)); + board_serial_init(); + omap2_hsmmc_init(mmc); + usb_musb_init(&musb_board_data); + omap4_tuna_create_board_props(); + omap4_tuna_display_init(); + omap4_tuna_input_init(); + omap4_tuna_nfc_init(); + omap4_tuna_power_init(); + omap4_tuna_sensors_init(); +#ifdef CONFIG_OMAP_HSI_DEVICE + if (TUNA_TYPE_MAGURO == omap4_tuna_get_type()) + omap_hsi_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; + + omap_reserve(); + memblock_remove(TUNA_RAMCONSOLE_START, TUNA_RAMCONSOLE_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); + } +} + +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..f1e604a --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna.h @@ -0,0 +1,39 @@ +/* + * 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_PRE_LUNCHBOX 0x5 +#define TUNA_REV_LUNCHBOX 0x0 +#define TUNA_REV_MASK 0xf + +#define TUNA_TYPE_TORO 0x10 +#define TUNA_TYPE_MAGURO 0x00 +#define TUNA_TYPE_MASK 0x10 + +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_nfc_init(void); +void omap4_tuna_power_init(void); +void omap4_tuna_sensors_init(void); +int tuna_wlan_init(void); +int omap_hsi_init(void); +void omap4_tuna_emif_init(void); + +extern struct mmc_platform_data tuna_wifi_data; + +#endif diff --git a/arch/arm/mach-omap2/omap_hsi.c b/arch/arm/mach-omap2/omap_hsi.c new file mode 100644 index 0000000..7c27f84 --- /dev/null +++ b/arch/arm/mach-omap2/omap_hsi.c @@ -0,0 +1,418 @@ +/* + * arch/arm/mach-omap2/hsi.c + * + * HSI device definition + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Original Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/jiffies.h> +#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> + +#include <../drivers/omap_hsi/hsi_driver.h> +#include "clock.h" +#include "mux.h" +#include "control.h" + +static int omap_hsi_wakeup_enable(int hsi_port); +static int omap_hsi_wakeup_disable(int hsi_port); +#define OMAP_HSI_PLATFORM_DEVICE_DRIVER_NAME "omap_hsi" +#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 + + +/* */ + +#define CA_WAKE_MUX_REG (0x4a1000C2) +static int omap_mux_read_signal(const char *muxname) +{ + u16 val = 0; + val = omap_readw(CA_WAKE_MUX_REG); + return val; +} + +static int omap_mux_enable_wakeup(const char *muxname) +{ + u16 val = 0; + val = omap_readw(CA_WAKE_MUX_REG); + val |= OMAP44XX_PADCONF_WAKEUPENABLE0; + omap_writew(val, CA_WAKE_MUX_REG); + return 0; +} + +static int omap_mux_disable_wakeup(const char *muxname) +{ + u16 val = 0; + val = omap_readw(CA_WAKE_MUX_REG); + val &= ~OMAP44XX_PADCONF_WAKEUPENABLE0; + omap_writew(val, CA_WAKE_MUX_REG); + return 0; +} + +/* + * NOTE: We abuse a little bit the struct port_ctx to use it also for + * initialization. + */ + + +static struct port_ctx hsi_port_ctx[] = { + [0] = { + .hst.mode = HSI_MODE_FRAME, + .hst.flow = HSI_FLOW_SYNCHRONIZED, + .hst.frame_size = HSI_FRAMESIZE_DEFAULT, + .hst.divisor = HSI_DIVISOR_DEFAULT, + .hst.channels = HSI_CHANNELS_DEFAULT, + .hst.arb_mode = HSI_ARBMODE_ROUNDROBIN, + .hsr.mode = HSI_MODE_FRAME, + .hsr.flow = HSI_FLOW_SYNCHRONIZED, + .hsr.frame_size = HSI_FRAMESIZE_DEFAULT, + .hsr.channels = HSI_CHANNELS_DEFAULT, + .hsr.divisor = HSI_DIVISOR_DEFAULT, + .hsr.counters = HSI_COUNTERS_FT_DEFAULT | + HSI_COUNTERS_TB_DEFAULT | + HSI_COUNTERS_FB_DEFAULT, + }, +}; + +static struct ctrl_ctx hsi_ctx = { + .sysconfig = 0, + .gdd_gcr = 0, + .dll = 0, + .pctx = hsi_port_ctx, +}; + +static struct hsi_platform_data omap_hsi_platform_data = { + .num_ports = ARRAY_SIZE(hsi_port_ctx), + .hsi_gdd_chan_count = HSI_HSI_DMA_CHANNEL_MAX, + .default_hsi_fclk = HSI_DEFAULT_FCLK, + .ctx = &hsi_ctx, + .device_enable = omap_device_enable, + .device_idle = omap_device_idle, + .device_shutdown = omap_device_shutdown, + .wakeup_enable = omap_hsi_wakeup_enable, + .wakeup_disable = omap_hsi_wakeup_disable, + .wakeup_is_from_hsi = omap_hsi_is_io_wakeup_from_hsi, + .board_suspend = omap_hsi_prepare_suspend, +}; + + +static struct platform_device *hsi_get_hsi_platform_device(void) +{ + struct device *dev; + struct platform_device *pdev; + + /* HSI_TODO: handle platform device id (or port) (0/1) */ + dev = bus_find_device_by_name(&platform_bus_type, NULL, + OMAP_HSI_PLATFORM_DEVICE_NAME); + if (!dev) { + pr_debug("Could not find platform device %s\n", + OMAP_HSI_PLATFORM_DEVICE_NAME); + return 0; + } + + if (!dev->driver) { + /* Could not find driver for platform device. */ + return 0; + } + + pdev = to_platform_device(dev); + + return pdev; +} + +static struct hsi_dev *hsi_get_hsi_controller_data(struct platform_device *pd) +{ + struct hsi_dev *hsi_ctrl; + + if (!pd) + return 0; + + hsi_ctrl = (struct hsi_dev *) platform_get_drvdata(pd); + if (!hsi_ctrl) { + pr_err("Could not find HSI controller data\n"); + return 0; + } + + return hsi_ctrl; +} + +/** +* omap_hsi_is_io_pad_hsi - Indicates if IO Pad has been muxed for HSI CAWAKE +* +* 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) +{ + u16 val; + + /* Check for IO pad */ + val = omap_mux_read_signal(OMAP_HSI_PADCONF_CAWAKE_PIN); + if (val == -ENODEV) + return 0; + + /* Continue only if CAWAKE is muxed */ + if ((val & OMAP_MUX_MODE_MASK) != OMAP_HSI_PADCONF_CAWAKE_MODE) + return 0; + + return 1; +} + +/** +* 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 +*/ +int omap_hsi_is_io_wakeup_from_hsi(void) +{ + u16 val; + + /* Check for IO pad wakeup */ + val = omap_mux_read_signal(OMAP_HSI_PADCONF_CAWAKE_PIN); + if (val == -ENODEV) + return 0; + + /* Continue only if CAWAKE is muxed */ + if ((val & OMAP_MUX_MODE_MASK) != OMAP_HSI_PADCONF_CAWAKE_MODE) + return 0; + + if (val & OMAP44XX_PADCONF_WAKEUPEVENT0) + return 1; + + return 0; +} + +/** +* omap_hsi_wakeup_enable - Enable HSI wakeup feature from RET/OFF mode +* +* @hsi_port - reference to the HSI port onto which enable wakeup feature. +* +* 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) +{ + 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"); + + + /* TODO: handle hsi_port param and use it to find the correct Pad */ + return ret; +} + +/** +* omap_hsi_wakeup_disable - Disable HSI wakeup feature from RET/OFF mode +* +* @hsi_port - reference to the HSI port onto which disable wakeup feature. +* +* 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) +{ + 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 */ + + return ret; +} + +/** +* omap_hsi_prepare_suspend - Prepare HSI for suspend mode +* +* Return value :* 0 if CAWAKE padconf has been configured properly +* * -ENODEV if CAWAKE is not muxed on padconf. +* +*/ +int omap_hsi_prepare_suspend(int hsi_port, bool dev_may_wakeup) +{ + int ret; + + if (dev_may_wakeup) + ret = omap_hsi_wakeup_enable(hsi_port); + else + ret = omap_hsi_wakeup_disable(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. +*/ +int omap_hsi_wakeup(int hsi_port) +{ + static struct platform_device *pdev; + static struct hsi_dev *hsi_ctrl; + + if (!pdev) { + pdev = hsi_get_hsi_platform_device(); + if (!pdev) + return -ENODEV; +} + + if (!device_may_wakeup(&pdev->dev)) { + dev_info(&pdev->dev, "Modem not allowed to wakeup platform"); + return -EPERM; + } + + if (!hsi_ctrl) { + 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"); + + /* CAWAKE falling or rising edge detected */ + hsi_ctrl->hsi_port->cawake_off_event = true; + tasklet_hi_schedule(&hsi_ctrl->hsi_port->hsi_tasklet); + + /* Disable interrupt until Bottom Half has cleared */ + /* the IRQ status register */ + disable_irq_nosync(hsi_ctrl->hsi_port->irq); + + return 0; +} + +/* HSI_TODO : This requires some fine tuning & completion of + * activate/deactivate latency values + */ +static struct omap_device_pm_latency omap_hsi_latency[] = { + [0] = { + .deactivate_func = omap_device_idle_hwmods, + .activate_func = omap_device_enable_hwmods, + .flags = OMAP_DEVICE_LATENCY_AUTO_ADJUST, + }, +}; + +/* HSI device registration */ +static int __init omap_hsi_register(struct omap_hwmod *oh, void *user) +{ + struct omap_device *od; + struct hsi_platform_data *pdata = &omap_hsi_platform_data; + + if (!oh) { + pr_err("Could not look up %s omap_hwmod\n", + OMAP_HSI_HWMOD_NAME); + return -EEXIST; + } + + od = omap_device_build(OMAP_HSI_PLATFORM_DEVICE_DRIVER_NAME, 0, oh, + pdata, sizeof(*pdata), omap_hsi_latency, + ARRAY_SIZE(omap_hsi_latency), false); + WARN(IS_ERR(od), "Can't build omap_device for %s:%s.\n", + OMAP_HSI_PLATFORM_DEVICE_DRIVER_NAME, oh->name); + + pr_info("HSI: device registered as omap_hwmod: %s\n", oh->name); + return 0; +} + +static void __init omap_4430hsi_pad_conf(void) +{ + /* + * HSI pad conf: hsi1_ca/ac_wake/flag/data/ready + * Also configure gpio_92/95/157/187 used by modem + */ + /* hsi1_cawake */ + omap_mux_init_signal("usbb1_ulpitll_clk.hsi1_cawake", \ + OMAP_PIN_INPUT_PULLDOWN | \ + OMAP_PIN_OFF_NONE | \ + OMAP_PIN_OFF_WAKEUPENABLE); + /* hsi1_caflag */ + omap_mux_init_signal("usbb1_ulpitll_dir.hsi1_caflag", \ + OMAP_PIN_INPUT | \ + OMAP_PIN_OFF_NONE); + /* hsi1_cadata */ + omap_mux_init_signal("usbb1_ulpitll_stp.hsi1_cadata", \ + OMAP_PIN_INPUT | \ + OMAP_PIN_OFF_NONE); + /* hsi1_acready */ + omap_mux_init_signal("usbb1_ulpitll_nxt.hsi1_acready", \ + OMAP_PIN_OUTPUT | \ + OMAP_PIN_OFF_OUTPUT_LOW); + /* hsi1_acwake */ + omap_mux_init_signal("usbb1_ulpitll_dat0.hsi1_acwake", \ + OMAP_PIN_OUTPUT | \ + OMAP_PIN_OFF_NONE); + /* hsi1_acdata */ + omap_mux_init_signal("usbb1_ulpitll_dat1.hsi1_acdata", \ + OMAP_PIN_OUTPUT | \ + OMAP_PIN_OFF_NONE); + /* hsi1_acflag */ + omap_mux_init_signal("usbb1_ulpitll_dat2.hsi1_acflag", \ + OMAP_PIN_OUTPUT | \ + OMAP_PIN_OFF_NONE); + /* hsi1_caready */ + omap_mux_init_signal("usbb1_ulpitll_dat3.hsi1_caready", \ + OMAP_PIN_INPUT | \ + OMAP_PIN_OFF_NONE); + /* gpio_92 */ + omap_mux_init_signal("usbb1_ulpitll_dat4.gpio_92", \ + OMAP_PULL_ENA); + /* gpio_95 */ + omap_mux_init_signal("usbb1_ulpitll_dat7.gpio_95", \ + OMAP_PIN_INPUT_PULLDOWN | \ + OMAP_PIN_OFF_NONE); + /* gpio_157 */ + omap_mux_init_signal("usbb2_ulpitll_clk.gpio_157", \ + OMAP_PIN_OUTPUT | \ + OMAP_PIN_OFF_NONE); + /* gpio_187 */ + omap_mux_init_signal("sys_boot3.gpio_187", \ + OMAP_PIN_OUTPUT | \ + OMAP_PIN_OFF_NONE); +} + +/* HSI devices registration */ +int __init omap_hsi_init(void) +{ + omap_4430hsi_pad_conf(); + /* Keep this for genericity, although there is only one hwmod for HSI */ + return omap_hwmod_for_each_by_class(OMAP_HSI_HWMOD_CLASSNAME, + omap_hsi_register, NULL); +} diff --git a/arch/arm/mach-omap2/omap_hwmod_44xx_data.c b/arch/arm/mach-omap2/omap_hwmod_44xx_data.c index eadc88e..2614dd3 100644 --- a/arch/arm/mach-omap2/omap_hwmod_44xx_data.c +++ b/arch/arm/mach-omap2/omap_hwmod_44xx_data.c @@ -2683,7 +2683,7 @@ static struct omap_hwmod_class_sysconfig omap44xx_hsi_sysc = { SYSC_HAS_SOFTRESET | SYSS_HAS_RESET_STATUS), .idlemodes = (SIDLE_FORCE | SIDLE_NO | SIDLE_SMART | SIDLE_SMART_WKUP | MSTANDBY_FORCE | MSTANDBY_NO | - MSTANDBY_SMART), + MSTANDBY_SMART | MSTANDBY_SMART_WKUP), .sysc_fields = &omap_hwmod_sysc_type1, }; @@ -5624,7 +5624,7 @@ static __initdata struct omap_hwmod *omap44xx_hwmods[] = { &omap44xx_gpio6_hwmod, /* hsi class */ -/* &omap44xx_hsi_hwmod, */ + &omap44xx_hsi_hwmod, /* gpu class */ &omap44xx_gpu_hwmod, diff --git a/arch/arm/mach-omap2/opp4xxx_data.c b/arch/arm/mach-omap2/opp4xxx_data.c index 0eec22a..2d70bd1 100644 --- a/arch/arm/mach-omap2/opp4xxx_data.c +++ b/arch/arm/mach-omap2/opp4xxx_data.c @@ -141,7 +141,10 @@ static struct omap_opp_def __initdata omap443x_opp_def_list[] = { OPP_INITIALIZER("dsp", "dpll_iva_m4x2_ck", "iva", true, 465500000, OMAP4430_VDD_IVA_OPP100_UV), /* DSP OPP3 - OPPTB */ OPP_INITIALIZER("dsp", "dpll_iva_m4x2_ck", "iva", false, 496000000, OMAP4430_VDD_IVA_OPPTURBO_UV), - + /* HSI OPP1 - OPP50 */ + OPP_INITIALIZER("hsi", "hsi_fck", "core", true, 96000000, OMAP4430_VDD_CORE_OPP50_UV), + /* HSI OPP2 - OPP100 */ + OPP_INITIALIZER("hsi", "hsi_fck", "core", true, 96000000, OMAP4430_VDD_CORE_OPP100_UV), /* TODO: add aess */ }; @@ -257,7 +260,6 @@ static struct omap_opp_def __initdata omap446x_opp_def_list[] = { OPP_INITIALIZER("gpu", "dpll_per_m7x2_ck", "core", true, 307200000, OMAP4460_VDD_CORE_OPP100_UV), /* SGX OPP3 - OPPOV */ OPP_INITIALIZER("gpu", "dpll_per_m7x2_ck", "core", true, 384000000, OMAP4460_VDD_CORE_OPP100_OV_UV), - /* FDIF OPP1 - OPP25 */ OPP_INITIALIZER("fdif", "fdif_fck", "core", true, 32000000, OMAP4430_VDD_CORE_OPP50_UV), /* FDIF OPP2 - OPP50 */ @@ -270,6 +272,10 @@ static struct omap_opp_def __initdata omap446x_opp_def_list[] = { OPP_INITIALIZER("dsp", "dpll_iva_m4x2_ck", "iva", true, 465500000, OMAP4430_VDD_IVA_OPP100_UV), /* DSP OPP3 - OPPTB */ OPP_INITIALIZER("dsp", "dpll_iva_m4x2_ck", "iva", false, 496000000, OMAP4430_VDD_IVA_OPPTURBO_UV), + /* HSI OPP1 - OPP50 */ + OPP_INITIALIZER("hsi", "hsi_fck", "core", true, 96000000, OMAP4460_VDD_CORE_OPP50_UV), + /* HSI OPP2 - OPP100 */ + OPP_INITIALIZER("hsi", "hsi_fck", "core", true, 96000000, OMAP4460_VDD_CORE_OPP100_UV), /* TODO: add aess */ }; diff --git a/arch/arm/mach-omap2/pm44xx.c b/arch/arm/mach-omap2/pm44xx.c index fae39a6..7496b32 100644 --- a/arch/arm/mach-omap2/pm44xx.c +++ b/arch/arm/mach-omap2/pm44xx.c @@ -94,14 +94,14 @@ void omap4_enter_sleep(unsigned int cpu, unsigned int power_state) omap_uart_prepare_idle(1); omap_uart_prepare_idle(2); omap_uart_prepare_idle(3); - omap2_gpio_prepare_for_idle(0); + //omap2_gpio_prepare_for_idle(0); omap4_trigger_ioctrl(); } omap4_enter_lowpower(cpu, power_state); if (core_next_state < PWRDM_POWER_ON) { - omap2_gpio_resume_after_idle(); + //omap2_gpio_resume_after_idle(); omap_uart_resume_idle(0); omap_uart_resume_idle(1); omap_uart_resume_idle(2); @@ -304,6 +304,17 @@ 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 */ + #define CA_WAKE_MUX_REG (0x4a1000C2) + #define CM_L3INIT_HSI_CLKCTRL (0x4a009338) + #define HSI_SYSCONFIG (0x4a058010) + if (omap_readw(CA_WAKE_MUX_REG) & (1<<15)) { + /* Enable HSI module */ + omap_writel(omap_readl(CM_L3INIT_HSI_CLKCTRL) | 0x1, CM_L3INIT_HSI_CLKCTRL); + /* Put HSI in: No-standby and No-idle */ + omap_writel( (1<<3) | (1<<12), HSI_SYSCONFIG); + } omap4_trigger_ioctrl(); } diff --git a/arch/arm/plat-omap/include/plat/omap_hsi.h b/arch/arm/plat-omap/include/plat/omap_hsi.h new file mode 100644 index 0000000..b5a5334 --- /dev/null +++ b/arch/arm/plat-omap/include/plat/omap_hsi.h @@ -0,0 +1,494 @@ +/* + * /mach/omap_hsi.h + * + * Hardware definitions for HSI and SSI. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* NOTE: This file defines the registers address offsets for both the + * SSI and HSI devices. Most of the registers share the same offset between + * these devices. + * When common or HSI only, the constants are name HSI*. Else the SSI specific + * constants are name HSI_SSI* + */ + +#ifndef __OMAP_HSI_H__ +#define __OMAP_HSI_H__ + +/* Set the HSI Functional Clock to 96MHz. + * This is to ensure HSI will function even at OPP50. */ +#define HSI_DEFAULT_FCLK 96000000 /* 96 MHz */ + + +#define HSI_PORT_OFFSET 0x1000 + +/* + * GDD base addr : 0x48059000 (SSI) + * GDD base addr : 0x4A059000 (HSI) + */ +#define HSI_GDD_OFFSET 0x1000 +#define HSI_GDD_BASE HSI_GDD_OFFSET /* 0x9000 */ + +/* + * HST base addr: + * port 1: 0x4805a000 (SSI) - 0x4A05a000 (HSI) + * port 2: 0x4805b000 (SSI) - 0x4a05b000 (HSI) + */ +#define HSI_HST_OFFSET 0x2000 +#define HSI_HST_BASE(port) (HSI_HST_OFFSET + (((port) - 1) *\ + (HSI_PORT_OFFSET))) + /* + * HSR base addr: + * port 1: 0x4805a800 (SSI) - 0x4A05a800 (HSI) + * port 2: 0x4805b800 (SSI) - 0x4A05b800 (HSI) + */ +#define HSI_HSR_OFFSET 0x2800 +#define HSI_HSR_BASE(port) (HSI_HSR_OFFSET + (((port) - 1) *\ + (HSI_PORT_OFFSET))) +/* + * HSI SYS registers + */ +#define HSI_SYS_REVISION_REG 0x0000 +#define HSI_SSI_REV_MASK 0x000000ff +#define HSI_SSI_REV_MAJOR 0xf0 +#define HSI_SSI_REV_MINOR 0x0f + +#define HSI_SYS_SYSCONFIG_REG 0x0010 +#define HSI_AUTOIDLE (1 << 0) +#define HSI_SOFTRESET (1 << 1) +#define HSI_FREE_EMU (1 << 2) /* Only for HSI */ +#define HSI_SIDLEMODE_FORCE 0 +#define HSI_SIDLEMODE_NO (1 << 3) +#define HSI_SIDLEMODE_SMART (1 << 4) +#define HSI_SIDLEMODE_SMART_WAKEUP (3 << 3) +#define HSI_SIDLEMODE_MASK 0x00000018 +#define HSI_MIDLEMODE_FORCE 0 +#define HSI_MIDLEMODE_NO (1 << 12) +#define HSI_MIDLEMODE_SMART (1 << 13) +#define HSI_MIDLEMODE_SMART_WAKEUP (3 << 12) +#define HSI_MIDLEMODE_MASK 0x00003000 + +#define HSI_SYS_SYSSTATUS_REG 0x0014 +#define HSI_RESETDONE 1 + +#define HSI_SYS_MPU_STATUS_BASE 0x0808 +#define HSI_SYS_MPU_STATUS_PORT_OFFSET 0x10 +#define HSI_SYS_MPU_STATUS_IRQ_OFFSET 8 + +#define HSI_SYS_MPU_STATUS_REG(port, irq) \ + (HSI_SYS_MPU_STATUS_BASE + \ + ((((port) - 1) * HSI_SYS_MPU_STATUS_PORT_OFFSET) +\ + ((irq) * HSI_SYS_MPU_STATUS_IRQ_OFFSET))) +#define HSI_SYS_MPU_ENABLE_BASE 0x080c +#define HSI_SYS_MPU_ENABLE_PORT_OFFSET 0x10 +#define HSI_SYS_MPU_ENABLE_IRQ_OFFSET 8 + +#define HSI_SYS_MPU_ENABLE_REG(port, irq) \ + (HSI_SYS_MPU_ENABLE_BASE + \ + ((((port) - 1) * HSI_SYS_MPU_ENABLE_PORT_OFFSET) +\ + ((irq) * HSI_SYS_MPU_ENABLE_IRQ_OFFSET))) +#define HSI_HST_DATAACCEPT(channel) (((channel) < 8) ? \ + (1 << (channel)) : \ + (1 << ((channel) - 8))) +#define HSI_HSR_DATAAVAILABLE(channel) ((channel) < 8 ? \ + (1 << ((channel) + 8)) : \ + (1 << ((channel) - 8 + 8))) +#define HSI_HSR_DATAOVERRUN(channel) ((channel) < 8 ? \ + (1 << ((channel) + 16)) : \ + (1 << ((channel) - 8 + 16))) + +#define HSI_ERROROCCURED (1 << 24) +#define HSI_BREAKDETECTED (1 << 25) +#define HSI_CAWAKEDETECTED (1 << 26) + +#define HSI_SYS_GDD_MPU_IRQ_STATUS_REG 0x0800 +#define HSI_SYS_GDD_MPU_IRQ_ENABLE_REG 0x0804 +#define HSI_GDD_LCH(channel) (1 << (channel)) + + +#define HSI_SYS_WAKE_OFFSET 0x10 +#define HSI_SYS_WAKE_BASE 0x0c00 +#define HSI_SYS_WAKE_REG(port) (HSI_SYS_WAKE_BASE +\ + (((port) - 1) * HSI_SYS_WAKE_OFFSET)) + +#define HSI_SYS_CLEAR_WAKE_BASE 0x0c04 +#define HSI_SYS_CLEAR_WAKE_REG(port) (HSI_SYS_CLEAR_WAKE_BASE +\ + (((port) - 1) * HSI_SYS_WAKE_OFFSET)) + +#define HSI_SYS_SET_WAKE_BASE 0x0c08 +#define HSI_SYS_SET_WAKE_REG(port) (HSI_SYS_SET_WAKE_BASE +\ + (((port) - 1) * HSI_SYS_WAKE_OFFSET)) + +#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_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_CLEAR_WAKE(channel) (1 << (channel)) +#define HSI_WAKE(channel) (1 << (channel)) + +#define HSI_SYS_HWINFO_REG 0x0004 /* only for HSI */ + +/* Additional registers definitions (for channels 8 .. 15) for HSI */ +#define HSI_SYS_MPU_U_STATUS_BASE 0x0408 +#define HSI_SYS_MPU_U_STATUS_REG(port, irq) \ + (HSI_SYS_MPU_U_STATUS_BASE + \ + ((((port) - 1) * HSI_SYS_MPU_STATUS_PORT_OFFSET) +\ + ((irq) * HSI_SYS_MPU_STATUS_IRQ_OFFSET))) + +#define HSI_SYS_MPU_U_ENABLE_BASE 0x040c +#define HSI_SYS_MPU_U_ENABLE_REG(port, irq) \ + (HSI_SYS_MPU_U_ENABLE_BASE + \ + ((((port) - 1) * HSI_SYS_MPU_ENABLE_PORT_OFFSET) +\ + ((irq) * HSI_SYS_MPU_ENABLE_IRQ_OFFSET))) + +/* + * HSI HST registers + */ +#define HSI_HST_ID_REG(port) (HSI_HST_BASE(port) + 0x0000) + +#define HSI_HST_MODE_REG(port) (HSI_HST_BASE(port) + 0x0004) +#define HSI_MODE_VAL_MASK 3 +#define HSI_MODE_SLEEP 0 +#define HSI_MODE_STREAM 1 +#define HSI_MODE_FRAME 2 +#define HSI_SSI_MODE_MULTIPOINTS 3 /* SSI only */ +#define HSI_FLOW_OFFSET 2 /* HSI only */ +#define HSI_FLOW_VAL_MASK 3 /* HSI only */ +#define HSI_FLOW_SYNCHRONIZED 0 /* HSI only */ +#define HSI_FLOW_PIPELINED 1 /* HSI only */ +#define HSI_FLOW_REAL_TIME 2 /* HSI only */ +#define HSI_HST_MODE_WAKE_CTRL_AUTO (1 << 4) /* HSI only */ +#define HSI_HST_MODE_WAKE_CTRL_SW (0 << 4) /* HSI only */ + +#define HSI_HST_FRAMESIZE_REG(port) (HSI_HST_BASE(port) + 0x0008) +#define HSI_FRAMESIZE_DEFAULT 31 +#define HSI_FRAMESIZE_MAX 0x1f + +#define HSI_HST_TXSTATE_REG(port) (HSI_HST_BASE(port) + 0x000c) +#define HSI_HST_TXSTATE_VAL_MASK 0x07 +#define HSI_HST_TXSTATE_IDLE 0 + +#define HSI_HST_BUFSTATE_REG(port) (HSI_HST_BASE(port) + 0x0010) +#define HSI_HST_BUFSTATE_FIFO_REG(fifo) (((fifo) < 8) ? \ + HSI_HST_BUFSTATE_REG(1) : \ + HSI_HST_BUFSTATE_REG(2)) +#define HSI_BUFSTATE_CHANNEL(channel) ((channel) < 8 ? \ + (1 << (channel)) : \ + (1 << ((channel) - 8))) + +#define HSI_HST_DIVISOR_REG(port) (HSI_HST_BASE(port) + 0x0018) +#define HSI_DIVISOR_DEFAULT 1 +#define HSI_SSI_MAX_TX_DIVISOR 0x7f /* for SSI */ +#define HSI_MAX_TX_DIVISOR 0xff /* for HSI */ + +#define HSI_HST_BREAK_REG(port) (HSI_HST_BASE(port) + 0x0020) +#define HSI_HST_CHANNELS_REG(port) (HSI_HST_BASE(port) + 0x0024) +#define HSI_CHANNELS_DEFAULT 4 +#define HSI_SSI_CHANNELS_MAX 8 /* for SSI */ +#define HSI_CHANNELS_MAX 16 /* for HSI */ + +#define HSI_HST_ARBMODE_REG(port) (HSI_HST_BASE(port) + 0x0028) +#define HSI_ARBMODE_ROUNDROBIN 0 +#define HSI_ARBMODE_PRIORITY 1 + +#define HSI_HST_BUFFER_BASE(port) (HSI_HST_BASE(port) + 0x0080) +#define HSI_HST_BUFFER_CH_REG(port, channel) (HSI_HST_BUFFER_BASE(port) +\ + ((channel) * 4)) +#define HSI_HST_BUFFER_FIFO_REG(fifo) (((fifo) < 8) ? \ + (HSI_HST_BUFFER_CH_REG(1, (fifo))) : \ + (HSI_HST_BUFFER_CH_REG(2, (fifo) - 8))) + +#define HSI_HST_SWAPBUF_BASE(port) (HSI_HST_BASE(port) + 0x00c0) +#define HSI_HST_SWAPBUF_CH_REG(port, channel) (HSI_HST_SWAPBUF_BASE(port) +\ + ((channel) * 4)) + + +/* Additional registers for HSI */ +#define HSI_HST_FIFO_COUNT 16 +#define HSI_HST_FIFO_SIZE 8 +#define HSI_HST_MAPPING_FIFO_REG(fifo) (HSI_HST_BASE(1) + 0x0100 +\ + ((fifo) * 4)) +#define HSI_MAPPING_ENABLE 1 +#define HSI_MAPPING_CH_NUMBER_OFFSET 1 +#define HSI_MAPPING_PORT_NUMBER_OFFSET 7 +#define HSI_HST_MAPPING_THRESH_OFFSET 10 +#define HSI_HST_MAPPING_THRESH_VALUE (0x0 << HSI_HST_MAPPING_THRESH_OFFSET) + +/* + * HSI HSR registers + */ +#define HSI_HSR_ID_REG(port) (HSI_HSR_BASE(port) + 0x0000) + +#define HSI_HSR_MODE_REG(port) (HSI_HSR_BASE(port) + 0x0004) + +#define HSI_HSR_MODE_MODE_VAL_MASK (3 << 0) /* HSI only */ +#define HSI_HSR_MODE_FLOW_VAL_MASK (3 << 2) /* HSI only */ +#define HSI_HSR_MODE_WAKE_STATUS (1 << 4) /* HSI only */ +#define HSI_HSR_MODE_MODE_VAL_SLEEP 0xFFFFFFFC /* HSI only */ + +#define HSI_HSR_FRAMESIZE_REG(port) (HSI_HSR_BASE(port) + 0x0008) + +#define HSI_HSR_RXSTATE_REG(port) (HSI_HSR_BASE(port) + 0x000c) + +#define HSI_HSR_BUFSTATE_REG(port) (HSI_HSR_BASE(port) + 0x0010) +#define HSI_HSR_BUFSTATE_FIFO_REG(fifo) (((fifo) < 8) ? \ + HSI_HSR_BUFSTATE_REG(1) : \ + HSI_HSR_BUFSTATE_REG(2)) + +#define HSI_HSR_BREAK_REG(port) (HSI_HSR_BASE(port) + 0x001c) + +#define HSI_HSR_ERROR_REG(port) (HSI_HSR_BASE(port) + 0x0020) +#define HSI_HSR_ERROR_SIG 1 +#define HSI_HSR_ERROR_FTE (1 << 1) /* HSI only */ +#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_ERRORACK_REG(port) (HSI_HSR_BASE(port) + 0x0024) + +#define HSI_HSR_CHANNELS_REG(port) (HSI_HSR_BASE(port) + 0x0028) + +#define HSI_HSR_OVERRUN_REG(port) (HSI_HSR_BASE(port) + 0x002c) + +#define HSI_HSR_OVERRUNACK_REG(port) (HSI_HSR_BASE(port) + 0x0030) + +#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 */ +#define HSI_SSI_RX_TIMEOUT_OFFSET 0 /* SSI only */ +#define HSI_SSI_RX_TIMEOUT_MASK 0x1ff /* SSI only */ +#define HSI_COUNTERS_FT_MASK 0x000fffff /* HSI only */ +#define HSI_COUNTERS_TB_MASK 0x00f00000 /* HSI only */ +#define HSI_COUNTERS_FB_MASK 0xff000000 /* HSI only */ +#define HSI_COUNTERS_FT_OFFSET 0 /* HSI only */ +#define HSI_COUNTERS_TB_OFFSET 20 /* HSI only */ +#define HSI_COUNTERS_FB_OFFSET 24 /* HSI only */ +/* Default FT value: 2 x max_bits_per_frame + 20% margin */ +#define HSI_COUNTERS_FT_DEFAULT (90 << HSI_COUNTERS_FT_OFFSET) +#define HSI_COUNTERS_TB_DEFAULT (6 << HSI_COUNTERS_TB_OFFSET) +#define HSI_COUNTERS_FB_DEFAULT (8 << HSI_COUNTERS_FB_OFFSET) +#define HSI_HSR_COMBINE_COUNTERS(FB, TB, FT) \ + (((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)) +#define SSI_SSR_COMBINE_COUNTERS(FT) \ + ((FT << HSI_SSI_RX_TIMEOUT_OFFSET) & HSI_SSI_RX_TIMEOUT_MASK) + +#define HSI_HSR_BUFFER_BASE(port) (HSI_HSR_BASE(port) + 0x0080) +#define HSI_HSR_BUFFER_CH_REG(port, channel) (HSI_HSR_BUFFER_BASE(port) +\ + ((channel) * 4)) +#define HSI_HSR_BUFFER_FIFO_REG(fifo) (((fifo) < 8) ? \ + (HSI_HSR_BUFFER_CH_REG(1, (fifo))) : \ + (HSI_HSR_BUFFER_CH_REG(2, (fifo) - 8))) + +#define HSI_HSR_SWAPBUF_BASE(port) (HSI_HSR_BASE(port) + 0x00c0) +#define HSI_HSR_SWAPBUF_CH_REG(port, channel) (HSI_HSR_SWAPBUF_BASE(port) +\ + ((channel) * 4)) + +/* Additional registers for HSI */ +#define HSI_HSR_FIFO_COUNT 16 +#define HSI_HSR_FIFO_SIZE 8 +#define HSI_HSR_MAPPING_FIFO_REG(fifo) (HSI_HSR_BASE(1) + 0x0100 +\ + ((fifo) * 4)) +#define HSI_HSR_MAPPING_WORDS_MASK (0xf << 10) + +#define HSI_HSR_DLL_REG (HSI_HSR_BASE(1) + 0x0144) +#define HSI_HSR_DLL_COCHRE 1 +#define HSI_HSR_DLL_COCHGR (1 << 4) +#define HSI_HSR_DLL_INCO_MASK 0x0003ff00 +#define HSI_HSR_DLL_INCO_OFFSET 8 + +#define HSI_HSR_DIVISOR_REG(port) (HSI_HSR_BASE(port) + 0x014C) +#define HSI_HSR_DIVISOR_MASK 0xff +#define HSI_MAX_RX_DIVISOR 0xff + +/* + * HSI GDD registers + */ +#define HSI_SSI_DMA_CHANNEL_MAX 8 +#define HSI_HSI_DMA_CHANNEL_MAX 16 + +#define HSI_SSI_GDD_HW_ID_REG (HSI_GDD_BASE + 0x0000) + +#define HSI_SSI_GDD_PPORT_ID_REG (HSI_GDD_BASE + 0x0010) + +#define HSI_SSI_GDD_MPORT_ID_REG (HSI_GDD_BASE + 0x0014) + +#define HSI_SSI_GDD_PPORT_SR_REG (HSI_GDD_BASE + 0x0020) +#define HSI_PPORT_ACTIVE_LCH_NUMBER_MASK 0xff + +#define HSI_GDD_MPORT_SR_REG (HSI_GDD_BASE + 0x0024) +#define HSI_SSI_MPORT_ACTIVE_LCH_NUMBER_MASK 0xff + +#define HSI_SSI_GDD_TEST_REG (HSI_GDD_BASE + 0x0040) +#define HSI_SSI_TEST 1 + +#define HSI_GDD_GCR_REG (HSI_GDD_BASE + 0x0100) +#define HSI_CLK_AUTOGATING_ON (1 << 3) +#define HSI_SWITCH_OFF (1 << 0) + +#define HSI_GDD_GRST_REG (HSI_GDD_BASE + 0x0200) +#define HSI_GDD_GRST_SWRESET 1 + +#define HSI_GDD_CSDP_BASE (HSI_GDD_BASE + 0x0800) +#define HSI_GDD_CSDP_OFFSET 0x40 +#define HSI_GDD_CSDP_REG(channel) (HSI_GDD_CSDP_BASE +\ + ((channel) * HSI_GDD_CSDP_OFFSET)) + +#define HSI_DST_BURST_EN_MASK 0xc000 +#define HSI_DST_SINGLE_ACCESS0 0 +#define HSI_DST_SINGLE_ACCESS (1 << 14) +#define HSI_DST_BURST_4X32_BIT (2 << 14) +#define HSI_DST_BURST_8x32_BIT (3 << 14) + +#define HSI_DST_MASK 0x1e00 +#define HSI_DST_MEMORY_PORT (8 << 9) +#define HSI_DST_PERIPHERAL_PORT (9 << 9) + +#define HSI_SRC_BURST_EN_MASK 0x0180 +#define HSI_SRC_SINGLE_ACCESS0 0 +#define HSI_SRC_SINGLE_ACCESS (1 << 7) +#define HSI_SRC_BURST_4x32_BIT (2 << 7) +#define HSI_SRC_BURST_8x32_BIT (3 << 7) + +#define HSI_SRC_MASK 0x003c +#define HSI_SRC_MEMORY_PORT (8 << 2) +#define HSI_SRC_PERIPHERAL_PORT (9 << 2) + +#define HSI_DATA_TYPE_MASK 3 +#define HSI_DATA_TYPE_S32 2 + +#define HSI_GDD_CCR_BASE (HSI_GDD_BASE + 0x0802) +#define HSI_GDD_CCR_OFFSET 0x40 +#define HSI_GDD_CCR_REG(channel) (HSI_GDD_CCR_BASE +\ + ((channel) * HSI_GDD_CCR_OFFSET)) +#define HSI_DST_AMODE_MASK (3 << 14) +#define HSI_DST_AMODE_CONST 0 +#define HSI_DST_AMODE_POSTINC (1 << 14) + +#define HSI_SRC_AMODE_MASK (3 << 12) +#define HSI_SRC_AMODE_CONST 0 +#define HSI_SRC_AMODE_POSTINC (1 << 12) + +#define HSI_CCR_ENABLE (1 << 7) + +#define HSI_CCR_SYNC_MASK 0x001f /* only for SSI */ + +#define HSI_GDD_CCIR_BASE (HSI_GDD_BASE + 0x0804) +#define HSI_GDD_CCIR_OFFSET 0x40 +#define HSI_GDD_CCIR_REG(channel) (HSI_GDD_CCIR_BASE +\ + ((channel) * HSI_GDD_CCIR_OFFSET)) + +#define HSI_BLOCK_IE (1 << 5) +#define HSI_HALF_IE (1 << 2) +#define HSI_TOUT_IE (1 << 0) + +#define HSI_GDD_CSR_BASE (HSI_GDD_BASE + 0x0806) +#define HSI_GDD_CSR_OFFSET 0x40 +#define HSI_GDD_CSR_REG(channel) (HSI_GDD_CSR_BASE +\ + ((channel) * HSI_GDD_CSR_OFFSET)) + +#define HSI_CSR_SYNC (1 << 6) +#define HSI_CSR_BLOCK (1 << 5) /* Full block is transferred */ +#define HSI_CSR_HALF (1 << 2) /* Half block is transferred */ +#define HSI_CSR_TOUT (1 << 0) /* Time-out overflow occurs */ + +#define HSI_GDD_CSSA_BASE (HSI_GDD_BASE + 0x0808) +#define HSI_GDD_CSSA_OFFSET 0x40 +#define HSI_GDD_CSSA_REG(channel) (HSI_GDD_CSSA_BASE +\ + ((channel) * HSI_GDD_CSSA_OFFSET)) + + +#define HSI_GDD_CDSA_BASE (HSI_GDD_BASE + 0x080c) +#define HSI_GDD_CDSA_OFFSET 0x40 +#define HSI_GDD_CDSA_REG(channel) (HSI_GDD_CDSA_BASE +\ + ((channel) * HSI_GDD_CDSA_OFFSET)) + +#define HSI_GDD_CEN_BASE (HSI_GDD_BASE + 0x0810) +#define HSI_GDD_CEN_OFFSET 0x40 +#define HSI_GDD_CEN_REG(channel) (HSI_GDD_CEN_BASE +\ + ((channel) * HSI_GDD_CEN_OFFSET)) + + +#define HSI_GDD_CSAC_BASE (HSI_GDD_BASE + 0x0818) +#define HSI_GDD_CSAC_OFFSET 0x40 +#define HSI_GDD_CSAC_REG(channel) (HSI_GDD_CSAC_BASE +\ + ((channel) * HSI_GDD_CSAC_OFFSET)) + +#define HSI_GDD_CDAC_BASE (HSI_GDD_BASE + 0x081a) +#define HSI_GDD_CDAC_OFFSET 0x40 +#define HSI_GDD_CDAC_REG(channel) (HSI_GDD_CDAC_BASE +\ + ((channel) * HSI_GDD_CDAC_OFFSET)) + +#define HSI_SSI_GDD_CLNK_CTRL_BASE (HSI_GDD_BASE + 0x0828) +#define HSI_SSI_GDD_CLNK_CTRL_OFFSET 0x40 +#define HSI_SSI_GDD_CLNK_CTRL_REG(channel) (HSI_SSI_GDD_CLNK_CTRL_BASE +\ + (channel * HSI_SSI_GDD_CLNK_CTRL_OFFSET)) + +#define HSI_SSI_ENABLE_LNK (1 << 15) +#define HSI_SSI_STOP_LNK (1 << 14) +#define HSI_SSI_NEXT_CH_ID_MASK 0xf + +/* + * HSI Helpers + */ +#define HSI_SYS_MPU_ENABLE_CH_REG(port, irq, channel) \ + (((channel) < HSI_SSI_CHANNELS_MAX) ? \ + HSI_SYS_MPU_ENABLE_REG(port, irq) : \ + HSI_SYS_MPU_U_ENABLE_REG(port, irq)) + +#define HSI_SYS_MPU_STATUS_CH_REG(port, irq, channel) \ + ((channel < HSI_SSI_CHANNELS_MAX) ? \ + HSI_SYS_MPU_STATUS_REG(port, irq) : \ + HSI_SYS_MPU_U_STATUS_REG(port, irq)) +/** + * struct omap_ssi_config - SSI board configuration + * @num_ports: Number of ports in use + * @cawake_line: Array of cawake gpio lines + */ +struct omap_ssi_board_config { + unsigned int num_ports; + int cawake_gpio[2]; +}; +extern int omap_ssi_config(struct omap_ssi_board_config *ssi_config); + +/** + * struct omap_hsi_config - HSI board configuration + * @num_ports: Number of ports in use + */ +struct omap_hsi_board_config { + unsigned int num_ports; +}; +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_wakeup(int hsi_port); +extern int omap_hsi_is_io_wakeup_from_hsi(void); +#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_wakeup(void) { return -ENOSYS; } +inline int omap_hsi_is_io_wakeup_from_hsi(void) { return -ENOSYS; } + +#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/drivers/Kconfig b/drivers/Kconfig index d930c6a..1cce7f2 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -133,4 +133,6 @@ source "drivers/remoteproc/Kconfig" source "drivers/virtio/Kconfig" source "drivers/rpmsg/Kconfig" + +source "drivers/omap_hsi/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 49c39b3..2f047a4 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -128,3 +128,4 @@ obj-$(CONFIG_REMOTE_PROC) += remoteproc/ obj-$(CONFIG_DMM_OMAP) += media/ obj-$(CONFIG_TILER_OMAP) += media/ +obj-$(CONFIG_OMAP_HSI) += omap_hsi/ 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..04a1de7 --- /dev/null +++ b/drivers/input/misc/gp2a.c @@ -0,0 +1,667 @@ +/* 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) + +#define ADC_BUFFER_NUM 6 + +/* 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 + +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; + int adc_value_buf[ADC_BUFFER_NUM]; + int adc_index_count; + bool adc_buf_initialized; + 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)); + hrtimer_start(&gp2a->timer, gp2a->light_poll_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); + /* mark the adc buff as not initialized + * so that it will be filled again on next light sensor start + */ + gp2a->adc_buf_initialized = false; +} + +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)); + 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 int lightsensor_get_adcvalue(struct gp2a_data *gp2a) +{ + int i = 0; + int j = 0; + unsigned int adc_total = 0; + int adc_avr_value; + unsigned int adc_index = 0; + unsigned int adc_max = 0; + unsigned int adc_min = 0; + int value = 0; + + /* get ADC */ + value = gp2a->pdata->light_adc_value(); + gp2a_dbgmsg("adc returned light value %d\n", value); + + adc_index = (gp2a->adc_index_count++) % ADC_BUFFER_NUM; + + /* ADC buffer initialize (light sensor off ---> light sensor on) */ + if (!gp2a->adc_buf_initialized) { + gp2a->adc_buf_initialized = true; + for (j = 0; j < ADC_BUFFER_NUM; j++) + gp2a->adc_value_buf[j] = value; + } else + gp2a->adc_value_buf[adc_index] = value; + + adc_max = gp2a->adc_value_buf[0]; + adc_min = gp2a->adc_value_buf[0]; + + for (i = 0; i < ADC_BUFFER_NUM; i++) { + adc_total += gp2a->adc_value_buf[i]; + + if (adc_max < gp2a->adc_value_buf[i]) + adc_max = gp2a->adc_value_buf[i]; + + if (adc_min > gp2a->adc_value_buf[i]) + adc_min = gp2a->adc_value_buf[i]; + } + adc_avr_value = (adc_total-(adc_max+adc_min))/(ADC_BUFFER_NUM-2); + + if (gp2a->adc_index_count == ADC_BUFFER_NUM-1) + gp2a->adc_index_count = 0; + + gp2a_dbgmsg("average adc light value %d\n", adc_avr_value); + return adc_avr_value; +} + +static void gp2a_work_func_light(struct work_struct *work) +{ + struct gp2a_data *gp2a = container_of(work, struct gp2a_data, + work_light); + int adc = lightsensor_get_adcvalue(gp2a); + 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; + + 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); + + ret = gp2a_setup_irq(gp2a); + if (ret) { + pr_err("%s: could not setup irq\n", __func__); + goto err_setup_irq; + } + + /* 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); + + 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, 1, 0, 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: +err_input_allocate_device_proximity: + free_irq(gp2a->irq, gp2a); + gpio_free(gp2a->pdata->p_out); +err_setup_irq: + 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 100644 index 0000000..a0beebc --- /dev/null +++ b/drivers/input/touchscreen/mms_ts.c @@ -0,0 +1,893 @@ +/* + * 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 + +#define REQUIRED_FW_VERSION 0x11 + +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 = true; +module_param_named(flash_from_probe, mms_flash_from_probe, 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; +}; + +#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; + + 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; + + ret = i2c_smbus_read_i2c_block_data(client, MMS_INPUT_EVENT0, sz, buf); + +#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; + 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; + + 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); +} + +static void isp_exit_mode(struct mms_ts_info *info) +{ + int i; + + gpio_direction_output(info->pdata->gpio_resetb, 0); + udelay(3); + + for (i = 0; i < 10; i++) + isp_toggle_clk(info, 1, 0, 3); +} + +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); + msleep(25); + 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; + + 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); + + return val; +} + +static void flash_writel(struct mms_ts_info *info, u16 addr, u32 val) +{ + 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 */ + udelay(40); + isp_toggle_clk(info, 1, 0, 10); + isp_toggle_clk(info, 1, 0, 10); + udelay(20); + isp_toggle_clk(info, 1, 0, 10); + udelay(40); + isp_toggle_clk(info, 1, 0, 10); + isp_toggle_clk(info, 1, 0, 10); + isp_toggle_clk(info, 1, 0, 10); + + gpio_direction_output(info->pdata->gpio_sda, 0); + isp_exit_mode(info); + 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 void fw_write_image(struct mms_ts_info *info, const u8 *data, size_t len) +{ + u16 addr = 0; + + for (addr = 0; addr < (len / 4); addr++, data += 4) { + udelay(40); + flash_writel(info, addr, get_unaligned_le32(data)); + } +} + +static bool fw_verify_image(struct mms_ts_info *info, const u8 *data, + size_t len) +{ + struct i2c_client *client = info->client; + u16 addr; + u32 val; + u32 correct_val; + + for (addr = 0; addr < (len / 4); addr++, data += 4) { + udelay(40); + val = flash_readl(info, addr); + correct_val = get_unaligned_le32(data); + if (val == correct_val) + continue; + dev_err(&client->dev, "mismatch @ addr 0x%x: 0x%x != 0x%x\n", + addr, val, correct_val); + return false; + } + return true; +} + +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); + fw_write_image(info, data, len); + usleep_range(1000, 1500); + + dev_info(&client->dev, "fw verify...\n"); + if (!fw_verify_image(info, data, len)) { + ret = -ENXIO; + goto err; + } + + 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); + 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); + 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(20 * MSEC_PER_SEC)); + + if (ret > 0) { + ret = mms_ts_enable(info); + } 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; + + if (!fw) { + dev_err(&client->dev, "could not find firmware file '%s'\n", + info->fw_name); + goto out; + } + + i2c_lock_adapter(adapter); + info->pdata->mux_fw_flash(true); + + ret = fw_download(info, fw->data, fw->size); + + 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", + REQUIRED_FW_VERSION); + goto out; + } + + ver = get_fw_version(info); + if (ver == REQUIRED_FW_VERSION) + dev_info(&client->dev, "fw update done. ver = 0x%02x\n", ver); + else + dev_err(&client->dev, + "ERROR: fw update succeeded, but fw version is still wrong (%d != %d)\n", + ver, REQUIRED_FW_VERSION); + + 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; + int ver; + + mms_pwr_on_reset(info); + + ver = get_fw_version(info); + if (ver < 0) { + ver = 0; + dev_err(&client->dev, + "can't read version, controller dead?! forcing reflash"); + } else if (ver == REQUIRED_FW_VERSION && !mms_force_reflash) { + dev_info(&client->dev, + "fw version 0x%02x already present\n", ver); + ret = mms_ts_finish_config(info); + goto out; + } + + dev_info(&client->dev, "need fw update (0x%02x != 0x%02x)\n", + ver, REQUIRED_FW_VERSION); + + if (!info->pdata || !info->pdata->mux_fw_flash) { + dev_err(&client->dev, + "fw cannot be updated, missing platform data\n"); + ret = -EINVAL; + goto out; + } + + if (nowait) { + const struct firmware *fw; + info->fw_name = kstrdup("melfas/mms144_ts.fw", GFP_KERNEL); + 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 = kasprintf(GFP_KERNEL, "mms144_v%02x.fw", + REQUIRED_FW_VERSION); + 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/misc/Kconfig b/drivers/misc/Kconfig index b575799..5e22f3b 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -494,6 +494,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 @@ -539,9 +549,11 @@ config APANIC_PLABEL 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 2d43048..42b922b 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 @@ -52,3 +53,5 @@ 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/ diff --git a/drivers/misc/bmp180.c b/drivers/misc/bmp180.c new file mode 100755 index 0000000..6de0021 --- /dev/null +++ b/drivers/misc/bmp180.c @@ -0,0 +1,645 @@ +/* + * 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_UPBOUND (500 * 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_UPBOUND) + return -EINVAL; + + 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/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..0ea6422 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/bma250.c @@ -0,0 +1,785 @@ +/* + $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; + result = inv_serial_read(mlsl_handle, pdata->address, + BMA250_STATUS_REG, 1, data); + 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..1e84ca6 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/yas530.c @@ -0,0 +1,579 @@ +/* + $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" + +/* -------------------------------------------------------------------------- */ +#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 char dx, dy1, dy2; +static char d2, d3, d4, d5, d6, d7, d8, d9, d0; +static char dck; + +/* -------------------------------------------------------------------------- */ + +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, + int *xo, int *yo, int *zo) +{ + int sx, sy1, sy2, sy, sz; + int 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 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; + + unsigned char dummyData = 0x00; + char offset[3] = { 0, 0, 0 }; + 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 = dx * 6 - 768; + Cy1 = dy1 * 6 - 768; + Cy2 = dy2 * 6 - 768; + a2 = d2 - 32; + a3 = d3 - 8; + a4 = d4 - 32; + a5 = d5 + 38; + a6 = d6 - 32; + a7 = d7 - 64; + a8 = d8 - 32; + a9 = d9; + k = 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; + } + + /* Offset Measurement and Set */ + result = measure_and_set_offset(mlsl_handle, slave, pdata, offset); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + 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; + int xyz[3]; + short rawfixed[3]; + + 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]); + + 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; + + return result; +} + +static struct ext_slave_descr yas530_descr = { + .init = NULL, + .exit = NULL, + .suspend = yas530_suspend, + .resume = yas530_resume, + .read = yas530_read, + .config = NULL, + .get_config = NULL, + .name = "yas530", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_YAS530, + .read_reg = 0x06, + .read_len = 6, + .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..1d93c97 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu-dev.c @@ -0,0 +1,1312 @@ +/* + $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; + struct timeval event_time; + 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; + } + + do_gettimeofday(&event_time); + mpu->mpu_pm_event.interruptcount++; + mpu->mpu_pm_event.irqtime = + (((long long)event_time.tv_sec) << 32) + event_time.tv_usec; + mpu->mpu_pm_event.data_type = MPUIRQ_DATA_TYPE_PM_EVENT; + mpu->mpu_pm_event.data = mpu->event; + + if (event == PM_SUSPEND_PREPARE) + mpu->event = MPU_PM_EVENT_SUSPEND_PREPARE; + if (event == PM_POST_SUSPEND) + mpu->event = MPU_PM_EVENT_POST_SUSPEND; + + 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) + sizeof(unsigned long)) + 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..9e0e593 --- /dev/null +++ b/drivers/misc/inv_mpu/mpuirq.c @@ -0,0 +1,256 @@ +/* + $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; + struct timeval irqtime; + 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; + + do_gettimeofday(&irqtime); + mpuirq_data.irqtime = (((long long)irqtime.tv_sec) << 32); + mpuirq_data.irqtime += irqtime.tv_usec; + 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..77a2917 --- /dev/null +++ b/drivers/misc/inv_mpu/slaveirq.c @@ -0,0 +1,265 @@ +/* + $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; + struct timeval irqtime; + mycount++; + + data->data.interruptcount++; + + /* wake up (unblock) for reading data from userspace */ + data->data_ready = 1; + + do_gettimeofday(&irqtime); + data->data.irqtime = (((long long)irqtime.tv_sec) << 32); + data->data.irqtime += irqtime.tv_usec; + 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..2ca324f --- /dev/null +++ b/drivers/misc/inv_mpu/timerirq.c @@ -0,0 +1,296 @@ +/* + $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; + struct timeval irqtime; + + data->data.interruptcount++; + + data->data_ready = 1; + + do_gettimeofday(&irqtime); + data->data.irqtime = (((long long)irqtime.tv_sec) << 32); + data->data.irqtime += irqtime.tv_usec; + 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/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..3baca23 --- /dev/null +++ b/drivers/misc/modem_if/Makefile @@ -0,0 +1,8 @@ +obj-y += modem.o modem_io_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
\ No newline at end of file diff --git a/drivers/misc/modem_if/modem.c b/drivers/misc/modem_if/modem.c new file mode 100755 index 0000000..a7bfe06 --- /dev/null +++ b/drivers/misc/modem_if/modem.c @@ -0,0 +1,220 @@ +/* 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; + + /* get platform data */ + pdata = pdev->dev.platform_data; + + modemctl->name = pdata->name; + + modemctl->gpio_cp_on = pdata->gpio_cp_on; + modemctl->gpio_reset_req_n = pdata->gpio_reset_req_n; + modemctl->gpio_cp_reset = pdata->gpio_cp_reset; + modemctl->gpio_pda_active = pdata->gpio_pda_active; + modemctl->gpio_phone_active = pdata->gpio_phone_active; + modemctl->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + modemctl->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + modemctl->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + + modemctl->irq_phone_active = platform_get_irq(pdev, 0); + +#ifdef CONFIG_LTE_MODEM_CMC221 + modemctl->gpio_cp_off = pdata->gpio_cp_off; + modemctl->gpio_slave_wakeup = pdata->gpio_slave_wakeup; + modemctl->gpio_host_active = pdata->gpio_host_active; + modemctl->gpio_host_wakeup = pdata->gpio_host_wakeup; + modemctl->irq_host_wakeup = platform_get_irq(pdev, 1); +#endif + /* init modemctl device for getting modemctl operations */ + ret = call_modem_init_func(modemctl, pdata); + if (ret) { + kfree(modemctl); + return NULL; + } + + pr_info("[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) +{ + 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; + + /* link between io device and modem control */ + iod->mc = modemctl; + modemctl->iod = iod; + + /* register misc device or net device */ + ret = init_io_device(iod); + if (ret) { + kfree(iod); + return NULL; + } + + pr_info("[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); + 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_info("[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 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, + .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..d663ba5 --- /dev/null +++ b/drivers/misc/modem_if/modem_io_device.c @@ -0,0 +1,774 @@ +/* /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/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 0x1000 /* 4*1024 */ + +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 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; + + 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; + 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 int rx_hdlc_head_start_check(char *buf) +{ + if (strncmp(buf, hdlc_start, sizeof(hdlc_start))) { + pr_err("[MODEM_IF] Wrong HDLC start: 0x%x\n", *buf); + return -EBADMSG; + } + return sizeof(hdlc_start); +} + +static int rx_hdlc_tail_check(char *buf) +{ + if (strncmp(buf, hdlc_end, sizeof(hdlc_end))) { + pr_err("[MODEM_IF] Wrong HDLC end: 0x%x\n", *buf); + return -EBADMSG; + } + return sizeof(hdlc_end); +} + +/* 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) + return len; /*Wrong hdlc start*/ + + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, + rest, __LINE__); + + memcpy(&hdr->start, hdlc_start, len); + hdr->len = 0; + + /* debug print */ + switch (iod->format) { + case IPC_FMT: + case IPC_RAW: + case IPC_MULTI_RAW: + case IPC_RFS: + /* TODO: print buf... */ + break; + + case IPC_CMD: + case IPC_BOOT: + default: + break; + } + 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; + struct sk_buff *skb_new; + + pr_debug("[MODEM_IF] head_size : %d, data_size : %d (%d)\n", head_size, + data_size, __LINE__); + + /* first payload data - alloc skb */ + if (!skb) { + switch (iod->format) { + case IPC_RFS: + alloc_size = min(data_size, (int)rest) + head_size; + alloc_size = min(alloc_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; + default: + skb = alloc_skb(alloc_size, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + break; + } + iod->skb_recv = skb; + } + + /* if recv packet size is larger than user space */ + while ((rest_len > MAX_RXDATA_SIZE) && (rest > 0)) { + len = MAX_RXDATA_SIZE - skb->len; + len = min(len, (int)rest); + len = min(len, rest_len); + memcpy(skb_put(skb, len), buf, len); + buf += len; + done_len += len; + rest -= len; + rest_len -= len; + + if (!rest_len) + break; + + rx_iodev_skb(iod); + iod->skb_recv = NULL; + + alloc_size = min(rest_len, MAX_RXDATA_SIZE); + skb_new = alloc_skb(alloc_size, GFP_ATOMIC); + if (unlikely(!skb_new)) + return -ENOMEM; + skb = iod->skb_recv = skb_new; + } + + /* copy data to skb */ + len = min(rest, alloc_size - skb->len); + len = min(len, rest_len); + pr_debug("[MODEM_IF] rest : %d, alloc_size : %d , len : %d (%d)\n", + rest, alloc_size, skb->len, __LINE__); + + memcpy(skb_put(skb, len), buf, len); + done_len += len; + hdr->flag_len += done_len; + + 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; + + 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; + skb->protocol = htons(ETH_P_IP); + + err = netif_rx(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 int rx_multipdp(struct io_device *iod) +{ + int ret; + 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]; + + real_iod->skb_recv = iod->skb_recv; + ret = rx_iodev_skb_raw(real_iod); + if (ret < 0) + pr_err("[MODEM_IF] failed!\n"); + return ret; +} + +/* 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; + 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) + 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 && iod->skb_recv) { + dev_kfree_skb_any(iod->skb_recv); + iod->skb_recv = NULL; + + /* clear headers */ + memset(&iod->h_data, 0x00, sizeof(struct header_data)); + } + + 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: + /* 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; +} + +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; + 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 + 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_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_ON\n"); + return iod->mc->ops.modem_on(iod->mc); + + case IOCTL_MODEM_OFF: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_OFF\n"); + return iod->mc->ops.modem_off(iod->mc); + + case IOCTL_MODEM_RESET: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_RESET\n"); + return iod->mc->ops.modem_reset(iod->mc); + + case IOCTL_MODEM_BOOT_ON: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_ON\n"); + return iod->mc->ops.modem_boot_on(iod->mc); + + case IOCTL_MODEM_BOOT_OFF: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_OFF\n"); + return iod->mc->ops.modem_boot_off(iod->mc); + + case IOCTL_MODEM_START: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_START\n"); + return iod->link->init_comm(iod->link, iod); + + case IOCTL_MODEM_STATUS: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_START\n"); + return iod->mc->phone_state; + + 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 */ + + frame_len = count + SIZE_OF_HDLC_START + get_header_size(iod) + + SIZE_OF_HDLC_END; + skb = alloc_skb(frame_len, GFP_ATOMIC); + if (!skb) { + pr_err("[MODEM_IF] fail alloc skb (%d)\n", __LINE__); + return -ENOMEM; + } + + switch (iod->format) { + case IPC_BOOT: + if (copy_from_user(skb_put(skb, count), buf, count) != 0) + 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) + 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) + 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) { + pr_err("[MODEM_IF] no data from sk_rx_q\n"); + return 0; + } + + if (skb->len > count) { + pr_err("[MODEM_IF] skb len is too big = %d,%d!(%d)", + count, skb->len, __LINE__); + dev_kfree_skb_any(skb); + return -EFAULT; + } + pr_debug("[MODEM_IF] skb len : %d\n", skb->len); + + pktsize = skb->len; + if (copy_to_user(buf, skb->data, pktsize) != 0) + return -EFAULT; + 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, + .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; + + 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) { + netif_stop_queue(ndev); + 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->hard_header_len = 0; + ndev->addr_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); + + 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: + iod->ndev = alloc_netdev(0, iod->name, vnet_setup); + if (!iod->ndev) { + pr_err("failed to alloc netdev\n"); + return -ENOMEM; + } + + ret = register_netdev(iod->ndev); + if (ret) + free_netdev(iod->ndev); + + pr_err("%s: %d(iod:0x%p)", __func__, __LINE__, iod); + vnet = netdev_priv(iod->ndev); + pr_err("%s: %d(vnet:0x%p)", __func__, __LINE__, vnet); + vnet->iod = iod; + + break; + + case IODEV_DUMMY: + break; + + default: + pr_err("wrong io_type : %d\n", iod->io_typ); + return -EINVAL; + } + + pr_info("[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_mipi.c b/drivers/misc/modem_if/modem_link_device_mipi.c new file mode 100755 index 0000000..b1df755 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_mipi.c @@ -0,0 +1,1327 @@ +/* /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: + return hsi_init_handshake(mipi_ld, + HSI_INIT_MODE_FLASHLESS_BOOT); + + case IPC_RFS: + case IPC_RAW: + default: + return 0; + } +} + +static int mipi_hsi_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + int ret; + u32 mipi_boot_len = 0; + 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_BOOT: /* sync operation */ + 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); + + ret = if_hsi_read(&mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL], + (const char *)&mipi_boot_len, 4); + if (ret < 0) { + pr_err("[MIPI-HSI] read len fail : %d\n", ret); + return ret; + } else + pr_debug("[MIPI-HSI] read len Done : %d\n", + mipi_boot_len); + + ret = if_hsi_read(&mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL], + (const char *)mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].rx_data, + mipi_boot_len); + if (ret < 0) { + pr_err("[MIPI-HSI] read len fail : %d\n", ret); + return ret; + } else + pr_debug("[MIPI-HSI] read len Done\n"); + + ret = iod->recv(iod, (char *)mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].rx_data, mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].rx_count); + if (ret < 0) { + pr_err("[MIPI-HSI] iod recv fail : %d\n", ret); + return ret; + } else + pr_debug("[MIPI-HSI] iod recv Done : %d\n", ret); + + return mipi_boot_len; + + 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 (!work_pending(&ld->tx_work)) + 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; + struct sk_buff *raw_skb; + int send_channel = 0; + + while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) { + pr_debug("[MIPI-HSI] fmt qlen : %d, raw qlen:%d\n", + ld->sk_fmt_tx_q.qlen, ld->sk_raw_tx_q.qlen); + + 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); + + if (ld->com_state != COM_ONLINE) { + pr_err("[MIPI-HSI] CP not ready\n"); + skb_queue_head(&ld->sk_fmt_tx_q, fmt_skb); + return; + } + + 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; + 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); + } + + raw_skb = skb_dequeue(&ld->sk_raw_tx_q); + if (raw_skb) { + if (ld->com_state != COM_ONLINE) { + pr_err("[MIPI-HSI] RAW CP not ready\n"); + skb_queue_head(&ld->sk_raw_tx_q, raw_skb); + return; + } + + pr_debug("[MIPI-HSI] dequeue. raw qlen:%d\n", + ld->sk_raw_tx_q.qlen); + + ret = if_hsi_protocol_send(mipi_ld, HSI_RAW_CHANNEL, + (u32 *)raw_skb->data, raw_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(raw_skb); + } + } +} + +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; + + ret = hsi_ioctl(channel->dev, state ? + HSI_IOCTL_ACWAKE_UP : HSI_IOCTL_ACWAKE_DOWN, NULL); + if (ret) { + pr_debug("[MIPI-HSI] ACWAKE(%d) setting fail : %d\n", state, + ret); + return ret; + } + + 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 = 1; 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); + return ret; + } + channel->opened = 1; + + channel->send_step = STEP_IDLE; + channel->recv_step = STEP_IDLE; + + pr_info("[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_ioctl(channel->dev, HSI_IOCTL_SW_RESET, NULL); + + hsi_close(channel->dev); + channel->opened = 0; + + channel->send_step = STEP_CLOSED; + channel->recv_step = STEP_CLOSED; + + pr_info("[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); + + 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_hsi_close_channel(&mipi_ld->hsi_channles[i]); + ret = if_hsi_open_channel(&mipi_ld->hsi_channles[i]); + if (ret) + return ret; + + 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[i].dev, + HSI_IOCTL_SET_ACREADY_NORMAL, NULL); + pr_debug("[MIPI-HSI] ACREADY_NORMAL\n"); + } + + 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); + + 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: + case HSI_INIT_MODE_CP_RAMDUMP: + mipi_ld->ld.com_state = COM_BOOT; + + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) + if_hsi_close_channel(&mipi_ld->hsi_channles[i]); + ret = if_hsi_open_channel( + &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL]); + if (ret) + return ret; + + 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"); + + 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); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_ACREADY_NORMAL, NULL); + pr_debug("[MIPI-HSI] ACREADY_NORMAL\n"); + + pr_info("[MIPI-HSI] hsi_init_handshake Done : FLASHLESS_BOOT\n"); + return 0; + + default: + return -EINVAL; + } +} + +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; + struct mipi_link_device *mipi_ld = + container_of(work, struct mipi_link_device, cmd_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"); + + if (!list_empty(&mipi_ld->list_of_hsi_cmd)) { + 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); + } else + return; + + do { + hsi_cmd = list_entry(mipi_ld->list_of_hsi_cmd.next, + struct if_hsi_command, list); + 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); + if_hsi_set_wakeline(channel, 0); + return; + } + pr_debug("[MIPI-HSI] SEND CMD : %08x\n", hsi_cmd->command); + + list_del(&hsi_cmd->list); + kfree(hsi_cmd); + } while (!list_empty(&mipi_ld->list_of_hsi_cmd)); + + if_hsi_set_wakeline(channel, 0); +} + +static int if_hsi_send_command(struct mipi_link_device *mipi_ld, + u32 cmd_type, int ch, u32 param) +{ + 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); + + list_add_tail(&hsi_cmd->list, &mipi_ld->list_of_hsi_cmd); + + pr_debug("[MIPI-HSI] queue_work : cmd_work\n"); + if (!work_pending(&mipi_ld->cmd_work)) + queue_work(mipi_ld->mipi_wq, &mipi_ld->cmd_work); + + 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_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\n", cmd); + } + + 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 : %08x\n", cmd); + 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 : %08x\n", cmd); + 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 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); + + if_hsi_set_wakeline(channel, 0); + + ack_timeout_cnt++; + if (ack_timeout_cnt < 10) { + if_hsi_set_wakeline(channel, 1); + pr_err("[MIPI-HSI] ch=%d, retry send open. cnt : %d\n", + channel->channel_id, ack_timeout_cnt); + goto retry_send; + } + + channel->send_step = STEP_IDLE; + return -ETIMEDOUT; + } + 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); + if_hsi_set_wakeline(channel, 0); + channel->send_step = STEP_IDLE; + return -ETIMEDOUT; + } + 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); + + pr_err("[MIPI-HSI] data : %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)); + + 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); + + pr_debug("[MIPI-HSI] len:%d, id:%d, data : %08x %08x %08x %08x %08x ...\n", + channel->tx_count, channel->channel_id, *channel->tx_data, + *(channel->tx_data + 1), *(channel->tx_data + 2), + *(channel->tx_data + 3), *(channel->tx_data + 4)); + + 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]; + + 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 int if_hsi_read(struct if_hsi_channel *channel, const char *data, + unsigned int size) +{ + int ret; + unsigned long int flags; + + spin_lock_irqsave(&channel->rx_state_lock, flags); + if (channel->rx_state & HSI_CHANNEL_RX_STATE_READING) { + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + return -EBUSY; + } + channel->rx_state |= HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + if (size % 4) + size += (4 - (size % 4)); + channel->rx_count = size; + + ret = hsi_read(channel->dev, (u32 *)data, channel->rx_count / 4); + if (ret) { + pr_err("[MIPI-HSI] ch=%d, hsi_read fail : %d\n", + channel->channel_id, ret); + + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + return ret; + } + + pr_debug("[MIPI-HSI] submit read data : 0x%x(%d)\n", + *(u32 *)channel->rx_data, channel->rx_count); + + if (down_timeout(&channel->read_done_sem, + HSI_READ_DONE_TIMEOUT) < 0) { + pr_err("[MIPI-HSI] ch=%d, hsi_read_done timeout : %d\n", + channel->channel_id, channel->rx_count); + + hsi_read_cancel(channel->dev); + + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + return -ETIMEDOUT; + } + + if (channel->rx_count != size) + pr_err("[MIPI-HSI] ch:%d,read_done fail,read_size:%d,origin_size:%d\n", + channel->channel_id, channel->rx_count, size); + + return channel->rx_count; +} + +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_err("[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); + + up(&channel->read_done_sem); + return; + + case COM_CRASH: + case COM_NONE: + default: + pr_err("[MIPI-HSI] receive data in wrong state : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + up(&channel->read_done_sem); + 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); + return; + + default: + up(&channel->read_done_sem); + 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); + + 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); + + channel->recv_step = STEP_IDLE; + + 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].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); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].write_done_sem, 0); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].read_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_info("[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 = create_singlethread_workqueue("mipi_cmd_wq"); + if (!mipi_ld->mipi_wq) { + pr_err("[MIPI-HSI] fail to create work Q.\n"); + return -ENOMEM; + } + INIT_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); + pr_info("[MIPI-HSI] setup_timer done\n"); + + /* 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; + } + + pr_info("[MIPI-HSI] if_hsi_init() done : %d\n", ret); + 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); + 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->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); + + ret = if_hsi_init(ld); + if (ret) + return NULL; + + pr_info("[MODEM_IF] %s : create_io_device DONE\n", mipi_ld->ld.name); + 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..484c949 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_mipi.h @@ -0,0 +1,158 @@ +/* + * 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) +#define HSI_CLOSE_CONN_DONE_TIMEOUT (HZ) +#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 + + +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, +}; + + +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; + + struct semaphore write_done_sem; + struct semaphore read_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; + + struct workqueue_struct *mipi_wq; + struct work_struct 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; +}; +/* 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, +}; +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_read(struct if_hsi_channel *channel, const char *data, + unsigned int size); +static int if_hsi_protocol_send(struct mipi_link_device *mipi_ld, int ch, + u32 *data, unsigned int len); + +#endif 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..d910988 --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c @@ -0,0 +1,195 @@ +/* /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); + 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 cp_dump_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); + cp_dump_value = gpio_get_value(mc->gpio_cp_dump_int); + + pr_info("[MODEM_IF] PA EVENT : reset =%d, pa=%d, cp_dump=%d\n", + phone_reset, phone_active_value, cp_dump_value); + + if (phone_reset && phone_active_value) + phone_state = STATE_ONLINE; + else if (phone_reset && !phone_active_value) { + if (cp_dump_value) + phone_state = STATE_CRASH_EXIT; + else + phone_state = STATE_CRASH_RESET; + } 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; + + pr_info("[MODEM_IF] xmm6260_get_ops() done\n"); +} + +int xmm6260_init_modemctl_device(struct modem_ctl *mc) +{ + int ret; + 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] failed to irq_phone_active request_irq:%d\n" + , ret); + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) + pr_err("[MODEM_IF] failed to irq_phone_active enable_irq_wake : %d\n", + ret); + + pr_info("[MODEM_IF] init_modemctl_device() done\n"); + return 0; +} + diff --git a/drivers/misc/modem_if/modem_prj.h b/drivers/misc/modem_if/modem_prj.h new file mode 100755 index 0000000..913b59c --- /dev/null +++ b/drivers/misc/modem_if/modem_prj.h @@ -0,0 +1,195 @@ +/* + * 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) + +/* modem status */ +#define MODEM_OFF 0 +#define MODEM_CRASHED 1 +#define MODEM_RAMDUMP 2 +#define MODEM_POWER_ON 3 +#define MODEM_BOOTING_NORMAL 4 +#define MODEM_BOOTING_RAMDUMP 5 +#define MODEM_DUMPING 6 +#define MODEM_RUNNING 7 + +#define IPC_HEADER_MAX_SIZE 6 /* fmt 3, raw 6, rfs 6 */ + +/* Does modem ctl structure will use state ? or status defined below ?*/ +enum modem_state { + STATE_OFFLINE, + STATE_CRASH_RESET, /* silent reset */ + STATE_CRASH_EXIT, /* cp ramdump */ + STATE_BOOTING, + STATE_ONLINE, +}; + +enum { + COM_NONE, + COM_ONLINE, + COM_HANDSHAKE, + COM_BOOT, + COM_CRASH, +}; + +struct header_data { + char hdr[IPC_HEADER_MAX_SIZE]; + unsigned len; + unsigned flag_len; + char start; /*hdlc start header 0x7F*/ +}; + +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; + + struct sk_buff_head sk_rx_q; + + /* 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 work_struct tx_work; + struct delayed_work tx_delayed_work; + + int irq; /* for dpram int */ + 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); + + /* 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); +}; + +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 *); +}; + +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; + + int irq_phone_active; + + struct work_struct work; + +#ifdef CONFIG_LTE_MODEM_CMC221 + const struct attribute_group *group; + unsigned gpio_cp_off; + 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; + 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..937add3 --- /dev/null +++ b/drivers/misc/modem_if/modem_variation.h @@ -0,0 +1,118 @@ +/* + * 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) +#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 *); +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); + 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/omap_hsi/Kconfig b/drivers/omap_hsi/Kconfig new file mode 100644 index 0000000..1f2862f --- /dev/null +++ b/drivers/omap_hsi/Kconfig @@ -0,0 +1,70 @@ +# +# OMAP HSI driver configuration +# + +config OMAP_HSI + bool "OMAP HSI hardware driver" + depends on (ARCH_OMAP34XX || ARCH_OMAP4) + default n + ---help--- + If you say Y here, you will enable the OMAP HSI hardware driver. + + Note: This module is a unified driver specific to OMAP. Efforts are + underway to create a vendor independent implementation. + + The MIPI HSI is a High Speed Synchronous Serial Interface and is + defined for communication between two Integrated Circuits (the + typical scenario is an application IC and cellular modem IC + communication). Data transaction model is peer-to-peer. + + Not all features required for a production device are implemented in + this driver. See the documentation for more information. + + This physical layer provides logical channeling and several modes of + operation. + + The OMAP HSI driver supports either: + - the OMAP MIPI HSI device + - the OMAP SSI device + +choice + prompt "Selected device support file" + depends on OMAP_HSI && y + default OMAP_HSI_DEVICE + ---help--- + Adds the device support for one of the devices handled by the HSI + driver. + + The OMAP HSI driver supports either: + - the OMAP MIPI HSI device + - the OMAP SSI device + +config OMAP_HSI_DEVICE + bool "HSI (OMAP MIPI HSI)" + depends on ARCH_OMAP4 + +config OMAP_SSI_DEVICE + bool "SSI (OMAP SSI)" + depends on ARCH_OMAP34XX + +endchoice + +# +# OMAP HSI char device kernel configuration +# + +config OMAP_HSI_CHAR + tristate "OMAP HSI character driver" + depends on OMAP_HSI + ---help--- + If you say Y here, you will enable the OMAP HSI character driver. + + This driver provides a simple character device interface for + serial communication over the HSI bus. + +config OMAP_HSI_PROTOCOL + tristate "HSI Protocol driver for Infineon Modem" + depends on OMAP_HSI + ---help--- + If you say Y here, you will enable the HSI Protocol driver. + This driver supports HSI protocol for Infineon Modem. diff --git a/drivers/omap_hsi/Makefile b/drivers/omap_hsi/Makefile new file mode 100644 index 0000000..0f072fb --- /dev/null +++ b/drivers/omap_hsi/Makefile @@ -0,0 +1,21 @@ +# +# Makefile for HSI drivers +# +EXTRA_CFLAGS := + +omap_hsi-objs := hsi_driver.o hsi_driver_dma.o hsi_driver_int.o \ + hsi_driver_if.o hsi_driver_bus.o hsi_driver_gpio.o \ + hsi_driver_fifo.o + +ifeq ($(CONFIG_DEBUG_FS), y) + omap_hsi-objs += hsi_driver_debugfs.o +endif + +hsi_char-objs := hsi-char.o hsi-if.o + +hsi-protocol-objs := hsi_protocol.o hsi_protocol_if.o \ + hsi_protocol_cmd.o + +obj-$(CONFIG_OMAP_HSI) += omap_hsi.o +obj-$(CONFIG_OMAP_HSI_CHAR) += hsi_char.o +obj-$(CONFIG_OMAP_HSI_PROTOCOL) += hsi-protocol.o diff --git a/drivers/omap_hsi/hsi-char.c b/drivers/omap_hsi/hsi-char.c new file mode 100644 index 0000000..871de30 --- /dev/null +++ b/drivers/omap_hsi/hsi-char.c @@ -0,0 +1,556 @@ +/* + * hsi-char.c + * + * HSI character device driver, implements the character device + * interface. + * + * Copyright (C) 2009 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Andras Domokos <andras.domokos@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/miscdevice.h> +#include <linux/file.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <asm/mach-types.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/hsi_driver_if.h> +#include <linux/hsi_char.h> + +#include <plat/omap_hsi.h> + +#include "hsi-char.h" + +#define DRIVER_VERSION "0.2.1" +#define HSI_CHAR_DEVICE_NAME "hsi_char" + +static unsigned int port = 1; +module_param(port, uint, 1); +MODULE_PARM_DESC(port, "HSI port to be probed"); + +static unsigned int num_channels; +static unsigned int channels_map[HSI_MAX_CHAR_DEVS] = { 0 }; +module_param_array(channels_map, uint, &num_channels, 0); +MODULE_PARM_DESC(channels_map, "HSI channels to be probed"); + +dev_t hsi_char_dev; + +struct char_queue { + struct list_head list; + u32 *data; + unsigned int count; +}; + +struct hsi_char { + unsigned int opened; + int poll_event; + struct list_head rx_queue; + struct list_head tx_queue; + spinlock_t lock; /* Serialize access to driver data and API */ + struct fasync_struct *async_queue; + wait_queue_head_t rx_wait; + wait_queue_head_t tx_wait; + wait_queue_head_t poll_wait; +}; + +static struct hsi_char hsi_char_data[HSI_MAX_CHAR_DEVS]; + +void if_hsi_notify(int ch, struct hsi_event *ev) +{ + struct char_queue *entry; + + pr_debug("%s, ev = {0x%x, 0x%p, %u}\n", __func__, ev->event, ev->data, + ev->count); + + spin_lock(&hsi_char_data[ch].lock); + + if (!hsi_char_data[ch].opened) { + pr_debug("%s, device not opened\n!", __func__); + spin_unlock(&hsi_char_data[ch].lock); + return; + } + + switch (HSI_EV_TYPE(ev->event)) { + case HSI_EV_IN: + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) { + pr_err("HSI-CHAR: entry allocation failed.\n"); + spin_unlock(&hsi_char_data[ch].lock); + return; + } + entry->data = ev->data; + entry->count = ev->count; + list_add_tail(&entry->list, &hsi_char_data[ch].rx_queue); + spin_unlock(&hsi_char_data[ch].lock); + pr_debug("%s, HSI_EV_IN\n", __func__); + wake_up_interruptible(&hsi_char_data[ch].rx_wait); + break; + case HSI_EV_OUT: + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) { + pr_err("HSI-CHAR: entry allocation failed.\n"); + spin_unlock(&hsi_char_data[ch].lock); + return; + } + entry->data = ev->data; + entry->count = ev->count; + hsi_char_data[ch].poll_event |= (POLLOUT | POLLWRNORM); + list_add_tail(&entry->list, &hsi_char_data[ch].tx_queue); + spin_unlock(&hsi_char_data[ch].lock); + pr_debug("%s, HSI_EV_OUT\n", __func__); + wake_up_interruptible(&hsi_char_data[ch].tx_wait); + break; + case HSI_EV_EXCEP: + hsi_char_data[ch].poll_event |= POLLPRI; + spin_unlock(&hsi_char_data[ch].lock); + pr_debug("%s, HSI_EV_EXCEP\n", __func__); + wake_up_interruptible(&hsi_char_data[ch].poll_wait); + break; + case HSI_EV_AVAIL: + hsi_char_data[ch].poll_event |= (POLLIN | POLLRDNORM); + spin_unlock(&hsi_char_data[ch].lock); + pr_debug("%s, HSI_EV_AVAIL\n", __func__); + wake_up_interruptible(&hsi_char_data[ch].poll_wait); + break; + default: + spin_unlock(&hsi_char_data[ch].lock); + break; + } +} + +static int hsi_char_fasync(int fd, struct file *file, int on) +{ + int ch = (int)file->private_data; + if (fasync_helper(fd, file, on, &hsi_char_data[ch].async_queue) >= 0) + return 0; + else + return -EIO; +} + +static unsigned int hsi_char_poll(struct file *file, poll_table * wait) +{ + int ch = (int)file->private_data; + unsigned int ret = 0; + + /*printk(KERN_DEBUG "%s\n", __func__); */ + + poll_wait(file, &hsi_char_data[ch].poll_wait, wait); + poll_wait(file, &hsi_char_data[ch].tx_wait, wait); + spin_lock_bh(&hsi_char_data[ch].lock); + ret = hsi_char_data[ch].poll_event; + spin_unlock_bh(&hsi_char_data[ch].lock); + + pr_debug("%s, ret = 0x%x\n", __func__, ret); + return ret; +} + +static ssize_t hsi_char_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int ch = (int)file->private_data; + DECLARE_WAITQUEUE(wait, current); + u32 *data; + unsigned int data_len; + struct char_queue *entry; + ssize_t ret; + + /*printk(KERN_DEBUG "%s, count = %d\n", __func__, count); */ + + /* only 32bit data is supported for now */ + if ((count < 4) || (count & 3)) + return -EINVAL; + + data = kmalloc(count, GFP_ATOMIC); + + ret = if_hsi_read(ch, data, count); + if (ret < 0) { + kfree(data); + goto out2; + } + + spin_lock_bh(&hsi_char_data[ch].lock); + add_wait_queue(&hsi_char_data[ch].rx_wait, &wait); + spin_unlock_bh(&hsi_char_data[ch].lock); + + for (;;) { + data = NULL; + data_len = 0; + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_bh(&hsi_char_data[ch].lock); + if (!list_empty(&hsi_char_data[ch].rx_queue)) { + entry = list_entry(hsi_char_data[ch].rx_queue.next, + struct char_queue, list); + data = entry->data; + data_len = entry->count; + list_del(&entry->list); + kfree(entry); + } + spin_unlock_bh(&hsi_char_data[ch].lock); + + pr_debug("%s, data = 0x%p, data_len = %d\n", + __func__, data, data_len); + + if (data_len) { + pr_debug("%s, RX finished\n", __func__); + spin_lock_bh(&hsi_char_data[ch].lock); + hsi_char_data[ch].poll_event &= ~(POLLIN | POLLRDNORM); + spin_unlock_bh(&hsi_char_data[ch].lock); + if_hsi_poll(ch); + break; + } else if (file->f_flags & O_NONBLOCK) { + pr_debug("%s, O_NONBLOCK\n", __func__); + ret = -EAGAIN; + goto out; + } else if (signal_pending(current)) { + pr_debug("%s, ERESTARTSYS\n", __func__); + ret = -EAGAIN; + if_hsi_cancel_read(ch); + /* goto out; */ + break; + } + + /*printk(KERN_DEBUG "%s, going to sleep...\n", __func__); */ + schedule(); + /*printk(KERN_DEBUG "%s, woke up\n", __func__); */ + } + + if (data_len) { + ret = copy_to_user((void __user *)buf, data, data_len); + if (!ret) + ret = data_len; + } + + kfree(data); + +out: + __set_current_state(TASK_RUNNING); + remove_wait_queue(&hsi_char_data[ch].rx_wait, &wait); + +out2: + /*printk(KERN_DEBUG "%s, ret = %d\n", __func__, ret); */ + return ret; +} + +static ssize_t hsi_char_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int ch = (int)file->private_data; + DECLARE_WAITQUEUE(wait, current); + u32 *data; + unsigned int data_len = 0; + struct char_queue *entry; + ssize_t ret; + + /*printk(KERN_DEBUG "%s, count = %d\n", __func__, count); */ + + /* only 32bit data is supported for now */ + if ((count < 4) || (count & 3)) + return -EINVAL; + + data = kmalloc(count, GFP_ATOMIC); + if (!data) { + WARN_ON(1); + return -ENOMEM; + } + if (copy_from_user(data, (void __user *)buf, count)) { + ret = -EFAULT; + kfree(data); + goto out2; + } else { + ret = count; + } + + ret = if_hsi_write(ch, data, count); + if (ret < 0) { + kfree(data); + goto out2; + } + spin_lock_bh(&hsi_char_data[ch].lock); + hsi_char_data[ch].poll_event &= ~(POLLOUT | POLLWRNORM); + add_wait_queue(&hsi_char_data[ch].tx_wait, &wait); + spin_unlock_bh(&hsi_char_data[ch].lock); + + for (;;) { + data = NULL; + data_len = 0; + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_bh(&hsi_char_data[ch].lock); + if (!list_empty(&hsi_char_data[ch].tx_queue)) { + entry = list_entry(hsi_char_data[ch].tx_queue.next, + struct char_queue, list); + data = entry->data; + data_len = entry->count; + list_del(&entry->list); + kfree(entry); + } + spin_unlock_bh(&hsi_char_data[ch].lock); + + if (data_len) { + pr_debug("%s, TX finished\n", __func__); + ret = data_len; + break; + } else if (file->f_flags & O_NONBLOCK) { + pr_debug("%s, O_NONBLOCK\n", __func__); + ret = -EAGAIN; + goto out; + } else if (signal_pending(current)) { + pr_debug("%s, ERESTARTSYS\n", __func__); + ret = -ERESTARTSYS; + goto out; + } + + /*printk(KERN_DEBUG "%s, going to sleep...\n", __func__); */ + schedule(); + /*printk(KERN_DEBUG "%s, woke up\n", __func__); */ + } + + kfree(data); + +out: + __set_current_state(TASK_RUNNING); + remove_wait_queue(&hsi_char_data[ch].tx_wait, &wait); + +out2: + /*printk(KERN_DEBUG "%s, ret = %d\n", __func__, ret); */ + return ret; +} + +static long hsi_char_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ch = (int)file->private_data; + unsigned int state; + size_t occ; + struct hsi_rx_config rx_cfg; + struct hsi_tx_config tx_cfg; + int ret = 0; + + pr_debug("%s, ch = %d, cmd = 0x%08x\n", __func__, ch, cmd); + + switch (cmd) { + case CS_SEND_BREAK: + if_hsi_send_break(ch); + break; + case CS_FLUSH_RX: + if_hsi_flush_rx(ch); + break; + case CS_FLUSH_TX: + if_hsi_flush_tx(ch); + break; + case CS_SET_ACWAKELINE: + if (copy_from_user(&state, (void __user *)arg, sizeof(state))) + ret = -EFAULT; + else + if_hsi_set_acwakeline(ch, state); + break; + case CS_GET_ACWAKELINE: + if_hsi_get_acwakeline(ch, &state); + if (copy_to_user((void __user *)arg, &state, sizeof(state))) + ret = -EFAULT; + break; + case CS_GET_CAWAKELINE: + if_hsi_get_cawakeline(ch, &state); + if (copy_to_user((void __user *)arg, &state, sizeof(state))) + ret = -EFAULT; + break; + case CS_SET_RX: + if (copy_from_user(&rx_cfg, (void __user *)arg, sizeof(rx_cfg))) + ret = -EFAULT; + else + ret = if_hsi_set_rx(ch, &rx_cfg); + break; + case CS_GET_RX: + if_hsi_get_rx(ch, &rx_cfg); + if (copy_to_user((void __user *)arg, &rx_cfg, sizeof(rx_cfg))) + ret = -EFAULT; + break; + case CS_SET_TX: + if (copy_from_user(&tx_cfg, (void __user *)arg, sizeof(tx_cfg))) + ret = -EFAULT; + else + ret = if_hsi_set_tx(ch, &tx_cfg); + break; + case CS_GET_TX: + if_hsi_get_tx(ch, &tx_cfg); + if (copy_to_user((void __user *)arg, &tx_cfg, sizeof(tx_cfg))) + ret = -EFAULT; + break; + case CS_SW_RESET: + if_hsi_sw_reset(ch); + break; + case CS_GET_FIFO_OCCUPANCY: + if_hsi_get_fifo_occupancy(ch, &occ); + if (copy_to_user((void __user *)arg, &occ, sizeof(occ))) + ret = -EFAULT; + break; + default: + ret = -ENOIOCTLCMD; + break; + } + + return ret; +} + +static int hsi_char_open(struct inode *inode, struct file *file) +{ + int ret = 0, ch = iminor(inode); + int i; + + for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) + if ((channels_map[i] - 1) == ch) + break; + + if (i == HSI_MAX_CHAR_DEVS) { + pr_err("HSI char open: Channel %d not found\n", ch); + return -ENODEV; + } + + pr_debug("HSI char open: opening channel %d\n", ch); + + spin_lock_bh(&hsi_char_data[ch].lock); + + if (hsi_char_data[ch].opened) { + spin_unlock_bh(&hsi_char_data[ch].lock); + pr_err("HSI char open: Channel %d already opened\n", ch); + return -EBUSY; + } + + file->private_data = (void *)ch; + hsi_char_data[ch].opened++; + hsi_char_data[ch].poll_event = (POLLOUT | POLLWRNORM); + spin_unlock_bh(&hsi_char_data[ch].lock); + + ret = if_hsi_start(ch); + + return ret; +} + +static int hsi_char_release(struct inode *inode, struct file *file) +{ + int ch = (int)file->private_data; + struct char_queue *entry; + struct list_head *cursor, *next; + + pr_debug("%s, ch = %d\n", __func__, ch); + + if_hsi_stop(ch); + spin_lock_bh(&hsi_char_data[ch].lock); + hsi_char_data[ch].opened--; + + if (!list_empty(&hsi_char_data[ch].rx_queue)) { + list_for_each_safe(cursor, next, &hsi_char_data[ch].rx_queue) { + entry = list_entry(cursor, struct char_queue, list); + list_del(&entry->list); + kfree(entry); + } + } + + if (!list_empty(&hsi_char_data[ch].tx_queue)) { + list_for_each_safe(cursor, next, &hsi_char_data[ch].tx_queue) { + entry = list_entry(cursor, struct char_queue, list); + list_del(&entry->list); + kfree(entry); + } + } + + spin_unlock_bh(&hsi_char_data[ch].lock); + + return 0; +} + +static const struct file_operations hsi_char_fops = { + .owner = THIS_MODULE, + .read = hsi_char_read, + .write = hsi_char_write, + .poll = hsi_char_poll, + .unlocked_ioctl = hsi_char_ioctl, + .open = hsi_char_open, + .release = hsi_char_release, + .fasync = hsi_char_fasync, +}; + +static struct cdev hsi_char_cdev; + +static int __init hsi_char_init(void) +{ + int ret, i; + + pr_info("HSI character device version " DRIVER_VERSION "\n"); + pr_info("HSI char driver: %d channels mapped\n", num_channels); + + for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) { + init_waitqueue_head(&hsi_char_data[i].rx_wait); + init_waitqueue_head(&hsi_char_data[i].tx_wait); + init_waitqueue_head(&hsi_char_data[i].poll_wait); + spin_lock_init(&hsi_char_data[i].lock); + hsi_char_data[i].opened = 0; + INIT_LIST_HEAD(&hsi_char_data[i].rx_queue); + INIT_LIST_HEAD(&hsi_char_data[i].tx_queue); + } + + /*printk(KERN_DEBUG "%s, devname = %s\n", __func__, devname); */ + + ret = if_hsi_init(port, channels_map, num_channels); + if (ret) + return ret; + + ret = + alloc_chrdev_region(&hsi_char_dev, 0, HSI_MAX_CHAR_DEVS, + HSI_CHAR_DEVICE_NAME); + if (ret < 0) { + pr_err("HSI character driver: Failed to register\n"); + return ret; + } + + cdev_init(&hsi_char_cdev, &hsi_char_fops); + ret = cdev_add(&hsi_char_cdev, hsi_char_dev, HSI_MAX_CHAR_DEVS); + if (ret < 0) { + pr_err("HSI character device: Failed to add char device\n"); + return ret; + } + + return 0; +} + +static void __exit hsi_char_exit(void) +{ + cdev_del(&hsi_char_cdev); + unregister_chrdev_region(hsi_char_dev, HSI_MAX_CHAR_DEVS); + if_hsi_exit(); +} + +MODULE_AUTHOR("Andras Domokos <andras.domokos@nokia.com>"); +MODULE_AUTHOR("Sebatien Jan <s-jan@ti.com> / Texas Instruments"); +MODULE_DESCRIPTION("HSI character device"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); + +module_init(hsi_char_init); +module_exit(hsi_char_exit); diff --git a/drivers/omap_hsi/hsi-char.h b/drivers/omap_hsi/hsi-char.h new file mode 100644 index 0000000..c4b1c4c --- /dev/null +++ b/drivers/omap_hsi/hsi-char.h @@ -0,0 +1,35 @@ +/* + * hsi-char.h + * + * HSI character driver private declaration header file. + * + * Copyright (C) 2009 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Andras Domokos <andras.domokos@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef _HSI_CHAR_H +#define _HSI_CHAR_H + +#include "hsi-if.h" + +/* how many char devices would be created at most */ +#define HSI_MAX_CHAR_DEVS 16 + +/* Max HSI channel id allowed to be handled as char device. */ +/* Current range [1, 16] */ +#define HSI_MAX_CHAR_DEV_ID 16 + +void if_hsi_notify(int ch, struct hsi_event *ev); + +#endif /* _HSI_CHAR_H */ diff --git a/drivers/omap_hsi/hsi-if.c b/drivers/omap_hsi/hsi-if.c new file mode 100644 index 0000000..5228b6a --- /dev/null +++ b/drivers/omap_hsi/hsi-if.c @@ -0,0 +1,672 @@ + /* + * hsi-if.c + * + * Part of the HSI character driver, implements the HSI interface. + * + * Copyright (C) 2009 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Andras Domokos <andras.domokos@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <asm/mach-types.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/ktime.h> +#include <linux/bitmap.h> + +#include <linux/hsi_driver_if.h> +#include <linux/hsi_char.h> + +#include "hsi-char.h" +#include "hsi-if.h" + +#define HSI_CHANNEL_STATE_UNAVAIL (1 << 0) +#define HSI_CHANNEL_STATE_READING (1 << 1) +#define HSI_CHANNEL_STATE_WRITING (1 << 2) + +#define PORT1 0 +#define PORT2 1 + +#define RXCONV(dst, src) \ + do { \ + (dst)->mode = (src)->mode; \ + (dst)->flow = (src)->flow; \ + (dst)->frame_size = (src)->frame_size; \ + (dst)->channels = (src)->channels; \ + (dst)->divisor = (src)->divisor; \ + (dst)->counters = (src)->counters; \ + } while (0) + +#define TXCONV(dst, src) \ + do { \ + (dst)->mode = (src)->mode; \ + (dst)->flow = (src)->flow; \ + (dst)->frame_size = (src)->frame_size; \ + (dst)->channels = (src)->channels; \ + (dst)->divisor = (src)->divisor; \ + (dst)->arb_mode = (src)->arb_mode; \ + } while (0) + +struct if_hsi_channel { + struct hsi_device *dev; + unsigned int channel_id; + u32 *tx_data; + unsigned int tx_count; /* Number of bytes to be written */ + u32 *rx_data; + unsigned int rx_count; /* Number of bytes to be read */ + unsigned int opened; + unsigned int state; + spinlock_t lock; /* Serializes access to channel data */ +}; + +struct if_hsi_iface { + struct if_hsi_channel channels[HSI_MAX_CHAR_DEVS]; + int bootstrap; + unsigned long init_chan_map; + spinlock_t lock; /* Serializes access to HSI functional interface */ +}; + +static void if_hsi_port_event(struct hsi_device *dev, unsigned int event, + void *arg); +static int __devinit if_hsi_probe(struct hsi_device *dev); +static int __devexit if_hsi_remove(struct hsi_device *dev); + +static struct hsi_device_driver if_hsi_char_driver = { + .ctrl_mask = ANY_HSI_CONTROLLER, + .probe = if_hsi_probe, + .remove = __devexit_p(if_hsi_remove), + .driver = { + .name = "hsi_char"}, +}; + +static struct if_hsi_iface hsi_iface; + +static int if_hsi_read_on(int ch, u32 *data, unsigned int count) +{ + struct if_hsi_channel *channel; + int ret; + + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + + spin_lock(&channel->lock); + if (channel->state & HSI_CHANNEL_STATE_READING) { + pr_err("Read still pending on channel %d\n", ch); + spin_unlock(&channel->lock); + return -EBUSY; + } + channel->state |= HSI_CHANNEL_STATE_READING; + channel->rx_data = data; + channel->rx_count = count; + spin_unlock(&channel->lock); + + ret = hsi_read(channel->dev, data, count / 4); + dev_dbg(&channel->dev->device, "%s, ch = %d, ret = %d\n", __func__, ch, + ret); + + return ret; +} + +/* HSI char driver read done callback */ +static void if_hsi_read_done(struct hsi_device *dev, unsigned int size) +{ + struct if_hsi_channel *channel; + struct hsi_event ev; + + channel = &hsi_iface.channels[dev->n_ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, dev->n_ch); + spin_lock(&channel->lock); + channel->state &= ~HSI_CHANNEL_STATE_READING; + ev.event = HSI_EV_IN; + ev.data = channel->rx_data; + ev.count = 4 * size; /* Convert size to number of u8, not u32 */ + spin_unlock(&channel->lock); + if_hsi_notify(dev->n_ch, &ev); +} + +int if_hsi_read(int ch, u32 *data, unsigned int count) +{ + int ret = 0; + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + ret = if_hsi_read_on(ch, data, count); + return ret; +} + +int if_hsi_poll(int ch) +{ + struct if_hsi_channel *channel; + int ret = 0; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + ret = hsi_poll(channel->dev); + return ret; +} + +static int if_hsi_write_on(int ch, u32 *address, unsigned int count) +{ + struct if_hsi_channel *channel; + int ret; + + channel = &hsi_iface.channels[ch]; + + spin_lock(&channel->lock); + if (channel->state & HSI_CHANNEL_STATE_WRITING) { + pr_err("Write still pending on channel %d\n", ch); + spin_unlock(&channel->lock); + return -EBUSY; + } + + channel->tx_data = address; + channel->tx_count = count; + channel->state |= HSI_CHANNEL_STATE_WRITING; + spin_unlock(&channel->lock); + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + ret = hsi_write(channel->dev, address, count / 4); + return ret; +} + +/* HSI char driver write done callback */ +static void if_hsi_write_done(struct hsi_device *dev, unsigned int size) +{ + struct if_hsi_channel *channel; + struct hsi_event ev; + + channel = &hsi_iface.channels[dev->n_ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, dev->n_ch); + + spin_lock(&channel->lock); + channel->state &= ~HSI_CHANNEL_STATE_WRITING; + ev.event = HSI_EV_OUT; + ev.data = channel->tx_data; + ev.count = 4 * size; /* Convert size to number of u8, not u32 */ + spin_unlock(&channel->lock); + if_hsi_notify(dev->n_ch, &ev); +} + +int if_hsi_write(int ch, u32 *data, unsigned int count) +{ + int ret = 0; + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + ret = if_hsi_write_on(ch, data, count); + return ret; +} + +void if_hsi_send_break(int ch) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + hsi_ioctl(channel->dev, HSI_IOCTL_SEND_BREAK, NULL); +} + +void if_hsi_flush_rx(int ch) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + hsi_ioctl(channel->dev, HSI_IOCTL_FLUSH_RX, NULL); +} + +void if_hsi_flush_ch(int ch) +{ + /* FIXME - Check the purpose of this function */ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; +} + +void if_hsi_flush_tx(int ch) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + hsi_ioctl(channel->dev, HSI_IOCTL_FLUSH_TX, NULL); +} + +void if_hsi_get_acwakeline(int ch, unsigned int *state) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + hsi_ioctl(channel->dev, HSI_IOCTL_GET_ACWAKE, state); +} + +void if_hsi_set_acwakeline(int ch, unsigned int state) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + hsi_ioctl(channel->dev, + state ? HSI_IOCTL_ACWAKE_UP : HSI_IOCTL_ACWAKE_DOWN, NULL); +} + +void if_hsi_get_cawakeline(int ch, unsigned int *state) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + hsi_ioctl(channel->dev, HSI_IOCTL_GET_CAWAKE, state); +} + +int if_hsi_set_rx(int ch, struct hsi_rx_config *cfg) +{ + int ret; + struct if_hsi_channel *channel; + struct hsr_ctx ctx; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + RXCONV(&ctx, cfg); + ret = hsi_ioctl(channel->dev, HSI_IOCTL_SET_RX, &ctx); + return ret; +} + +void if_hsi_get_rx(int ch, struct hsi_rx_config *cfg) +{ + struct if_hsi_channel *channel; + struct hsr_ctx ctx; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + hsi_ioctl(channel->dev, HSI_IOCTL_GET_RX, &ctx); + RXCONV(cfg, &ctx); +} + +int if_hsi_set_tx(int ch, struct hsi_tx_config *cfg) +{ + int ret; + struct if_hsi_channel *channel; + struct hst_ctx ctx; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + TXCONV(&ctx, cfg); + ret = hsi_ioctl(channel->dev, HSI_IOCTL_SET_TX, &ctx); + return ret; +} + +void if_hsi_get_tx(int ch, struct hsi_tx_config *cfg) +{ + struct if_hsi_channel *channel; + struct hst_ctx ctx; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + hsi_ioctl(channel->dev, HSI_IOCTL_GET_TX, &ctx); + TXCONV(cfg, &ctx); +} + +void if_hsi_sw_reset(int ch) +{ + struct if_hsi_channel *channel; + int i; + channel = &hsi_iface.channels[ch]; + hsi_ioctl(channel->dev, HSI_IOCTL_SW_RESET, NULL); + + spin_lock_bh(&hsi_iface.lock); + /* Reset HSI channel states */ + for (i = 0; i < HSI_MAX_PORTS; i++) + if_hsi_char_driver.ch_mask[i] = 0; + + for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) { + channel = &hsi_iface.channels[i]; + channel->opened = 0; + channel->state = HSI_CHANNEL_STATE_UNAVAIL; + } + spin_unlock_bh(&hsi_iface.lock); +} + +void if_hsi_get_fifo_occupancy(int ch, size_t *occ) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + hsi_ioctl(channel->dev, HSI_IOCTL_GET_FIFO_OCCUPANCY, occ); +} + +void if_hsi_cancel_read(int ch) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + if (channel->state & HSI_CHANNEL_STATE_READING) + hsi_read_cancel(channel->dev); + spin_lock(&channel->lock); + channel->state &= ~HSI_CHANNEL_STATE_READING; + spin_unlock(&channel->lock); +} + +void if_hsi_cancel_write(int ch) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + if (channel->state & HSI_CHANNEL_STATE_WRITING) + hsi_write_cancel(channel->dev); + spin_lock(&channel->lock); + channel->state &= ~HSI_CHANNEL_STATE_WRITING; + spin_unlock(&channel->lock); +} + +static int if_hsi_openchannel(struct if_hsi_channel *channel) +{ + int ret = 0; + + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, + channel->channel_id); + spin_lock(&channel->lock); + + if (channel->state == HSI_CHANNEL_STATE_UNAVAIL) { + pr_err("Channel %d is not available\n", channel->channel_id); + ret = -ENODEV; + goto leave; + } + + if (channel->opened) { + pr_err("Channel %d is busy\n", channel->channel_id); + ret = -EBUSY; + goto leave; + } + + if (!channel->dev) { + pr_err("Channel %d is not ready??\n", channel->channel_id); + ret = -ENODEV; + goto leave; + } + spin_unlock(&channel->lock); + + ret = hsi_open(channel->dev); + + spin_lock(&channel->lock); + if (ret < 0) { + pr_err("Could not open channel %d\n", channel->channel_id); + goto leave; + } + + channel->opened = 1; + +leave: + spin_unlock(&channel->lock); + return ret; +} + +static int if_hsi_closechannel(struct if_hsi_channel *channel) +{ + int ret = 0; + + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, + channel->channel_id); + spin_lock(&channel->lock); + + if (!channel->opened) + goto leave; + + if (!channel->dev) { + pr_err("Channel %d is not ready??\n", channel->channel_id); + ret = -ENODEV; + goto leave; + } + + /* Stop any pending read/write */ + if (channel->state & HSI_CHANNEL_STATE_READING) { + channel->state &= ~HSI_CHANNEL_STATE_READING; + spin_unlock(&channel->lock); + hsi_read_cancel(channel->dev); + spin_lock(&channel->lock); + } + + if (channel->state & HSI_CHANNEL_STATE_WRITING) { + channel->state &= ~HSI_CHANNEL_STATE_WRITING; + spin_unlock(&channel->lock); + hsi_write_cancel(channel->dev); + } else + spin_unlock(&channel->lock); + + hsi_close(channel->dev); + + spin_lock(&channel->lock); + channel->opened = 0; +leave: + spin_unlock(&channel->lock); + return ret; +} + +int if_hsi_start(int ch) +{ + struct if_hsi_channel *channel; + int ret = 0; + + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + + spin_lock_bh(&channel->lock); + channel->state = 0; + spin_unlock_bh(&channel->lock); + + ret = if_hsi_openchannel(channel); + if (ret < 0) { + pr_err("Could not open channel %d\n", ch); + goto error; + } + + if_hsi_poll(ch); +error: + return ret; +} + +void if_hsi_stop(int ch) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + + if_hsi_closechannel(channel); +} + +static int __devinit if_hsi_probe(struct hsi_device *dev) +{ + struct if_hsi_channel *channel; + unsigned long *address; + int ret = -ENXIO, port; + + dev_dbg(&dev->device, "%s, port = %d, ch = %d\n", __func__, dev->n_p, + dev->n_ch); + + for (port = 0; port < HSI_MAX_PORTS; port++) { + if (if_hsi_char_driver.ch_mask[port]) + break; + } + + if (port == HSI_MAX_PORTS) + return -ENXIO; + + if (dev->n_ch >= HSI_MAX_CHAR_DEV_ID) { + pr_err("HSI char driver cannot handle channel %d\n", dev->n_ch); + return -ENXIO; + } + + address = &if_hsi_char_driver.ch_mask[port]; + + spin_lock_bh(&hsi_iface.lock); + if (test_bit(dev->n_ch, address) && (dev->n_p == port)) { + hsi_set_read_cb(dev, if_hsi_read_done); + hsi_set_write_cb(dev, if_hsi_write_done); + hsi_set_port_event_cb(dev, if_hsi_port_event); + channel = &hsi_iface.channels[dev->n_ch]; + channel->dev = dev; + channel->state = 0; + ret = 0; + hsi_iface.init_chan_map ^= (1 << dev->n_ch); + } + spin_unlock_bh(&hsi_iface.lock); + + return ret; +} + +static int __devexit if_hsi_remove(struct hsi_device *dev) +{ + struct if_hsi_channel *channel; + unsigned long *address; + int ret = -ENXIO, port; + + dev_dbg(&dev->device, "%s, port = %d, ch = %d\n", __func__, dev->n_p, + dev->n_ch); + + for (port = 0; port < HSI_MAX_PORTS; port++) { + if (if_hsi_char_driver.ch_mask[port]) + break; + } + + if (port == HSI_MAX_PORTS) + return -ENXIO; + + address = &if_hsi_char_driver.ch_mask[port]; + + spin_lock_bh(&hsi_iface.lock); + if (test_bit(dev->n_ch, address) && (dev->n_p == port)) { + hsi_set_read_cb(dev, NULL); + hsi_set_write_cb(dev, NULL); + hsi_set_port_event_cb(dev, NULL); + channel = &hsi_iface.channels[dev->n_ch]; + channel->dev = NULL; + channel->state = HSI_CHANNEL_STATE_UNAVAIL; + ret = 0; + } + spin_unlock_bh(&hsi_iface.lock); + + return ret; +} + +static void if_hsi_port_event(struct hsi_device *dev, unsigned int event, + void *arg) +{ + struct hsi_event ev; + int i; + + ev.event = HSI_EV_EXCEP; + ev.data = (u32 *) 0; + ev.count = 0; + + switch (event) { + case HSI_EVENT_BREAK_DETECTED: + pr_debug("%s, HWBREAK detected\n", __func__); + ev.data = (u32 *) HSI_HWBREAK; + for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) { + if (hsi_iface.channels[i].opened) + if_hsi_notify(i, &ev); + } + break; + case HSI_EVENT_HSR_DATAAVAILABLE: + i = (int)arg; + pr_debug("%s, HSI_EVENT_HSR_DATAAVAILABLE channel = %d\n", + __func__, i); + ev.event = HSI_EV_AVAIL; + if (hsi_iface.channels[i].opened) + if_hsi_notify(i, &ev); + break; + case HSI_EVENT_CAWAKE_UP: + pr_debug("%s, CAWAKE up\n", __func__); + break; + case HSI_EVENT_CAWAKE_DOWN: + pr_debug("%s, CAWAKE down\n", __func__); + break; + case HSI_EVENT_ERROR: + pr_debug("%s, HSI ERROR occured\n", __func__); + break; + default: + pr_warning("%s, Unknown event(%d)\n", __func__, event); + break; + } +} + +int __init if_hsi_init(unsigned int port, unsigned int *channels_map, + unsigned int num_channels) +{ + struct if_hsi_channel *channel; + int i, ret = 0; + + pr_debug("%s, port = %d\n", __func__, port); + + port -= 1; + if (port >= HSI_MAX_PORTS) + return -EINVAL; + + hsi_iface.bootstrap = 1; + spin_lock_init(&hsi_iface.lock); + + for (i = 0; i < HSI_MAX_PORTS; i++) + if_hsi_char_driver.ch_mask[i] = 0; + + for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) { + channel = &hsi_iface.channels[i]; + channel->dev = NULL; + channel->opened = 0; + channel->state = HSI_CHANNEL_STATE_UNAVAIL; + channel->channel_id = i; + spin_lock_init(&channel->lock); + } + + for (i = 0; (i < num_channels) && channels_map[i]; i++) { + pr_debug("%s, port = %d, channels_map[i] = %d\n", __func__, + port, channels_map[i]); + if ((channels_map[i] - 1) < HSI_MAX_CHAR_DEV_ID) + if_hsi_char_driver.ch_mask[port] |= + (1 << ((channels_map[i] - 1))); + else { + pr_err("Channel %d cannot be handled by the HSI " + "driver.\n", channels_map[i]); + return -EINVAL; + } + + } + hsi_iface.init_chan_map = if_hsi_char_driver.ch_mask[port]; + + ret = hsi_register_driver(&if_hsi_char_driver); + if (ret) + pr_err("Error while registering HSI driver %d", ret); + + if (hsi_iface.init_chan_map) { + ret = -ENXIO; + pr_err("HSI: Some channels could not be registered (out of " + "range or already registered?)\n"); + } + return ret; +} + +int __devexit if_hsi_exit(void) +{ + struct if_hsi_channel *channel; + unsigned long *address; + int i, port; + + pr_debug("%s\n", __func__); + + for (port = 0; port < HSI_MAX_PORTS; port++) { + if (if_hsi_char_driver.ch_mask[port]) + break; + } + + if (port == HSI_MAX_PORTS) + return -ENXIO; + + address = &if_hsi_char_driver.ch_mask[port]; + + for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) { + channel = &hsi_iface.channels[i]; + if (channel->opened) { + if_hsi_set_acwakeline(i, HSI_IOCTL_ACWAKE_DOWN); + if_hsi_closechannel(channel); + } + } + hsi_unregister_driver(&if_hsi_char_driver); + return 0; +} diff --git a/drivers/omap_hsi/hsi-if.h b/drivers/omap_hsi/hsi-if.h new file mode 100644 index 0000000..96afdd4 --- /dev/null +++ b/drivers/omap_hsi/hsi-if.h @@ -0,0 +1,69 @@ +/* + * hsi-if.h + * + * Part of the HSI character driver, private headers. + * + * Copyright (C) 2009 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Andras Domokos <andras.domokos@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef _HSI_IF_H +#define _HSI_IF_H + +#define HSI_EV_MASK (0xffff << 0) +#define HSI_EV_TYPE_MASK (0x0f << 16) +#define HSI_EV_IN (0x01 << 16) +#define HSI_EV_OUT (0x02 << 16) +#define HSI_EV_EXCEP (0x03 << 16) +#define HSI_EV_AVAIL (0x04 << 16) +#define HSI_EV_TYPE(event) ((event) & HSI_EV_TYPE_MASK) + +#define HSI_HWBREAK 1 +#define HSI_ERROR 2 + +struct hsi_event { + unsigned int event; + u32 *data; + unsigned int count; +}; + +int if_hsi_init(unsigned int port, unsigned int *channels_map, + unsigned int num_channels); +int if_hsi_exit(void); + +int if_hsi_start(int ch); +void if_hsi_stop(int ch); + +void if_hsi_send_break(int ch); +void if_hsi_flush_rx(int ch); +void if_hsi_flush_tx(int ch); +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); +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); +void if_hsi_get_tx(int ch, struct hsi_tx_config *cfg); +void if_hsi_sw_reset(int ch); +void if_hsi_get_fifo_occupancy(int ch, size_t *occ); + +int if_hsi_read(int ch, u32 *data, unsigned int count); +int if_hsi_poll(int ch); +int if_hsi_write(int ch, u32 *data, unsigned int count); + +void if_hsi_cancel_read(int ch); +void if_hsi_cancel_write(int ch); + +#endif /* _HSI_IF_H */ diff --git a/drivers/omap_hsi/hsi-protocol-if.h b/drivers/omap_hsi/hsi-protocol-if.h new file mode 100644 index 0000000..f56ef36 --- /dev/null +++ b/drivers/omap_hsi/hsi-protocol-if.h @@ -0,0 +1,187 @@ +/* + * hsi-if.h + * + * Part of the HSI character driver, private headers. + * + * Copyright (C) 2009 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Andras Domokos <andras.domokos@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef _HSI_IF_H +#define _HSI_IF_H + +#define HSI_EV_MASK (0xffff << 0) +#define HSI_EV_TYPE_MASK (0x0f << 16) +#define HSI_EV_IN (0x01 << 16) +#define HSI_EV_OUT (0x02 << 16) +#define HSI_EV_EXCEP (0x03 << 16) +#define HSI_EV_AVAIL (0x04 << 16) +#define HSI_EV_TYPE(event) ((event) & HSI_EV_TYPE_MASK) + +#define HSI_HWBREAK 1 +#define HSI_ERROR 2 + +#define HSI_MAX_CHANNELS 16 +#define CHANNEL_MASK 0xFF +#define HSI_LL_INVALID_CHANNEL 0xFF + +struct hsi_event { + unsigned int event; + u32 *data; + unsigned int count; +}; + +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 opened; + unsigned int state; + u32 *tx_buf; + u32 *rx_buf; + unsigned int tx_state; + unsigned int rx_state; + unsigned int tx_nak_count; + unsigned int rx_nak_count; + spinlock_t lock; /* Serializes access to channel data */ +}; + +struct if_hsi_iface { + struct if_hsi_channel channels[HSI_MAX_CHANNELS]; +#if 0 + int bootstrap; +#endif + unsigned long init_chan_map; + spinlock_t lock; /* Serializes access to HSI functional interface */ +}; + +struct if_hsi_cmd { + u32 tx_cmd[50]; + u32 rx_cmd[50]; + struct timespec tx_cmd_time[50]; + struct timespec rx_cmd_time[50]; +}; + +enum { + HSI_LL_MSG_BREAK = 0x00, + HSI_LL_MSG_ECHO = 0x01, + HSI_LL_MSG_INFO_REQ = 0x02, + HSI_LL_MSG_INFO = 0x03, + HSI_LL_MSG_CONFIGURE = 0x04, + HSI_LL_MSG_ALLOCATE_CH = 0x05, + HSI_LL_MSG_RELEASE_CH = 0x06, + HSI_LL_MSG_OPEN_CONN = 0x07, + HSI_LL_MSG_CONN_READY = 0x08, + HSI_LL_MSG_CONN_CLOSED = 0x09, + HSI_LL_MSG_CANCEL_CONN = 0x0A, + HSI_LL_MSG_ACK = 0x0B, + HSI_LL_MSG_NAK = 0x0C, + HSI_LL_MSG_CONF_RATE = 0x0D, + HSI_LL_MSG_OPEN_CONN_OCTET = 0x0E, + HSI_LL_MSG_INVALID = 0xFF, +}; + +enum { + HSI_LL_TX_STATE_UNDEF, + HSI_LL_TX_STATE_CLOSED, + HSI_LL_TX_STATE_IDLE, + HSI_LL_TX_STATE_POWER_DOWN, + HSI_LL_TX_STATE_ERROR, + HSI_LL_TX_STATE_SEND_OPEN_CONN, + HSI_LL_TX_STATE_WAIT_FOR_ACK, + HSI_LL_TX_STATE_NACK, + HSI_LL_TX_STATE_WAIT_FOR_CONN_READY, + HSI_LL_TX_STATE_SEND_CONF_RATE, + HSI_LL_TX_STATE_WAIT_FOR_CONF_ACK, + HSI_LL_TX_STATE_TX, + HSI_LL_TX_STATE_WAIT_FOR_CONN_CLOSED, + HSI_LL_TX_STATE_TO_OPEN_CONN, + HSI_LL_TX_STATE_TO_ACK, + HSI_LL_TX_STATE_TO_READY, + HSI_LL_TX_STATE_TO_CONF, + HSI_LL_TX_STATE_TO_CONF_ACK, + HSI_LL_TX_STATE_TO_TX, + HSI_LL_TX_STATE_TO_CLOSE, + HSI_LL_TX_STATE_SEND_BREAK, +}; + +enum { + HSI_LL_RX_STATE_UNDEF, + HSI_LL_RX_STATE_CLOSED, + HSI_LL_RX_STATE_IDLE, + HSI_LL_RX_STATE_POWER_DOWN, + HSI_LL_RX_STATE_ERROR, + HSI_LL_RX_STATE_BLOCKED, + HSI_LL_RX_STATE_SEND_ACK, + HSI_LL_RX_STATE_SEND_NACK, + HSI_LL_RX_STATE_SEND_CONN_READY, + HSI_LL_RX_STATE_RX, + HSI_LL_RX_STATE_SEND_CONN_CLOSED, + HSI_LL_RX_STATE_SEND_CONN_CANCEL, + HSI_LL_RX_STATE_WAIT_FOR_CANCEL_CONN_ACK, + HSI_LL_RX_STATE_SEND_CONF_ACK, + HSI_LL_RX_STATE_SEND_CONF_NACK, + HSI_LL_RX_STATE_TO_RX, + HSI_LL_RX_STATE_TO_ACK, + HSI_LL_RX_STATE_TO_NACK, + HSI_LL_RX_STATE_TO_CONN_READY, + HSI_LL_RX_STATE_TO_CONN_CLOSED, + HSI_LL_RX_STATE_TO_CONN_CANCEL, + HSI_LL_RX_STATE_TO_CONN_CANCEL_ACK, + HSI_LL_RX_STATE_TO_CONF_ACK, + HSI_LL_RX_STATE_SEND_BREAK, +}; + + +int if_hsi_init(void); +int if_hsi_exit(void); + +int if_hsi_start(int ch); +void if_hsi_stop(int ch); + +void if_hsi_send_break(int ch); +void if_hsi_flush_rx(int ch); +void if_hsi_flush_tx(int ch); +void if_hsi_bootstrap(int ch); +void if_hsi_set_wakeline(int ch, unsigned int state); +void if_hsi_get_wakeline(int ch, unsigned int *state); + +#if 0 +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); +void if_hsi_get_tx(int ch, struct hsi_tx_config *cfg); +#endif + +int if_hsi_read(int ch, u32 *data, unsigned int count); +int if_hsi_poll(int ch); +int if_hsi_write(int ch, u32 *data, unsigned int count); + +void if_hsi_cancel_read(int ch); +void if_hsi_cancel_write(int ch); + +void if_notify(int ch, struct hsi_event *ev); +int hsi_proto_read(int ch, u32 *buffer, int count); +int hsi_proto_write(int ch, u32 *buffer, int length); +int hsi_decode_cmd(u32 *data, u32 *cmd, u32 *ch, u32 *param); +int protocol_create_cmd(int cmd_type, unsigned int channel, void *arg); +int hsi_protocol_send_command(u32 cmd, u32 channel, u32 param); +void rx_stm(u32 cmd, u32 ch, u32 param); +#if 0 +int hsi_start_protocol(void); +#endif +#endif /* _HSI_IF_H */ diff --git a/drivers/omap_hsi/hsi_driver.c b/drivers/omap_hsi/hsi_driver.c new file mode 100644 index 0000000..69c2b3d --- /dev/null +++ b/drivers/omap_hsi/hsi_driver.c @@ -0,0 +1,1138 @@ +/* + * hsi_driver.c + * + * Implements HSI module interface, initialization, and PM related functions. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> + +#include <mach/omap4-common.h> +#include <plat/omap_device.h> + +#include "hsi_driver.h" + +#if 0 +static struct pm_qos_request_list *pm_qos_handle; +#endif + +#define HSI_MODULENAME "omap_hsi" +#define HSI_DRIVER_VERSION "0.4.1" +#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 */ + +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; + int port; + + pdata->ctx->sysconfig = hsi_inl(base, HSI_SYS_SYSCONFIG_REG); + pdata->ctx->gdd_gcr = hsi_inl(base, HSI_GDD_GCR_REG); + if (hsi_driver_device_is_hsi(pdev)) + pdata->ctx->dll = hsi_inl(base, HSI_HSR_DLL_REG); + + for (port = 1; port <= pdata->num_ports; port++) { + p = &pdata->ctx->pctx[port - 1]; + /* HSI TOP */ + p->sys_mpu_enable[0] = hsi_inl(base, + HSI_SYS_MPU_ENABLE_REG(port, 0)); + p->sys_mpu_enable[1] = hsi_inl(base, + HSI_SYS_MPU_U_ENABLE_REG(port, 0)); + + /* HST */ + p->hst.mode = hsi_inl(base, HSI_HST_MODE_REG(port)); + if (!hsi_driver_device_is_hsi(pdev)) + p->hst.frame_size = hsi_inl(base, + HSI_HST_FRAMESIZE_REG(port)); + p->hst.divisor = hsi_inl(base, HSI_HST_DIVISOR_REG(port)); + p->hst.channels = hsi_inl(base, HSI_HST_CHANNELS_REG(port)); + p->hst.arb_mode = hsi_inl(base, HSI_HST_ARBMODE_REG(port)); + + /* HSR */ + p->hsr.mode = hsi_inl(base, HSI_HSR_MODE_REG(port)); + if (!hsi_driver_device_is_hsi(pdev)) + p->hsr.frame_size = hsi_inl(base, + HSI_HSR_FRAMESIZE_REG(port)); + p->hsr.divisor = hsi_inl(base, HSI_HSR_DIVISOR_REG(port)); + p->hsr.channels = hsi_inl(base, HSI_HSR_CHANNELS_REG(port)); + p->hsr.counters = hsi_inl(base, HSI_HSR_COUNTERS_REG(port)); + } +} + +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; + int port; + + hsi_outl(pdata->ctx->sysconfig, base, HSI_SYS_SYSCONFIG_REG); + hsi_outl(pdata->ctx->gdd_gcr, base, HSI_GDD_GCR_REG); + if (hsi_driver_device_is_hsi(pdev)) + hsi_outl(pdata->ctx->dll, base, HSI_HSR_DLL_REG); + + for (port = 1; port <= pdata->num_ports; port++) { + p = &pdata->ctx->pctx[port - 1]; + /* HSI TOP */ + hsi_outl(p->sys_mpu_enable[0], base, + HSI_SYS_MPU_ENABLE_REG(port, 0)); + hsi_outl(p->sys_mpu_enable[1], base, + HSI_SYS_MPU_U_ENABLE_REG(port, 0)); + + /* HST */ + hsi_outl(p->hst.mode, base, HSI_HST_MODE_REG(port)); + if (!hsi_driver_device_is_hsi(pdev)) + hsi_outl(p->hst.frame_size, base, + HSI_HST_FRAMESIZE_REG(port)); + hsi_outl(p->hst.divisor, base, HSI_HST_DIVISOR_REG(port)); + hsi_outl(p->hst.channels, base, HSI_HST_CHANNELS_REG(port)); + hsi_outl(p->hst.arb_mode, base, HSI_HST_ARBMODE_REG(port)); + + /* HSR */ + if (!hsi_driver_device_is_hsi(pdev)) + hsi_outl(p->hsr.frame_size, base, + HSI_HSR_FRAMESIZE_REG(port)); + hsi_outl(p->hsr.divisor, base, HSI_HSR_DIVISOR_REG(port)); + hsi_outl(p->hsr.channels, base, HSI_HSR_CHANNELS_REG(port)); + hsi_outl(p->hsr.counters, base, HSI_HSR_COUNTERS_REG(port)); + } + + 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); + } + + /* As a last step move HSR from MODE_VAL.SLEEP to the relevant mode. */ + /* This will enable the ACREADY flow control mechanism. */ + for (port = 1; port <= pdata->num_ports; port++) { + p = &pdata->ctx->pctx[port - 1]; + hsi_outl(p->hsr.mode, base, HSI_HSR_MODE_REG(port)); + } +} + + +/* NOTE: Function called in soft interrupt context (tasklet) */ +int hsi_port_event_handler(struct hsi_port *p, unsigned int event, void *arg) +{ + struct hsi_channel *hsi_channel; + int ch; + + + if (event == HSI_EVENT_HSR_DATAAVAILABLE) { + /* The data-available event is channel-specific and must not be + * broadcasted + */ + hsi_channel = p->hsi_channel + (int)arg; + read_lock(&hsi_channel->rw_lock); + if ((hsi_channel->dev) && (hsi_channel->port_event)) + hsi_channel->port_event(hsi_channel->dev, event, arg); + read_unlock(&hsi_channel->rw_lock); + } else { + for (ch = 0; ch < p->max_ch; ch++) { + hsi_channel = p->hsi_channel + ch; + read_lock(&hsi_channel->rw_lock); + if ((hsi_channel->dev) && (hsi_channel->port_event)) + hsi_channel->port_event(hsi_channel->dev, + event, arg); + read_unlock(&hsi_channel->rw_lock); + } + } + return 0; +} + +static void hsi_dev_release(struct device *dev) +{ + /* struct device kfree is already made in unregister_hsi_devices(). + * Registering this function is necessary to avoid an error from + * the device_release() function. + */ +} + +/* Register a hsi_device, linked to a port and channel id */ +static int __init reg_hsi_dev_ch(struct hsi_dev *hsi_ctrl, unsigned int p, + unsigned int ch) +{ + struct hsi_device *dev; + struct hsi_port *port = &hsi_ctrl->hsi_port[p]; + int err; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->n_ctrl = hsi_ctrl->id; + dev->n_p = p; + dev->n_ch = ch; + dev->ch = &port->hsi_channel[ch]; + dev->device.bus = &hsi_bus_type; + dev->device.parent = hsi_ctrl->dev; + dev->device.release = hsi_dev_release; + if (dev->n_ctrl < 0) + dev_set_name(&dev->device, "omap_hsi-p%u.c%u", p, ch); + else + dev_set_name(&dev->device, "omap_hsi%d-p%u.c%u", dev->n_ctrl, p, + ch); + + dev_dbg(hsi_ctrl->dev, + "reg_hsi_dev_ch, port %d, ch %d, hsi_ctrl->dev:0x%x," + "&dev->device:0x%x\n", + p, ch, (unsigned int)hsi_ctrl->dev, (unsigned int)&dev->device); + + err = device_register(&dev->device); + if (err >= 0) { + write_lock_bh(&port->hsi_channel[ch].rw_lock); + port->hsi_channel[ch].dev = dev; + write_unlock_bh(&port->hsi_channel[ch].rw_lock); + } else { + kfree(dev); + } + return err; +} + +static int __init register_hsi_devices(struct hsi_dev *hsi_ctrl) +{ + int port; + int ch; + int err; + + for (port = 0; port < hsi_ctrl->max_p; port++) + for (ch = 0; ch < hsi_ctrl->hsi_port[port].max_ch; ch++) { + err = reg_hsi_dev_ch(hsi_ctrl, port, ch); + if (err < 0) + return err; + } + + return 0; +} + +static void __exit unregister_hsi_devices(struct hsi_dev *hsi_ctrl) +{ + struct hsi_port *hsi_p; + struct hsi_device *device; + unsigned int port; + unsigned int ch; + + for (port = 0; port < hsi_ctrl->max_p; port++) { + hsi_p = &hsi_ctrl->hsi_port[port]; + for (ch = 0; ch < hsi_p->max_ch; ch++) { + device = hsi_p->hsi_channel[ch].dev; + hsi_close(device); + device_unregister(&device->device); + kfree(device); + } + } +} + +void hsi_set_pm_default(struct hsi_dev *hsi_ctrl) +{ + /* Set default SYSCONFIG PM settings */ + hsi_outl((HSI_AUTOIDLE | HSI_SIDLEMODE_SMART_WAKEUP | + HSI_MIDLEMODE_SMART_WAKEUP), + hsi_ctrl->base, HSI_SYS_SYSCONFIG_REG); + hsi_outl(HSI_CLK_AUTOGATING_ON, hsi_ctrl->base, HSI_GDD_GCR_REG); + + /* HSI_TODO : use the HWMOD API : omap_hwmod_set_slave_idlemode() */ +} + +void hsi_set_pm_force_hsi_on(struct hsi_dev *hsi_ctrl) +{ + /* Force HSI to ON by never acknowledging a PRCM idle request */ + /* SIdleAck and MStandby are never asserted */ + hsi_outl((HSI_AUTOIDLE | HSI_SIDLEMODE_NO | + HSI_MIDLEMODE_NO), + hsi_ctrl->base, HSI_SYS_SYSCONFIG_REG); + hsi_outl(HSI_CLK_AUTOGATING_ON, hsi_ctrl->base, HSI_GDD_GCR_REG); + + /* HSI_TODO : use the HWMOD API : omap_hwmod_set_slave_idlemode() */ +} + +int hsi_softreset(struct hsi_dev *hsi_ctrl) +{ + unsigned int ind = 0; + void __iomem *base = hsi_ctrl->base; + u32 status; + + /* Reseting HSI Block */ + hsi_outl_or(HSI_SOFTRESET, base, HSI_SYS_SYSCONFIG_REG); + do { + status = hsi_inl(base, HSI_SYS_SYSSTATUS_REG); + ind++; + } while ((!(status & HSI_RESETDONE)) && + (ind < HSI_RESETDONE_MAX_RETRIES)); + + if (ind >= HSI_RESETDONE_MAX_RETRIES) { + dev_err(hsi_ctrl->dev, "HSI SW_RESET failed to complete within" + " %d retries.\n", HSI_RESETDONE_MAX_RETRIES); + return -EIO; + } else if (ind > HSI_RESETDONE_NORMAL_RETRIES) { + dev_warn(hsi_ctrl->dev, "HSI SW_RESET abnormally long:" + " %d retries to complete.\n", ind); + } + + ind = 0; + /* Reseting DMA Engine */ + hsi_outl_or(HSI_GDD_GRST_SWRESET, base, HSI_GDD_GRST_REG); + do { + status = hsi_inl(base, HSI_GDD_GRST_REG); + ind++; + } while ((status & HSI_GDD_GRST_SWRESET) && + (ind < HSI_RESETDONE_MAX_RETRIES)); + + if (ind >= HSI_RESETDONE_MAX_RETRIES) { + dev_err(hsi_ctrl->dev, "HSI DMA SW_RESET failed to complete" + " within %d retries.\n", HSI_RESETDONE_MAX_RETRIES); + return -EIO; + } + + if (ind > HSI_RESETDONE_NORMAL_RETRIES) { + dev_warn(hsi_ctrl->dev, "HSI DMA SW_RESET abnormally long:" + " %d retries to complete.\n", ind); + } + + return 0; +} + +static void hsi_set_ports_default(struct hsi_dev *hsi_ctrl, + struct platform_device *pd) +{ + struct port_ctx *cfg; + struct hsi_platform_data *pdata = pd->dev.platform_data; + unsigned int port = 0; + void __iomem *base = hsi_ctrl->base; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + + for (port = 1; port <= pdata->num_ports; port++) { + cfg = &pdata->ctx->pctx[port - 1]; + /* HST */ + hsi_outl(cfg->hst.mode | cfg->hst.flow | + HSI_HST_MODE_WAKE_CTRL_SW, base, + HSI_HST_MODE_REG(port)); + if (!hsi_driver_device_is_hsi(pdev)) + hsi_outl(cfg->hst.frame_size, base, + HSI_HST_FRAMESIZE_REG(port)); + hsi_outl(cfg->hst.divisor, base, HSI_HST_DIVISOR_REG(port)); + hsi_outl(cfg->hst.channels, base, HSI_HST_CHANNELS_REG(port)); + hsi_outl(cfg->hst.arb_mode, base, HSI_HST_ARBMODE_REG(port)); + + /* HSR */ + hsi_outl(cfg->hsr.mode | cfg->hsr.flow, base, + HSI_HSR_MODE_REG(port)); + if (!hsi_driver_device_is_hsi(pdev)) + hsi_outl(cfg->hsr.frame_size, base, + HSI_HSR_FRAMESIZE_REG(port)); + hsi_outl(cfg->hsr.channels, base, HSI_HSR_CHANNELS_REG(port)); + if (hsi_driver_device_is_hsi(pdev)) + hsi_outl(cfg->hsr.divisor, base, + HSI_HSR_DIVISOR_REG(port)); + hsi_outl(cfg->hsr.counters, base, HSI_HSR_COUNTERS_REG(port)); + } + + 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_outl(pdata->ctx->dll, base, HSI_HSR_DLL_REG); + } +} + +static int __init hsi_port_channels_init(struct hsi_port *port) +{ + struct hsi_channel *ch; + unsigned int ch_i; + + for (ch_i = 0; ch_i < port->max_ch; ch_i++) { + ch = &port->hsi_channel[ch_i]; + ch->channel_number = ch_i; + rwlock_init(&ch->rw_lock); + ch->flags = 0; + ch->hsi_port = port; + ch->read_data.addr = NULL; + ch->read_data.size = 0; + ch->read_data.lch = -1; + ch->write_data.addr = NULL; + ch->write_data.size = 0; + ch->write_data.lch = -1; + ch->dev = NULL; + ch->read_done = NULL; + ch->write_done = NULL; + ch->port_event = NULL; + } + + return 0; +} + +static int hsi_port_channels_reset(struct hsi_port *port) +{ + struct hsi_channel *ch; + unsigned int ch_i; + + for (ch_i = 0; ch_i < port->max_ch; ch_i++) { + ch = &port->hsi_channel[ch_i]; + ch->flags = 0; + ch->read_data.addr = NULL; + ch->read_data.size = 0; + ch->read_data.lch = -1; + ch->write_data.addr = NULL; + ch->write_data.size = 0; + ch->write_data.lch = -1; + } + + return 0; +} + +void hsi_softreset_driver(struct hsi_dev *hsi_ctrl) +{ + struct platform_device *pd = to_platform_device(hsi_ctrl->dev); + struct hsi_platform_data *pdata = pd->dev.platform_data; + struct hsi_port *hsi_p; + unsigned int port; + u32 revision; + + /* HSI port reset */ + for (port = 0; port < hsi_ctrl->max_p; port++) { + hsi_p = &hsi_ctrl->hsi_port[port]; + hsi_p->counters_on = 1; + hsi_p->reg_counters = pdata->ctx->pctx[port].hsr.counters; + hsi_port_channels_reset(&hsi_ctrl->hsi_port[port]); + } + + hsi_set_pm_default(hsi_ctrl); + + /* Re-Configure HSI ports */ + hsi_set_ports_default(hsi_ctrl, pd); + + /* Gather info from registers for the driver.(REVISION) */ + revision = hsi_inl(hsi_ctrl->base, HSI_SYS_REVISION_REG); + if (hsi_driver_device_is_hsi(pd)) + dev_info(hsi_ctrl->dev, "HSI Hardware REVISION 0x%x\n", + revision); + else + dev_info(hsi_ctrl->dev, "SSI Hardware REVISION %d.%d\n", + (revision & HSI_SSI_REV_MAJOR) >> 4, + (revision & HSI_SSI_REV_MINOR)); +} + +static int __init hsi_request_mpu_irq(struct hsi_port *hsi_p) +{ + struct hsi_dev *hsi_ctrl = hsi_p->hsi_controller; + struct platform_device *pd = to_platform_device(hsi_ctrl->dev); + struct resource *mpu_irq; + + if (hsi_driver_device_is_hsi(pd)) + mpu_irq = platform_get_resource(pd, IORESOURCE_IRQ, + hsi_p->port_number - 1); + else /* SSI support 2 IRQs per port */ + mpu_irq = platform_get_resource(pd, IORESOURCE_IRQ, + (hsi_p->port_number - 1) * 2); + + if (!mpu_irq) { + dev_err(hsi_ctrl->dev, "HSI misses info for MPU IRQ on" + " port %d\n", hsi_p->port_number); + return -ENXIO; + } + hsi_p->n_irq = 0; /* We only use one irq line */ + hsi_p->irq = mpu_irq->start; + return hsi_mpu_init(hsi_p, mpu_irq->name); +} + +static int __init hsi_request_cawake_irq(struct hsi_port *hsi_p) +{ + struct hsi_dev *hsi_ctrl = hsi_p->hsi_controller; + struct platform_device *pd = to_platform_device(hsi_ctrl->dev); + struct resource *cawake_irq; + + if (hsi_driver_device_is_hsi(pd)) { + hsi_p->cawake_gpio = -1; + return 0; + } else { + cawake_irq = platform_get_resource(pd, IORESOURCE_IRQ, + 4 + hsi_p->port_number); + } + + if (!cawake_irq) { + dev_err(hsi_ctrl->dev, "SSI device misses info for CAWAKE" + "IRQ on port %d\n", hsi_p->port_number); + return -ENXIO; + } + + if (cawake_irq->flags & IORESOURCE_UNSET) { + dev_info(hsi_ctrl->dev, "No CAWAKE GPIO support\n"); + hsi_p->cawake_gpio = -1; + return 0; + } + + hsi_p->cawake_gpio_irq = cawake_irq->start; + hsi_p->cawake_gpio = irq_to_gpio(cawake_irq->start); + return hsi_cawake_init(hsi_p, cawake_irq->name); +} + +static void hsi_ports_exit(struct hsi_dev *hsi_ctrl, unsigned int max_ports) +{ + struct hsi_port *hsi_p; + unsigned int port; + + for (port = 0; port < max_ports; port++) { + hsi_p = &hsi_ctrl->hsi_port[port]; + hsi_mpu_exit(hsi_p); + hsi_cawake_exit(hsi_p); + } +} + +static int __init hsi_ports_init(struct hsi_dev *hsi_ctrl) +{ + struct platform_device *pd = to_platform_device(hsi_ctrl->dev); + struct hsi_platform_data *pdata = pd->dev.platform_data; + struct hsi_port *hsi_p; + unsigned int port; + int err; + + for (port = 0; port < hsi_ctrl->max_p; port++) { + hsi_p = &hsi_ctrl->hsi_port[port]; + hsi_p->port_number = port + 1; + 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->cawake_status = -1; /* Unknown */ + hsi_p->cawake_off_event = false; + hsi_p->acwake_status = 0; + hsi_p->in_int_tasklet = false; + hsi_p->in_cawake_tasklet = false; + 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]); + if (err < 0) + goto rback1; + err = hsi_request_mpu_irq(hsi_p); + if (err < 0) + goto rback2; + err = hsi_request_cawake_irq(hsi_p); + if (err < 0) + goto rback3; + } + return 0; +rback3: + hsi_mpu_exit(hsi_p); +rback2: + hsi_ports_exit(hsi_ctrl, port + 1); +rback1: + return err; +} + +static int __init hsi_request_gdd_irq(struct hsi_dev *hsi_ctrl) +{ + struct platform_device *pd = to_platform_device(hsi_ctrl->dev); + struct resource *gdd_irq; + + if (hsi_driver_device_is_hsi(pd)) + gdd_irq = platform_get_resource(pd, IORESOURCE_IRQ, 2); + else + gdd_irq = platform_get_resource(pd, IORESOURCE_IRQ, 4); + + if (!gdd_irq) { + dev_err(hsi_ctrl->dev, "HSI has no GDD IRQ resource\n"); + return -ENXIO; + } + + hsi_ctrl->gdd_irq = gdd_irq->start; + return hsi_gdd_init(hsi_ctrl, gdd_irq->name); +} + +static int __init hsi_init_gdd_chan_count(struct hsi_dev *hsi_ctrl) +{ + struct platform_device *pd = to_platform_device(hsi_ctrl->dev); + u8 gdd_chan_count; + struct hsi_platform_data *pdata = + (struct hsi_platform_data *)pd->dev.platform_data; + int i; + + if (!pdata) { + dev_err(hsi_ctrl->dev, "HSI has no platform data\n"); + return -ENXIO; + } + + gdd_chan_count = pdata->hsi_gdd_chan_count; + + if (!gdd_chan_count) { + dev_warn(hsi_ctrl->dev, "HSI device has no GDD channel count " + "(use %d as default)\n", + HSI_DMA_CHANNEL_DEFAULT); + hsi_ctrl->gdd_chan_count = HSI_DMA_CHANNEL_DEFAULT; + } else { + hsi_ctrl->gdd_chan_count = gdd_chan_count; + /* Check that the number of channels is power of 2 */ + for (i = 0; i < 16; i++) { + if (hsi_ctrl->gdd_chan_count == (1 << i)) + break; + } + if (i >= 16) + dev_err(hsi_ctrl->dev, "The Number of DMA channels " + "shall be a power of 2! (=%d)\n", + hsi_ctrl->gdd_chan_count); + } + return 0; +} + +/** +* hsi_clocks_disable_channel - virtual wrapper for disabling HSI clocks for +* a given channel +* @dev - reference to the hsi device. +* @channel_number - channel number which requests clock to be disabled +* 0xFF means no particular channel +* +* Note : there is no real HW clock management per HSI channel, this is only +* virtual to keep track of active channels and ease debug +* +* Function to be called with lock +*/ +void hsi_clocks_disable_channel(struct device *dev, u8 channel_number, + const char *s) +{ + struct platform_device *pd = to_platform_device(dev); + struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd); + + if (channel_number != HSI_CH_NUMBER_NONE) + dev_dbg(dev, "CLK: hsi_clocks_disable for " + "channel %d: %s\n", channel_number, s); + else + dev_dbg(dev, "CLK: hsi_clocks_disable: %s\n", s); + + if (!hsi_ctrl->clock_enabled) { + dev_dbg(dev, "Clocks already disabled, skipping...\n"); + return; + } + if (hsi_is_hsi_controller_busy(hsi_ctrl)) { + dev_dbg(dev, "Cannot disable clocks, HSI port busy\n"); + return; + } + + if (hsi_is_hst_controller_busy(hsi_ctrl)) + dev_dbg(dev, "Disabling clocks with HST FSM not IDLE !\n"); + +#ifdef K3_0_PORTING_HSI_MISSING_FEATURE + /* Allow Fclk to change */ + if (dpll_cascading_blocker_release(dev) < 0) + dev_warn(dev, "Error releasing DPLL cascading constraint\n"); +#endif + + /* HSI_TODO : this can probably be changed + * to return pm_runtime_put(dev); + */ + pm_runtime_put_sync(dev); +} + +/** +* hsi_clocks_enable_channel - virtual wrapper for enabling HSI clocks for +* a given channel +* @dev - reference to the hsi device. +* @channel_number - channel number which requests clock to be enabled +* 0xFF means no particular channel +* +* Note : there is no real HW clock management per HSI channel, this is only +* virtual to keep track of active channels and ease debug +* +* Function to be called with lock +*/ +int hsi_clocks_enable_channel(struct device *dev, u8 channel_number, + const char *s) +{ + struct platform_device *pd = to_platform_device(dev); + struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd); + + if (channel_number != HSI_CH_NUMBER_NONE) + dev_dbg(dev, "CLK: hsi_clocks_enable for " + "channel %d: %s\n", channel_number, s); + else + dev_dbg(dev, "CLK: hsi_clocks_enable: %s\n", s); + + if (hsi_ctrl->clock_enabled) { + dev_dbg(dev, "Clocks already enabled, skipping...\n"); + return -EEXIST; + } + +#ifdef K3_0_PORTING_HSI_MISSING_FEATURE + /* Prevent Fclk change */ + if (dpll_cascading_blocker_hold(dev) < 0) + dev_warn(dev, "Error holding DPLL cascading constraint\n"); +#endif + + return pm_runtime_get_sync(dev); +} + +static int __init hsi_controller_init(struct hsi_dev *hsi_ctrl, + struct platform_device *pd) +{ + struct hsi_platform_data *pdata = pd->dev.platform_data; + struct resource *mem, *ioarea; + int err; + + mem = platform_get_resource(pd, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pd->dev, "HSI device does not have " + "HSI IO memory region information\n"); + return -ENXIO; + } + dev_dbg(&pd->dev, "hsi_controller_init : IORESOURCE_MEM %s [%x, %x]\n", + mem->name, mem->start, mem->end); + + ioarea = devm_request_mem_region(&pd->dev, mem->start, + (mem->end - mem->start) + 1, + dev_name(&pd->dev)); + if (!ioarea) { + dev_err(&pd->dev, "Unable to request HSI IO mem region\n"); + return -EBUSY; + } + dev_dbg(&pd->dev, "hsi_controller_init : ioarea %s [%x, %x]\n", + ioarea->name, ioarea->start, ioarea->end); + + hsi_ctrl->phy_base = mem->start; + hsi_ctrl->base = devm_ioremap(&pd->dev, mem->start, + (mem->end - mem->start) + 1); + if (!hsi_ctrl->base) { + dev_err(&pd->dev, "Unable to ioremap HSI base IO address\n"); + return -ENXIO; + } + dev_dbg(&pd->dev, "hsi_controller_init : hsi_ctrl->base=%x\n", + (unsigned int)hsi_ctrl->base); + + hsi_ctrl->id = pd->id; + if (pdata->num_ports > HSI_MAX_PORTS) { + dev_err(&pd->dev, "The HSI driver does not support enough " + "ports!\n"); + return -ENXIO; + } + hsi_ctrl->max_p = pdata->num_ports; + hsi_ctrl->in_dma_tasklet = false; + hsi_ctrl->fifo_mapping_strategy = HSI_FIFO_MAPPING_UNDEF; + hsi_ctrl->dev = &pd->dev; + spin_lock_init(&hsi_ctrl->lock); + err = hsi_init_gdd_chan_count(hsi_ctrl); + if (err < 0) + goto rback1; + + err = hsi_ports_init(hsi_ctrl); + if (err < 0) + goto rback1; + + err = hsi_request_gdd_irq(hsi_ctrl); + if (err < 0) + goto rback2; + + /* Everything is fine */ + return 0; +rback2: + hsi_ports_exit(hsi_ctrl, hsi_ctrl->max_p); +rback1: + dev_err(&pd->dev, "Error on hsi_controller initialization\n"); + return err; +} + +static void hsi_controller_exit(struct hsi_dev *hsi_ctrl) +{ + hsi_gdd_exit(hsi_ctrl); + hsi_ports_exit(hsi_ctrl, hsi_ctrl->max_p); +} + +/* HSI Platform Device probing & hsi_device registration */ +static int __init hsi_platform_device_probe(struct platform_device *pd) +{ + struct hsi_platform_data *pdata = pd->dev.platform_data; + struct hsi_dev *hsi_ctrl; + u32 revision; + int err; + + dev_dbg(&pd->dev, "HSI DRIVER : hsi_platform_device_probe\n"); + + dev_dbg(&pd->dev, "The platform device probed is an %s\n", + hsi_driver_device_is_hsi(pd) ? "HSI" : "SSI"); + + if (!pdata) { + dev_err(&pd->dev, "No platform_data found on hsi device\n"); + return -ENXIO; + } + + hsi_ctrl = kzalloc(sizeof(*hsi_ctrl), GFP_KERNEL); + if (hsi_ctrl == NULL) { + dev_err(&pd->dev, "Could not allocate memory for" + " struct hsi_dev\n"); + return -ENOMEM; + } + + platform_set_drvdata(pd, hsi_ctrl); + err = hsi_controller_init(hsi_ctrl, pd); + if (err < 0) { + dev_err(&pd->dev, "Could not initialize hsi controller:" + " %d\n", err); + goto rollback1; + } + /* Wakeup dependency was disabled for HSI <-> MPU PM_L3INIT_HSI_WKDEP */ +#if 0 + omap_writel(0x141, 0x4A307338); +#endif + pm_runtime_enable(hsi_ctrl->dev); + hsi_clocks_enable(hsi_ctrl->dev, __func__); + + /* Non critical SW Reset */ + err = hsi_softreset(hsi_ctrl); + if (err < 0) + goto rollback2; + + hsi_set_pm_default(hsi_ctrl); + + /* Configure HSI ports */ + hsi_set_ports_default(hsi_ctrl, pd); + + /* Gather info from registers for the driver.(REVISION) */ + revision = hsi_inl(hsi_ctrl->base, HSI_SYS_REVISION_REG); + if (hsi_driver_device_is_hsi(pd)) + dev_info(hsi_ctrl->dev, "HSI Hardware REVISION 0x%x\n", + revision); + else + dev_info(hsi_ctrl->dev, "SSI Hardware REVISION %d.%d\n", + (revision & HSI_SSI_REV_MAJOR) >> 4, + (revision & HSI_SSI_REV_MINOR)); + + err = hsi_debug_add_ctrl(hsi_ctrl); + if (err < 0) { + dev_err(&pd->dev, + "Could not add hsi controller to debugfs: %d\n", err); + goto rollback2; + } + + err = register_hsi_devices(hsi_ctrl); + if (err < 0) { + dev_err(&pd->dev, "Could not register hsi_devices: %d\n", err); + goto rollback3; + } + + /* 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", + pdata->default_hsi_fclk); +#endif + + /* From here no need for HSI HW access */ + hsi_clocks_disable(hsi_ctrl->dev, __func__); + + return err; + +rollback3: + hsi_debug_remove_ctrl(hsi_ctrl); +rollback2: + hsi_controller_exit(hsi_ctrl); + + /* From here no need for HSI HW access */ + hsi_clocks_disable(hsi_ctrl->dev, __func__); + +rollback1: + kfree(hsi_ctrl); + return err; +} + +static int __exit hsi_platform_device_remove(struct platform_device *pd) +{ + struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd); + + dev_dbg(&pd->dev, "HSI DRIVER : hsi_platform_device_remove\n"); + + if (!hsi_ctrl) + return 0; + + unregister_hsi_devices(hsi_ctrl); + + /* From here no need for HSI HW access */ + pm_runtime_disable(hsi_ctrl->dev); + + hsi_debug_remove_ctrl(hsi_ctrl); + hsi_controller_exit(hsi_ctrl); + + kfree(hsi_ctrl); + + return 0; +} + +#ifdef CONFIG_SUSPEND +static int hsi_suspend_noirq(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); + + dev_dbg(dev, "%s\n", __func__); + + /* If HSI is enabled, CAWAKE IO wakeup has been disabled and */ + /* 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; + } + + /* Perform HSI board specific action before platform suspend */ + if (pdata->board_suspend) + pdata->board_suspend(0, device_may_wakeup(dev)); + + return 0; +} + +static int hsi_resume_noirq(struct device *dev) +{ + struct hsi_platform_data *pdata = dev->platform_data; + + dev_dbg(dev, "%s\n", __func__); + + /* This function shall not schedule the tasklet, because it is */ + /* redundant with what is already done in the PRCM interrupt handler. */ + /* 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. */ + /* 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 */ + /* the winner. */ + + /* Perform (optional) HSI board specific action after platform wakeup */ + if (pdata->board_resume) + pdata->board_resume(0); + + return 0; +} +#endif /* CONFIG_PM_SUSPEND */ + +#ifdef CONFIG_PM_RUNTIME +/** +* hsi_runtime_resume - executed by the PM core for the bus type of the device being woken up +* @dev - reference to the hsi device. +* +* +*/ +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; + dev_dbg(dev, "%s\n", __func__); + + if (hsi_ctrl->clock_enabled) + dev_warn(dev, "Warning: clock status mismatch vs runtime PM\n"); + + hsi_ctrl->clock_enabled = true; + + /* Restore context */ + hsi_restore_ctx(hsi_ctrl); + + /* When HSI is ON, no need for IO wakeup mechanism */ + pdata->wakeup_disable(0); + + /* HSI device is now fully operational and _must_ be able to */ + /* complete I/O operations */ + + return 0; +} + +/** +* hsi_runtime_suspend - Prepare HSI for low power : device will not process data and will + not communicate with the CPU +* @dev - reference to the hsi device. +* +* Return value : -EBUSY or -EAGAIN if device is busy and still operational +* +*/ +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; + 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 */ + if (device_may_wakeup(dev)) + pdata->wakeup_enable(0); + else + pdata->wakeup_disable(0); + + /* HSI is now ready to be put in low power state */ + + return 0; +} + +/* Based on counters, device appears to be idle. + * Check if the device can be suspended. + */ +static int hsi_runtime_idle(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_hsi_controller_busy(hsi_ctrl)) { + dev_dbg(dev, "hsi_runtime_idle: HSI port busy\n"); + return -EBUSY; + } + + if (hsi_is_hst_controller_busy(hsi_ctrl)) { + dev_dbg(dev, "hsi_runtime_idle: HST FSM not IDLE !\n"); + return -EBUSY; + } + + /* HSI_TODO : check also the interrupt status registers.*/ + + return 0; +} + +#endif /* CONFIG_PM_RUNTIME */ + +int hsi_driver_device_is_hsi(struct platform_device *dev) +{ + struct platform_device_id *id = + (struct platform_device_id *)platform_get_device_id(dev); + return (id->driver_data == HSI_DRV_DEVICE_HSI); +} + +/* List of devices supported by this driver */ +static struct platform_device_id hsi_id_table[] = { + {"omap_hsi", HSI_DRV_DEVICE_HSI}, + {"omap_ssi", HSI_DRV_DEVICE_SSI}, + {}, +}; + +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, +#endif +#ifdef CONFIG_PM_RUNTIME + .runtime_suspend = hsi_runtime_suspend, + .runtime_resume = hsi_runtime_resume, + .runtime_idle = hsi_runtime_idle, +#endif +}; + +#define HSI_DRIVER_PM_OPS_PTR (&hsi_driver_pm_ops) + +#else /* !CONFIG_PM */ + +#define HSI_DRIVER_PM_OPS_PTR NULL + +#endif + +static struct platform_driver hsi_pdriver = { + .driver = { + .name = HSI_MODULENAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = HSI_DRIVER_PM_OPS_PTR, +#endif + }, + .id_table = hsi_id_table, + .remove = __exit_p(hsi_platform_device_remove), +}; + +/* HSI bus and platform driver registration */ +static int __init hsi_driver_init(void) +{ + int err = 0; + + pr_info(LOG_NAME "HSI DRIVER Version " HSI_DRIVER_VERSION "\n"); + + /* Register the (virtual) HSI bus */ + err = hsi_bus_init(); + if (err < 0) { + pr_err(LOG_NAME "HSI bus_register err %d\n", err); + return err; + } + + err = hsi_debug_init(); + if (err < 0) { + pr_err(LOG_NAME "HSI Debugfs failed %d\n", err); + goto rback1; + } + + /* Register the HSI platform driver */ + err = platform_driver_probe(&hsi_pdriver, hsi_platform_device_probe); + if (err < 0) { + pr_err(LOG_NAME "Platform DRIVER register FAILED: %d\n", err); + goto rback2; + } + + return 0; +rback2: + hsi_debug_exit(); +rback1: + hsi_bus_exit(); + return err; +} + +static void __exit hsi_driver_exit(void) +{ + platform_driver_unregister(&hsi_pdriver); + hsi_debug_exit(); + hsi_bus_exit(); + + pr_info(LOG_NAME "HSI DRIVER removed\n"); +} + +module_init(hsi_driver_init); +module_exit(hsi_driver_exit); + +MODULE_ALIAS("platform:" HSI_MODULENAME); +MODULE_AUTHOR("Carlos Chinea / Nokia"); +MODULE_AUTHOR("Sebastien JAN / Texas Instruments"); +MODULE_AUTHOR("Djamil ELAIDI / Texas Instruments"); +MODULE_DESCRIPTION("MIPI High-speed Synchronous Serial Interface (HSI) Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/omap_hsi/hsi_driver.h b/drivers/omap_hsi/hsi_driver.h new file mode 100644 index 0000000..0991d98 --- /dev/null +++ b/drivers/omap_hsi/hsi_driver.h @@ -0,0 +1,398 @@ +/* + * hsi_driver.h + * + * Header file for the HSI driver low level interface. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef __HSI_DRIVER_H__ +#define __HSI_DRIVER_H__ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/notifier.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/pm_runtime.h> + +#include <linux/hsi_driver_if.h> +#include <plat/omap_hsi.h> + +/* Channel states */ +#define HSI_CH_OPEN 0x01 +#define HSI_CH_RX_POLL 0x10 +#define HSI_CH_ACWAKE 0x02 /* ACWAKE line status */ + +#define HSI_CH_NUMBER_NONE 0xFF +/* + * The number of channels handled by the driver in the ports, or the highest + * port channel number (+1) used. (MAX:8 for SSI; 16 for HSI) + * Reducing this value optimizes the driver memory footprint. + */ +#define HSI_PORT_MAX_CH HSI_CHANNELS_MAX + +/* Number of DMA channels when nothing is defined for the device */ +#define HSI_DMA_CHANNEL_DEFAULT 8 + + +#define LOG_NAME "OMAP HSI: " + +/* SW strategies for HSI FIFO mapping */ +enum { + HSI_FIFO_MAPPING_UNDEF = 0, + 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 { + HSI_DRV_DEVICE_HSI, + HSI_DRV_DEVICE_SSI +}; + +/** + * struct hsi_data - HSI buffer descriptor + * @addr: pointer to the buffer where to send or receive data + * @size: size in words (32 bits) of the buffer + * @lch: associated GDD (DMA) logical channel number, if any + */ +struct hsi_data { + u32 *addr; + unsigned int size; + int lch; +}; + +/** + * struct hsi_channel - HSI channel data + * @read_data: Incoming HSI buffer descriptor + * @write_data: Outgoing HSI buffer descriptor + * @hsi_port: Reference to port where the channel belongs to + * @flags: Tracks if channel has been open + * @channel_number: HSI channel number + * @rw_lock: Read/Write lock to serialize access to callback and hsi_device + * @dev: Reference to the associated hsi_device channel + * @write_done: Callback to signal TX completed. + * @read_done: Callback to signal RX completed. + * @port_event: Callback to signal port events (RX Error, HWBREAK, CAWAKE ...) + */ +struct hsi_channel { + struct hsi_data read_data; + struct hsi_data write_data; + struct hsi_port *hsi_port; + u8 flags; + u8 channel_number; + rwlock_t rw_lock; + struct hsi_device *dev; + void (*write_done) (struct hsi_device *dev, unsigned int size); + void (*read_done) (struct hsi_device *dev, unsigned int size); + void (*port_event) (struct hsi_device *dev, unsigned int event, + void *arg); +}; + +/** + * 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 + * @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 + * @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 + * @cawake_off_event: True if CAWAKE event was detected from OFF mode + * @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 + * @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 + * @hsi_tasklet: Bottom half for interrupts when clocks are enabled + * @cawake_tasklet: Bottom half for cawake events + */ +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] */ + u8 max_ch; + u8 n_irq; + int irq; + int cawake_gpio; + int cawake_gpio_irq; + int cawake_status; + bool cawake_off_event; + unsigned int acwake_status; /* HSI_TODO : fine tune init values */ + bool in_int_tasklet; + bool in_cawake_tasklet; + int counters_on; + 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 hsi_dev - hsi controller driver data + * This structure is saved into platform_device->dev->p->driver_data + * + * @hsi_port: Array of hsi ports enabled in the controller + * @id: HSI controller platform id number + * @max_p: Number of ports enabled in the controller + * @hsi_clk: Reference to the HSI custom clock + * @base: HSI registers base virtual address + * @phy_base: HSI registers base physical address + * @lock: Serializes access to internal data and regs + * @clock_enabled: Indicates if HSI Clocks are ON + * @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 + * @last_gdd_lch: Last used GDD logical channel + * @gdd_chan_count: Number of available DMA channels on the device (must be ^2) + * @in_dma_tasklet: True if DMA tasklet for the controller is currently running + * @set_min_bus_tput: (PM) callback to set minimun bus throuput + * @clk_notifier_register: (PM) callabck for DVFS support + * @clk_notifier_unregister: (PM) callabck for DVFS support + * @hsi_nb: (PM) Notification block for DVFS notification chain + * @hsi_gdd_tasklet: Bottom half for DMA Interrupts when clocks are enabled + * @dir: debugfs base directory + * @dev: Reference to the HSI platform device + */ +struct hsi_dev { /* HSI_TODO: should be later renamed into hsi_controller*/ + struct hsi_port hsi_port[HSI_MAX_PORTS]; + int id; + u8 max_p; + void __iomem *base; + unsigned long phy_base; + spinlock_t lock; /* Serializes access to internal data and regs */ + bool clock_enabled; + int gdd_irq; + unsigned int fifo_mapping_strategy; + unsigned int gdd_usecount; + unsigned int last_gdd_lch; + unsigned int gdd_chan_count; + bool in_dma_tasklet; + void (*set_min_bus_tput) (struct device *dev, u8 agent_id, + unsigned long r); + struct notifier_block hsi_nb; + struct tasklet_struct hsi_gdd_tasklet; +#ifdef CONFIG_DEBUG_FS + struct dentry *dir; +#endif + struct device *dev; +}; + +/** + * struct hsi_platform_data - Board specific data +*/ +struct hsi_platform_data { + void (*set_min_bus_tput) (struct device *dev, u8 agent_id, + unsigned long r); + int (*device_enable) (struct platform_device *pdev); + int (*device_shutdown) (struct platform_device *pdev); + int (*device_idle) (struct platform_device *pdev); + int (*wakeup_enable) (int hsi_port); + int (*wakeup_disable) (int hsi_port); + int (*wakeup_is_from_hsi) (void); + int (*board_suspend)(int hsi_port, bool dev_may_wakeup); + int (*board_resume)(int hsi_port); + u8 num_ports; + struct ctrl_ctx *ctx; + u8 hsi_gdd_chan_count; + unsigned long default_hsi_fclk; +}; + +/* HSI Bus */ +extern struct bus_type hsi_bus_type; + +int hsi_port_event_handler(struct hsi_port *p, unsigned int event, void *arg); +int hsi_bus_init(void); +void hsi_bus_exit(void); +/* End HSI Bus */ + +void hsi_reset_ch_read(struct hsi_channel *ch); +void hsi_reset_ch_write(struct hsi_channel *ch); +bool hsi_is_channel_busy(struct hsi_channel *ch); +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); + +int hsi_driver_enable_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, + u32 *data); +bool hsi_is_dma_read_int_pending(struct hsi_dev *hsi_ctrl); +int hsi_driver_read_dma(struct hsi_channel *hsi_channel, u32 * data, + unsigned int count); +int hsi_driver_write_dma(struct hsi_channel *hsi_channel, u32 * data, + unsigned int count); + +int hsi_driver_cancel_read_interrupt(struct hsi_channel *ch); +int hsi_driver_cancel_write_interrupt(struct hsi_channel *ch); +void hsi_driver_disable_read_interrupt(struct hsi_channel *ch); +void hsi_driver_disable_write_interrupt(struct hsi_channel *ch); +int hsi_driver_cancel_write_dma(struct hsi_channel *ch); +int hsi_driver_cancel_read_dma(struct hsi_channel *ch); +int hsi_do_cawake_process(struct hsi_port *pport); + +int hsi_driver_device_is_hsi(struct platform_device *dev); + +int hsi_mpu_init(struct hsi_port *hsi_p, const char *irq_name); +void hsi_mpu_exit(struct hsi_port *hsi_p); + +int hsi_gdd_init(struct hsi_dev *hsi_ctrl, const char *irq_name); +void hsi_gdd_exit(struct hsi_dev *hsi_ctrl); + +int hsi_cawake_init(struct hsi_port *port, const char *irq_name); +void hsi_cawake_exit(struct hsi_port *port); + +int hsi_fifo_get_id(struct hsi_dev *hsi_ctrl, unsigned int channel, + unsigned int port); +int hsi_fifo_get_chan(struct hsi_dev *hsi_ctrl, unsigned int fifo, + unsigned int *channel, unsigned int *port); +int hsi_fifo_mapping(struct hsi_dev *hsi_ctrl, unsigned int mtype); +long hsi_hst_bufstate_f_reg(struct hsi_dev *hsi_ctrl, + unsigned int port, unsigned int channel); +long hsi_hsr_bufstate_f_reg(struct hsi_dev *hsi_ctrl, + unsigned int port, unsigned int channel); +long hsi_hst_buffer_reg(struct hsi_dev *hsi_ctrl, + unsigned int port, unsigned int channel); +long hsi_hsr_buffer_reg(struct hsi_dev *hsi_ctrl, + unsigned int port, unsigned int channel); +u8 hsi_get_rx_fifo_occupancy(struct hsi_dev *hsi_ctrl, u8 fifo); +void hsi_set_pm_force_hsi_on(struct hsi_dev *hsi_ctrl); +void hsi_set_pm_default(struct hsi_dev *hsi_ctrl); +int hsi_softreset(struct hsi_dev *hsi_ctrl); +void hsi_softreset_driver(struct hsi_dev *hsi_ctrl); + +void hsi_clocks_disable_channel(struct device *dev, u8 channel_number, + const char *s); +int hsi_clocks_enable_channel(struct device *dev, u8 channel_number, + const char *s); +#ifdef CONFIG_PM_RUNTIME +extern int hsi_runtime_resume(struct device *dev); +extern int hsi_runtime_suspend(struct device *dev); +#else +static inline int hsi_runtime_resume(struct device *dev) { return -ENOSYS; } +static inline int hsi_runtime_suspend(struct device *dev) { return -ENOSYS; } +#endif +void hsi_save_ctx(struct hsi_dev *hsi_ctrl); +void hsi_restore_ctx(struct hsi_dev *hsi_ctrl); + + +#ifdef CONFIG_DEBUG_FS +int hsi_debug_init(void); +void hsi_debug_exit(void); +int hsi_debug_add_ctrl(struct hsi_dev *hsi_ctrl); +void hsi_debug_remove_ctrl(struct hsi_dev *hsi_ctrl); +#else +#define hsi_debug_add_ctrl(hsi_ctrl) 0 +#define hsi_debug_remove_ctrl(hsi_ctrl) +#define hsi_debug_init() 0 +#define hsi_debug_exit() +#endif /* CONFIG_DEBUG_FS */ + +static inline struct hsi_channel *hsi_ctrl_get_ch(struct hsi_dev *hsi_ctrl, + unsigned int port, + unsigned int channel) +{ + return &hsi_ctrl->hsi_port[port - 1].hsi_channel[channel]; +} + +/* HSI IO access */ +static inline u32 hsi_inl(void __iomem *base, u32 offset) +{ + return inl((unsigned int)base + offset); +} + +static inline void hsi_outl(u32 data, void __iomem *base, u32 offset) +{ + outl(data, (unsigned int)base + offset); +} + +static inline void hsi_outl_or(u32 data, void __iomem *base, u32 offset) +{ + u32 tmp = hsi_inl(base, offset); + hsi_outl((tmp | data), base, offset); +} + +static inline void hsi_outl_and(u32 data, void __iomem *base, u32 offset) +{ + u32 tmp = hsi_inl(base, offset); + hsi_outl((tmp & data), base, offset); +} + +static inline u16 hsi_inw(void __iomem *base, u32 offset) +{ + return inw((unsigned int)base + offset); +} + +static inline void hsi_outw(u16 data, void __iomem *base, u32 offset) +{ + outw(data, (unsigned int)base + offset); +} + +static inline void hsi_outw_or(u16 data, void __iomem *base, u32 offset) +{ + u16 tmp = hsi_inw(base, offset); + hsi_outw((tmp | data), base, offset); +} + +static inline void hsi_outw_and(u16 data, void __iomem *base, u32 offset) +{ + u16 tmp = hsi_inw(base, offset); + hsi_outw((tmp & data), base, offset); +} + +static inline int hsi_get_cawake(struct hsi_port *port) +{ + struct platform_device *pdev = + to_platform_device(port->hsi_controller->dev); + + if (hsi_driver_device_is_hsi(pdev)) + return (HSI_HSR_MODE_WAKE_STATUS == + (hsi_inl(port->hsi_controller->base, + HSI_HSR_MODE_REG(port->port_number)) & + HSI_HSR_MODE_WAKE_STATUS)); + else if (port->cawake_gpio >= 0) + return gpio_get_value(port->cawake_gpio); + else + return -ENXIO; +} + +static inline void hsi_clocks_disable(struct device *dev, const char *s) +{ + hsi_clocks_disable_channel(dev, HSI_CH_NUMBER_NONE, s); +} + +static inline int hsi_clocks_enable(struct device *dev, const char *s) +{ + return hsi_clocks_enable_channel(dev, HSI_CH_NUMBER_NONE, s); +} + +#endif /* __HSI_DRIVER_H__ */ diff --git a/drivers/omap_hsi/hsi_driver_bus.c b/drivers/omap_hsi/hsi_driver_bus.c new file mode 100644 index 0000000..4bce43d --- /dev/null +++ b/drivers/omap_hsi/hsi_driver_bus.c @@ -0,0 +1,203 @@ +/* + * hsi_driver_bus.c + * + * Implements an HSI bus, device and driver interface. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/device.h> +#include "hsi_driver.h" + +#define HSI_PREFIX "hsi:" + +struct bus_type hsi_bus_type; + +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + return snprintf(buf, PAGE_SIZE + 1, "%s%s\n", HSI_PREFIX, + dev_name(dev)); +} + +static struct device_attribute hsi_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int hsi_bus_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + add_uevent_var(env, "MODALIAS=%s%s", HSI_PREFIX, dev_name(dev)); + return 0; +} + +static int hsi_bus_match(struct device *device, struct device_driver *driver) +{ + struct hsi_device *dev = to_hsi_device(device); + struct hsi_device_driver *drv = to_hsi_device_driver(driver); + + pr_debug("HSI DRIVER BUS : hsi_bus_match for ctrl:%d, port:%d, ch%d\n", + dev->n_ctrl, dev->n_p, dev->n_ch); + + if (!test_bit(dev->n_ctrl, &drv->ctrl_mask)) + return 0; + + if (!test_bit(dev->n_ch, &drv->ch_mask[dev->n_p])) + return 0; + + pr_info + ("HSI DRIVER BUS : hsi_bus_match SUCCESS : ctrl:%d (mask:%x)," + " port:%d, ch:%d (mask:%x)\n", + dev->n_ctrl, (u32) drv->ctrl_mask, dev->n_p, dev->n_ch, + (u32) drv->ch_mask[dev->n_p]); + + return 1; +} + +int hsi_bus_unreg_dev(struct device *device, void *p) +{ + device->release(device); + device_unregister(device); + + return 0; +} + +int __init hsi_bus_init(void) +{ + return bus_register(&hsi_bus_type); +} + +void hsi_bus_exit(void) +{ + bus_for_each_dev(&hsi_bus_type, NULL, NULL, hsi_bus_unreg_dev); + bus_unregister(&hsi_bus_type); +} + +static int hsi_bus_probe(struct device *dev) +{ + struct hsi_device_driver *drv; + int rc; + + pr_debug("HSI DRIVER BUS : hsi_bus_probe\n"); + + if (!dev->driver) + return 0; + + drv = to_hsi_device_driver(dev->driver); + + if (!drv->probe) + return -ENODEV; + + rc = drv->probe(to_hsi_device(dev)); + + return rc; +} + +static int hsi_bus_remove(struct device *dev) +{ + struct hsi_device_driver *drv; + int ret; + + pr_debug("HSI DRIVER BUS : hsi_bus_remove\n"); + + if (!dev->driver) + return 0; + + drv = to_hsi_device_driver(dev->driver); + if (drv->remove) { + ret = drv->remove(to_hsi_device(dev)); + } else { + dev->driver = NULL; + ret = 0; + } + + return ret; +} + +static int hsi_bus_suspend(struct device *dev, pm_message_t mesg) +{ + struct hsi_device_driver *drv; + + if (!dev->driver) + return 0; + + drv = to_hsi_device_driver(dev->driver); + if (!drv->suspend) + return 0; + + return drv->suspend(to_hsi_device(dev), mesg); +} + +static int hsi_bus_resume(struct device *dev) +{ + struct hsi_device_driver *drv; + + if (!dev->driver) + return 0; + + drv = to_hsi_device_driver(dev->driver); + if (!drv->resume) + return 0; + + return drv->resume(to_hsi_device(dev)); +} + +struct bus_type hsi_bus_type = { + .name = "hsi", + .dev_attrs = hsi_dev_attrs, + .match = hsi_bus_match, + .uevent = hsi_bus_uevent, + .probe = hsi_bus_probe, + .remove = hsi_bus_remove, + .suspend = hsi_bus_suspend, + .resume = hsi_bus_resume, +}; + +/** + * hsi_register_driver - Register HSI device driver + * @driver - reference to the HSI device driver. + */ +int hsi_register_driver(struct hsi_device_driver *driver) +{ + int ret = 0; + + if (driver == NULL) + return -EINVAL; + + driver->driver.bus = &hsi_bus_type; + + ret = driver_register(&driver->driver); + + if (ret == 0) + pr_debug("hsi: driver %s registered\n", driver->driver.name); + + return ret; +} +EXPORT_SYMBOL(hsi_register_driver); + +/** + * hsi_unregister_driver - Unregister HSI device driver + * @driver - reference to the HSI device driver. + */ +void hsi_unregister_driver(struct hsi_device_driver *driver) +{ + if (driver == NULL) + return; + + driver_unregister(&driver->driver); + + pr_debug("hsi: driver %s unregistered\n", driver->driver.name); +} +EXPORT_SYMBOL(hsi_unregister_driver); diff --git a/drivers/omap_hsi/hsi_driver_debugfs.c b/drivers/omap_hsi/hsi_driver_debugfs.c new file mode 100644 index 0000000..d1f32dd --- /dev/null +++ b/drivers/omap_hsi/hsi_driver_debugfs.c @@ -0,0 +1,500 @@ +/* + * hsi_driver_debugfs.c + * + * Implements HSI debugfs. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/err.h> +#include <linux/list.h> +#include <linux/uaccess.h> +#include <linux/ctype.h> +#include "hsi_driver.h" + +#define HSI_DIR_NAME_SIZE 64 + +static struct dentry *hsi_dir; + +static int hsi_debug_show(struct seq_file *m, void *p) +{ + struct hsi_dev *hsi_ctrl = m->private; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + + hsi_clocks_enable(hsi_ctrl->dev, __func__); + + seq_printf(m, "REVISION\t: 0x%08x\n", + hsi_inl(hsi_ctrl->base, HSI_SYS_REVISION_REG)); + if (hsi_driver_device_is_hsi(pdev)) + seq_printf(m, "HWINFO\t\t: 0x%08x\n", + hsi_inl(hsi_ctrl->base, HSI_SYS_HWINFO_REG)); + seq_printf(m, "SYSCONFIG\t: 0x%08x\n", + hsi_inl(hsi_ctrl->base, HSI_SYS_SYSCONFIG_REG)); + seq_printf(m, "SYSSTATUS\t: 0x%08x\n", + hsi_inl(hsi_ctrl->base, HSI_SYS_SYSSTATUS_REG)); + + hsi_clocks_disable(hsi_ctrl->dev, __func__); + + return 0; +} + +static int hsi_debug_port_show(struct seq_file *m, void *p) +{ + struct hsi_port *hsi_port = m->private; + struct hsi_dev *hsi_ctrl = hsi_port->hsi_controller; + void __iomem *base = hsi_ctrl->base; + unsigned int port = hsi_port->port_number; + int ch, fifo; + long buff_offset; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + + hsi_clocks_enable(hsi_ctrl->dev, __func__); + + if (hsi_port->cawake_gpio >= 0) + seq_printf(m, "CAWAKE\t\t: %d\n", hsi_get_cawake(hsi_port)); + + seq_printf(m, "WAKE\t\t: 0x%08x\n", + hsi_inl(base, HSI_SYS_WAKE_REG(port))); + seq_printf(m, "MPU_ENABLE_IRQ%d\t: 0x%08x\n", hsi_port->n_irq, + hsi_inl(base, + HSI_SYS_MPU_ENABLE_REG(port, hsi_port->n_irq))); + seq_printf(m, "MPU_STATUS_IRQ%d\t: 0x%08x\n", hsi_port->n_irq, + hsi_inl(base, + HSI_SYS_MPU_STATUS_REG(port, hsi_port->n_irq))); + if (hsi_driver_device_is_hsi(pdev)) { + seq_printf(m, "MPU_U_ENABLE_IRQ%d\t: 0x%08x\n", + hsi_port->n_irq, + hsi_inl(base, HSI_SYS_MPU_U_ENABLE_REG(port, + hsi_port->n_irq))); + seq_printf(m, "MPU_U_STATUS_IRQ%d\t: 0x%08x\n", hsi_port->n_irq, + hsi_inl(base, + HSI_SYS_MPU_U_STATUS_REG(port, + hsi_port->n_irq))); + } + /* HST */ + seq_printf(m, "\nHST\n===\n"); + seq_printf(m, "MODE\t\t: 0x%08x\n", + hsi_inl(base, HSI_HST_MODE_REG(port))); + seq_printf(m, "FRAMESIZE\t: 0x%08x\n", + hsi_inl(base, HSI_HST_FRAMESIZE_REG(port))); + seq_printf(m, "DIVISOR\t\t: 0x%08x\n", + hsi_inl(base, HSI_HST_DIVISOR_REG(port))); + seq_printf(m, "CHANNELS\t: 0x%08x\n", + hsi_inl(base, HSI_HST_CHANNELS_REG(port))); + seq_printf(m, "ARBMODE\t\t: 0x%08x\n", + hsi_inl(base, HSI_HST_ARBMODE_REG(port))); + seq_printf(m, "TXSTATE\t\t: 0x%08x\n", + hsi_inl(base, HSI_HST_TXSTATE_REG(port))); + if (hsi_driver_device_is_hsi(pdev)) { + seq_printf(m, "BUFSTATE P1\t: 0x%08x\n", + hsi_inl(base, HSI_HST_BUFSTATE_REG(1))); + seq_printf(m, "BUFSTATE P2\t: 0x%08x\n", + hsi_inl(base, HSI_HST_BUFSTATE_REG(2))); + } else { + seq_printf(m, "BUFSTATE\t: 0x%08x\n", + hsi_inl(base, HSI_HST_BUFSTATE_REG(port))); + } + seq_printf(m, "BREAK\t\t: 0x%08x\n", + hsi_inl(base, HSI_HST_BREAK_REG(port))); + for (ch = 0; ch < 8; ch++) { + buff_offset = hsi_hst_buffer_reg(hsi_ctrl, port, ch); + if (buff_offset >= 0) + seq_printf(m, "BUFFER_CH%d\t: 0x%08x\n", ch, + hsi_inl(base, buff_offset)); + } + if (hsi_driver_device_is_hsi(pdev)) { + for (fifo = 0; fifo < HSI_HST_FIFO_COUNT; fifo++) { + seq_printf(m, "FIFO MAPPING%d\t: 0x%08x\n", fifo, + hsi_inl(base, + HSI_HST_MAPPING_FIFO_REG(fifo))); + } + } + /* HSR */ + seq_printf(m, "\nHSR\n===\n"); + seq_printf(m, "MODE\t\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_MODE_REG(port))); + seq_printf(m, "FRAMESIZE\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_FRAMESIZE_REG(port))); + seq_printf(m, "CHANNELS\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_CHANNELS_REG(port))); + seq_printf(m, "COUNTERS\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_COUNTERS_REG(port))); + seq_printf(m, "RXSTATE\t\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_RXSTATE_REG(port))); + if (hsi_driver_device_is_hsi(pdev)) { + seq_printf(m, "BUFSTATE P1\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_BUFSTATE_REG(1))); + seq_printf(m, "BUFSTATE P2\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_BUFSTATE_REG(2))); + } else { + seq_printf(m, "BUFSTATE\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_BUFSTATE_REG(port))); + } + seq_printf(m, "BREAK\t\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_BREAK_REG(port))); + seq_printf(m, "ERROR\t\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_ERROR_REG(port))); + seq_printf(m, "ERRORACK\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_ERRORACK_REG(port))); + for (ch = 0; ch < 8; ch++) { + buff_offset = hsi_hsr_buffer_reg(hsi_ctrl, port, ch); + if (buff_offset >= 0) + seq_printf(m, "BUFFER_CH%d\t: 0x%08x\n", ch, + hsi_inl(base, buff_offset)); + } + if (hsi_driver_device_is_hsi(pdev)) { + for (fifo = 0; fifo < HSI_HSR_FIFO_COUNT; fifo++) { + seq_printf(m, "FIFO MAPPING%d\t: 0x%08x\n", fifo, + hsi_inl(base, + HSI_HSR_MAPPING_FIFO_REG(fifo))); + } + seq_printf(m, "DLL\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_DLL_REG)); + seq_printf(m, "DIVISOR\t: 0x%08x\n", + hsi_inl(base, HSI_HSR_DIVISOR_REG(port))); + } + + hsi_clocks_disable(hsi_ctrl->dev, __func__); + + return 0; +} + +static int hsi_debug_gdd_show(struct seq_file *m, void *p) +{ + struct hsi_dev *hsi_ctrl = m->private; + void __iomem *base = hsi_ctrl->base; + int lch; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + + hsi_clocks_enable(hsi_ctrl->dev, __func__); + + seq_printf(m, "GDD_MPU_STATUS\t: 0x%08x\n", + hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG)); + seq_printf(m, "GDD_MPU_ENABLE\t: 0x%08x\n\n", + hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG)); + + if (!hsi_driver_device_is_hsi(pdev)) { + seq_printf(m, "HW_ID\t\t: 0x%08x\n", + hsi_inl(base, HSI_SSI_GDD_HW_ID_REG)); + seq_printf(m, "PPORT_ID\t: 0x%08x\n", + hsi_inl(base, HSI_SSI_GDD_PPORT_ID_REG)); + seq_printf(m, "MPORT_ID\t: 0x%08x\n", + hsi_inl(base, HSI_SSI_GDD_MPORT_ID_REG)); + seq_printf(m, "TEST\t\t: 0x%08x\n", + hsi_inl(base, HSI_SSI_GDD_TEST_REG)); + } + + seq_printf(m, "GCR\t\t: 0x%08x\n", hsi_inl(base, HSI_GDD_GCR_REG)); + + for (lch = 0; lch < hsi_ctrl->gdd_chan_count; lch++) { + seq_printf(m, "\nGDD LCH %d\n=========\n", lch); + seq_printf(m, "CSDP\t\t: 0x%04x\n", + hsi_inw(base, HSI_GDD_CSDP_REG(lch))); + seq_printf(m, "CCR\t\t: 0x%04x\n", + hsi_inw(base, HSI_GDD_CCR_REG(lch))); + seq_printf(m, "CICR\t\t: 0x%04x\n", + hsi_inw(base, HSI_GDD_CCIR_REG(lch))); + seq_printf(m, "CSR\t\t: 0x%04x\n", + hsi_inw(base, HSI_GDD_CSR_REG(lch))); + seq_printf(m, "CSSA\t\t: 0x%08x\n", + hsi_inl(base, HSI_GDD_CSSA_REG(lch))); + seq_printf(m, "CDSA\t\t: 0x%08x\n", + hsi_inl(base, HSI_GDD_CDSA_REG(lch))); + seq_printf(m, "CEN\t\t: 0x%04x\n", + hsi_inw(base, HSI_GDD_CEN_REG(lch))); + seq_printf(m, "CSAC\t\t: 0x%04x\n", + hsi_inw(base, HSI_GDD_CSAC_REG(lch))); + seq_printf(m, "CDAC\t\t: 0x%04x\n", + hsi_inw(base, HSI_GDD_CDAC_REG(lch))); + if (!hsi_driver_device_is_hsi(pdev)) + seq_printf(m, "CLNK_CTRL\t: 0x%04x\n", + hsi_inw(base, + HSI_SSI_GDD_CLNK_CTRL_REG(lch))); + } + + hsi_clocks_disable(hsi_ctrl->dev, __func__); + + return 0; +} + +static int hsi_port_counters_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static int hsi_port_counters_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static loff_t hsi_port_counters_seek(struct file *file, loff_t off, int whence) +{ + return 0; +} + +static ssize_t hsi_port_counters_read(struct file *filep, char __user * buff, + size_t count, loff_t *offp) +{ + ssize_t ret; + struct hsi_port *hsi_port = filep->private_data; + struct hsi_dev *hsi_ctrl = hsi_port->hsi_controller; + void __iomem *base = hsi_ctrl->base; + unsigned int port = hsi_port->port_number; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + char str[50]; + unsigned long reg; + + if (*offp > 0) { + ret = 0; + goto hsi_cnt_rd_bk; + } + + hsi_clocks_enable(hsi_ctrl->dev, __func__); + + reg = hsi_inl(base, HSI_HSR_COUNTERS_REG(port)); + + hsi_clocks_disable(hsi_ctrl->dev, __func__); + + if (hsi_driver_device_is_hsi(pdev)) { + sprintf(str, "FT:%d, TB:%d, FB:%d\n", + (int)(reg & HSI_COUNTERS_FT_MASK) >> + HSI_COUNTERS_FT_OFFSET, + (int)(reg & HSI_COUNTERS_TB_MASK) >> + HSI_COUNTERS_TB_OFFSET, + (int)(reg & HSI_COUNTERS_FB_MASK) >> + HSI_COUNTERS_FB_OFFSET); + } else { + sprintf(str, "timeout:%d\n", (int)reg); + } + + ret = strlen(str); + if (copy_to_user((void __user *)buff, str, ret)) { + dev_err(hsi_ctrl->dev, "copy_to_user failed\n"); + ret = 0; + } else { + *offp = ret; + } + +hsi_cnt_rd_bk: + return ret; +} + +/* + * Split the buffer `buf' into space-separated words. + * Return the number of words or <0 on error. + */ +static int hsi_debug_tokenize(char *buf, char *words[], int maxwords) +{ + int nwords = 0; + + while (*buf) { + char *end; + + /* Skip leading whitespace */ + while (*buf && isspace(*buf)) + buf++; + if (!*buf) + break; /* oh, it was trailing whitespace */ + + /* Run `end' over a word */ + for (end = buf; *end && !isspace(*end); end++) + ; + /* `buf' is the start of the word, `end' is one past the end */ + + if (nwords == maxwords) + return -EINVAL; /* ran out of words[] before bytes */ + if (*end) + *end++ = '\0'; /* terminate the word */ + words[nwords++] = buf; + buf = end; + } + return nwords; +} + +static ssize_t hsi_port_counters_write(struct file *filep, + const char __user *buff, size_t count, + loff_t *offp) +{ + ssize_t ret; + struct hsi_port *hsi_port = filep->private_data; + struct hsi_dev *hsi_ctrl = hsi_port->hsi_controller; + void __iomem *base = hsi_ctrl->base; + unsigned int port = hsi_port->port_number; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); +#define MAXWORDS 4 + int nwords; + char *words[MAXWORDS]; + char tmpbuf[256]; + unsigned long reg, ft, tb, fb; + + if (count == 0) + return 0; + if (count > sizeof(tmpbuf) - 1) + return -E2BIG; + if (copy_from_user(tmpbuf, buff, count)) + return -EFAULT; + tmpbuf[count] = '\0'; + dev_dbg(hsi_ctrl->dev, "%s: read %d bytes from userspace\n", + __func__, (int)count); + + nwords = hsi_debug_tokenize(tmpbuf, words, MAXWORDS); + if (nwords < 0) { + dev_warn(hsi_ctrl->dev, + "HSI counters write usage: echo <values> > counters\n"); + return -EINVAL; + } + + hsi_clocks_enable(hsi_ctrl->dev, __func__); + + if (hsi_driver_device_is_hsi(pdev)) { + if (nwords != 3) { + dev_warn(hsi_ctrl->dev, "HSI counters write usage: " + "echo \"FT TB FB\" > counters\n"); + ret = -EINVAL; + goto hsi_cnt_w_bk1; + } + strict_strtoul(words[0], 0, &ft); + strict_strtoul(words[1], 0, &tb); + strict_strtoul(words[2], 0, &fb); + reg = ((ft << HSI_COUNTERS_FT_OFFSET & HSI_COUNTERS_FT_MASK) | + (tb << HSI_COUNTERS_TB_OFFSET & HSI_COUNTERS_TB_MASK) | + (fb << HSI_COUNTERS_FB_OFFSET & HSI_COUNTERS_FB_MASK)); + } else { + if (nwords != 1) { + dev_warn(hsi_ctrl->dev, "HSI counters write usage: " + "echo \"timeout\" > counters\n"); + ret = -EINVAL; + goto hsi_cnt_w_bk1; + } + strict_strtoul(words[0], 0, ®); + } + hsi_outl(reg, base, HSI_HSR_COUNTERS_REG(port)); + ret = count; + *offp += count; + +hsi_cnt_w_bk1: + + hsi_clocks_disable(hsi_ctrl->dev, __func__); + + return ret; +} + +static int hsi_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, hsi_debug_show, inode->i_private); +} + +static int hsi_port_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, hsi_debug_port_show, inode->i_private); +} + +static int hsi_gdd_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, hsi_debug_gdd_show, inode->i_private); +} + +static const struct file_operations hsi_regs_fops = { + .open = hsi_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations hsi_port_regs_fops = { + .open = hsi_port_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations hsi_port_counters_fops = { + .open = hsi_port_counters_open, + .read = hsi_port_counters_read, + .write = hsi_port_counters_write, + .llseek = hsi_port_counters_seek, + .release = hsi_port_counters_release, +}; + +static const struct file_operations hsi_gdd_regs_fops = { + .open = hsi_gdd_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int __init hsi_debug_add_ctrl(struct hsi_dev *hsi_ctrl) +{ + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + unsigned char dir_name[HSI_DIR_NAME_SIZE]; + struct dentry *dir; + unsigned int port; + + if (pdev->id < 0) { + hsi_ctrl->dir = debugfs_create_dir(pdev->name, hsi_dir); + } else { + snprintf(dir_name, sizeof(dir_name), "%s%d", pdev->name, + pdev->id); + hsi_ctrl->dir = debugfs_create_dir(dir_name, hsi_dir); + } + if (IS_ERR(hsi_ctrl->dir)) + return PTR_ERR(hsi_ctrl->dir); + + debugfs_create_file("regs", S_IRUGO, hsi_ctrl->dir, hsi_ctrl, + &hsi_regs_fops); + + for (port = 0; port < hsi_ctrl->max_p; port++) { + snprintf(dir_name, sizeof(dir_name), "port%d", port + 1); + dir = debugfs_create_dir(dir_name, hsi_ctrl->dir); + if (IS_ERR(dir)) + goto rback; + 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, + &hsi_ctrl->hsi_port[port], + &hsi_port_counters_fops); + } + + dir = debugfs_create_dir("gdd", hsi_ctrl->dir); + if (IS_ERR(dir)) + goto rback; + debugfs_create_file("regs", S_IRUGO, dir, hsi_ctrl, &hsi_gdd_regs_fops); + + return 0; +rback: + debugfs_remove_recursive(hsi_ctrl->dir); + return PTR_ERR(dir); +} + +void hsi_debug_remove_ctrl(struct hsi_dev *hsi_ctrl) +{ + debugfs_remove_recursive(hsi_ctrl->dir); +} + +int __init hsi_debug_init(void) +{ + hsi_dir = debugfs_create_dir("hsi", NULL); + if (IS_ERR(hsi_dir)) + return PTR_ERR(hsi_dir); + + return 0; +} + +void hsi_debug_exit(void) +{ + debugfs_remove_recursive(hsi_dir); +} diff --git a/drivers/omap_hsi/hsi_driver_dma.c b/drivers/omap_hsi/hsi_driver_dma.c new file mode 100644 index 0000000..ad819f5 --- /dev/null +++ b/drivers/omap_hsi/hsi_driver_dma.c @@ -0,0 +1,643 @@ +/* + * hsi_driver_dma.c + * + * Implements HSI low level interface driver functionality with DMA support. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/dma-mapping.h> +#include "hsi_driver.h" + +#define HSI_SYNC_WRITE 0 +#define HSI_SYNC_READ 1 +#define HSI_L3_TPUT 13428 /* 13428 KiB/s => ~110 Mbit/s */ + +static unsigned char hsi_sync_table[2][2][8] = { + { + {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00} + }, { + {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}, + {0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f} + } +}; + +/** + * hsi_is_dma_read_int_pending - Indicates if a DMA read interrupt is pending + * @hsi_ctrl - HSI controller of the GDD. + * + * Needs to be called holding the hsi_controller lock + * + * Returns true if DMA read interrupt is pending, else false + */ +bool hsi_is_dma_read_int_pending(struct hsi_dev *hsi_ctrl) +{ + void __iomem *base = hsi_ctrl->base; + unsigned int gdd_lch = 0; + u32 status_reg = 0; + int i, j; + status_reg = hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG); + status_reg &= hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + if (!status_reg) + return false; + + /* Scan all enabled DMA channels */ + for (gdd_lch = 0; gdd_lch < hsi_ctrl->gdd_chan_count; gdd_lch++) { + if (!(status_reg & HSI_GDD_LCH(gdd_lch))) + continue; + 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 == gdd_lch) + return true; + } + return false; +} +/** + * hsi_get_free_lch - Get a free GDD(DMA) logical channel + * @hsi_ctrl - HSI controller of the GDD. + * + * Needs to be called holding the hsi_controller lock + * + * Returns the logical channel number, or -EBUSY if none available + */ +static int hsi_get_free_lch(struct hsi_dev *hsi_ctrl) +{ + unsigned int enable_reg; + int i, lch; + + enable_reg = hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + lch = hsi_ctrl->last_gdd_lch; + for (i = 0; i < hsi_ctrl->gdd_chan_count; i++) { + if (++lch >= hsi_ctrl->gdd_chan_count) + lch = 0; + if ((enable_reg & HSI_GDD_LCH(lch)) == 0) { + hsi_ctrl->last_gdd_lch = lch; + return lch; + } + } + return -EBUSY; +} + +/** + * hsi_driver_write_dma - Program GDD [DMA] to write data from memory to + * the hsi channel buffer. + * @hsi_channel - pointer to the hsi_channel to write data to. + * @data - 32-bit word pointer to the data. + * @size - Number of 32bit words to be transfered. + * + * hsi_controller lock must be held before calling this function. + * + * Return 0 on success and < 0 on error. + */ +int hsi_driver_write_dma(struct hsi_channel *hsi_channel, u32 * data, + unsigned int size) +{ + struct hsi_dev *hsi_ctrl = hsi_channel->hsi_port->hsi_controller; + void __iomem *base = hsi_ctrl->base; + unsigned int port = hsi_channel->hsi_port->port_number; + unsigned int channel = hsi_channel->channel_number; + unsigned int sync; + int lch; + dma_addr_t src_addr; + dma_addr_t dest_addr; + u16 tmp; + int fifo; + + if ((size < 1) || (data == NULL)) + return -EINVAL; + + lch = hsi_get_free_lch(hsi_ctrl); + if (lch < 0) { + dev_err(hsi_ctrl->dev, "No free DMA channels.\n"); + return -EBUSY; /* No free GDD logical channels. */ + } else { + dev_dbg(hsi_ctrl->dev, "Allocated DMA channel %d for write on" + " HSI channel %d.\n", lch, + hsi_channel->channel_number); + } + + /* NOTE: Getting a free gdd logical channel and + * reserve it must be done atomicaly. */ + hsi_channel->write_data.lch = lch; + + /* Sync is required for SSI but not for HSI */ + sync = hsi_sync_table[HSI_SYNC_WRITE][port - 1][channel]; + + src_addr = dma_map_single(hsi_ctrl->dev, data, size * 4, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(hsi_ctrl->dev, src_addr))) { + dev_err(hsi_ctrl->dev, "Failed to create DMA write mapping.\n"); + return -ENOMEM; + } + + tmp = HSI_SRC_SINGLE_ACCESS0 | + HSI_SRC_MEMORY_PORT | + HSI_DST_SINGLE_ACCESS0 | + HSI_DST_PERIPHERAL_PORT | HSI_DATA_TYPE_S32; + hsi_outw(tmp, base, HSI_GDD_CSDP_REG(lch)); + + tmp = HSI_SRC_AMODE_POSTINC | HSI_DST_AMODE_CONST | sync; + hsi_outw(tmp, base, HSI_GDD_CCR_REG(lch)); + + hsi_outw((HSI_BLOCK_IE | HSI_TOUT_IE), base, HSI_GDD_CCIR_REG(lch)); + + if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) { + fifo = hsi_fifo_get_id(hsi_ctrl, channel, port); + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, "No valid FIFO id for DMA " + "transfer to FIFO.\n"); + return -EFAULT; + } + /* HSI CDSA register takes a FIFO ID when copying to FIFO */ + hsi_outl(fifo, base, HSI_GDD_CDSA_REG(lch)); + } else { + dest_addr = hsi_ctrl->phy_base + HSI_HST_BUFFER_CH_REG(port, + channel); + /* SSI CDSA register always takes a 32-bit address */ + hsi_outl(dest_addr, base, HSI_GDD_CDSA_REG(lch)); + } + + /* HSI CSSA register takes a 32-bit address when copying from memory */ + /* SSI CSSA register always takes a 32-bit address */ + hsi_outl(src_addr, base, HSI_GDD_CSSA_REG(lch)); + hsi_outw(size, base, HSI_GDD_CEN_REG(lch)); + + /* TODO : Need to clean interrupt status here to avoid spurious int */ + + hsi_outl_or(HSI_GDD_LCH(lch), base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + hsi_outw_or(HSI_CCR_ENABLE, base, HSI_GDD_CCR_REG(lch)); + + return 0; +} + +/** + * hsi_driver_read_dma - Program GDD [DMA] to write data to memory from + * the hsi channel buffer. + * @hsi_channel - pointer to the hsi_channel to read data from. + * @data - 32-bit word pointer where to store the incoming data. + * @size - Number of 32bit words to be transfered to the buffer. + * + * hsi_controller lock must be held before calling this function. + * + * Return 0 on success and < 0 on error. + */ +int hsi_driver_read_dma(struct hsi_channel *hsi_channel, u32 * data, + unsigned int count) +{ + struct hsi_dev *hsi_ctrl = hsi_channel->hsi_port->hsi_controller; + void __iomem *base = hsi_ctrl->base; + unsigned int port = hsi_channel->hsi_port->port_number; + unsigned int channel = hsi_channel->channel_number; + unsigned int sync; + int lch; + dma_addr_t src_addr; + dma_addr_t dest_addr; + u16 tmp; + int fifo; + + lch = hsi_get_free_lch(hsi_ctrl); + if (lch < 0) { + dev_err(hsi_ctrl->dev, "No free DMA channels.\n"); + return -EBUSY; /* No free GDD logical channels. */ + } else { + dev_dbg(hsi_ctrl->dev, "Allocated DMA channel %d for read on" + " HSI channel %d.\n", lch, + hsi_channel->channel_number); + } + + /* When DMA is used for Rx, disable the Rx Interrupt. + * (else DATAAVAILLABLE event would get triggered on first + * received data word) + * (Rx interrupt might be active for polling feature) + */ +#if 0 + if (omap_readl(0x4A05A810)) { + dev_err(hsi_ctrl->dev, + "READ INTERRUPT IS PENDING DMA() but still disabling %0x\n", + omap_readl(0x4A05A810)); + } +#endif + hsi_driver_disable_read_interrupt(hsi_channel); + + /* + * NOTE: Gettting a free gdd logical channel and + * reserve it must be done atomicaly. + */ + hsi_channel->read_data.lch = lch; + + /* Sync is required for SSI but not for HSI */ + sync = hsi_sync_table[HSI_SYNC_READ][port - 1][channel]; + + dest_addr = dma_map_single(hsi_ctrl->dev, data, count * 4, + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(hsi_ctrl->dev, dest_addr))) { + dev_err(hsi_ctrl->dev, "Failed to create DMA read mapping.\n"); + return -ENOMEM; + } + + tmp = HSI_DST_SINGLE_ACCESS0 | + HSI_DST_MEMORY_PORT | + HSI_SRC_SINGLE_ACCESS0 | + HSI_SRC_PERIPHERAL_PORT | HSI_DATA_TYPE_S32; + hsi_outw(tmp, base, HSI_GDD_CSDP_REG(lch)); + + tmp = HSI_DST_AMODE_POSTINC | HSI_SRC_AMODE_CONST | sync; + hsi_outw(tmp, base, HSI_GDD_CCR_REG(lch)); + + hsi_outw((HSI_BLOCK_IE | HSI_TOUT_IE), base, HSI_GDD_CCIR_REG(lch)); + + if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) { + fifo = hsi_fifo_get_id(hsi_ctrl, channel, port); + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, "No valid FIFO id for DMA " + "transfer from FIFO.\n"); + return -EFAULT; + } + /* HSI CSSA register takes a FIFO ID when copying from FIFO */ + hsi_outl(fifo, base, HSI_GDD_CSSA_REG(lch)); + } else{ + src_addr = hsi_ctrl->phy_base + HSI_HSR_BUFFER_CH_REG(port, + channel); + /* SSI CSSA register always takes a 32-bit address */ + hsi_outl(src_addr, base, HSI_GDD_CSSA_REG(lch)); + } + + /* HSI CDSA register takes a 32-bit address when copying to memory */ + /* SSI CDSA register always takes a 32-bit address */ + hsi_outl(dest_addr, base, HSI_GDD_CDSA_REG(lch)); + hsi_outw(count, base, HSI_GDD_CEN_REG(lch)); + + /* TODO : Need to clean interrupt status here to avoid spurious int */ + + hsi_outl_or(HSI_GDD_LCH(lch), base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + hsi_outw_or(HSI_CCR_ENABLE, base, HSI_GDD_CCR_REG(lch)); + + return 0; +} + +/** + * hsi_driver_cancel_write_dma - Cancel an ongoing GDD [DMA] write for the + * specified hsi channel. + * @hsi_ch - pointer to the hsi_channel to cancel DMA write. + * + * hsi_controller lock must be held before calling this function. + * + * Return: -ENXIO : No DMA channel found for specified HSI channel + * -ECANCELED : DMA cancel success, data not transfered to TX FIFO + * 0 : DMA transfer is already over, data already transfered to TX FIFO + * + * Note: whatever returned value, write callback will not be called after + * write cancel. + */ +int hsi_driver_cancel_write_dma(struct hsi_channel *hsi_ch) +{ + int lch = hsi_ch->write_data.lch; + unsigned int port = hsi_ch->hsi_port->port_number; + unsigned int channel = hsi_ch->channel_number; + struct hsi_dev *hsi_ctrl = hsi_ch->hsi_port->hsi_controller; + u16 ccr, gdd_csr; + long buff_offset; + u32 status_reg; + dma_addr_t dma_h; + size_t size; + + dev_err(&hsi_ch->dev->device, "hsi_driver_cancel_write_dma( " + "channel %d\n", hsi_ch->channel_number); + + if (lch < 0) { + dev_err(&hsi_ch->dev->device, "No DMA channel found for HSI " + "channel %d\n", hsi_ch->channel_number); + return -ENXIO; + } + ccr = hsi_inw(hsi_ctrl->base, HSI_GDD_CCR_REG(lch)); + if (!(ccr & HSI_CCR_ENABLE)) { + dev_dbg(&hsi_ch->dev->device, "Write cancel on not " + "enabled logical channel %d CCR REG 0x%04X\n", + lch, ccr); + } + status_reg = hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG); + status_reg &= hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + hsi_outw_and(~HSI_CCR_ENABLE, hsi_ctrl->base, HSI_GDD_CCR_REG(lch)); + + /* Clear CSR register by reading it, as it is cleared automaticaly */ + /* by HW after SW read. */ + gdd_csr = hsi_inw(hsi_ctrl->base, HSI_GDD_CSR_REG(lch)); + hsi_outl_and(~HSI_GDD_LCH(lch), hsi_ctrl->base, + HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + hsi_outl(HSI_GDD_LCH(lch), hsi_ctrl->base, + HSI_SYS_GDD_MPU_IRQ_STATUS_REG); + + /* Unmap DMA region */ + dma_h = hsi_inl(hsi_ctrl->base, HSI_GDD_CSSA_REG(lch)); + size = hsi_inw(hsi_ctrl->base, HSI_GDD_CEN_REG(lch)) * 4; + dma_unmap_single(hsi_ctrl->dev, dma_h, size, DMA_TO_DEVICE); + + buff_offset = hsi_hst_bufstate_f_reg(hsi_ctrl, port, channel); + if (buff_offset >= 0) + hsi_outl_and(~HSI_BUFSTATE_CHANNEL(channel), hsi_ctrl->base, + buff_offset); + + hsi_reset_ch_write(hsi_ch); + return status_reg & HSI_GDD_LCH(lch) ? 0 : -ECANCELED; +} + +/** + * hsi_driver_cancel_read_dma - Cancel an ongoing GDD [DMA] read for the + * specified hsi channel. + * @hsi_ch - pointer to the hsi_channel to cancel DMA read. + * + * hsi_controller lock must be held before calling this function. + * + * Return: -ENXIO : No DMA channel found for specified HSI channel + * -ECANCELED : DMA cancel success, data not available at expected + * address. + * 0 : DMA transfer is already over, data already available at + * expected address. + * + * Note: whatever returned value, read callback will not be called after cancel. + */ +int hsi_driver_cancel_read_dma(struct hsi_channel *hsi_ch) +{ + int lch = hsi_ch->read_data.lch; + struct hsi_dev *hsi_ctrl = hsi_ch->hsi_port->hsi_controller; + u16 ccr, gdd_csr; + u32 status_reg; + dma_addr_t dma_h; + size_t size; + + dev_err(&hsi_ch->dev->device, "hsi_driver_cancel_read_dma " + "channel %d\n", hsi_ch->channel_number); + + /* Re-enable interrupts for polling if needed */ + if (hsi_ch->flags & HSI_CH_RX_POLL) + hsi_driver_enable_read_interrupt(hsi_ch, NULL); + + if (lch < 0) { + dev_err(&hsi_ch->dev->device, "No DMA channel found for HSI " + "channel %d\n", hsi_ch->channel_number); + return -ENXIO; + } + + ccr = hsi_inw(hsi_ctrl->base, HSI_GDD_CCR_REG(lch)); + if (!(ccr & HSI_CCR_ENABLE)) { + dev_dbg(&hsi_ch->dev->device, "Read cancel on not " + "enabled logical channel %d CCR REG 0x%04X\n", + lch, ccr); + } + + status_reg = hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG); + status_reg &= hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + hsi_outw_and(~HSI_CCR_ENABLE, hsi_ctrl->base, HSI_GDD_CCR_REG(lch)); + + /* Clear CSR register by reading it, as it is cleared automaticaly */ + /* by HW after SW read */ + gdd_csr = hsi_inw(hsi_ctrl->base, HSI_GDD_CSR_REG(lch)); + hsi_outl_and(~HSI_GDD_LCH(lch), hsi_ctrl->base, + HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + hsi_outl(HSI_GDD_LCH(lch), hsi_ctrl->base, + HSI_SYS_GDD_MPU_IRQ_STATUS_REG); + + /* Unmap DMA region - Access to the buffer is now safe */ + dma_h = hsi_inl(hsi_ctrl->base, HSI_GDD_CDSA_REG(lch)); + size = hsi_inw(hsi_ctrl->base, HSI_GDD_CEN_REG(lch)) * 4; + dma_unmap_single(hsi_ctrl->dev, dma_h, size, DMA_FROM_DEVICE); + + hsi_reset_ch_read(hsi_ch); + return status_reg & HSI_GDD_LCH(lch) ? 0 : -ECANCELED; +} + +/** + * hsi_get_info_from_gdd_lch - Retrieve channels information from DMA channel + * @hsi_ctrl - HSI device control structure + * @lch - DMA logical channel + * @port - HSI port + * @channel - HSI channel + * @is_read_path - channel is used for reading + * + * Updates the port, channel and is_read_path parameters depending on the + * lch DMA channel status. + * + * Return 0 on success and < 0 on error. + */ +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 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) { + *is_read_path = 1; + *port = i_ports + 1; + *channel = i_chans; + err = 0; + goto get_info_bk; + } else if (hsi_ctrl->hsi_port[i_ports]. + hsi_channel[i_chans].write_data.lch == lch) { + *is_read_path = 0; + *port = i_ports + 1; + *channel = i_chans; + err = 0; + goto get_info_bk; + } +get_info_bk: + return err; +} + +static void do_hsi_gdd_lch(struct hsi_dev *hsi_ctrl, unsigned int gdd_lch) +{ + void __iomem *base = hsi_ctrl->base; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + struct hsi_channel *ch; + unsigned int port; + unsigned int channel; + unsigned int is_read_path; + u32 gdd_csr; + dma_addr_t dma_h; + size_t size; + int fifo, fifo_words_avail; + + if (hsi_get_info_from_gdd_lch(hsi_ctrl, gdd_lch, &port, &channel, + &is_read_path) < 0) { + dev_err(hsi_ctrl->dev, "Unable to match the DMA channel %d with" + " an HSI channel\n", gdd_lch); + return; + } else { + dev_dbg(hsi_ctrl->dev, "DMA event on gdd_lch=%d => port=%d, " + "channel=%d, read=%d\n", gdd_lch, port, channel, + is_read_path); + } + + hsi_outl_and(~HSI_GDD_LCH(gdd_lch), base, + HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + /* Warning : CSR register is cleared automaticaly by HW after SW read */ + gdd_csr = hsi_inw(base, HSI_GDD_CSR_REG(gdd_lch)); + + if (!(gdd_csr & HSI_CSR_TOUT)) { + if (is_read_path) { /* Read path */ + dma_h = hsi_inl(base, HSI_GDD_CDSA_REG(gdd_lch)); + size = hsi_inw(base, HSI_GDD_CEN_REG(gdd_lch)) * 4; + dma_sync_single_for_cpu(hsi_ctrl->dev, dma_h, size, + DMA_FROM_DEVICE); + dma_unmap_single(hsi_ctrl->dev, dma_h, size, + DMA_FROM_DEVICE); + ch = hsi_ctrl_get_ch(hsi_ctrl, port, channel); + hsi_reset_ch_read(ch); + + dev_dbg(hsi_ctrl->dev, "Calling ch %d read callback " + "(size %d).\n", channel, size/4); + spin_unlock(&hsi_ctrl->lock); + ch->read_done(ch->dev, size / 4); + spin_lock(&hsi_ctrl->lock); + + /* Check if FIFO is correctly emptied */ + if (hsi_driver_device_is_hsi(pdev)) { + fifo = hsi_fifo_get_id(hsi_ctrl, channel, port); + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, "No valid FIFO " + "id found for channel %d.\n", + channel); + return; + } + fifo_words_avail = + hsi_get_rx_fifo_occupancy(hsi_ctrl, + fifo); + if (fifo_words_avail) + dev_dbg(hsi_ctrl->dev, + "WARNING: FIFO %d not empty " + "after DMA copy, remaining " + "%d/%d frames\n", + fifo, fifo_words_avail, + HSI_HSR_FIFO_SIZE); + } + /* Re-enable interrupts for polling if needed */ + if (ch->flags & HSI_CH_RX_POLL) + hsi_driver_enable_read_interrupt(ch, NULL); + } else { /* Write path */ + dma_h = hsi_inl(base, HSI_GDD_CSSA_REG(gdd_lch)); + size = hsi_inw(base, HSI_GDD_CEN_REG(gdd_lch)) * 4; + dma_unmap_single(hsi_ctrl->dev, dma_h, size, + DMA_TO_DEVICE); + ch = hsi_ctrl_get_ch(hsi_ctrl, port, channel); + hsi_reset_ch_write(ch); + + dev_dbg(hsi_ctrl->dev, "Calling ch %d write callback " + "(size %d).\n", channel, size/4); + spin_unlock(&hsi_ctrl->lock); + ch->write_done(ch->dev, size / 4); + spin_lock(&hsi_ctrl->lock); + } + } else { + dev_err(hsi_ctrl->dev, "Time-out overflow Error on GDD transfer" + " on gdd channel %d\n", gdd_lch); + spin_unlock(&hsi_ctrl->lock); + /* TODO : need to perform a DMA soft reset */ + hsi_port_event_handler(&hsi_ctrl->hsi_port[port - 1], + HSI_EVENT_ERROR, NULL); + spin_lock(&hsi_ctrl->lock); + } +} + +static u32 hsi_process_dma_event(struct hsi_dev *hsi_ctrl) +{ + void __iomem *base = hsi_ctrl->base; + unsigned int gdd_lch = 0; + u32 status_reg = 0; + u32 lch_served = 0; + unsigned int gdd_max_count = hsi_ctrl->gdd_chan_count; + + status_reg = hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG); + status_reg &= hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + if (!status_reg) { + dev_dbg(hsi_ctrl->dev, "DMA : no event, exit.\n"); + return 0; + } + + for (gdd_lch = 0; gdd_lch < gdd_max_count; gdd_lch++) { + if (status_reg & HSI_GDD_LCH(gdd_lch)) { + do_hsi_gdd_lch(hsi_ctrl, gdd_lch); + lch_served |= HSI_GDD_LCH(gdd_lch); + } + } + + /* Acknowledge interrupt for DMA channel */ + hsi_outl(lch_served, base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG); + + + return status_reg; +} + +static void do_hsi_gdd_tasklet(unsigned long device) +{ + struct hsi_dev *hsi_ctrl = (struct hsi_dev *)device; + + dev_dbg(hsi_ctrl->dev, "DMA Tasklet : clock_enabled=%d\n", + hsi_ctrl->clock_enabled); + + spin_lock(&hsi_ctrl->lock); + hsi_clocks_enable(hsi_ctrl->dev, __func__); + hsi_ctrl->in_dma_tasklet = true; + + hsi_process_dma_event(hsi_ctrl); + + hsi_ctrl->in_dma_tasklet = false; + hsi_clocks_disable(hsi_ctrl->dev, __func__); + spin_unlock(&hsi_ctrl->lock); + + enable_irq(hsi_ctrl->gdd_irq); +} + +static irqreturn_t hsi_gdd_mpu_handler(int irq, void *p) +{ + struct hsi_dev *hsi_ctrl = p; + + tasklet_hi_schedule(&hsi_ctrl->hsi_gdd_tasklet); + + /* Disable interrupt until Bottom Half has cleared the IRQ status */ + /* register */ + disable_irq_nosync(hsi_ctrl->gdd_irq); + + return IRQ_HANDLED; +} + +int __init hsi_gdd_init(struct hsi_dev *hsi_ctrl, const char *irq_name) +{ + tasklet_init(&hsi_ctrl->hsi_gdd_tasklet, do_hsi_gdd_tasklet, + (unsigned long)hsi_ctrl); + + dev_info(hsi_ctrl->dev, "Registering IRQ %s (%d)\n", + irq_name, hsi_ctrl->gdd_irq); + + if (request_irq(hsi_ctrl->gdd_irq, hsi_gdd_mpu_handler, + IRQF_NO_SUSPEND | IRQF_TRIGGER_HIGH, + irq_name, hsi_ctrl) < 0) { + dev_err(hsi_ctrl->dev, "FAILED to request GDD IRQ %d\n", + hsi_ctrl->gdd_irq); + return -EBUSY; + } + + return 0; +} + +void hsi_gdd_exit(struct hsi_dev *hsi_ctrl) +{ + tasklet_kill(&hsi_ctrl->hsi_gdd_tasklet); + free_irq(hsi_ctrl->gdd_irq, hsi_ctrl); +} diff --git a/drivers/omap_hsi/hsi_driver_fifo.c b/drivers/omap_hsi/hsi_driver_fifo.c new file mode 100644 index 0000000..aa33a1a --- /dev/null +++ b/drivers/omap_hsi/hsi_driver_fifo.c @@ -0,0 +1,325 @@ +/* + * hsi_driver_fifo.c + * + * Implements HSI module fifo management. + * + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/list.h> +#include "hsi_driver.h" + +/** + * hsi_fifo_get_id - Get fifo index corresponding to (port, channel) + * @hsi_ctrl - HSI controler data + * @channel - channel used + * @port - HSI port used + * + * Returns the fifo index associated to the provided (port, channel). + * Notes: 1) The fifo <=> (port, channel) correspondance depends on the selected + * SW strategy for channels mapping (fifo management). + * 2) the mapping is identical for Read and Write path. + * This exclusively applies to HSI devices. + */ +int hsi_fifo_get_id(struct hsi_dev *hsi_ctrl, unsigned int channel, + unsigned int port) +{ + int fifo_index = 0; + int err = 0; + + if (unlikely((channel >= HSI_CHANNELS_MAX) || (port < 1) || + (port > 2))) { + err = -EINVAL; + goto fifo_id_bk; + } + + if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_ALL_PORT1) { + if (unlikely(port != 1)) { + err = -EINVAL; + goto fifo_id_bk; + } else { + fifo_index = channel; + } + } else if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_SSI) { + if (unlikely(channel >= 8)) { + err = -EINVAL; + goto fifo_id_bk; + } else { + fifo_index = channel + 8 * (port - 1); + } + } else { + err = -EPERM; + goto fifo_id_bk; + } + +fifo_id_bk: + if (unlikely(err < 0)) { + fifo_index = err; + dev_err(hsi_ctrl->dev, "Cannot map a FIFO to the requested " + "params: channel:%d, port:%d; ERR=%d\n", channel, port, + err); + } + + return fifo_index; +} + +/** + * hsi_fifo_get_chan - Get (port, channel) from a fifo index + * @hsi_ctrl - HSI controler data + * @fifo - HSI fifo used (0..HSI_HST_FIFO_COUNT) + * @channel - related channel if any (0..) + * @port - related port if any (1..2) + * + * Returns 0 in case of success, and errocode (< 0) else + * Notes: 1) The fifo <=> (port, channel) correspondance depends on the selected + * SW strategy for channels mapping (fifo management). + * 2) the mapping is identical for Read and Write path. + * This exclusively applies to HSI devices. + */ +int hsi_fifo_get_chan(struct hsi_dev *hsi_ctrl, unsigned int fifo, + unsigned int *channel, unsigned int *port) +{ + int err = 0; + + if (unlikely(fifo >= HSI_HST_FIFO_COUNT)) { + err = -EINVAL; + goto fifo_id_bk; + } + + 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_SSI) { + if (fifo < 8) { + *channel = fifo; + *port = 1; + } else { + *channel = fifo - 8; + *port = 2; + } + } else { + err = -EPERM; + goto fifo_id_bk; + } + +fifo_id_bk: + if (unlikely(err < 0)) + dev_err(hsi_ctrl->dev, "Cannot map a channel / port to the " + "requested params: fifo:%d; ERR=%d\n", fifo, err); + + return err; +} + +/** + * hsi_fifo_mapping - Configures the HSI FIFO mapping registers. + * @hsi_ctrl - HSI controler data + * @mtype - mapping strategy + * + * Returns 0 in case of success, and errocode (< 0) else + * Configures the HSI FIFO mapping registers. Several mapping strategies are + * proposed. + * Note: The mapping is identical for Read and Write path. + * This exclusively applies to HSI devices. + */ +int hsi_fifo_mapping(struct hsi_dev *hsi_ctrl, unsigned int mtype) +{ + int err = 0; + void __iomem *base = hsi_ctrl->base; + int i; + unsigned int channel, port; + + if (mtype == HSI_FIFO_MAPPING_ALL_PORT1) { + 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) | + 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), + 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; + } else if (mtype == HSI_FIFO_MAPPING_SSI) { + channel = 0; + port = 0; + for (i = 0; i < HSI_HST_FIFO_COUNT; i++) { + hsi_outl(HSI_MAPPING_ENABLE | + (channel << HSI_MAPPING_CH_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) | + (port << HSI_MAPPING_PORT_NUMBER_OFFSET), + base, HSI_HSR_MAPPING_FIFO_REG(i)); + channel++; + if (channel == 8) { + channel = 0; + port = 1; + } + } + + 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; + } + + return err; +} + +/** + * hsi_hst_bufstate_f_reg - Return the proper HSI_HST_BUFSTATE register offset + * @hsi_ctrl - HSI controler data + * @port - HSI port used + * @channel - channel used + * + * Returns the HSI_HST_BUFSTATE register offset + * Note: indexing of BUFSTATE registers is different on SSI and HSI: + * On SSI: it is linked to the ports + * On HSI: it is linked to the FIFOs (and depend on the SW strategy) + */ +long hsi_hst_bufstate_f_reg(struct hsi_dev *hsi_ctrl, + unsigned int port, unsigned int channel) +{ + int fifo; + if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) { + fifo = hsi_fifo_get_id(hsi_ctrl, channel, port); + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, + "hsi_hst_bufstate_f_reg ERROR : %d\n", fifo); + return fifo; + } else + return HSI_HST_BUFSTATE_FIFO_REG(fifo); + } else { + return HSI_HST_BUFSTATE_REG(port); + } +} + +/** + * hsi_hsr_bufstate_f_reg - Return the proper HSI_HSR_BUFSTATE register offset + * @hsi_ctrl - HSI controler data + * @port - HSI port used + * @channel - channel used + * + * Returns the HSI_HSR_BUFSTATE register offset + * Note: indexing of BUFSTATE registers is different on SSI and HSI: + * On SSI: it is linked to the ports + * On HSI: it is linked to the FIFOs (and depend on the SW strategy) + */ +long hsi_hsr_bufstate_f_reg(struct hsi_dev *hsi_ctrl, + unsigned int port, unsigned int channel) +{ + int fifo; + if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) { + fifo = hsi_fifo_get_id(hsi_ctrl, channel, port); + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, + "hsi_hsr_bufstate_f_reg ERROR : %d\n", fifo); + return fifo; + } else + return HSI_HSR_BUFSTATE_FIFO_REG(fifo); + } else { + return HSI_HSR_BUFSTATE_REG(port); + } +} + +/** + * hsi_hst_buffer_f_reg - Return the proper HSI_HST_BUFFER register offset + * @hsi_ctrl - HSI controler data + * @port - HSI port used + * @channel - channel used + * + * Returns the HSI_HST_BUFFER register offset + * Note: indexing of BUFFER registers is different on SSI and HSI: + * On SSI: it is linked to the ports + * On HSI: it is linked to the FIFOs (and depend on the SW strategy) + */ +long hsi_hst_buffer_reg(struct hsi_dev *hsi_ctrl, + unsigned int port, unsigned int channel) +{ + int fifo; + if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) { + fifo = hsi_fifo_get_id(hsi_ctrl, channel, port); + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, + "hsi_hst_bufstate_f_reg ERROR : %d\n", fifo); + return fifo; + } else + return HSI_HST_BUFFER_FIFO_REG(fifo); + } else { + return HSI_HST_BUFFER_CH_REG(port, channel); + } +} + +/** + * hsi_hsr_buffer_f_reg - Return the proper HSI_HSR_BUFFER register offset + * @hsi_ctrl - HSI controler data + * @port - HSI port used + * @channel - channel used + * + * Returns the HSI_HSR_BUFFER register offset + * Note: indexing of BUFFER registers is different on SSI and HSI: + * On SSI: it is linked to the ports + * On HSI: it is linked to the FIFOs (and depend on the SW strategy) + */ +long hsi_hsr_buffer_reg(struct hsi_dev *hsi_ctrl, + unsigned int port, unsigned int channel) +{ + int fifo; + if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) { + fifo = hsi_fifo_get_id(hsi_ctrl, channel, port); + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, + "hsi_hsr_bufstate_f_reg ERROR : %d\n", fifo); + return fifo; + } else + return HSI_HSR_BUFFER_FIFO_REG(fifo); + } else { + return HSI_HSR_BUFFER_CH_REG(port, channel); + } +} + +/** + * hsi_get_rx_fifo_occupancy - Return the size of data remaining + * in the given FIFO + * @hsi_ctrl - HSI controler data + * @fifo - FIFO to look at + * + * Returns the number of frames (32bits) remaining in the FIFO + */ +u8 hsi_get_rx_fifo_occupancy(struct hsi_dev *hsi_ctrl, u8 fifo) +{ + void __iomem *base = hsi_ctrl->base; + int hsr_mapping, mapping_words; + + 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_gpio.c b/drivers/omap_hsi/hsi_driver_gpio.c new file mode 100644 index 0000000..4c8810b --- /dev/null +++ b/drivers/omap_hsi/hsi_driver_gpio.c @@ -0,0 +1,75 @@ +/* + * hsi_driver_gpio.c + * + * Implements HSI GPIO related functionality. (i.e: wake lines management) + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/gpio.h> +#include "hsi_driver.h" + +static void do_hsi_cawake_tasklet(unsigned long hsi_p) +{ + struct hsi_port *port = (struct hsi_port *)hsi_p; + struct hsi_dev *hsi_ctrl = port->hsi_controller; + + spin_lock(&hsi_ctrl->lock); + hsi_clocks_enable(hsi_ctrl->dev, __func__); + port->in_cawake_tasklet = true; + + port->cawake_status = hsi_get_cawake(port); + hsi_do_cawake_process(port); + + port->in_cawake_tasklet = false; + hsi_clocks_disable(hsi_ctrl->dev, __func__); + spin_unlock(&hsi_ctrl->lock); +} + +static irqreturn_t hsi_cawake_isr(int irq, void *hsi_p) +{ + struct hsi_port *port = hsi_p; + + tasklet_hi_schedule(&port->cawake_tasklet); + + return IRQ_HANDLED; +} + +int __init hsi_cawake_init(struct hsi_port *port, const char *irq_name) +{ + tasklet_init(&port->cawake_tasklet, do_hsi_cawake_tasklet, + (unsigned long)port); + + if (request_irq(port->cawake_gpio_irq, hsi_cawake_isr, + IRQF_DISABLED | IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, irq_name, port) < 0) { + dev_err(port->hsi_controller->dev, + "FAILED to request %s GPIO IRQ %d on port %d\n", + irq_name, port->cawake_gpio_irq, port->port_number); + return -EBUSY; + } + + return 0; +} + +void hsi_cawake_exit(struct hsi_port *port) +{ + if (port->cawake_gpio < 0) + return; /* Nothing to do (case SSI with GPIO or */ + /* HSI with IO ring wakeup */ + + tasklet_kill(&port->cawake_tasklet); + free_irq(port->cawake_gpio_irq, port); +} diff --git a/drivers/omap_hsi/hsi_driver_if.c b/drivers/omap_hsi/hsi_driver_if.c new file mode 100644 index 0000000..19012e5 --- /dev/null +++ b/drivers/omap_hsi/hsi_driver_if.c @@ -0,0 +1,965 @@ +/* + * hsi_driver_if.c + * + * Implements HSI hardware driver interfaces for the upper layers. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "hsi_driver.h" + +#define NOT_SET (-1) + +/* Manage HSR divisor update + * A special divisor value allows switching to auto-divisor mode in Rx + * (but with error counters deactivated). This function implements the + * the transitions to/from this mode. + */ +int hsi_set_rx_divisor(struct hsi_port *sport, struct hsr_ctx *cfg) +{ + struct hsi_dev *hsi_ctrl = sport->hsi_controller; + void __iomem *base = hsi_ctrl->base; + int port = sport->port_number; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + + if (cfg->divisor == NOT_SET) + return 0; + + if (hsi_driver_device_is_hsi(pdev)) { + if (cfg->divisor == HSI_HSR_DIVISOR_AUTO && + sport->counters_on) { + /* auto mode: deactivate counters + set divisor = 0 */ + sport->reg_counters = hsi_inl(base, HSI_HSR_COUNTERS_REG + (port)); + sport->counters_on = 0; + hsi_outl(0, base, HSI_HSR_COUNTERS_REG(port)); + hsi_outl(0, base, HSI_HSR_DIVISOR_REG(port)); + dev_dbg(hsi_ctrl->dev, "Switched to HSR auto mode\n"); + } 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, + HSI_HSR_COUNTERS_REG(port)); + hsi_outl(cfg->divisor, base, HSI_HSR_DIVISOR_REG(port)); + dev_dbg(hsi_ctrl->dev, "Left HSR auto mode. " + "Counters=0x%08x, Divisor=0x%08x\n", + cfg->counters, cfg->divisor); + } + } else { + if (cfg->divisor == HSI_HSR_DIVISOR_AUTO && + sport->counters_on) { + /* auto mode: deactivate timeout */ + sport->reg_counters = hsi_inl(base, + SSI_TIMEOUT_REG(port)); + sport->counters_on = 0; + hsi_outl(0, base, SSI_TIMEOUT_REG(port)); + dev_dbg(hsi_ctrl->dev, "Deactivated SSR timeout\n"); + } else if (cfg->divisor == HSI_SSR_DIVISOR_USE_TIMEOUT) { + /* Leave auto mode: use new counters values */ + sport->reg_counters = cfg->counters; + sport->counters_on = 1; + hsi_outl(cfg->counters, base, SSI_TIMEOUT_REG(port)); + dev_dbg(hsi_ctrl->dev, "Left SSR auto mode. " + "Timeout=0x%08x\n", cfg->counters); + } + } + + return 0; +} + +int hsi_set_rx(struct hsi_port *sport, struct hsr_ctx *cfg) +{ + struct hsi_dev *hsi_ctrl = sport->hsi_controller; + void __iomem *base = hsi_ctrl->base; + int port = sport->port_number; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + + if (((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_STREAM) && + ((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_FRAME) && + ((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_SLEEP) && + (cfg->mode != NOT_SET)) + return -EINVAL; + + if (hsi_driver_device_is_hsi(pdev)) { + if (((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_SYNCHRONIZED) + && ((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_PIPELINED) + && (cfg->flow != NOT_SET)) + return -EINVAL; + /* HSI only supports payload size of 32bits */ + if ((cfg->frame_size != HSI_FRAMESIZE_MAX) && + (cfg->frame_size != NOT_SET)) + return -EINVAL; + } else { + if (((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_SYNCHRONIZED) + && (cfg->flow != NOT_SET)) + return -EINVAL; + /* HSI only supports payload size of 32bits */ + if ((cfg->frame_size != HSI_FRAMESIZE_MAX) && + (cfg->frame_size != NOT_SET)) + return -EINVAL; + } + + if ((cfg->channels == 0) || + ((cfg->channels > sport->max_ch) && (cfg->channels != NOT_SET))) + return -EINVAL; + + if (hsi_driver_device_is_hsi(pdev)) { + if ((cfg->divisor > HSI_MAX_RX_DIVISOR) && + (cfg->divisor != HSI_HSR_DIVISOR_AUTO) && + (cfg->divisor != NOT_SET)) + return -EINVAL; + } + + if ((cfg->mode != NOT_SET) && (cfg->flow != NOT_SET)) + hsi_outl(cfg->mode | ((cfg->flow & HSI_FLOW_VAL_MASK) + << HSI_FLOW_OFFSET), base, + HSI_HSR_MODE_REG(port)); + + if (cfg->frame_size != NOT_SET) + hsi_outl(cfg->frame_size, base, HSI_HSR_FRAMESIZE_REG(port)); + + if (cfg->channels != NOT_SET) { + if ((cfg->channels & (-cfg->channels)) ^ cfg->channels) + return -EINVAL; + else + hsi_outl(cfg->channels, base, + HSI_HSR_CHANNELS_REG(port)); + } + + return hsi_set_rx_divisor(sport, cfg); +} + +void hsi_get_rx(struct hsi_port *sport, struct hsr_ctx *cfg) +{ + struct hsi_dev *hsi_ctrl = sport->hsi_controller; + void __iomem *base = hsi_ctrl->base; + int port = sport->port_number; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + + cfg->mode = hsi_inl(base, HSI_HSR_MODE_REG(port)) & HSI_MODE_VAL_MASK; + cfg->flow = (hsi_inl(base, HSI_HSR_MODE_REG(port)) & HSI_FLOW_VAL_MASK) + >> HSI_FLOW_OFFSET; + cfg->frame_size = hsi_inl(base, HSI_HSR_FRAMESIZE_REG(port)); + cfg->channels = hsi_inl(base, HSI_HSR_CHANNELS_REG(port)); + if (hsi_driver_device_is_hsi(pdev)) { + cfg->divisor = hsi_inl(base, HSI_HSR_DIVISOR_REG(port)); + cfg->counters = hsi_inl(base, HSI_HSR_COUNTERS_REG(port)); + } else { + cfg->counters = hsi_inl(base, SSI_TIMEOUT_REG(port)); + } +} + +int hsi_set_tx(struct hsi_port *sport, struct hst_ctx *cfg) +{ + struct hsi_dev *hsi_ctrl = sport->hsi_controller; + void __iomem *base = hsi_ctrl->base; + int port = sport->port_number; + struct platform_device *pdev = to_platform_device(hsi_ctrl->dev); + unsigned int max_divisor = hsi_driver_device_is_hsi(pdev) ? + HSI_MAX_TX_DIVISOR : HSI_SSI_MAX_TX_DIVISOR; + + if (((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_STREAM) && + ((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_FRAME) && + (cfg->mode != NOT_SET)) + return -EINVAL; + + if (hsi_driver_device_is_hsi(pdev)) { + if (((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_SYNCHRONIZED) + && ((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_PIPELINED) + && (cfg->flow != NOT_SET)) + return -EINVAL; + /* HSI only supports payload size of 32bits */ + if ((cfg->frame_size != HSI_FRAMESIZE_MAX) && + (cfg->frame_size != NOT_SET)) + return -EINVAL; + } else { + if (((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_SYNCHRONIZED) + && (cfg->flow != NOT_SET)) + return -EINVAL; + + if ((cfg->frame_size > HSI_FRAMESIZE_MAX) && + (cfg->frame_size != NOT_SET)) + return -EINVAL; + } + + if ((cfg->channels == 0) || + ((cfg->channels > sport->max_ch) && (cfg->channels != NOT_SET))) + return -EINVAL; + + if ((cfg->divisor > max_divisor) && (cfg->divisor != NOT_SET)) + return -EINVAL; + + if ((cfg->arb_mode != HSI_ARBMODE_ROUNDROBIN) && + (cfg->arb_mode != HSI_ARBMODE_PRIORITY) && (cfg->mode != NOT_SET)) + return -EINVAL; + + if ((cfg->mode != NOT_SET) && (cfg->flow != NOT_SET)) + hsi_outl(cfg->mode | ((cfg->flow & HSI_FLOW_VAL_MASK) << + HSI_FLOW_OFFSET) | + HSI_HST_MODE_WAKE_CTRL_SW, base, + HSI_HST_MODE_REG(port)); + + if (cfg->frame_size != NOT_SET) + hsi_outl(cfg->frame_size, base, HSI_HST_FRAMESIZE_REG(port)); + + if (cfg->channels != NOT_SET) { + if ((cfg->channels & (-cfg->channels)) ^ cfg->channels) + return -EINVAL; + else + hsi_outl(cfg->channels, base, + HSI_HST_CHANNELS_REG(port)); + } + + if (cfg->divisor != NOT_SET) + hsi_outl(cfg->divisor, base, HSI_HST_DIVISOR_REG(port)); + + if (cfg->arb_mode != NOT_SET) + hsi_outl(cfg->arb_mode, base, HSI_HST_ARBMODE_REG(port)); + + return 0; +} + +void hsi_get_tx(struct hsi_port *sport, struct hst_ctx *cfg) +{ + struct hsi_dev *hsi_ctrl = sport->hsi_controller; + void __iomem *base = hsi_ctrl->base; + int port = sport->port_number; + + cfg->mode = hsi_inl(base, HSI_HST_MODE_REG(port)) & HSI_MODE_VAL_MASK; + cfg->flow = (hsi_inl(base, HSI_HST_MODE_REG(port)) & HSI_FLOW_VAL_MASK) + >> HSI_FLOW_OFFSET; + cfg->frame_size = hsi_inl(base, HSI_HST_FRAMESIZE_REG(port)); + cfg->channels = hsi_inl(base, HSI_HST_CHANNELS_REG(port)); + cfg->divisor = hsi_inl(base, HSI_HST_DIVISOR_REG(port)); + cfg->arb_mode = hsi_inl(base, HSI_HST_ARBMODE_REG(port)); +} + +/** + * hsi_open - open a hsi device channel. + * @dev - Reference to the hsi device channel to be openned. + * + * Returns 0 on success, -EINVAL on bad parameters, -EBUSY if is already opened. + */ +int hsi_open(struct hsi_device *dev) +{ + struct hsi_channel *ch; + struct hsi_port *port; + struct hsi_dev *hsi_ctrl; + + if (!dev || !dev->ch) { + pr_err(LOG_NAME "Wrong HSI device %p\n", dev); + return -EINVAL; + } + dev_dbg(dev->device.parent, "%s ch %d\n", __func__, dev->n_ch); + + ch = dev->ch; + if (!ch->read_done || !ch->write_done) { + dev_err(dev->device.parent, + "Trying to open with no (read/write) callbacks " + "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; + } + + /* Restart with flags cleaned up */ + ch->flags = HSI_CH_OPEN; + + 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 */ + + hsi_clocks_disable_channel(dev->device.parent, ch->channel_number, + __func__); + spin_unlock_bh(&hsi_ctrl->lock); + + return 0; +} +EXPORT_SYMBOL(hsi_open); + +/** + * hsi_write - write data into the hsi device channel + * @dev - reference to the hsi device channel to write into. + * @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. + * A success value only indicates that the request has been accepted. + * Transfer is only completed when the write_done callback is called. + * + */ +int hsi_write(struct hsi_device *dev, u32 *addr, unsigned int size) +{ + struct hsi_channel *ch; + int err; + + if (unlikely(!dev)) { + pr_err(LOG_NAME "Null dev pointer in hsi_write\n"); + return -EINVAL; + } + + if (unlikely(!dev->ch || !addr || (size <= 0))) { + dev_err(dev->device.parent, + "Wrong parameters hsi_device %p data %p count %d", + dev, addr, size); + return -EINVAL; + } + dev_dbg(dev->device.parent, "%s ch %d, @%x, size %d u32\n", __func__, + dev->n_ch, (u32) addr, size); + + if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) { + dev_err(dev->device.parent, "HSI device NOT open\n"); + return -EINVAL; + } + + ch = dev->ch; + + spin_lock_bh(&ch->hsi_port->hsi_controller->lock); + if (pm_runtime_suspended(dev->device.parent) || + !ch->hsi_port->hsi_controller->clock_enabled) + dev_dbg(dev->device.parent, + "hsi_write with HSI clocks OFF, clock_enabled = %d\n", + ch->hsi_port->hsi_controller->clock_enabled); + + 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; + + if (size == 1) + err = hsi_driver_enable_write_interrupt(ch, addr); + else + err = hsi_driver_write_dma(ch, addr, size); + + if (unlikely(err < 0)) { + ch->write_data.addr = NULL; + ch->write_data.size = 0; + dev_err(dev->device.parent, "Failed to program write\n"); + } + + spin_unlock_bh(&ch->hsi_port->hsi_controller->lock); + + /* Leave clocks enabled until transfer is complete (write callback */ + /* is called */ + return err; +} +EXPORT_SYMBOL(hsi_write); + +/** + * hsi_read - read data from the hsi device channel + * @dev - hsi device channel reference to read data from. + * @addr - pointer to a 32-bit word data to store the data. + * @size - number of 32-bit word to be stored. + * + * Return 0 on sucess, a negative value on failure. + * A success value only indicates that the request has been accepted. + * Data is only available in the buffer when the read_done callback is called. + * + */ +int hsi_read(struct hsi_device *dev, u32 *addr, unsigned int size) +{ + struct hsi_channel *ch; + int err; + + if (unlikely(!dev)) { + pr_err(LOG_NAME "Null dev pointer in hsi_read\n"); + return -EINVAL; + } + + if (unlikely(!dev->ch || !addr || (size <= 0))) { + dev_err(dev->device.parent, "Wrong parameters " + "hsi_device %p data %p count %d", dev, addr, size); + return -EINVAL; + } +#if 0 + if (dev->n_ch == 0) + dev_info(dev->device.parent, "%s ch %d, @%x, size %d u32\n", + __func__, dev->n_ch, (u32) addr, size); +#endif + if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) { + dev_err(dev->device.parent, "HSI device NOT open\n"); + return -EINVAL; + } + + ch = dev->ch; + + spin_lock_bh(&ch->hsi_port->hsi_controller->lock); + if (pm_runtime_suspended(dev->device.parent) || + !ch->hsi_port->hsi_controller->clock_enabled) + dev_dbg(dev->device.parent, + "hsi_read with HSI clocks OFF, clock_enabled = %d\n", + ch->hsi_port->hsi_controller->clock_enabled); + + hsi_clocks_enable_channel(dev->device.parent, ch->channel_number, + __func__); + + if (ch->read_data.addr != NULL) { + dev_err(dev->device.parent, "# Invalid request - Read " + "operation pending port %d channel %d\n", + ch->hsi_port->port_number, + ch->channel_number); + err = -EINVAL; + goto done; + } + + ch->read_data.addr = addr; + ch->read_data.size = size; + ch->read_data.lch = -1; + + if (size == 1) + err = hsi_driver_enable_read_interrupt(ch, addr); + else + err = hsi_driver_read_dma(ch, addr, size); + + if (unlikely(err < 0)) { + ch->read_data.addr = NULL; + ch->read_data.size = 0; + dev_err(dev->device.parent, "Failed to program read\n"); + } + +done: + hsi_clocks_disable_channel(dev->device.parent, ch->channel_number, + __func__); + spin_unlock_bh(&ch->hsi_port->hsi_controller->lock); + + return err; +} +EXPORT_SYMBOL(hsi_read); + +int __hsi_write_cancel(struct hsi_channel *ch) +{ + int err = -ENODATA; + if (ch->write_data.size == 1) + err = hsi_driver_cancel_write_interrupt(ch); + else if (ch->write_data.size > 1) + err = hsi_driver_cancel_write_dma(ch); + else + dev_dbg(ch->dev->device.parent, "%s : Nothing to cancel %d\n", + __func__, ch->write_data.size); + dev_err(ch->dev->device.parent, "%s : %d\n", __func__, err); + return err; +} + +/** + * hsi_write_cancel - Cancel pending write request. + * @dev - hsi device channel where to cancel the pending write. + * + * write_done() callback will not be called after success of this function. + * + * Return: -ENXIO : No DMA channel found for specified HSI channel + * -ECANCELED : write cancel success, data not transfered to TX FIFO + * 0 : transfer is already over, data already transfered to TX FIFO + * + * Note: whatever returned value, write callback will not be called after + * write cancel. + */ +int hsi_write_cancel(struct hsi_device *dev) +{ + int err; + if (unlikely(!dev || !dev->ch)) { + pr_err(LOG_NAME "Wrong HSI device %p\n", dev); + return -ENODEV; + } + dev_err(dev->device.parent, "%s ch %d\n", __func__, dev->n_ch); + + if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) { + dev_err(dev->device.parent, "HSI device NOT open\n"); + return -ENODEV; + } + + spin_lock_bh(&dev->ch->hsi_port->hsi_controller->lock); + hsi_clocks_enable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + + err = __hsi_write_cancel(dev->ch); + + hsi_clocks_disable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + spin_unlock_bh(&dev->ch->hsi_port->hsi_controller->lock); + return err; +} +EXPORT_SYMBOL(hsi_write_cancel); + +int __hsi_read_cancel(struct hsi_channel *ch) +{ + int err = -ENODATA; + if (ch->read_data.size == 1) + err = hsi_driver_cancel_read_interrupt(ch); + else if (ch->read_data.size > 1) + err = hsi_driver_cancel_read_dma(ch); + else + dev_dbg(ch->dev->device.parent, "%s : Nothing to cancel %d\n", + __func__, ch->read_data.size); + + dev_err(ch->dev->device.parent, "%s : %d\n", __func__, err); + return err; +} + +/** + * hsi_read_cancel - Cancel pending read request. + * @dev - hsi device channel where to cancel the pending read. + * + * read_done() callback will not be called after success of this function. + * + * Return: -ENXIO : No DMA channel found for specified HSI channel + * -ECANCELED : read cancel success, data not available at expected + * address. + * 0 : transfer is already over, data already available at expected + * address. + * + * Note: whatever returned value, read callback will not be called after cancel. + */ +int hsi_read_cancel(struct hsi_device *dev) +{ + int err; + if (unlikely(!dev || !dev->ch)) { + pr_err(LOG_NAME "Wrong HSI device %p\n", dev); + return -ENODEV; + } + dev_err(dev->device.parent, "%s ch %d\n", __func__, dev->n_ch); + + if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) { + dev_err(dev->device.parent, "HSI device NOT open\n"); + return -ENODEV; + } + + spin_lock_bh(&dev->ch->hsi_port->hsi_controller->lock); + hsi_clocks_enable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + + err = __hsi_read_cancel(dev->ch); + + hsi_clocks_disable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + spin_unlock_bh(&dev->ch->hsi_port->hsi_controller->lock); + return err; +} +EXPORT_SYMBOL(hsi_read_cancel); + +/** + * hsi_poll - HSI poll feature, enables data interrupt on frame reception + * @dev - hsi device channel reference to apply the I/O control + * (or port associated to it) + * + * Return 0 on success, a negative value on failure. + * + */ +int hsi_poll(struct hsi_device *dev) +{ + struct hsi_channel *ch; + struct hsi_dev *hsi_ctrl; + int err; + + if (unlikely(!dev || !dev->ch)) + return -EINVAL; + dev_dbg(dev->device.parent, "%s ch %d\n", __func__, dev->n_ch); + + if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) { + dev_err(dev->device.parent, "HSI device NOT open\n"); + return -EINVAL; + } + + ch = dev->ch; + hsi_ctrl = ch->hsi_port->hsi_controller; + + spin_lock_bh(&hsi_ctrl->lock); + hsi_clocks_enable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + + ch->flags |= HSI_CH_RX_POLL; + + err = hsi_driver_enable_read_interrupt(ch, NULL); + + hsi_clocks_disable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + spin_unlock_bh(&hsi_ctrl->lock); + + return err; +} +EXPORT_SYMBOL(hsi_poll); + +/** + * hsi_unpoll - HSI poll feature, disables data interrupt on frame reception + * @dev - hsi device channel reference to apply the I/O control + * (or port associated to it) + * + * Return 0 on success, a negative value on failure. + * + */ +int hsi_unpoll(struct hsi_device *dev) +{ + struct hsi_channel *ch; + struct hsi_dev *hsi_ctrl; + + if (unlikely(!dev || !dev->ch)) + return -EINVAL; + dev_dbg(dev->device.parent, "%s ch %d\n", __func__, dev->n_ch); + + if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) { + dev_err(dev->device.parent, "HSI device NOT open\n"); + return -EINVAL; + } + + ch = dev->ch; + hsi_ctrl = ch->hsi_port->hsi_controller; + + spin_lock_bh(&hsi_ctrl->lock); + hsi_clocks_enable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + + ch->flags &= ~HSI_CH_RX_POLL; + + hsi_driver_disable_read_interrupt(ch); + + hsi_clocks_disable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + spin_unlock_bh(&hsi_ctrl->lock); + + return 0; +} +EXPORT_SYMBOL(hsi_unpoll); + +/** + * hsi_ioctl - HSI I/O control + * @dev - hsi device channel reference to apply the I/O control + * (or port associated to it) + * @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. + * + */ +int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg) +{ + struct hsi_channel *ch; + struct hsi_dev *hsi_ctrl; + struct hsi_port *pport; + void __iomem *base; + unsigned int port, channel; + u32 acwake; + int err = 0; + int fifo = 0; + + if (unlikely((!dev) || + (!dev->ch) || + (!dev->ch->hsi_port) || + (!dev->ch->hsi_port->hsi_controller)) || + (!(dev->ch->flags & HSI_CH_OPEN))) { + pr_err(LOG_NAME "HSI IOCTL Invalid parameter\n"); + return -EINVAL; + } + + ch = dev->ch; + pport = ch->hsi_port; + hsi_ctrl = ch->hsi_port->hsi_controller; + port = ch->hsi_port->port_number; + channel = ch->channel_number; + base = hsi_ctrl->base; + + dev_dbg(dev->device.parent, "IOCTL: ch %d, command %d\n", + channel, command); + + spin_lock_bh(&hsi_ctrl->lock); + hsi_clocks_enable_channel(dev->device.parent, channel, __func__); + + switch (command) { + case HSI_IOCTL_ACWAKE_UP: + 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); + + /* We only claim once the wake line per channel */ + acwake = hsi_inl(base, HSI_SYS_WAKE_REG(port)); + if (!(acwake & HSI_WAKE(channel))) { + hsi_outl(HSI_SET_WAKE(channel), base, + HSI_SYS_SET_WAKE_REG(port)); + } + + goto out; + break; + case HSI_IOCTL_ACWAKE_DOWN: + /* Low power request initiation (OMAP initiated, typically */ + /* following inactivity timeout) */ + /* ACPU HSI block shall still be capable of receiving */ + if (!(ch->flags & HSI_CH_ACWAKE)) { + dev_dbg(dev->device.parent, "Duplicate ACWAKE DOWN\n"); + err = -EPERM; + goto out; + } + + acwake = hsi_inl(base, HSI_SYS_WAKE_REG(port)); + if (unlikely(pport->acwake_status != + (acwake & HSI_WAKE_MASK))) { + dev_warn(dev->device.parent, + "ACWAKE shadow register mismatch" + " acwake_status: 0x%x, HSI_SYS_WAKE_REG: 0x%x", + pport->acwake_status, acwake); + pport->acwake_status = acwake & HSI_WAKE_MASK; + } + /* SSI_TODO: add safety check for SSI also */ + + ch->flags &= ~HSI_CH_ACWAKE; + pport->acwake_status &= ~BIT(channel); + + /* Release the wake line per channel */ + if ((acwake & HSI_WAKE(channel))) { + hsi_outl(HSI_CLEAR_WAKE(channel), base, + HSI_SYS_CLEAR_WAKE_REG(port)); + } + + goto out; + break; + case HSI_IOCTL_SEND_BREAK: + hsi_outl(1, base, HSI_HST_BREAK_REG(port)); + /*HSI_TODO : need to deactivate clock after BREAK frames sent*/ + /*Use interrupt ? (if TX BREAK INT exists)*/ + break; + case HSI_IOCTL_GET_ACWAKE: + if (!arg) { + err = -EINVAL; + goto out; + } + *(u32 *)arg = hsi_inl(base, HSI_SYS_WAKE_REG(port)); + break; + case HSI_IOCTL_FLUSH_RX: + hsi_outl(0, base, HSI_HSR_RXSTATE_REG(port)); + break; + case HSI_IOCTL_FLUSH_TX: + hsi_outl(0, base, HSI_HST_TXSTATE_REG(port)); + break; + case HSI_IOCTL_GET_CAWAKE: + if (!arg) { + err = -EINVAL; + goto out; + } + err = hsi_get_cawake(dev->ch->hsi_port); + if (err < 0) { + err = -ENODEV; + goto out; + } + *(u32 *)arg = err; + break; + case HSI_IOCTL_SET_RX: + if (!arg) { + err = -EINVAL; + goto out; + } + err = hsi_set_rx(dev->ch->hsi_port, (struct hsr_ctx *)arg); + break; + case HSI_IOCTL_GET_RX: + if (!arg) { + err = -EINVAL; + goto out; + } + hsi_get_rx(dev->ch->hsi_port, (struct hsr_ctx *)arg); + break; + case HSI_IOCTL_SET_TX: + if (!arg) { + err = -EINVAL; + goto out; + } + err = hsi_set_tx(dev->ch->hsi_port, (struct hst_ctx *)arg); + break; + case HSI_IOCTL_GET_TX: + if (!arg) { + err = -EINVAL; + goto out; + } + hsi_get_tx(dev->ch->hsi_port, (struct hst_ctx *)arg); + break; + case HSI_IOCTL_SW_RESET: + dev_info(dev->device.parent, "SW Reset\n"); + err = hsi_softreset(hsi_ctrl); + + /* Reset HSI config to default */ + hsi_softreset_driver(hsi_ctrl); + break; + case HSI_IOCTL_GET_FIFO_OCCUPANCY: + if (!arg) { + err = -EINVAL; + goto out; + } + fifo = hsi_fifo_get_id(hsi_ctrl, channel, port); + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, "No valid FIFO id found for " + "channel %d.\n", channel); + err = -EFAULT; + goto out; + } + *(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); + break; + case HSI_IOCTL_SET_4WIRE_MODE: + omap_writel((omap_readl(0x4A058C08) & 0xFFFF), 0x4A058C08); + break; + default: + err = -ENOIOCTLCMD; + break; + } +out: + /* All IOCTL end by disabling the clocks, except ACWAKE high. */ + hsi_clocks_disable_channel(dev->device.parent, channel, __func__); + + spin_unlock_bh(&hsi_ctrl->lock); + + return err; +} +EXPORT_SYMBOL(hsi_ioctl); + +/** + * hsi_close - close given hsi device channel + * @dev - reference to hsi device channel. + */ +void hsi_close(struct hsi_device *dev) +{ + struct hsi_dev *hsi_ctrl; + + if (!dev || !dev->ch) { + pr_err(LOG_NAME "Trying to close wrong HSI device %p\n", dev); + return; + } + dev_dbg(dev->device.parent, "%s ch %d\n", __func__, dev->n_ch); + + hsi_ctrl = dev->ch->hsi_port->hsi_controller; + + spin_lock_bh(&hsi_ctrl->lock); + hsi_clocks_enable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + + if (dev->ch->flags & HSI_CH_OPEN) { + dev->ch->flags &= ~HSI_CH_OPEN; + __hsi_write_cancel(dev->ch); + __hsi_read_cancel(dev->ch); + } + + hsi_clocks_disable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + spin_unlock_bh(&hsi_ctrl->lock); +} +EXPORT_SYMBOL(hsi_close); + +/** + * hsi_set_read_cb - register read_done() callback. + * @dev - reference to hsi device channel where the callback is associated to. + * @read_cb - callback to signal read transfer completed. + * size is expressed in number of 32-bit words. + * + * NOTE: Write callback must be only set when channel is not open ! + */ +void hsi_set_read_cb(struct hsi_device *dev, + void (*read_cb) (struct hsi_device *dev, + unsigned int size)) +{ + dev_dbg(dev->device.parent, "%s ch %d\n", __func__, dev->n_ch); + + dev->ch->read_done = read_cb; +} +EXPORT_SYMBOL(hsi_set_read_cb); + +/** + * hsi_set_read_cb - register write_done() callback. + * @dev - reference to hsi device channel where the callback is associated to. + * @write_cb - callback to signal read transfer completed. + * size is expressed in number of 32-bit words. + * + * NOTE: Read callback must be only set when channel is not open ! + */ +void hsi_set_write_cb(struct hsi_device *dev, + void (*write_cb) (struct hsi_device *dev, + unsigned int size)) +{ + dev_dbg(dev->device.parent, "%s ch %d\n", __func__, dev->n_ch); + + dev->ch->write_done = write_cb; +} +EXPORT_SYMBOL(hsi_set_write_cb); + +/** + * hsi_set_port_event_cb - register port_event callback. + * @dev - reference to hsi device channel where the callback is associated to. + * @port_event_cb - callback to signal events from the channel port. + */ +void hsi_set_port_event_cb(struct hsi_device *dev, + void (*port_event_cb) (struct hsi_device *dev, + unsigned int event, + void *arg)) +{ + struct hsi_port *port = dev->ch->hsi_port; + struct hsi_dev *hsi_ctrl = port->hsi_controller; + + dev_dbg(dev->device.parent, "%s ch %d\n", __func__, dev->n_ch); + + write_lock_bh(&dev->ch->rw_lock); + dev->ch->port_event = port_event_cb; + write_unlock_bh(&dev->ch->rw_lock); + + /* Since we now have a callback registered for events, we can now */ + /* enable the CAWAKE, ERROR and BREAK interrupts */ + 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); + hsi_clocks_disable_channel(dev->device.parent, dev->ch->channel_number, + __func__); + spin_unlock_bh(&hsi_ctrl->lock); +} +EXPORT_SYMBOL(hsi_set_port_event_cb); diff --git a/drivers/omap_hsi/hsi_driver_int.c b/drivers/omap_hsi/hsi_driver_int.c new file mode 100644 index 0000000..ce67e5f --- /dev/null +++ b/drivers/omap_hsi/hsi_driver_int.c @@ -0,0 +1,717 @@ +/* + * hsi_driver_int.c + * + * Implements HSI interrupt functionality. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#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; + ch->read_data.size = 0; + ch->read_data.lch = -1; +} + +void hsi_reset_ch_write(struct hsi_channel *ch) +{ + ch->write_data.addr = NULL; + ch->write_data.size = 0; + ch->write_data.lch = -1; +} + +/* Check if a Write (data transfer from AP to CP) is + * ongoing for a given HSI channel + */ +bool hsi_is_channel_busy(struct hsi_channel *ch) +{ + if (ch->write_data.addr == NULL) + return false; + + /* Note: we do not check if there is a read pending, because incoming */ + /* data will trigger an interrupt (FIFO or DMA), and wake up the */ + /* platform, so no need to keep the clocks ON. */ + return true; +} + +/* Check if a HSI port is busy : + * - data transfer (Write) is ongoing for a given HSI channel + * - CAWAKE is high + * - Currently in HSI interrupt tasklet + * - Currently in HSI CAWAKE tasklet (for SSI) + */ +bool hsi_is_hsi_port_busy(struct hsi_port *pport) +{ + struct hsi_dev *hsi_ctrl = pport->hsi_controller; + bool cur_cawake = hsi_get_cawake(pport); + int ch; + + if (pport->in_int_tasklet) { + dev_dbg(hsi_ctrl->dev, "Interrupt tasklet running\n"); + return true; + } + + if (pport->in_cawake_tasklet) { + dev_dbg(hsi_ctrl->dev, "SSI Cawake tasklet running\n"); + return true; + } + + if (cur_cawake) { + dev_dbg(hsi_ctrl->dev, "Port %d: WAKE status: acwake_status %d," + "cur_cawake %d", pport->port_number, + pport->acwake_status, cur_cawake); + return true; + } + + for (ch = 0; ch < pport->max_ch; ch++) + if (hsi_is_channel_busy(&pport->hsi_channel[ch])) { + dev_dbg(hsi_ctrl->dev, "Port %d; channel %d " + "busy\n", pport->port_number, ch); + return true; + } + + return false; +} + +/* Check if HSI controller is busy : + * - One of the HSI port is busy + * - Currently in HSI DMA tasklet + */ +bool hsi_is_hsi_controller_busy(struct hsi_dev *hsi_ctrl) +{ + int port; + + if (hsi_ctrl->in_dma_tasklet) { + dev_dbg(hsi_ctrl->dev, "DMA tasklet running\n"); + return true; + } + + for (port = 0; port < hsi_ctrl->max_p; port++) + if (hsi_is_hsi_port_busy(&hsi_ctrl->hsi_port[port])) { + dev_dbg(hsi_ctrl->dev, "Port %d busy\n", port + 1); + return true; + } + + dev_dbg(hsi_ctrl->dev, "No activity on HSI controller\n"); + return false; +} + +bool hsi_is_hst_port_busy(struct hsi_port *pport) +{ + unsigned int port = pport->port_number; + void __iomem *base = pport->hsi_controller->base; + u32 txstateval; + + txstateval = hsi_inl(base, HSI_HST_TXSTATE_REG(port)) & + HSI_HST_TXSTATE_VAL_MASK; + + if (txstateval != HSI_HST_TXSTATE_IDLE) { + dev_dbg(pport->hsi_controller->dev, "HST port %d busy, " + "TXSTATE=%d\n", port, txstateval); + return true; + } + + return false; +} + +bool hsi_is_hst_controller_busy(struct hsi_dev *hsi_ctrl) +{ + int port; + + for (port = 0; port < hsi_ctrl->max_p; port++) + if (hsi_is_hst_port_busy(&hsi_ctrl->hsi_port[port])) + return true; + + return false; +} + + +/* Enables the CAWAKE, BREAK, or ERROR interrupt for the given port */ +int hsi_driver_enable_interrupt(struct hsi_port *pport, u32 flag) +{ + hsi_outl_or(flag, pport->hsi_controller->base, + HSI_SYS_MPU_ENABLE_REG(pport->port_number, pport->n_irq)); + + return 0; +} + +/* Enables the Data Accepted Interrupt of HST for the given channel */ +int hsi_driver_enable_write_interrupt(struct hsi_channel *ch, u32 * data) +{ + struct hsi_port *p = ch->hsi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + + hsi_outl_or(HSI_HST_DATAACCEPT(channel), p->hsi_controller->base, + HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel)); + + return 0; +} + +/* Enables the Data Available Interrupt of HSR for the given channel */ +int hsi_driver_enable_read_interrupt(struct hsi_channel *ch, u32 * data) +{ + struct hsi_port *p = ch->hsi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + + hsi_outl_or(HSI_HSR_DATAAVAILABLE(channel), p->hsi_controller->base, + HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel)); + + return 0; +} + +/** + * hsi_driver_cancel_write_interrupt - Cancel pending write interrupt. + * @dev - hsi device channel where to cancel the pending interrupt. + * + * Return: -ECANCELED : write cancel success, data not transfered to TX FIFO + * 0 : transfer is already over, data already transfered to TX FIFO + * + * Note: whatever returned value, write callback will not be called after + * write cancel. + */ +int hsi_driver_cancel_write_interrupt(struct hsi_channel *ch) +{ + struct hsi_port *p = ch->hsi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + void __iomem *base = p->hsi_controller->base; + u32 status_reg; + long buff_offset; + + status_reg = hsi_inl(base, + HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel)); + + if (!(status_reg & HSI_HST_DATAACCEPT(channel))) { + dev_dbg(&ch->dev->device, "Write cancel on not " + "enabled channel %d ENABLE REG 0x%08X", channel, + status_reg); + } + status_reg &= hsi_inl(base, + HSI_SYS_MPU_STATUS_CH_REG(port, p->n_irq, channel)); + + hsi_outl_and(~HSI_HST_DATAACCEPT(channel), base, + HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel)); + + buff_offset = hsi_hst_bufstate_f_reg(p->hsi_controller, port, channel); + if (buff_offset >= 0) + hsi_outl_and(~HSI_BUFSTATE_CHANNEL(channel), base, buff_offset); + hsi_reset_ch_write(ch); + return status_reg & HSI_HST_DATAACCEPT(channel) ? 0 : -ECANCELED; +} + +/** + * hsi_driver_cancel_read_interrupt - Cancel pending read interrupt. + * @dev - hsi device channel where to cancel the pending interrupt. + * + * Return: -ECANCELED : read cancel success data not available at expected + * address. + * 0 : transfer is already over, data already available at expected + * address. + * + * Note: whatever returned value, read callback will not be called after cancel. + */ +int hsi_driver_cancel_read_interrupt(struct hsi_channel *ch) +{ + struct hsi_port *p = ch->hsi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + void __iomem *base = p->hsi_controller->base; + u32 status_reg; + + status_reg = hsi_inl(base, + HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel)); + if (!(status_reg & HSI_HSR_DATAAVAILABLE(channel))) { + dev_dbg(&ch->dev->device, "Read cancel on not " + "enabled channel %d ENABLE REG 0x%08X", channel, + status_reg); + } + status_reg &= hsi_inl(base, + HSI_SYS_MPU_STATUS_CH_REG(port, p->n_irq, channel)); + hsi_outl_and(~HSI_HSR_DATAAVAILABLE(channel), base, + HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel)); + hsi_reset_ch_read(ch); + return status_reg & HSI_HSR_DATAAVAILABLE(channel) ? 0 : -ECANCELED; +} + +void hsi_driver_disable_write_interrupt(struct hsi_channel *ch) +{ + struct hsi_port *p = ch->hsi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + void __iomem *base = p->hsi_controller->base; + + hsi_outl_and(~HSI_HST_DATAACCEPT(channel), base, + HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel)); +} + +void hsi_driver_disable_read_interrupt(struct hsi_channel *ch) +{ + struct hsi_port *p = ch->hsi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + void __iomem *base = p->hsi_controller->base; + + hsi_outl_and(~HSI_HSR_DATAAVAILABLE(channel), base, + HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel)); +} + +/* HST_ACCEPTED interrupt processing */ +static void hsi_do_channel_tx(struct hsi_channel *ch) +{ + struct hsi_dev *hsi_ctrl = ch->hsi_port->hsi_controller; + void __iomem *base = hsi_ctrl->base; + unsigned int n_ch; + unsigned int n_p; + unsigned int irq; + long buff_offset; + + n_ch = ch->channel_number; + n_p = ch->hsi_port->port_number; + irq = ch->hsi_port->n_irq; + + dev_dbg(hsi_ctrl->dev, + "Data Accepted interrupt for channel %d.\n", n_ch); + + hsi_driver_disable_write_interrupt(ch); + + if (ch->write_data.addr == NULL) { + dev_err(hsi_ctrl->dev, "Error, NULL Write address.\n"); + hsi_reset_ch_write(ch); + + } else { + buff_offset = hsi_hst_buffer_reg(hsi_ctrl, n_p, n_ch); + if (buff_offset >= 0) { + hsi_outl(*(ch->write_data.addr), base, buff_offset); + ch->write_data.addr = NULL; + } + } + + spin_unlock(&hsi_ctrl->lock); + dev_dbg(hsi_ctrl->dev, "Calling ch %d write callback.\n", n_ch); + (*ch->write_done) (ch->dev, 1); + spin_lock(&hsi_ctrl->lock); +} + +/* HSR_AVAILABLE interrupt processing */ +static void hsi_do_channel_rx(struct hsi_channel *ch) +{ + struct hsi_dev *hsi_ctrl = ch->hsi_port->hsi_controller; + void __iomem *base = ch->hsi_port->hsi_controller->base; + unsigned int n_ch; + unsigned int n_p; + unsigned int irq; + long buff_offset; + 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; + irq = ch->hsi_port->n_irq; + + dev_dbg(hsi_ctrl->dev, + "Data Available interrupt for channel %d.\n", n_ch); + + /* Check if there is data in FIFO available for reading */ + if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) { + fifo = hsi_fifo_get_id(hsi_ctrl, n_ch, n_p); + if (unlikely(fifo < 0)) { + dev_err(hsi_ctrl->dev, "No valid FIFO id found for " + "channel %d.\n", n_ch); + return; + } + fifo_words_avail = hsi_get_rx_fifo_occupancy(hsi_ctrl, fifo); + if (!fifo_words_avail) { + dev_dbg(hsi_ctrl->dev, + "WARNING: RX FIFO %d empty before CPU copy\n", + fifo); + + /* Do not disable interrupt becaue another interrupt */ + /* can still come, this time with a real frame. */ + return; + } + } + + /* + * 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); + goto done; + } + + if (ch->flags & HSI_CH_RX_POLL) + rx_poll = 1; + + if (ch->read_data.addr) { + 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); + } + } +#if 0 + if (omap_readl(0x4A05A810)) + dev_err(hsi_ctrl->dev, + "RX BUF state is full. " + "Warning disabling interrupt %0x\n", + omap_readl(0x4A05A810)); +#endif + hsi_driver_disable_read_interrupt(ch); + hsi_reset_ch_read(ch); + +done: + if (rx_poll) { + spin_unlock(&hsi_ctrl->lock); + hsi_port_event_handler(ch->hsi_port, + HSI_EVENT_HSR_DATAAVAILABLE, + (void *)n_ch); + spin_lock(&hsi_ctrl->lock); + } + + if (data_read) { + spin_unlock(&hsi_ctrl->lock); +#if 0 + dev_warn(hsi_ctrl->dev, "Read callback %d.\n", n_ch); + if (n_ch == 0) + dev_warn(hsi_ctrl->dev, + "Read callback %d \t DATA 0x%0x .\n", + n_ch, data); +#endif + (*ch->read_done) (ch->dev, 1); + spin_lock(&hsi_ctrl->lock); + } +} + +/** + * hsi_do_cawake_process - CAWAKE line management + * @pport - HSI port to process + * + * This function handles the CAWAKE L/H transitions and call the event callback + * accordingly. + * + * Returns 0 if CAWAKE event process, -EAGAIN if CAWAKE event processing is + * delayed due to a pending DMA interrupt. + * If -EAGAIN is returned, pport->hsi_tasklet has to be re-scheduled once + * DMA tasklet has be executed. This should be done automatically by driver. + * +*/ +int hsi_do_cawake_process(struct hsi_port *pport) +{ + struct hsi_dev *hsi_ctrl = pport->hsi_controller; + bool cawake_status = hsi_get_cawake(pport); + + /* Deal with init condition */ + if (unlikely(pport->cawake_status < 0)) + pport->cawake_status = !cawake_status; + dev_dbg(hsi_ctrl->dev, + "Interrupts are not enabled but CAWAKE has come\n: 0x%0x.\n", + omap_readl(0x4A05880c)); + dev_dbg(hsi_ctrl->dev, + "Interrupts are not enabled but CAWAKE has come\n: 0x%0x.\n", + omap_readl(0x4A058804)); + + /* Check CAWAKE line status */ + if (cawake_status) { + dev_dbg(hsi_ctrl->dev, "CAWAKE rising edge detected\n"); + + /* 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"); + spin_unlock(&hsi_ctrl->lock); + hsi_port_event_handler(pport, HSI_EVENT_CAWAKE_DOWN, + NULL); + spin_lock(&hsi_ctrl->lock); + } + pport->cawake_status = 1; + if (omap_readl(0x4A306404) != 0x0) { + omap_writel(0x00000002, 0x4A004400); + omap_writel(0x003F0703, 0x4A306400); + omap_writel(0x003F0700, 0x4A306400); + omap_writel(0x00000003, 0x4A004400); + } + /* Force HSI to ON_ACTIVE when CAWAKE is high */ + hsi_set_pm_force_hsi_on(hsi_ctrl); + /* TODO: Use omap_pm_set_max_dev_wakeup_lat() 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); + } else { + dev_dbg(hsi_ctrl->dev, "CAWAKE falling edge detected\n"); + + /* Check for pending DMA interrupt */ + if (hsi_is_dma_read_int_pending(hsi_ctrl)) { + dev_dbg(hsi_ctrl->dev, "Pending DMA Read interrupt " + "before CAWAKE->L, exiting " + "Interrupt tasklet.\n"); + return -EAGAIN; + } + if (unlikely(!pport->cawake_status)) { + dev_warn(hsi_ctrl->dev, + "CAWAKE race is detected: %s.\n", + "LOW -> HI -> LOW"); + spin_unlock(&hsi_ctrl->lock); + hsi_port_event_handler(pport, HSI_EVENT_CAWAKE_UP, + NULL); + spin_lock(&hsi_ctrl->lock); + } + pport->cawake_status = 0; + + /* Allow HSI HW to enter IDLE when CAWAKE is low */ + hsi_set_pm_default(hsi_ctrl); + /* TODO: Use omap_pm_set_max_dev_wakeup_lat() to release */ + /* latency constraint to prevent 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); + } + return 0; +} + +/** + * hsi_driver_int_proc - check all channels / ports for interrupts events + * @hsi_ctrl - HSI controler data + * @status_offset: interrupt status register offset + * @enable_offset: interrupt enable regiser offset + * @start: interrupt index to start on + * @stop: interrupt index to stop on + * + * returns the bitmap of processed events + * + * This function calls the related processing functions and triggered events. + * Events are cleared after corresponding function has been called. +*/ +static u32 hsi_driver_int_proc(struct hsi_port *pport, + unsigned long status_offset, + unsigned long enable_offset, unsigned int start, + unsigned int stop) +{ + struct hsi_dev *hsi_ctrl = pport->hsi_controller; + void __iomem *base = hsi_ctrl->base; + unsigned int port = pport->port_number; + unsigned int channel; + u32 status_reg; + u32 hsr_err_reg; + u32 channels_served = 0; + + /* Get events status */ + status_reg = hsi_inl(base, status_offset); + status_reg &= hsi_inl(base, enable_offset); + + if (pport->cawake_off_event) { + dev_dbg(hsi_ctrl->dev, "CAWAKE detected from OFF mode.\n"); + } else if (!status_reg) { + dev_dbg(hsi_ctrl->dev, "Channels [%d,%d] : no event, exit.\n", + start, stop); + return 0; + } else { + dev_dbg(hsi_ctrl->dev, "Channels [%d,%d] : Events 0x%08x\n", + start, stop, status_reg); + } + + 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); + + channels_served |= HSI_BREAKDETECTED; + } + + if (status_reg & HSI_ERROROCCURED) { + hsr_err_reg = hsi_inl(base, HSI_HSR_ERROR_REG(port)); + if (hsr_err_reg & HSI_HSR_ERROR_SIG) + dev_err(hsi_ctrl->dev, "HSI ERROR Port %d: 0x%x: %s\n", + port, hsr_err_reg, "Signal Error"); + if (hsr_err_reg & HSI_HSR_ERROR_FTE) + dev_err(hsi_ctrl->dev, "HSI ERROR Port %d: 0x%x: %s\n", + port, hsr_err_reg, "Frame Timeout Error"); + if (hsr_err_reg & HSI_HSR_ERROR_TBE) + dev_err(hsi_ctrl->dev, "HSI ERROR Port %d: 0x%x: %s\n", + port, hsr_err_reg, "Tailing Bit Error"); + if (hsr_err_reg & HSI_HSR_ERROR_RME) + dev_err(hsi_ctrl->dev, "HSI ERROR Port %d: 0x%x: %s\n", + port, hsr_err_reg, "RX Mapping Error"); + if (hsr_err_reg & HSI_HSR_ERROR_TME) + dev_err(hsi_ctrl->dev, "HSI ERROR Port %d: 0x%x: %s\n", + port, hsr_err_reg, "TX Mapping Error"); + /* Clear error event bit */ + hsi_outl(hsr_err_reg, base, HSI_HSR_ERRORACK_REG(port)); + if (hsr_err_reg) { /* ignore spurious errors */ + spin_unlock(&hsi_ctrl->lock); + hsi_port_event_handler(pport, HSI_EVENT_ERROR, NULL); + spin_lock(&hsi_ctrl->lock); + } else + dev_dbg(hsi_ctrl->dev, "Spurious HSI error!\n"); + + channels_served |= HSI_ERROROCCURED; + } + + for (channel = start; channel <= stop; channel++) { + if (status_reg & HSI_HST_DATAACCEPT(channel)) { + hsi_do_channel_tx(&pport->hsi_channel[channel]); + channels_served |= HSI_HST_DATAACCEPT(channel); + } + + if (status_reg & HSI_HSR_DATAAVAILABLE(channel)) { + hsi_do_channel_rx(&pport->hsi_channel[channel]); + channels_served |= HSI_HSR_DATAAVAILABLE(channel); + } + + if (status_reg & HSI_HSR_DATAOVERRUN(channel)) { + /*HSI_TODO : Data overrun handling*/ + dev_err(hsi_ctrl->dev, + "Data overrun in real time mode !\n"); + } + } + + /* CAWAKE falling or rising edge detected */ + if ((status_reg & HSI_CAWAKEDETECTED) || pport->cawake_off_event) { + if (hsi_do_cawake_process(pport) == -EAGAIN) + goto proc_done; + + channels_served |= HSI_CAWAKEDETECTED; + pport->cawake_off_event = false; + } +proc_done: + /* Reset status bits */ + hsi_outl(channels_served, base, status_offset); + + return channels_served; +} + +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; + + /* 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); + + /* Process events for channels 8..15 */ + if (pport->max_ch > HSI_SSI_CHANNELS_MAX) + 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); + + return status_reg; +} + +static void do_hsi_tasklet(unsigned long hsi_port) +{ + struct hsi_port *pport = (struct hsi_port *)hsi_port; + struct hsi_dev *hsi_ctrl = pport->hsi_controller; + u32 status_reg; + + dev_dbg(hsi_ctrl->dev, "Int Tasklet : clock_enabled=%d\n", + hsi_ctrl->clock_enabled); +#if 0 + if (pport->cawake_off_event == true) + dev_info(hsi_ctrl->dev, + "Tasklet called from OFF/RET MODE THRU PAD CPU ID %d\n", + smp_processor_id()); + else + dev_info(hsi_ctrl->dev, + "Tasklet called from ACTIVE MODE CPU ID %d\n", + smp_processor_id()); +#endif + spin_lock(&hsi_ctrl->lock); + hsi_clocks_enable(hsi_ctrl->dev, __func__); + pport->in_int_tasklet = true; + + status_reg = hsi_process_int_event(pport); + + pport->in_int_tasklet = false; + 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 0 + printk(KERN_INFO "Tasklet called from MPU HANDLER CPU ID %d " + "\t STS 0x%0x \t ENB 0x%0x\n", smp_processor_id(), + omap_readl(0x4A058808), omap_readl(0x4A05880C)); +#endif + if (shceduled_already_flag == 0) { +#if 0 + tasklet_hi_schedule(&pport->hsi_tasklet); + if (TASKLET_STATE_SCHED == pport->hsi_tasklet.state) { + printk(KERN_INFO "MPU TASKLET ALREADY SCHEDULED RETURNING\n"); + return IRQ_HANDLED; + } +#endif + shceduled_already_flag = 1; + tasklet_hi_schedule(&pport->hsi_tasklet); + /* Disable interrupt until Bottom Half has cleared the */ + /* IRQ status register */ + disable_irq_nosync(pport->irq); + } + return IRQ_HANDLED; +} + +int __init hsi_mpu_init(struct hsi_port *hsi_p, const char *irq_name) +{ + int err; + + tasklet_init(&hsi_p->hsi_tasklet, do_hsi_tasklet, (unsigned long)hsi_p); + + dev_info(hsi_p->hsi_controller->dev, "Registering IRQ %s (%d)\n", + irq_name, hsi_p->irq); + err = request_irq(hsi_p->irq, hsi_mpu_handler, + IRQF_NO_SUSPEND | IRQF_TRIGGER_HIGH, + irq_name, hsi_p); + if (err < 0) { + dev_err(hsi_p->hsi_controller->dev, "FAILED to MPU request" + " IRQ (%d) on port %d", hsi_p->irq, hsi_p->port_number); + return -EBUSY; + } + + return 0; +} + +void hsi_mpu_exit(struct hsi_port *hsi_p) +{ + tasklet_kill(&hsi_p->hsi_tasklet); + free_irq(hsi_p->irq, hsi_p); +} diff --git a/drivers/omap_hsi/hsi_protocol.c b/drivers/omap_hsi/hsi_protocol.c new file mode 100644 index 0000000..e1451e7 --- /dev/null +++ b/drivers/omap_hsi/hsi_protocol.c @@ -0,0 +1,308 @@ +/* + * File - hsi_protocol.c + * + * Implements HSI protocol for Infineon Modem. + * + * Copyright (C) 2011 Samsung Electronics. + * + * Author: Rupesh Gujare <rupesh.g@samsung.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#if 0 +#define DEBUG 1 +#endif + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/io.h> + +#include "hsi-protocol-if.h" +#include <linux/hsi_driver_if.h> + +#define DRIVER_VERSION "1.0" + +char test_data[10] = "abcdefghij"; + +dev_t hsi_protocol_dev; + +struct protocol_queue { + struct list_head list; + u32 *data; + unsigned int count; +}; + +struct hsi_protocol { + unsigned int opened; + int poll_event; + struct list_head rx_queue; + struct list_head tx_queue; + spinlock_t lock; /* Serialize access to driver data and API */ + struct fasync_struct *async_queue; + wait_queue_head_t rx_wait; + wait_queue_head_t tx_wait; + wait_queue_head_t poll_wait; +}; + +static struct hsi_protocol hsi_protocol_data[HSI_MAX_CHANNELS]; + +void if_notify(int ch, struct hsi_event *ev) +{ + struct protocol_queue *entry; + + pr_debug("%s, ev = {0x%x, 0x%p, %u}\n", + __func__, ev->event, ev->data, ev->count); + + spin_lock(&hsi_protocol_data[ch].lock); + +/* Not Required */ + /*if (!hsi_protocol_data[ch].opened) { + pr_debug("%s, device not opened\n!", __func__); + printk(KERN_INFO "%s, device not opened\n!", __func__); + spin_unlock(&hsi_protocol_data[ch].lock); + return; + }*/ + + switch (HSI_EV_TYPE(ev->event)) { + case HSI_EV_IN: + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) { + pr_err("HSI-CHAR: entry allocation failed.\n"); + spin_unlock(&hsi_protocol_data[ch].lock); + return; + } + entry->data = ev->data; + entry->count = ev->count; + list_add_tail(&entry->list, &hsi_protocol_data[ch].rx_queue); + spin_unlock(&hsi_protocol_data[ch].lock); + pr_debug("%s, HSI_EV_IN\n", __func__); + wake_up_interruptible(&hsi_protocol_data[ch].rx_wait); + break; + case HSI_EV_OUT: + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) { + pr_err("HSI-CHAR: entry allocation failed.\n"); + spin_unlock(&hsi_protocol_data[ch].lock); + return; + } + entry->data = ev->data; + entry->count = ev->count; + hsi_protocol_data[ch].poll_event |= (POLLOUT | POLLWRNORM); + list_add_tail(&entry->list, &hsi_protocol_data[ch].tx_queue); + spin_unlock(&hsi_protocol_data[ch].lock); + pr_debug("%s, HSI_EV_OUT\n", __func__); + wake_up_interruptible(&hsi_protocol_data[ch].tx_wait); + break; + case HSI_EV_EXCEP: + hsi_protocol_data[ch].poll_event |= POLLPRI; + spin_unlock(&hsi_protocol_data[ch].lock); + pr_debug("%s, HSI_EV_EXCEP\n", __func__); + wake_up_interruptible(&hsi_protocol_data[ch].poll_wait); + break; + case HSI_EV_AVAIL: + hsi_protocol_data[ch].poll_event |= (POLLIN | POLLRDNORM); + spin_unlock(&hsi_protocol_data[ch].lock); + pr_debug("%s, HSI_EV_AVAIL\n", __func__); + wake_up_interruptible(&hsi_protocol_data[ch].poll_wait); + break; + default: + spin_unlock(&hsi_protocol_data[ch].lock); + break; + } +} + +int hsi_proto_read(int ch, u32 *buffer, int count) +{ + DECLARE_WAITQUEUE(wait, current); + u32 *data; + unsigned int data_len = 0; + struct protocol_queue *entry; + int ret, recv_data = 0; + + /*if (count > MAX_HSI_IPC_BUFFER) + count = MAX_HSI_IPC_BUFFER; + + data = kmalloc(count, GFP_ATOMIC);*/ + + ret = if_hsi_read(ch, buffer, count); + if (ret < 0) { + pr_err("Can not submit read. READ Error\n"); + goto out2; + } + + spin_lock_bh(&hsi_protocol_data[ch].lock); + add_wait_queue(&hsi_protocol_data[ch].rx_wait, &wait); + spin_unlock_bh(&hsi_protocol_data[ch].lock); + + for (;;) { + data = NULL; + data_len = 0; + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_bh(&hsi_protocol_data[ch].lock); + if (!list_empty(&hsi_protocol_data[ch].rx_queue)) { + entry = list_entry(hsi_protocol_data[ch].rx_queue.next, + struct protocol_queue, list); + data = entry->data; + data_len = entry->count; + list_del(&entry->list); + kfree(entry); + } + spin_unlock_bh(&hsi_protocol_data[ch].lock); + + pr_debug("%s, data = 0x%p, data_len = %d\n", + __func__, data, data_len); + + if (data_len) { + pr_debug("%s, RX finished, ch-> %d, length = %d\n", + __func__, ch, count); + spin_lock_bh(&hsi_protocol_data[ch].lock); + hsi_protocol_data[ch].poll_event &= + ~(POLLIN | POLLRDNORM); + spin_unlock_bh(&hsi_protocol_data[ch].lock); + if_hsi_poll(ch); +#if 0 + memcpy(buffer, data, count); +#endif + recv_data += data_len; +#if 0 + buffer += data_len; + if ((recv_data == count) || (recv_data >= MAX_HSI_IPC_BUFFER)) +#endif + break; + } else if (signal_pending(current)) { + pr_debug("%s, ERESTARTSYS\n", __func__); + recv_data = -EAGAIN; + if_hsi_cancel_read(ch); + /* goto out; */ + break; + } + + /*printk(KERN_DEBUG "%s, going to sleep...\n", __func__); */ + schedule(); + /*printk(KERN_DEBUG "%s, woke up\n", __func__); */ + } + +/*out:*/ + __set_current_state(TASK_RUNNING); + remove_wait_queue(&hsi_protocol_data[ch].rx_wait, &wait); + +out2: + /*To Do- Set bit if data to be received is + * greater than 512K Bytes and return to IPC call + */ + + return recv_data; +} + +int hsi_proto_write(int ch, u32 *buffer, int length) +{ + + DECLARE_WAITQUEUE(wait, current); + u32 *data; + unsigned int data_len = 0, ret = -1; + struct protocol_queue *entry; + + ret = if_hsi_write(ch, buffer, length); + if (ret < 0) { + pr_err("HSI Write ERROR %s\n", __func__); + goto out2; + } else + spin_lock_bh(&hsi_protocol_data[ch].lock); + hsi_protocol_data[ch].poll_event &= ~(POLLOUT | POLLWRNORM); + add_wait_queue(&hsi_protocol_data[ch].tx_wait, &wait); + spin_unlock_bh(&hsi_protocol_data[ch].lock); + + for (;;) { + data = NULL; + data_len = 0; + + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_bh(&hsi_protocol_data[ch].lock); + if (!list_empty(&hsi_protocol_data[ch].tx_queue)) { + entry = list_entry(hsi_protocol_data[ch].tx_queue.next, + struct protocol_queue, list); + data = entry->data; + data_len = entry->count; + list_del(&entry->list); + kfree(entry); + } + spin_unlock_bh(&hsi_protocol_data[ch].lock); + + if (data_len) { + pr_debug("%s, TX finished, data_len = %d, ch-> %d\n", + __func__, length, ch); + ret = data_len; + break; + } else if (signal_pending(current)) { + pr_debug("%s, ERESTARTSYS\n", __func__); + ret = -ERESTARTSYS; + goto out; + } + + schedule(); + } + +out: + __set_current_state(TASK_RUNNING); + remove_wait_queue(&hsi_protocol_data[ch].tx_wait, &wait); + +out2: + return ret; +} +EXPORT_SYMBOL(hsi_proto_write); + +static int __init hsi_protocol_init(void) +{ + int i, ret = 0; + + pr_info("HSI Infineon Protocol driver version " DRIVER_VERSION "\n"); + + for (i = 0; i < HSI_MAX_CHANNELS; i++) { + init_waitqueue_head(&hsi_protocol_data[i].rx_wait); + init_waitqueue_head(&hsi_protocol_data[i].tx_wait); + init_waitqueue_head(&hsi_protocol_data[i].poll_wait); + spin_lock_init(&hsi_protocol_data[i].lock); + hsi_protocol_data[i].opened = 0; + INIT_LIST_HEAD(&hsi_protocol_data[i].rx_queue); + INIT_LIST_HEAD(&hsi_protocol_data[i].tx_queue); + } + + printk(KERN_INFO "hsi_protocol_init : hsi_mux_setting Done.\n"); + + ret = if_hsi_init(); + + return ret; +} + + +static void __exit hsi_protocol_exit(void) +{ + if_hsi_exit(); +} + + +MODULE_AUTHOR("Rupesh Gujare <rupesh.g@samsung.com> / Samsung Electronics"); +MODULE_DESCRIPTION("HSI Protocol for Infineon Modem"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); + +module_init(hsi_protocol_init); +module_exit(hsi_protocol_exit); diff --git a/drivers/omap_hsi/hsi_protocol_cmd.c b/drivers/omap_hsi/hsi_protocol_cmd.c new file mode 100644 index 0000000..d256631 --- /dev/null +++ b/drivers/omap_hsi/hsi_protocol_cmd.c @@ -0,0 +1,429 @@ +/* + * File - hsi_protocol_if_cmd.c + * + * Implements HSI protocol for Infineon Modem. + * + * Copyright (C) 2011 Samsung Electronics. All rights reserved. + * + * Author: Rupesh Gujare <rupesh.g@samsung.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> + +#include <linux/hsi_driver_if.h> +#include "hsi-protocol-if.h" + +extern struct if_hsi_iface hsi_protocol_iface; +extern wait_queue_head_t ipc_read_wait, ipc_write_wait; +int if_hsi_openchannel(struct if_hsi_channel *channel); +int if_hsi_closechannel(struct if_hsi_channel *channel); + +extern struct if_hsi_cmd hsi_cmd_history; +extern int tx_cmd_history_p; +extern int rx_cmd_history_p; + +/*Decode command from received PDU on channle 0*/ +int hsi_decode_cmd(u32 *cmd_data, u32 *cmd, u32 *ch, u32 *param) +{ + int ret = 0; + 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("Command MSG_BREAK Received.\n"); + break; + + case HSI_LL_MSG_OPEN_CONN: + *ch = ((data & 0x0F000000) >> 24); + *param = ((data & 0x00FFFF00) >> 8); + /*Check LRC*/ + 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) + ret = -1; + break; + + 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); + break; + + case HSI_LL_MSG_ACK: + *ch = ((data & 0x0F000000) >> 24); + *param = (data & 0x00FFFFFF); + //printk(KERN_INFO "ACK Received ch=%d, param=%d\n",*ch, *param); + break; + + case HSI_LL_MSG_CONF_RATE: + *ch = ((data & 0x0F000000) >> 24); + *param = ((data & 0x0F000000) >> 24); + break; + + case HSI_LL_MSG_OPEN_CONN_OCTET: + *ch = ((data & 0x0F000000) >> 24); + *param = (data & 0x00FFFFFF); + break; + + 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: + *cmd = HSI_LL_MSG_INVALID; + *ch = HSI_LL_INVALID_CHANNEL; + ret = -1; + break; + } + return ret; +} + +int protocol_create_cmd(int cmd_type, unsigned int channel, void *arg) +{ + unsigned int command = 0; + int ret = 0; + + switch (cmd_type) { + case HSI_LL_MSG_BREAK: + { + command = 0; + } + break; + + case HSI_LL_MSG_OPEN_CONN: + { + unsigned int size = *(unsigned int *)arg; + unsigned int lcr = 0; + +/* if(size > 4) + size = (size & 0x3) ? ((size >> 2) + 1):(size >> 2); + else + size = 1;*/ + + command = ((HSI_LL_MSG_OPEN_CONN & 0x0000000F) << 28) | + ((channel & 0x000000FF) << 24) | + ((size & 0x0000FFFF) << 8); + + lcr = ((command & 0xFF000000) >> 24) ^ + ((command & 0x00FF0000) >> 16) ^ + ((command & 0x0000FF00) >> 8); + + command = command | (lcr & 0x000000FF); + } + break; + + case HSI_LL_MSG_CONN_READY: + { + command = ((HSI_LL_MSG_CONN_READY & 0x0000000F) << 28) | + ((channel & 0x000000FF) << 24); + } + break; + + case HSI_LL_MSG_CONN_CLOSED: + { + command = ((HSI_LL_MSG_CONN_CLOSED & 0x0000000F) << 28) | + ((channel & 0x000000FF) << 24); + } + break; + + case HSI_LL_MSG_CANCEL_CONN: + { + unsigned int role = *(unsigned int *)arg; + + command = ((HSI_LL_MSG_CANCEL_CONN & 0x0000000F) << 28) | + ((channel & 0x000000FF) << 24) | + ((role & 0x000000FF) << 16); + } + break; + + case HSI_LL_MSG_ACK: + { + unsigned int echo_params = *(unsigned int *)arg; + + command = ((HSI_LL_MSG_ACK & 0x0000000F) << 28) | + ((channel & 0x000000FF) << 24) | + ((echo_params & 0x00FFFFFF)); + } + break; + + case HSI_LL_MSG_NAK: + { + command = ((HSI_LL_MSG_NAK & 0x0000000F) << 28) | + ((channel & 0x000000FF) << 24); + } + break; + + case HSI_LL_MSG_CONF_RATE: + { + unsigned int baud_rate = *(unsigned int *)arg; + + command = ((HSI_LL_MSG_CONF_RATE & 0x0000000F) << 28) | + ((channel & 0x000000FF) << 24) | + ((baud_rate & 0x00FFFFFF)); + } + break; + + case HSI_LL_MSG_OPEN_CONN_OCTET: + { + unsigned int size = *(unsigned int *)arg; + + command = ((HSI_LL_MSG_OPEN_CONN_OCTET & 0x0000000F) << 28) | + ((channel & 0x000000FF) << 24) | + ((size & 0x00FFFFFF)); + + } + break; + + 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: + ret = -1; + break; + } + return command; +} + +int set_tx_config(struct if_hsi_channel *ch, u32 mode, u32 max_channels) +{ + struct hst_ctx tx_config; + int ret; + + hsi_ioctl(ch->dev, HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = mode; + tx_config.channels = max_channels; + ret = hsi_ioctl(ch->dev, HSI_IOCTL_SET_TX, &tx_config); + return ret; +} + +static int saved_cmd_queue = 0; +static u32 cmd_saved[5]; +int hsi_protocol_send_command(u32 cmd, u32 channel, u32 param) +{ + struct if_hsi_channel *channel_zero; + u32 cmd_array[4] = {0x00000000, 0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC}, ret = -1; + + channel_zero = &hsi_protocol_iface.channels[0]; + cmd_array[0] = protocol_create_cmd(cmd, channel, ¶m); + pr_debug("[%s] CMD = %08x\n",__func__, cmd_array[0]); + while (channel_zero->tx_state != HSI_LL_TX_STATE_IDLE) { + cmd_saved[saved_cmd_queue] = cmd_array[0]; + saved_cmd_queue++; + pr_debug("(%s) cmd_saved : %x(%d)\n", __func__, cmd_array[0], saved_cmd_queue); + + return 0; + } + +send_retry: + + channel_zero->tx_state = HSI_LL_TX_STATE_TX; + + // For es 2.1 ver. + ret = hsi_proto_write(0, cmd_array, 4); + if (ret < 0) { + pr_err("(%s) Command Write failed, CMD->%X\n", __func__, cmd_array[0]); + channel_zero->tx_state = HSI_LL_TX_STATE_IDLE; + return -1; + } else { + channel_zero->tx_state = HSI_LL_TX_STATE_IDLE; + + pr_debug("[%s] CMD = %08x\n", __func__, cmd_array[0]); + + hsi_cmd_history.tx_cmd[tx_cmd_history_p] = cmd_array[0]; + hsi_cmd_history.tx_cmd_time[tx_cmd_history_p] = CURRENT_TIME; + tx_cmd_history_p++; + if (tx_cmd_history_p >= 50) + tx_cmd_history_p = 0; + + if (saved_cmd_queue) { + saved_cmd_queue--; + cmd_array[0] = cmd_saved[saved_cmd_queue]; + + goto SEND_RETRY; + } + + return 0; + } +} + +void rx_stm(u32 cmd, u32 ch, u32 param) +{ + struct if_hsi_channel *channel; + u32 size = 0, tmp_cmd = 0, ret, i; + channel = &hsi_protocol_iface.channels[ch]; + + switch (cmd) { + case HSI_LL_MSG_OPEN_CONN: + pr_err("ERROR... OPEN_CONN Not supported. Should use OPEN_CONN_OCTECT instead.\n"); + break; + + case HSI_LL_MSG_ECHO: + pr_err("ERROR... HSI_LL_MSG_ECHO not supported.\n"); + break; + + case HSI_LL_MSG_CONN_CLOSED: + switch (channel->tx_state) { + case HSI_LL_TX_STATE_WAIT_FOR_CONN_CLOSED: + channel->tx_state = HSI_LL_TX_STATE_IDLE; + + /* ACWAKE ->LOW */ + ret = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_ACWAKE_DOWN, NULL); + if (ret == 0) + pr_debug("ACWAKE pulled low in %s()\n", __func__); + else + pr_err("ACWAKE pulled low in %s() ERROR : %d\n", __func__, ret); + + pr_debug("[%s] Received CONN_CLOSED. ch-> %d\n", __func__,ch); + break; + + default: + pr_err("Wrong STATE for CONN_CLOSED\n"); + } + break; + + case HSI_LL_MSG_CANCEL_CONN: + pr_debug("Received CANCEL_CONN\n"); + break; + + case HSI_LL_MSG_ACK: + switch (channel->tx_state) { + case HSI_LL_TX_STATE_WAIT_FOR_ACK: + case HSI_LL_TX_STATE_SEND_OPEN_CONN: + //printk(KERN_INFO "ACK received %s()\n",__func__); + + channel->tx_state = HSI_LL_TX_STATE_TX; + size = param; +#if 0 + // TEMP: send/read by 16 byte unit for v.11A(CP) + if ((size > 16) && (size % 16)) + size += (16 - (size % 16)); + else if (size < 16) + size = 16; +#endif + + // For es 2.1 ver. + if (size % 4) + size += (4 - (size % 4)); + + pr_debug("Writing %d bytes data on channel %d, tx_buf = %x, in %s()\n", size, ch, channel->tx_buf, __func__); + ret = hsi_proto_write(ch, channel->tx_buf, size); + channel->tx_state = HSI_LL_TX_STATE_WAIT_FOR_CONN_CLOSED; + wake_up_interruptible(&ipc_write_wait); + channel->tx_nak_count = 0; + break; + + case HSI_LL_TX_STATE_CLOSED:/* ACK as response to CANCEL_CONN */ + if (channel->rx_state == HSI_LL_RX_STATE_WAIT_FOR_CANCEL_CONN_ACK) + channel->rx_state = HSI_LL_RX_STATE_IDLE; + break; + + case HSI_LL_TX_STATE_WAIT_FOR_CONF_ACK: /* ACK as response to CONF_RATE */ + //TODO: SET CONF RATE + pr_debug("ACK Received for CONF_RATE\n"); + break; + + default: + pr_err("ACK Received for Unknown state\n"); + } + break; + + case HSI_LL_MSG_NAK: + switch (channel->tx_state) { + case HSI_LL_TX_STATE_WAIT_FOR_ACK: + printk(KERN_INFO "(%s) NAK received. ch->%d\n", __func__, ch); + //channel->tx_state = HSI_LL_TX_STATE_NACK; + if (channel->tx_nak_count < 10) { + msleep(10); + + tmp_cmd = ((HSI_LL_MSG_OPEN_CONN_OCTET & 0x0000000F) << 28) | + ((ch & 0x000000FF) << 24); + for (i = 49; i >= 0; i--) { + if ((hsi_cmd_history.tx_cmd[i] & 0xFFF00000) == tmp_cmd) + break; + } + size = (hsi_cmd_history.tx_cmd[i] & 0x000FFFFF); + + pr_debug("(%s) Re Send OPEN CONN ch->%d, size->%d, count->%d\n", __func__, ch, size, channel->tx_nak_count); + + hsi_protocol_send_command(HSI_LL_MSG_OPEN_CONN_OCTET, ch, size); + channel->tx_nak_count++; + } else { + hsi_protocol_send_command(HSI_LL_MSG_BREAK, ch, size); + pr_debug("(%s) Sending MSG_BREAK. ch->%d\n", __func__, ch); + //TODO Reset All channels and inform IPC write about failure (Possibly by sending signal) + } + break; + + case HSI_LL_TX_STATE_WAIT_FOR_CONF_ACK: /* NAK as response to CONF_RATE */ + channel->tx_state = HSI_LL_TX_STATE_IDLE; + break; + + default: + pr_err("ERROR - Received NAK in invalid state. state->%d\n", channel->tx_state); + } + break; + + case HSI_LL_MSG_CONF_RATE: + //TODO: Set Conf Rate + pr_debug("CONF_RATE Received\n"); + break; + + case HSI_LL_MSG_OPEN_CONN_OCTET: + switch (channel->rx_state) { + /* case HSI_LL_RX_STATE_CLOSED: */ + case HSI_LL_RX_STATE_IDLE: + pr_debug("OPEN_CONN_OCTET in %s(), ch-> %d\n", __func__, ch); + channel->rx_state = HSI_LL_RX_STATE_TO_ACK; + hsi_protocol_send_command(HSI_LL_MSG_ACK, ch, param); + + channel->rx_count = param; + channel->rx_state = HSI_LL_RX_STATE_RX; + wake_up_interruptible(&ipc_read_wait); + break; + + case HSI_LL_RX_STATE_BLOCKED: + /* TODO */ + break; + + default: + pr_err("OPEN_CONN_OCTET in invalid state, Current State -> %d\n", channel->rx_state); + pr_info("Sending NAK to channel-> %d\n", ch); + hsi_protocol_send_command(HSI_LL_MSG_NAK, ch, param); + } + break; + + default: + pr_err("Invalid Command encountered in rx_state()\n"); + } + +} diff --git a/drivers/omap_hsi/hsi_protocol_if.c b/drivers/omap_hsi/hsi_protocol_if.c new file mode 100644 index 0000000..ced5dae --- /dev/null +++ b/drivers/omap_hsi/hsi_protocol_if.c @@ -0,0 +1,896 @@ +/* + * File - hsi_protocol_if.c + * + * Implements HSI protocol for Infineon Modem. + * + * Copyright (C) 2011 Samsung Electronics. + * + * Author: Rupesh Gujare <rupesh.g@samsung.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <asm/mach-types.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/ktime.h> +#include <linux/bitmap.h> +#include <linux/poll.h> +#include <linux/kthread.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> + +#include <linux/hsi_driver_if.h> +#include "hsi-protocol-if.h" + +//#define DEBUG 1 +//#define DEBUG_PHY_DATA 1 + +#define HSI_CHANNEL_STATE_UNAVAIL (1 << 0) +#define HSI_CHANNEL_STATE_READING (1 << 1) +#define HSI_CHANNEL_STATE_WRITING (1 << 2) + + +struct if_hsi_iface hsi_protocol_iface; +wait_queue_head_t ipc_read_wait, ipc_write_wait; + + +static void if_hsi_protocol_port_event(struct hsi_device *dev, unsigned int event, + void *arg); +static int __devinit hsi_protocol_probe(struct hsi_device *dev); +static int __devexit hsi_protocol_remove(struct hsi_device *dev); + +static struct hsi_device_driver if_hsi_protocol_driver = { + .ctrl_mask = ANY_HSI_CONTROLLER, + .probe = hsi_protocol_probe, + .remove = __devexit_p(hsi_protocol_remove), + .driver = { + .name = "hsi_protocol" + }, +}; + +struct if_hsi_cmd hsi_cmd_history; +int tx_cmd_history_p = 0; +int rx_cmd_history_p = 0; + +static int if_hsi_read_on(int ch, u32 *data, unsigned int count) +{ + struct if_hsi_channel *channel; + int ret; + + channel = &hsi_protocol_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + + spin_lock(&channel->lock); + if (channel->state & HSI_CHANNEL_STATE_READING) { + pr_err("Read still pending on channel %d\n", ch); + spin_unlock(&channel->lock); + return -EBUSY; + } + channel->state |= HSI_CHANNEL_STATE_READING; + channel->rx_data = data; + channel->rx_count = count; + spin_unlock(&channel->lock); + + ret = hsi_read(channel->dev, data, count / 4); + dev_dbg(&channel->dev->device, "%s, ch = %d, ret = %d\n", __func__, ch, + ret); + + return ret; +} + +static void if_hsi_proto_read_done(struct hsi_device *dev, unsigned int size) +{ + struct if_hsi_channel *channel; + struct hsi_event ev; + +#ifdef DEBUG_PHY_DATA + u32 *tmp; + u32 i; +#endif + + //printk(KERN_INFO "if_hsi_proto_read_done() is called for ch-> %d\n", dev->n_ch); + channel = &hsi_protocol_iface.channels[dev->n_ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, dev->n_ch); + spin_lock(&channel->lock); + channel->state &= ~HSI_CHANNEL_STATE_READING; + ev.event = HSI_EV_IN; + ev.data = channel->rx_data; + ev.count = 4 * size; + spin_unlock(&channel->lock); + +#ifdef DEBUG_PHY_DATA + //Check received data -> Commented as it adds delay which causes MSG_BREAK + tmp = channel->rx_data; + printk(KERN_INFO "[%s](%d)(%d) RX = ", __func__, dev->n_ch, ev.count); + for (i = 0; i < ((size > 5) ? 5 : size); i++) { + printk(KERN_INFO "%08x ", *tmp); + tmp++; + } + printk(KERN_INFO "\n"); +#endif + + if_notify(dev->n_ch, &ev); +} + +int if_hsi_read(int ch, u32 *data, unsigned int count) +{ + int ret = 0; + struct if_hsi_channel *channel; + channel = &hsi_protocol_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + ret = if_hsi_read_on(ch, data, count); + return ret; +} + +int if_hsi_poll(int ch) +{ + struct if_hsi_channel *channel; + int ret = 0; + channel = &hsi_protocol_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + ret = hsi_poll(channel->dev); + return ret; +} + +static int if_hsi_write_on(int ch, u32 *address, unsigned int count) +{ + struct if_hsi_channel *channel; + int ret; + + channel = &hsi_protocol_iface.channels[ch]; + + spin_lock(&channel->lock); + if (channel->state & HSI_CHANNEL_STATE_WRITING) { + pr_err("Write still pending on channel %d\n", ch); + printk(KERN_INFO "Write still pending on channel %d\n", ch); + spin_unlock(&channel->lock); + return -EBUSY; + } + + channel->tx_data = address; + channel->tx_count = count; + channel->state |= HSI_CHANNEL_STATE_WRITING; + spin_unlock(&channel->lock); + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + ret = hsi_write(channel->dev, address, count / 4); + return ret; +} + + +static void if_hsi_proto_write_done(struct hsi_device *dev, unsigned int size) +{ + struct if_hsi_channel *channel; + struct hsi_event ev; + +#ifdef DEBUG_PHY_DATA + u32 *tmp; + u32 i; +#endif + + //printk(KERN_INFO "if_hsi_proto_write_done() is called for ch-> %d\n", dev->n_ch); + channel = &hsi_protocol_iface.channels[dev->n_ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, dev->n_ch); + + spin_lock(&channel->lock); + channel->state &= ~HSI_CHANNEL_STATE_WRITING; + ev.event = HSI_EV_OUT; + ev.data = channel->tx_data; + ev.count = 4 * size; + spin_unlock(&channel->lock); + +#ifdef DEBUG_PHY_DATA + //Check Outgoing data, Commented as it adds delay which causes MSG_BREAK + tmp = channel->tx_data; + printk(KERN_INFO "[%s](%d)(%d) TX = ", __func__, dev->n_ch, ev.count); + for (i = 0; i < ((size > 5) ? 5 : size); i++) { + printk(KERN_INFO "%08x ", *tmp); + tmp++; + } + printk(KERN_INFO "\n"); +#endif + + if_notify(dev->n_ch, &ev); + +} + +int if_hsi_write(int ch, u32 *data, unsigned int count) +{ + int ret = 0; + struct if_hsi_channel *channel; + channel = &hsi_protocol_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + ret = if_hsi_write_on(ch, data, count); + return ret; +} + +void if_hsi_cancel_read(int ch) +{ + struct if_hsi_channel *channel; + channel = &hsi_protocol_iface.channels[ch]; + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch); + if (channel->state & HSI_CHANNEL_STATE_READING) + hsi_read_cancel(channel->dev); + spin_lock(&channel->lock); + channel->state &= ~HSI_CHANNEL_STATE_READING; + spin_unlock(&channel->lock); +} + +void if_hsi_set_wakeline(int ch, unsigned int state) +{ + struct if_hsi_channel *channel; + channel = &hsi_protocol_iface.channels[ch]; + hsi_ioctl(channel->dev, + state ? HSI_IOCTL_ACWAKE_UP : HSI_IOCTL_ACWAKE_DOWN, NULL); +} + + +static void if_hsi_protocol_port_event(struct hsi_device *dev, unsigned int event, + void *arg) +{ + struct hsi_event ev; + int i; + + ev.event = HSI_EV_EXCEP; + ev.data = (u32 *) 0; + ev.count = 0; + + + switch (event) { + case HSI_EVENT_BREAK_DETECTED: + pr_debug("%s, HWBREAK detected\n", __func__); + ev.data = (u32 *) HSI_HWBREAK; + for (i = 0; i < HSI_MAX_CHANNELS; i++) { + if (hsi_protocol_iface.channels[i].opened) + if_notify(i, &ev); + } + break; + case HSI_EVENT_HSR_DATAAVAILABLE: + i = (int)arg; + pr_debug("%s, HSI_EVENT_HSR_DATAAVAILABLE channel = %d\n", + __func__, i); + ev.event = HSI_EV_AVAIL; + if (hsi_protocol_iface.channels[i].opened) + if_notify(i, &ev); + break; + case HSI_EVENT_CAWAKE_UP: + pr_debug("%s, CAWAKE up\n", __func__); + break; + case HSI_EVENT_CAWAKE_DOWN: + pr_debug("%s, CAWAKE down\n", __func__); + break; + case HSI_EVENT_ERROR: + pr_debug("%s, HSI ERROR occured\n", __func__); + break; + default: + pr_warning("%s, Unknown event(%d)\n", __func__, event); + break; + } +} + +int if_hsi_openchannel(struct if_hsi_channel *channel) +{ + int ret = 0; + + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, + channel->channel_id); + spin_lock(&channel->lock); + + if (channel->state == HSI_CHANNEL_STATE_UNAVAIL) { + ret = -ENODEV; + goto leave; + } + + if (channel->opened) { + ret = -EBUSY; + goto leave; + } + + if (!channel->dev) { + pr_err("Channel %d is not ready??\n", channel->channel_id); + ret = -ENODEV; + goto leave; + } + spin_unlock(&channel->lock); + + ret = hsi_open(channel->dev); + spin_lock(&channel->lock); + if (ret < 0) { + pr_err("Could not open channel %d\n", channel->channel_id); + goto leave; + } + + channel->opened = 1; + channel->tx_state = HSI_LL_TX_STATE_IDLE; + channel->rx_state = HSI_LL_RX_STATE_TO_CONN_READY; + printk(KERN_INFO "setting channel->opened=1 for channel %d\n", channel->dev->n_ch); +leave: + spin_unlock(&channel->lock); + return ret; +} + +int if_hsi_closechannel(struct if_hsi_channel *channel) +{ + int ret = 0; + + dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, + channel->channel_id); + spin_lock(&channel->lock); + + if (!channel->opened) + goto leave; + + if (!channel->dev) { + pr_err("Channel %d is not ready??\n", channel->channel_id); + ret = -ENODEV; + goto leave; + } + + /* Stop any pending read/write */ + if (channel->state & HSI_CHANNEL_STATE_READING) { + channel->state &= ~HSI_CHANNEL_STATE_READING; + spin_unlock(&channel->lock); + hsi_read_cancel(channel->dev); + spin_lock(&channel->lock); + } + if (channel->state & HSI_CHANNEL_STATE_WRITING) { + channel->state &= ~HSI_CHANNEL_STATE_WRITING; + + spin_unlock(&channel->lock); + hsi_write_cancel(channel->dev); + } else + spin_unlock(&channel->lock); + + hsi_close(channel->dev); + + spin_lock(&channel->lock); + channel->opened = 0; + channel->tx_state = HSI_LL_TX_STATE_CLOSED; + channel->rx_state = HSI_LL_RX_STATE_CLOSED; +leave: + spin_unlock(&channel->lock); + return ret; +} + + +/* Read Thread +* Should be responsible for handling commands +* Should wait on port events - waitqueue +* +*/ +static int hsi_read_thrd(void *data) +{ + u32 cmd_data[4], cmd, channel, param = 0; + int ret; + + printk(KERN_INFO "Inside read thread\n"); + while (1) { + /*Call hsi_proto_read*/ + /*Read 16 bytes due to Modem limitation*/ + //hsi_proto_read(0, cmd_data, (4 * 4)); + + // For es 2.1 ver. + hsi_proto_read(0, cmd_data, 4); + + hsi_cmd_history.rx_cmd[rx_cmd_history_p] = cmd_data[0]; + hsi_cmd_history.rx_cmd_time[rx_cmd_history_p] = CURRENT_TIME; + rx_cmd_history_p++; + if (rx_cmd_history_p >= 50) + rx_cmd_history_p = 0; + + /*Decode Command*/ + ret = hsi_decode_cmd(&cmd_data[0], &cmd, &channel, ¶m); + if (ret != 0) { + pr_err("Can not decode command\n"); + } else { + printk(KERN_INFO "%s(),CMD Received-> %x, ch-> %d, param-> %d.\n", __func__, cmd, channel, param); + /*Rx State Machine*/ + rx_stm(cmd, channel, param); + } + } + return 0; +} + + +int hsi_start_protocol(void) +{ + struct hst_ctx tx_config; + struct hsr_ctx rx_config; + int i, ret = 0; + + printk(KERN_INFO "In function %s()\n", __func__); + /*Open All channels */ + for (i = 0; i <= 5; i++) { + ret = if_hsi_openchannel(&hsi_protocol_iface.channels[i]); + if (ret < 0) + pr_err("Can not Open channel->%d . Can not start HSI protocol\n", i); + else + printk(KERN_INFO "Channel->%d Open Successful\n", i); + + /*Set Rx Config*/ + hsi_ioctl(hsi_protocol_iface.channels[i].dev, HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 1; + rx_config.channels = HSI_MAX_CHANNELS; + ret = hsi_ioctl(hsi_protocol_iface.channels[i].dev, HSI_IOCTL_SET_RX, &rx_config); + if (ret == 0) + printk(KERN_INFO "SET_RX Successful for ch->%d\n", i); + + /*Set Tx Config*/ + hsi_ioctl(hsi_protocol_iface.channels[i].dev, HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 1; + tx_config.channels = HSI_MAX_CHANNELS; + ret = hsi_ioctl(hsi_protocol_iface.channels[i].dev, HSI_IOCTL_SET_TX, &tx_config); + if (ret == 0) + printk(KERN_INFO "SET_TX Successful for ch->%d\n", i); + } + /*Make channel-0 tx_state to IDLE*/ + hsi_protocol_iface.channels[0].tx_state = HSI_LL_TX_STATE_IDLE; + return ret; +} +EXPORT_SYMBOL(hsi_start_protocol); + +static int hsi_protocol_proc(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + char *p = page; + int len, i; + + p += sprintf(p, "======= HISTORY OF CMD =======\n"); + p += sprintf(p, " tx_cmd_history_p : %d\n", tx_cmd_history_p); + p += sprintf(p, " rx_cmd_history_p : %d\n", rx_cmd_history_p); + for (i = 0; i < 50; i++) { + p += sprintf(p, " [%d] tx : 0x%08x(%lu.%09lu), rx : 0x%08x(%lu.%09lu)\n", + i, hsi_cmd_history.tx_cmd[i], (unsigned long)hsi_cmd_history.tx_cmd_time[i].tv_sec, (unsigned long)hsi_cmd_history.tx_cmd_time[i].tv_nsec, + hsi_cmd_history.rx_cmd[i], (unsigned long)hsi_cmd_history.rx_cmd_time[i].tv_sec, (unsigned long)hsi_cmd_history.rx_cmd_time[i].tv_nsec); + } + p += sprintf(p, "======= HISTORY OF CMD =======\n"); + + len = (p - page) - off; + if (len < 0) + len = 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return len; +} + +int __devexit hsi_protocol_remove(struct hsi_device *dev) +{ + struct if_hsi_channel *channel; + unsigned long *address; + int port, ret; + + //dev_dbg(&dev->device, "%s, port = %d, ch = %d\n", __func__, dev->n_p, + // dev->n_ch); + + for (port = 0; port < HSI_MAX_PORTS; port++) { + if (if_hsi_protocol_driver.ch_mask[port]) + break; + } + + address = &if_hsi_protocol_driver.ch_mask[port]; + + spin_lock_bh(&hsi_protocol_iface.lock); + if (test_bit(dev->n_ch, address) && (dev->n_p == port)) { + hsi_set_read_cb(dev, NULL); + hsi_set_write_cb(dev, NULL); + hsi_set_port_event_cb(dev, NULL); + channel = &hsi_protocol_iface.channels[dev->n_ch]; + channel->dev = NULL; + channel->state = HSI_CHANNEL_STATE_UNAVAIL; + ret = 0; + } + spin_unlock_bh(&hsi_protocol_iface.lock); + + return ret; +} + +int __devinit hsi_protocol_probe(struct hsi_device *dev) +{ + struct if_hsi_channel *channel; + unsigned long *address; + int port; + + printk(KERN_INFO "Inside Function %s\n", __func__); + for (port = 0; port < HSI_MAX_PORTS; port++) { + if (if_hsi_protocol_driver.ch_mask[port]) + break; + } + + address = &if_hsi_protocol_driver.ch_mask[port]; + + spin_lock_bh(&hsi_protocol_iface.lock); + if (test_bit(dev->n_ch, address) && (dev->n_p == port)) { + printk(KERN_INFO "Regestering callback functions\n"); + hsi_set_read_cb(dev, if_hsi_proto_read_done); + hsi_set_write_cb(dev, if_hsi_proto_write_done); + hsi_set_port_event_cb(dev, if_hsi_protocol_port_event); + channel = &hsi_protocol_iface.channels[dev->n_ch]; + channel->dev = dev; + channel->state = 0; + channel->rx_state = HSI_LL_RX_STATE_CLOSED; + channel->tx_state = HSI_LL_TX_STATE_CLOSED; + channel->tx_count = 0; + channel->rx_count = 0; + channel->tx_nak_count = 0; + channel->rx_nak_count = 0; + channel->rx_buf = NULL; + channel->tx_buf = NULL; + hsi_protocol_iface.init_chan_map ^= (1 << dev->n_ch); + } + spin_unlock_bh(&hsi_protocol_iface.lock); + + return 0; + +} + + +int __init if_hsi_init(void) +{ + struct if_hsi_channel *channel; + int i, ret; + struct proc_dir_entry *dir; + + for (i = 0; i < HSI_MAX_PORTS; i++) + if_hsi_protocol_driver.ch_mask[i] = 0; + + for (i = 0; i < HSI_MAX_CHANNELS; i++) { + channel = &hsi_protocol_iface.channels[i]; + channel->dev = NULL; + channel->opened = 0; + channel->state = HSI_CHANNEL_STATE_UNAVAIL; + channel->channel_id = i; + spin_lock_init(&channel->lock); + } + + /*Initialize waitqueue for IPC read*/ + init_waitqueue_head(&ipc_read_wait); + init_waitqueue_head(&ipc_write_wait); + + /*Select All Channels of PORT-1.*/ + if_hsi_protocol_driver.ch_mask[0] = CHANNEL_MASK; + + ret = hsi_register_driver(&if_hsi_protocol_driver); + if (ret) + pr_err("Error while registering HSI driver %d", ret); + + dir = create_proc_read_entry("driver/hsi_cmd", 0, 0, hsi_protocol_proc, NULL); + if (dir == NULL) + printk(KERN_INFO "create_proc_read_entry Fail.\n"); + printk(KERN_INFO "create_proc_read_entry Done.\n"); + + return ret; +} + +int __devexit if_hsi_exit(void) +{ + struct if_hsi_channel *channel; + unsigned long *address; + int i, port; + + pr_debug("%s\n", __func__); + + for (port = 0; port < HSI_MAX_PORTS; port++) { + if (if_hsi_protocol_driver.ch_mask[port]) + break; + } + + address = &if_hsi_protocol_driver.ch_mask[port]; + + for (i = 0; i < HSI_MAX_CHANNELS; i++) { + channel = &hsi_protocol_iface.channels[i]; + if (channel->opened) { + if_hsi_set_wakeline(i, HSI_IOCTL_ACWAKE_DOWN); + if_hsi_closechannel(channel); + } + } + + hsi_unregister_driver(&if_hsi_protocol_driver); + return 0; + +} + +u32 initialization = 0; + +/*Write data to channel*/ +int write_hsi(u32 ch, u32 *data, int length) +{ + int ret; + //u32 cmd[4] = {0x00000000, 0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC}; + struct if_hsi_channel *channel; + struct task_struct *read_thread; + + channel = &hsi_protocol_iface.channels[ch]; + channel->tx_buf = data; + channel->tx_count = 0; + + //cmd[0] = protocol_create_cmd(HSI_LL_MSG_OPEN_CONN_OCTET, ch, (void *)&length); + //printk(KERN_INFO "data ptr is %x\n", data); + + if (initialization == 0) { + +#if 0 + /* ACWAKE ->HIGH */ + ret = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_ACWAKE_UP, NULL); + if (ret == 0) + printk(KERN_INFO "ACWAKE pulled high in %s()\n", __func__); + else + printk(KERN_INFO "ACWAKE pulled high in %s() ERROR : %d\n", __func__, ret); +#endif + + /*Creating read thread*/ + read_thread = kthread_run(hsi_read_thrd, NULL, "hsi_read_thread"); + + initialization++; + } + /*Wait till previous data transfer is over*/ + while (channel->tx_state != HSI_LL_TX_STATE_IDLE) { + //printk(KERN_INFO "Wait 5ms previous data transfer isn't over %s()\n", __func__); + + //msleep(5); + + return -EAGAIN; + } + +#if 1 + /* ACWAKE ->HIGH */ + ret = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_ACWAKE_UP, NULL); + if (ret == 0) + printk(KERN_INFO "ACWAKE pulled high in %s()\n", __func__); + else + printk(KERN_INFO "ACWAKE pulled high in %s() ERROR : %d\n", __func__, ret); +#endif + + channel->tx_state = HSI_LL_TX_STATE_WAIT_FOR_ACK; + + //send_cmd(cmd, channel, data) + //ret = hsi_proto_write(0, &cmd, 4*4); + //printk(KERN_INFO "Write returned %d\n", ret); + hsi_protocol_send_command(HSI_LL_MSG_OPEN_CONN_OCTET, ch, length); + + wait_event_interruptible(ipc_write_wait, channel->tx_count != 0); + + return channel->tx_count; + + +} +EXPORT_SYMBOL(write_hsi); + + +int read_hsi(u8 *data, u32 ch, u32 *length) +{ + int ret, size, tmp, actual_length; + struct if_hsi_channel *channel; + + channel = &hsi_protocol_iface.channels[ch]; + channel->rx_state = HSI_LL_RX_STATE_IDLE; + + //printk(KERN_INFO "In read_hsi() function, Sleeping ... channel-> %d\n", ch); + wait_event_interruptible(ipc_read_wait, (channel->rx_count != 0)); + //printk(KERN_INFO "In read_hsi() function, Waking Up ... channel-> %d\n", ch); + + actual_length = channel->rx_count; + size = channel->rx_count; + +#if 0 + // TEMP: send/read by 16 byte unit for v.11A(CP) + if ((size > 16) && (size % 16)) + size += (16 - (size % 16)); + else if (size < 16) + size = 16; +#endif + + // For es 2.1 ver. + if (size % 4) + size += (4 - (size % 4)); + + ret = hsi_proto_read(ch, (u32 *)data, size); + if (ret < 0) + printk(KERN_INFO "Read in IPC failed, %s()\n", __func__); + + //printk(KERN_INFO "%s() read returned %d, actual_length = %d, ch-> %d\n", __func__, ret, actual_length, ch); + //printk(KERN_INFO "%s() sending CONN_CLOSED.\n", __func__); + tmp = hsi_protocol_send_command(HSI_LL_MSG_CONN_CLOSED, ch, 0); + //printk(KERN_INFO "%s() Sending CONN_CLOSED Finished. ret = %d\n", __func__, tmp); + + *length = actual_length; + channel->rx_count = 0; + + //printk(KERN_INFO "%s() RETURNING TO IPC with ret = %d\n", __func__, ret); + return ret; + +} +EXPORT_SYMBOL(read_hsi); + + +//========================================================// +// ++ Flashless Boot. ++ // +//========================================================// +int hsi_start_protocol_single(void) +{ + int ret = 0; + + struct hst_ctx tx_config; + struct hsr_ctx rx_config; + + /*Open channel 0 */ + ret = if_hsi_openchannel(&hsi_protocol_iface.channels[0]); + if (ret < 0) { + pr_err("Can not Open channel 0. Can not start HSI protocol\n"); + goto err; + } else + printk(KERN_INFO "if_hsi_openchannel() returned %d\n", ret); + + + /*Set Tx Config*/ + hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.channels = 1; + tx_config.divisor = 0; + ret = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_SET_TX, &tx_config); + if (ret < 0) { + printk(KERN_INFO "write_hsi_direct : SET_TX Fail : %d\n", ret); + return ret; + } + + hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.channels = 1; + rx_config.divisor = 0; + //rx_config.timeout = HZ / 2; + ret = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_SET_RX, &rx_config); + if (ret < 0) { + printk(KERN_INFO "write_hsi_direct : SET_RX Fail : %d\n", ret); + return ret; + } + + /* ACWAKE ->HIGH */ + ret = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_ACWAKE_UP, NULL); + if (ret == 0) + printk(KERN_INFO "ACWAKE pulled high in %s()\n", __func__); + +err: + + return ret; +} +EXPORT_SYMBOL(hsi_start_protocol_single); + +int hsi_reconfigure_protocol(void) +{ + int ret = 0; + + /* ACWAKE ->LOW */ + ret = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_ACWAKE_DOWN, NULL); + if (ret == 0) + printk(KERN_INFO "ACWAKE pulled low in %s()\n", __func__); + else + printk(KERN_INFO "ACWAKE down fail!! %d\n", ret); + + + /*Clse channel 0 */ + ret = if_hsi_closechannel(&hsi_protocol_iface.channels[0]); + if (ret < 0) { + pr_err("Can not Close channel 0. Can not Stop HSI protocol for flashless\n"); + goto err; + } + + + printk(KERN_INFO "(%s)(%d) hsi_start_protocol Start.\n", __func__, __LINE__); + hsi_start_protocol(); + printk(KERN_INFO "(%s)(%d) hsi_start_protocol Done.\n", __func__, __LINE__); + +err: + + return ret; +} +EXPORT_SYMBOL(hsi_reconfigure_protocol); + +int write_hsi_direct(u32 *data, int length) +{ + int retval = 0; +#if 0 + struct hst_ctx tx_config; + + + printk(KERN_INFO "write_hsi_direct : len : %d\n", length); + hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.channels = 1; + tx_config.divisor = 47; + retval = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_SET_TX, &tx_config); + if (retval < 0) { + printk(KERN_INFO "write_hsi_direct : SET_TX Fail : %d\n", retval); + return retval; + } + printk(KERN_INFO "write_hsi_direct : SET_TX Successful\n"); + + retval = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_ACWAKE_UP, NULL); + if (retval < 0) { + printk(KERN_INFO "write_hsi_direct : ACWAKE High Fail : %d\n", retval); + return retval; + } +#endif + +#if 0 + if ((length > 16) && (length % 4)) + length += (4 - (length % 4)); + else if (length < 16) + length = 16; +#endif + +// printk(KERN_INFO "write_hsi_direct : new len : %d\n", length); + + retval = hsi_proto_write(0, data, length); + if (retval < 0) { + printk(KERN_INFO "write_hsi_direct : hsi_proto_write Fail : %d\n", retval); + return retval; + } + //printk(KERN_INFO "write_hsi_direct : Write returned %d\n", retval); + + return retval; +} +EXPORT_SYMBOL(write_hsi_direct); + +int read_hsi_direct(u32 *data, int length) +{ + int retval = 0; +#if 0 + struct hsr_ctx rx_config; + + + printk(KERN_INFO "read_hsi_direct : len : %d\n", length); + hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.channels = 1; + rx_config.divisor = 47; + retval = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_SET_RX, &rx_config); + if (retval < 0) { + printk(KERN_INFO "read_hsi_direct : SET_RX Fail : %d\n", retval); + return retval; + } + printk(KERN_INFO "read_hsi_direct : SET_RX Successful\n"); + + retval = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_ACWAKE_UP, NULL); + if (retval < 0) { + printk(KERN_INFO "read_hsi_direct : ACWAKE High Fail : %d\n", retval); + return retval; + } + printk(KERN_INFO "read_hsi_direct : ACWAKE High\n"); +#endif + +#if 0 + if ((length > 16) && (length % 4)) + length += (4 - (length % 4)); + else if (length < 16) + length = 16; +#endif + //printk(KERN_INFO "read_hsi_direct : new len : %d\n", length); + + retval = hsi_proto_read(0, data, length); + if (retval < 0) { + printk(KERN_INFO "read_hsi_direct : hsi_proto_read Fail : %d\n", retval); + return retval; + } + //printk(KERN_INFO "read_hsi_direct : Read returned %d\n", retval); + + return retval; +} +EXPORT_SYMBOL(read_hsi_direct); + +//========================================================// +// -- Flashless Boot. -- // +//========================================================// diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c index 2f2f9a6..61b6471 100644 --- a/drivers/power/max17040_battery.c +++ b/drivers/power/max17040_battery.c @@ -107,6 +107,11 @@ static void max17040_reset(struct i2c_client *client) { max17040_write_reg(client, MAX17040_CMD_MSB, 0x54); max17040_write_reg(client, MAX17040_CMD_LSB, 0x00); + + msleep(125); + + max17040_write_reg(client, MAX17040_MODE_MSB, 0x40); + max17040_write_reg(client, MAX17040_MODE_LSB, 0x00); } static void max17040_get_vcell(struct i2c_client *client) @@ -118,7 +123,7 @@ static void max17040_get_vcell(struct i2c_client *client) msb = max17040_read_reg(client, MAX17040_VCELL_MSB); lsb = max17040_read_reg(client, MAX17040_VCELL_LSB); - chip->vcell = (msb << 4) + (lsb >> 4); + chip->vcell = ((msb << 4) + (lsb >> 4)) * 1250; } static void max17040_get_soc(struct i2c_client *client) @@ -130,7 +135,7 @@ static void max17040_get_soc(struct i2c_client *client) msb = max17040_read_reg(client, MAX17040_SOC_MSB); lsb = max17040_read_reg(client, MAX17040_SOC_LSB); - chip->soc = msb; + chip->soc = min(msb, (u8)100); } static void max17040_get_version(struct i2c_client *client) @@ -187,7 +192,7 @@ static void max17040_work(struct work_struct *work) max17040_get_online(chip->client); max17040_get_status(chip->client); - schedule_delayed_work(&chip->work, MAX17040_DELAY); + schedule_delayed_work(&chip->work, msecs_to_jiffies(MAX17040_DELAY)); } static enum power_supply_property max17040_battery_props[] = { @@ -233,7 +238,7 @@ static int __devinit max17040_probe(struct i2c_client *client, max17040_get_version(client); INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work); - schedule_delayed_work(&chip->work, MAX17040_DELAY); + schedule_delayed_work(&chip->work, msecs_to_jiffies(MAX17040_DELAY)); return 0; } @@ -263,7 +268,7 @@ static int max17040_resume(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - schedule_delayed_work(&chip->work, MAX17040_DELAY); + schedule_delayed_work(&chip->work, msecs_to_jiffies(MAX17040_DELAY)); return 0; } diff --git a/drivers/rpmsg/rpmsg_omx.c b/drivers/rpmsg/rpmsg_omx.c index b27095f..75fd315 100644 --- a/drivers/rpmsg/rpmsg_omx.c +++ b/drivers/rpmsg/rpmsg_omx.c @@ -34,6 +34,7 @@ #include <linux/sched.h> #include <linux/rpmsg.h> #include <linux/rpmsg_omx.h> +#include <linux/pm_qos_params.h> #include <mach/tiler.h> @@ -52,6 +53,7 @@ struct rpmsg_omx_service { struct device *dev; struct rpmsg_channel *rpdev; int minor; + struct pm_qos_request_list *pm_qos; }; struct rpmsg_omx_instance { @@ -140,8 +142,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: @@ -294,6 +298,9 @@ static int rpmsg_omx_open(struct inode *inode, struct file *filp) dev_info(omxserv->dev, "local addr assigned: 0x%x\n", omx->ept->addr); + /* Request for CORE latency. REVISIT: Move to proper code path */ + pm_qos_add_request(omxserv->pm_qos, PM_QOS_CPU_DMA_LATENCY, 400); + return 0; } @@ -307,6 +314,10 @@ static int rpmsg_omx_release(struct inode *inode, struct file *filp) int use, ret; /* todo: release resources here */ + if (omxserv->pm_qos) { + pm_qos_remove_request(omxserv->pm_qos); + kfree(omxserv->pm_qos); + } /* send a disconnect msg with the OMX instance addr */ hdr->type = OMX_DISCONNECT; @@ -501,6 +512,12 @@ static int rpmsg_omx_probe(struct rpmsg_channel *rpdev) goto clean_cdev; } + omxserv->pm_qos = kzalloc(sizeof(struct pm_qos_request_list), + GFP_KERNEL); + if (!omxserv->pm_qos) { + goto clean_cdev; + } + dev_set_drvdata(&rpdev->dev, omxserv); dev_info(omxserv->dev, "new OMX connection srv channel: %u -> %u!\n", diff --git a/drivers/rpmsg/rpmsg_resmgr.c b/drivers/rpmsg/rpmsg_resmgr.c index 3fbf20d..293e0ed 100644 --- a/drivers/rpmsg/rpmsg_resmgr.c +++ b/drivers/rpmsg/rpmsg_resmgr.c @@ -43,6 +43,8 @@ #define MHZ 1000000 #define MAX_MSG (sizeof(struct rprm_ack) + sizeof(struct rprm_sdma)) +#define RPRM_DEBUG_CONSTRAINTS + static struct dentry *rprm_dbg; static char *regulator_name[] = { @@ -76,6 +78,14 @@ static const char *rname(u32 type) { return rnames[type]; } +#ifdef RPRM_DEBUG_CONSTRAINTS +static struct rprm_constraints_data test_data = { + .mask = 0x6, + .latency = 10, + .bandwidth = 800000, +}; +#endif + struct rprm_elem { struct list_head next; u32 src; @@ -532,6 +542,13 @@ static int rprm_rpres_request(struct rprm_elem *e, int type) } e->handle = res; +#ifdef RPRM_DEBUG_CONSTRAINTS + if (!strcmp(res_name, "rpres_iss")) { + _set_constraints(e, &test_data); + e->constraints->mask = test_data.mask; + } +#endif + return 0; } diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index 5dde5fc..f6ff61b 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -484,8 +484,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; @@ -532,8 +534,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/musb/musb_core.c b/drivers/usb/musb/musb_core.c index f7f0872..17b1e67 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -915,6 +915,7 @@ 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); @@ -926,12 +927,11 @@ void musb_start(struct musb *musb) 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); diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 6aeb363..9412410 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1704,7 +1704,9 @@ static int musb_gadget_pullup(struct usb_gadget *gadget, int is_on) spin_lock_irqsave(&musb->lock, flags); if (is_on != musb->softconnect) { musb->softconnect = is_on; + pm_runtime_get_sync(musb->controller); musb_pullup(musb, is_on); + pm_runtime_put(musb->controller); } spin_unlock_irqrestore(&musb->lock, flags); return 0; @@ -1917,13 +1919,6 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, 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); 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..17ca5f1 --- /dev/null +++ b/drivers/video/omap2/displays/panel-s6e8aa0.c @@ -0,0 +1,982 @@ +/* + * 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/delay.h> +#include <linux/err.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/backlight.h> +#include <linux/fb.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <linux/mutex.h> +#include <linux/i2c.h> + + +#include <video/omapdss.h> + +#include <linux/platform_data/panel-s6e8aa0.h> + +/* DSI Command Virtual channel */ +#define CMD_VC_CHANNEL 1 + +#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 = 80842, + .hfp = 158, + .hsw = 2, + .hbp = 160, + .vfp = 13, + .vsw = 1, + .vbp = 2, +}; + +static const struct s6e8aa0_gamma_adj_points default_gamma_adj_points = { + .v0 = BV_0, + .v1 = BV_1, + .v15 = BV_15, + .v35 = BV_35, + .v59 = BV_59, + .v87 = BV_87, + .v171 = BV_171, + .v255 = BV_255, +}; + +struct s6e8aa0_gamma_reg_offsets { + s16 v[3][7]; +}; + +struct s6e8aa0_data { + struct mutex lock; + + struct omap_dss_device *dssdev; + struct backlight_device *bldev; + bool enabled; + u8 rotate; + bool mirror; + bool use_dsi_bl; + unsigned int bl; + const struct s6e8aa0_gamma_adj_points *gamma_adj_points; + struct s6e8aa0_gamma_reg_offsets gamma_reg_offsets; + u32 color_mult[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; +}; + +const u8 s6e8aa0_init_pre[] = { + 0xF0, + 0x5A, + 0x5A, +}; + +const u8 s6e8aa0_mtp_unlock[] = { + 0xF1, + 0x5A, + 0x5A, +}; + +const u8 s6e8aa0_mtp_lock[] = { + 0xF1, + 0xA5, + 0xA5, +}; + +const u8 s6e8aa0_init_panel[] = { + 0xF8, + 0x25, + 0x34, + 0x00, + 0x00, + 0x00, + 0x8D, + 0x00, + 0x43, + 0x6E, + 0x10, + 0x27, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x20, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x02, + 0x08, + 0x08, + 0x23, + 0x23, + 0xC0, + 0xC1, + 0x01, + 0x81, + 0xC1, + 0x00, + 0xC8, + 0xC1, + 0xD3, + 0x01, +}; + +const u8 s6e8aa0_init_display[] = { + 0xF2, + 0x80, + 0x03, + 0x0D, +}; + +const u8 s6e8aa0_init_post0[] = { + 0xF6, + 0x00, + 0x02, + 0x00, +}; + +const u8 s6e8aa0_init_post1[] = { + 0xB6, + 0x0C, + 0x02, + 0x03, + 0x32, + 0xFF, + 0x44, + 0x44, + 0xC0, + 0x00, +}; + +const u8 s6e8aa0_init_post2[] = { + 0xD9, + 0x14, + 0x40, + 0x0C, + 0xCB, + 0xCE, + 0x6E, + 0xC4, + 0x0F, + 0x40, + 0x40, + 0xCE, + 0x00, + 0x60, + 0x19, +}; + +static int s6e8aa0_write(struct omap_dss_device *dssdev, u8 val) +{ + return dsi_vc_dcs_write(dssdev, 1, &val, 1); +} + +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 * + int ret; + msleep(10); // XxX: why do we have to wait + + ret = dsi_vc_dcs_write(dssdev, 1, (u8 *)data, len); + msleep(10); // XxX: why do we have to wait + return ret; +} + +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); +} + +/*********************** +*** 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_gamma_lookup(struct s6e8aa0_data *s6, + u8 brightness, u32 val, int c) +{ + int i; + u32 bl = 0; + u32 bh = 0; + u32 vl = 0; + u32 vh; + u32 b; + u32 ret; + u64 tmp; + struct panel_s6e8aa0_data *pdata = s6->pdata; + const struct s6e8aa0_gamma_adj_points *bv = s6->gamma_adj_points; + + if (!val) { + b = 0; + } else { + tmp = bv->v255 - bv->v0; + tmp *= brightness; + do_div(tmp, 255); + + tmp *= s6->color_mult[c]; + do_div(tmp, 0xffffffff); + + tmp *= (val - bv->v0); + do_div(tmp, bv->v255 - bv->v0); + b = tmp + bv->v0; + } + + for (i = 0; i < pdata->gamma_table_size; i++) { + bl = bh; + bh = pdata->gamma_table[i].brightness; + if (bh >= b) + break; + } + vh = pdata->gamma_table[i].v[c]; + if (i == 0 || (b - bl) == 0) { + ret = vl = vh; + } else { + vl = pdata->gamma_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 %3d %08x c %d, %08x, " + "found %08x:%08x, v %7d:%7d, ret %7d\n", + __func__, brightness, val, c, b, bl, bh, vl, vh, ret); + + return ret; +} + +static void s6e8aa0_setup_gamma_regs(struct s6e8aa0_data *s6, u8 gamma_regs[]) +{ + 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 v0 = s6e8aa0_gamma_lookup(s6, brightness, BV_0, c); + u32 vx[7]; + u32 v1; + u32 v255; + + v1 = vx[0] = s6e8aa0_gamma_lookup(s6, brightness, bv->v1, c); + adj = 600 - 5 - DIV_ROUND_CLOSEST(600 * v1, v0); + adj -= s6->gamma_reg_offsets.v[c][0]; + if (adj > 140) { + pr_debug("%s: bad adj value %d, v0 %d, v1 %d, c %d\n", + __func__, adj, v0, v1, c); + if ((int)adj < 0) + adj = 0; + else + adj = 140; + } + gamma_regs[c] = adj; + + v255 = s6e8aa0_gamma_lookup(s6, brightness, bv->v255, c); + vx[6] = v255; + adj = 600 - 100 - DIV_ROUND_CLOSEST(600 * v255, v0); + adj -= s6->gamma_reg_offsets.v[c][6]; + if (adj > 380) { + pr_debug("%s: bad adj value %d, v0 %d, v255 %d, c %d\n", + __func__, adj, v0, v255, c); + if ((int)adj < 0) + adj = 0; + else + adj = 380; + } + gamma_regs[3 * 6 + 2 * c] = adj >> 8; + gamma_regs[3 * 6 + 2 * c + 1] = (adj & 0xff); + + vx[1] = s6e8aa0_gamma_lookup(s6, brightness, bv->v15, c); + vx[2] = s6e8aa0_gamma_lookup(s6, brightness, bv->v35, c); + vx[3] = s6e8aa0_gamma_lookup(s6, brightness, bv->v59, c); + vx[4] = s6e8aa0_gamma_lookup(s6, brightness, bv->v87, c); + vx[5] = s6e8aa0_gamma_lookup(s6, brightness, bv->v171, c); + + for (i = 5; i >= 1; i--) { + if (v1 <= vx[i + 1]) { + adj = -1; + } else { + adj = DIV_ROUND_CLOSEST(320 * (v1 - vx[i]), + v1 - vx[i + 1]) - 65; + adj -= s6->gamma_reg_offsets.v[c][i]; + } + if (adj > 255) { + pr_debug("%s: bad adj value %d, " + "vh %d, v %d, c %d\n", + __func__, adj, vx[i + 1], vx[i], c); + if ((int)adj < 0) + adj = 0; + else + adj = 255; + } + gamma_regs[3 * i + c] = adj; + } + } +} + +static int s6e8aa0_update_brightness(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + int ret = 0; + u8 gamma_regs[26]; + + gamma_regs[0] = 0xFA; + gamma_regs[1] = 0x01; + + s6e8aa0_setup_gamma_regs(s6, gamma_regs + 2); + s6e8aa0_write_block(dssdev, gamma_regs, sizeof(gamma_regs)); + s6e8aa0_write_reg(dssdev, 0xF7, 0x01); + + return ret; +} + +static u64 s6e8aa0_voltage_lookup(struct s6e8aa0_data *s6, int c, u32 v) +{ + int i; + u32 vh = ~0, vl = ~0; + u32 bl, 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 void s6e8aa0_adjust_brightness_from_mtp(struct s6e8aa0_data *s6) +{ + int c; + u32 v255[3]; + u64 bc[3]; + u64 bcmax; + int shift; + struct panel_s6e8aa0_data *pdata = s6->pdata; + const struct s6e8aa0_gamma_reg_offsets *offset = &s6->gamma_reg_offsets; + const u16 *factory_v255_regs = pdata->factory_v255_regs; + + for (c = 0; c < 3; c++) { + int scale = s6e8aa0_gamma_lookup(s6, 255, BV_0, c); + v255[c] = DIV_ROUND_CLOSEST((600 - 100 - factory_v255_regs[c] - + offset->v[c][6]) * scale, 600); + bc[c] = s6e8aa0_voltage_lookup(s6, c, v255[c]); + } + + shift = pdata->color_adj.rshift; + if (shift) + for (c = 0; c < 3; c++) + bc[c] = bc[c] * pdata->color_adj.mult[c] >> shift; + + bcmax = 0xffffffff; + for (c = 0; c < 3; c++) + if (bc[c] > bcmax) + bcmax = bc[c]; + + if (bcmax != 0xffffffff) { + pr_warn("s6e8aa: factory calibration info is out of range: " + "scale to 0x%llx\n", bcmax); + bcmax += 1; + shift = fls(bcmax >> 32); + for (c = 0; c < 3; c++) { + bc[c] <<= 32 - shift; + do_div(bc[c], bcmax >> shift); + } + } + + for (c = 0; c < 3; c++) { + s6->color_mult[c] = bc[c]; + pr_info("s6e8aa: c%d, b-%08llx, got v %d, factory wants %d\n", + c, bc[c], s6e8aa0_gamma_lookup(s6, 255, BV_255, c), + v255[c]); + } +} + +static s16 s9_to_s16(s16 v) +{ + return (s16)(v << 7) >> 7; +} + +static void s6e8aa0_read_mtp_info(struct s6e8aa0_data *s6) +{ + int ret; + int c, i; + u8 mtp_data[24]; + 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, 0xD3, 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 < 6; i++) + s6->gamma_reg_offsets.v[c][i] = (s8)mtp_data[c * 8 + i]; + + s6->gamma_reg_offsets.v[c][6] = + s9_to_s16(mtp_data[c * 8 + 6] << 8 | + mtp_data[c * 8 + 7]); + } +} + +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 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->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; + + 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; + } + + 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); + 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); + if (!s6->color_mult[0]) { + s6e8aa0_read_mtp_info(s6); + s6e8aa0_adjust_brightness_from_mtp(s6); + } + s6e8aa0_write_block(dssdev, s6e8aa0_init_pre, ARRAY_SIZE(s6e8aa0_init_pre)); + + s6e8aa0_write(dssdev, 0x11); + + s6e8aa0_write_block(dssdev, s6e8aa0_init_panel, ARRAY_SIZE(s6e8aa0_init_panel)); + s6e8aa0_write_block(dssdev, s6e8aa0_init_display, ARRAY_SIZE(s6e8aa0_init_display)); + s6e8aa0_update_brightness(dssdev); + + s6e8aa0_write_reg(dssdev, 0xF7, 0x01); + + s6e8aa0_write_block(dssdev, s6e8aa0_init_post0, ARRAY_SIZE(s6e8aa0_init_post0)); + s6e8aa0_write_block(dssdev, s6e8aa0_init_post1, ARRAY_SIZE(s6e8aa0_init_post1)); + s6e8aa0_write_block(dssdev, s6e8aa0_init_post2, ARRAY_SIZE(s6e8aa0_init_post1)); + + msleep(250); //XXX: find minimum time + + s6e8aa0_write(dssdev, 0x29); +} + +static int s6e8aa0_power_on(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + int ret = 0; + + 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 */ + s6e8aa0_hw_reset(dssdev); + + /* XXX */ + msleep(100); + s6e8aa0_config(dssdev); + + dsi_video_mode_enable(dssdev, 0x3E); /* DSI_DT_PXLSTREAM_24BPP_PACKED; */ + + s6->enabled = 1; + } + +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) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + int r = 0; + + mutex_lock(&s6->lock); + + 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); + } + + mutex_unlock(&s6->lock); + + return r; +} + +static void s6e8aa0_stop(struct omap_dss_device *dssdev) +{ + struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev); + + mutex_lock(&s6->lock); + + dssdev->manager->disable(dssdev->manager); + + dsi_bus_lock(dssdev); + + s6e8aa0_power_off(dssdev); + + dsi_bus_unlock(dssdev); + + mutex_unlock(&s6->lock); +} + +static void s6e8aa0_disable(struct omap_dss_device *dssdev) +{ + dev_dbg(&dssdev->dev, "disable\n"); + + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) + s6e8aa0_stop(dssdev); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static int s6e8aa0_enable(struct omap_dss_device *dssdev) +{ + dev_dbg(&dssdev->dev, "enable\n"); + + if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) + return -EINVAL; + + return s6e8aa0_start(dssdev); +} + +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) +{ + dev_dbg(&dssdev->dev, "resume\n"); + + if (dssdev->state != OMAP_DSS_DISPLAY_SUSPENDED) + return -EINVAL; + + return s6e8aa0_start(dssdev); +} + +static int s6e8aa0_suspend(struct omap_dss_device *dssdev) +{ + dev_dbg(&dssdev->dev, "suspend\n"); + + if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) + return -EINVAL; + + s6e8aa0_stop(dssdev); + + dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; + + return 0; +} +#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/firmware/Makefile b/firmware/Makefile index 5f43bfb..90709c5 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -141,6 +141,7 @@ fw-shipped-$(CONFIG_USB_SERIAL_XIRCOM) += keyspan_pda/xircom_pgs.fw fw-shipped-$(CONFIG_USB_VICAM) += vicam/firmware.fw fw-shipped-$(CONFIG_VIDEO_CPIA2) += cpia2/stv0672_vp4.bin fw-shipped-$(CONFIG_YAM) += yam/1200.bin yam/9600.bin +fw-shipped-$(CONFIG_TOUCHSCREEN_MMS) += melfas/mms144_ts.fw fw-shipped-all := $(fw-shipped-y) $(fw-shipped-m) $(fw-shipped-) diff --git a/firmware/melfas/mms144_ts.fw.ihex b/firmware/melfas/mms144_ts.fw.ihex new file mode 100755 index 0000000..ad792bb --- /dev/null +++ b/firmware/melfas/mms144_ts.fw.ihex @@ -0,0 +1,565 @@ +:10000000F01F0020C1000000D5000000D700000054
+:1000100000000000000000000000000000000000E0
+:10002000000000000000000000000000D9000000F7
+:100030000000000000000000DB000000DD00000008
+:10004000051E00000F150000351E0000831E000075
+:10005000DF000000ED000000EB150000E7000000ED
+:10006000E7000000E7000000E7000000E7000000F4
+:10007000E7000000E7000000E7000000E7000000E4
+:10008000E7000000E7000000E7000000E7000000D4
+:10009000E7000000E7000000E7000000E7000000C4
+:1000A000E7000000E7000000E7000000E7000000B4
+:1000B000E7000000E7000000E7000000E7000000A4
+:1000C0000348854602F0DCF800480047931E000014
+:1000D000F01F0020FEE7FEE7FEE7FEE7FEE7FEE793
+:1000E000FEE7FEE7FEE7FEE7FEE7FEE7FEE7FEE7E8
+:1000F0000A4603467047FEE7704700000000000014
+:100100004F435441000000000000000000000000C8
+:1001100000000000000000000000000000000000DF
+:100120005241303156413131323031313036313651
+:1001300010B55B2109019E4802F077F85B21C900E8
+:100140009C4802F072F8FF216D319B4802F06DF877
+:1001500000209A49C0439A4A0880108005234880AD
+:100160005080091D121D088010805B1EF7D110BD44
+:10017000F0B594480021415685B0934802910D2274
+:10018000192102608160002141600D46C160EA01D1
+:1001900003926822864B6A43D218019286481A223B
+:1001A0006A4310180090834934200024684346189D
+:1001B00003986200811885480F180421795EB05EAB
+:1001C000B152401A00D54042042803DD00980022B5
+:1001D000025506E000980057052802D0009A401CFE
+:1001E00010550198A300C258C80184460121101A75
+:1001F0008902884211DD74494A68AA4200DA4D60DA
+:10020000CA68A24200DACC600A68AA4200DD0D602A
+:100210008A68A24211DD8C600FE06D49884208DADD
+:10022000002A0AD101996246CA50009900220A5553
+:1002300003E0C111521A0199CA50029900290BD04A
+:10024000FF210131884204DD410262480818001490
+:100250000FE00020B8800DE0002817DD002C03D04F
+:10026000192C01D0C01104E0C0114000032101F09D
+:10027000C1FFB880641C1A2C9ADB6D1C0E2D86DB26
+:1002800051490868022803DD801E04E00020F0E7E1
+:10029000012801DD401E08608868022801DD801EFB
+:1002A00002E0012801DD401E886048680C2801DA60
+:1002B000801C02E00D2801DA401C4860C86818283C
+:1002C00001DA801C02E0192801DA401CC8604248AB
+:1002D000007800280DD041A000F0CBFE344D002462
+:1002E0006820604341191A2000F033FF641C0E2C73
+:1002F000F6DB05B0F0BDF8B500223C4B11460024FA
+:100300000D2095012E19F6187170B170A41C401EB5
+:10031000F8D1521C0E2AF2DB354F36480122D501A6
+:10032000EE19318094012418217071806170324E71
+:10033000AD196983617729832177521C112AEEDB7D
+:100340002E4E2F4B01223D46540067193980171855
+:1003500039702C4FE7193980244FD7193970A719F4
+:100360003980D7183970284FE4192180274C141987
+:100370002170521C1D2AE7DB29801F4A51833180DE
+:10038000234A5183017041771970597721480078C9
+:1003900000280CD020A000F06CFE224D0024E001CB
+:1003A00041191A2000F09DFE641C0E2CF7DBF8BDED
+:1003B00034000020E4050020BC0800208E0D002041
+:1003C000A40D00201100002000000020004100408A
+:1003D00000FCFFFF0000FEFF1B0000205265666569
+:1003E00072656E636520496D61676500402000405D
+:1003F0000040004000200040204000408048004075
+:10040000402400408040004000480040002400405C
+:10041000A04800401C000020496E74656E7369742A
+:100420007900000004410040F0B5D3B00024FF483B
+:10043000C4210668FE4800680394089404940D944F
+:10044000059406940794099412900A940B940C94C2
+:100450000E9421A801F0E9FEF648007800280CD09F
+:10046000F5A000F006FEF74F0025A801C1191A20DB
+:1004700000F0CBFD6D1C0E2DF7DBF3480321415638
+:10048000022901DA032000E00A201A90EF48761CC6
+:1004900005681396B54239DA129A521CEC4B129E3B
+:1004A00018680021361A771C002F01DD0121B143A5
+:1004B0000B1883420FDDAE01E64B3718FB180227FD
+:1004C000DF570C2F01DD00279F70E14B401C1B689C
+:1004D000CB188342F0DCDE4800680818904211DA3D
+:1004E0000026AB01DB491F1879180227CF570C2FC4
+:1004F00000DD8E700327CF570C2F00DDCE70801CDF
+:100500009042EFDB13986D1C8542C7DBCF481399EF
+:100510000068029088427EDA1298401C1B90CC48FA
+:1005200002681B98824277DA0298C0B22090029843
+:10053000C0011C90029880011F908018C549844614
+:100540004318022018561C9D51006D18C2496D18A1
+:100550000421695E002809D003AD2E5C8E4205DCC3
+:10056000295407AE209D35540BAD2A54844200DA3D
+:10057000C4B2002853D11A98814250DDB74DFF20F4
+:1005800065446E780021AF78ED781D96DE7815977A
+:100590006746AC46B24D17967D19EF78AE786D7808
+:1005A00018975F7819961D9E002E03D00121FF2E0B
+:1005B00000D23046159E002E03D0491C864200D240
+:1005C00030466646002E03D0491C844500D2304692
+:1005D000179E002E03D0491C864200D23046189E3A
+:1005E000002E03D0491C864200D23046199E002EB0
+:1005F00003D0491C864200D23046002D03D0491C4E
+:10060000854200D228463D0003D0491C874200D2D3
+:100610002846002901E094E007E000D098701B987C
+:10062000521C824201DA1F9887E702981399401CF6
+:100630000290884200DA72E70220641C1494019050
+:10064000022C7ED9019907A8425C01980BA9085C8D
+:10065000921C801C8446D10140000918814808186A
+:100660000024045F804BCD181E23EB5E1B1D9C42B3
+:1006700021DC0024045F7D4BCD180023EB5E0933A1
+:100680009C4208DD0024045F794BCD181E23EB5EED
+:1006900009339C420FDC9501764C654401232C19EB
+:1006A0000026A657744C2D191F242C570E2721AD58
+:1006B0007C43641933550025455F6C4BCC180223ED
+:1006C0001594E35E1B1D9D421EDC0025455F2346FD
+:1006D00000241C5F0934A54206DD0024045F0223C8
+:1006E000C35E09339C420FDC950164462C19614DB1
+:1006F000012365190026AE57604D6519EC560E278B
+:1007000021AD7C43641933550024045F5C4BCD1844
+:100710001E23EB5E1B1D9C4224DC0024045F594B0E
+:10072000CD180023EB5E09339C4208DD0024045FF2
+:100730004F4BCD181E23EB5E09339C4212DC012384
+:1007400000E0D0E0950164462C194A4D6519002659
+:10075000AE574D4D65191F242C570E2721AD7C43F4
+:10076000641933550024045F464BCB1802251693B9
+:100770005D5F2D1DAC421DDC0025455F00241C5F24
+:100780000934A54206DD0024045F0223C35E093359
+:100790009C420FDC950164462C19364D01236519E6
+:1007A0000026AE572D4D6519EC560E2721AD7C4322
+:1007B000641933550023C35E159C0025655F2D1D0C
+:1007C000AB4210DC950164462C192A4D01236519B2
+:1007D0000026AE57294D651900242C570E2721AD50
+:1007E0007C43641933550025455F169C0023E35E66
+:1007F0001B1D9D4210DC950164462C191D4D0123E3
+:1008000065190026AE57154D651900242C570E2783
+:1008100021AD7C43641933550024045F144BCB187D
+:100820001E21595E091D8C4241DC2FE0040000208E
+:100830000C0000201D00002047726F757020496D6C
+:100840006167650082200040100000200000002049
+:10085000080000208020004000410040402000406F
+:10086000C020004000400040603F0040803F00400A
+:10087000E03F004000200040A01F0040C01F00409B
+:10088000604000408040004020200040012394014F
+:1008900061466118FF4C0D1900242C57FE4D491973
+:1008A0001F254D570E2621A975436918635400234F
+:1008B000C35E0221415E091D8B420CDC9201F548AA
+:1008C0006244012110180023C35642560E2421A869
+:1008D00062431018195401981499401CC0B2019039
+:1008E000884200D2AEE60D2121A801F09EFC022133
+:1008F000012421A80022002908DD0E2353431B18E0
+:100900005B5C012B11D0521C8A42F6DB491C0D297D
+:10091000F0DB002501462A460D2300240C57002C4D
+:100920000FD06D1CEDB2045D0CE08356002B01D09E
+:10093000435400E042540E224A4312185218D47312
+:10094000E4E7541B0C70491C521C5B1EE5D1D349D3
+:100950000E6813998E4215DA129AD14F521CD14962
+:10096000096891420ADAB4016318DB199D7845578A
+:10097000002D00D09D70491C9142F5DB1399761C27
+:100980008E42ECDB53B0F0BDF0B595B0FFF74CFDF7
+:10099000C548C7490568C548C64A04680020C64B13
+:1009A000C64E0A900B900C900D270D900020C0436E
+:1009B00001C1002001C201C33070761C7F1EF5D139
+:1009C000BF48007800280CD0BEA000F052FBC14FF9
+:1009D0000026B001C1191A2000F017FB761C0E2E5C
+:1009E000F7DB00200490AD486D1C0068069505906B
+:1009F000A84257DAAB48641C00680E900E98A042DB
+:100A00004ADA059989018C460599C9010791614621
+:100A10000A18A349511802228A56002A39D004998B
+:100A2000914200DA0492079D41006918AA4D531EB5
+:100A30004D190421695E95009D4E2D1F7759059D26
+:100A400096004D437D199A4F361FBD519200994E25
+:100A5000121FB258002807D0182808D019283DD0F6
+:100A6000C5002D1D4D4304E08D006D4201E0C625FB
+:100A700035E0AA189D0072518F4A56597618565182
+:100A80003432D55C6D1CD5540AAAD55C8D4200DA8F
+:100A9000D154401CA042BADB05980699401C059031
+:100AA0008842ABDB04988D4940B288700790049867
+:100AB000002863DD0AA80F9080480E907E4F884E74
+:100AC0007B4C7C4D0498019001CF0090002848D0C9
+:100AD0000F9800780C2804D2012003E0D4254D4360
+:100AE000C7E7002030707F492068096848430D211E
+:100AF00001F080FB009901F07DFB002120600028BF
+:100B000000DD0146216077492868496848431A2179
+:100B100001F070FB009901F06DFBC117490F081837
+:100B2000C01028600E980078322802D96B49012045
+:100B300008702168002902DA0020206005E0694879
+:100B40000068814200DC084620602968002902DA3A
+:100B50000020286005E063484068814200DC0846C8
+:100B600028600F98761C401C0F900E98241D401C86
+:100B70000E9001982D1D401E0190A5D104980028CB
+:100B80003BD05649002048700020411E4A4C099134
+:100B900006902368544A5B101BB203935348544B8E
+:100BA0000B250021C943118001801970921C801C03
+:100BB0005B1C6D1EAC46F4D1604641004D480A9165
+:100BC000405E0B90401C59D0604680004A4D049016
+:100BD00029584A48081800900590079800282BDDEE
+:100BE00033484349334A079C002305686E1C1DD0D7
+:100BF00000268E57761C04D018E038490120487032
+:100C0000C2E70B9E1768AE1BB6463D4D0A9EAD5F10
+:100C100076467D1B76436D437519009EB54205DA15
+:100C20005EB20996344F049E0095BD51001D491CCB
+:100C3000121D5B1C641ED8D10599009888421DD0F6
+:100C400009981B498000284A0A9B0958D152194922
+:100C50000A9B0A589831CA5264390A5829490A9895
+:100C60000A5216480999425C274861464254069840
+:100C70001F49401C0690099A604688546046401CF3
+:100C800084460B2898DB0020079A0146002A63DD82
+:100C9000144A3BE000200040E01F0040000000201C
+:100CA0008020004008000020040000200C000020EC
+:100CB000480B00207C0B0020B00B0020E40B002030
+:100CC0001D00002047726F757020496D61676532A5
+:100CD0000000000082200040004100401000002081
+:100CE0005C0A0020780C0020FE0B0020140C002071
+:100CF000F10B00204C0C0020280A002010270000D7
+:100D0000620C00202A0C0020400C0020167AA34D13
+:100D1000A34A5256521C1BD1079A012A05DDA14A4B
+:100D20008B00D358039A934212DB9F4A525C002AED
+:100D30000ED19C4A8B00D458B44209DB0B2807DA49
+:100D40009A4A4700D75F7F1C7ED0401C0B28F8DBF7
+:100D5000079A491C9142DBDB954B069898709549A0
+:100D60000020D8708C468D4D9048934A93490B24AF
+:100D700063461E889B1C9C460023CB5E2F889E46A4
+:100D80000780931C16801A4673465B100B80002365
+:100D9000C35EAD1C891C801C5B1C03D0844EF378A1
+:100DA0005B1CF370641EE3D1824A60008718163220
+:100DB00082180592824A7949121913924618142210
+:100DC000804B6243085ED518401C7ED00020285618
+:100DD0000321421C032A00DC114648B20D906888AA
+:100DE000059A40B28446C01CC10F09184908490041
+:100DF000401A40B20E90002000210023305E795E40
+:100E0000D35E0290139802786046400000930492EB
+:100E1000039140190423C35E08219C460C22102331
+:100E2000415E825EC35E0D980F9102280893069280
+:100E300069DD02996046431A08466146401A00D4AB
+:100E4000034603990F9800E086E00F9A401A891A2A
+:100E500000D408460A9000990698069A401A891A02
+:100E600000D40846019004990898421A0846089947
+:100E7000401A00D402460A98109218180A904B2083
+:100E80006246C00042434B210A98C90041184810ED
+:100E900012180991129002990A9841435018099921
+:100EA00001F0A8F900B202904B200F99C000414315
+:100EB00012980A1803990A9841435018099901F0A9
+:100EC00099F900B20390642100E05CE00698484381
+:100ED000019964314A108318009A01984243981886
+:100EE00001F088F900B2009008980101081A1099E1
+:100EF0000F314A108318109A04985043181801F0C3
+:100F000079F900B20490029931800E98400040199E
+:100F10008180039939800181059A0099118081812E
+:100F20000499139AC9B2117001820E9868800D98C5
+:100F300028700D98022809DA0020C04330803880DC
+:100F40001B48C178491EC17000214170641C0B2CE4
+:100F500000DA29E715B0F0BD1B4AD7584200AF525E
+:100F60001A4FFB589837BB52144B9C52184A8400B6
+:100F7000535C5C321354174B00221A51069A521CD0
+:100F8000401C0692E4E600202870401E3080388025
+:100F9000DCE710B500F023F910BD0000FE0B0020C7
+:100FA000F10B0020B00B00205C0A00204C0C00204C
+:100FB00010000020140C0020620C00202A0C0020DD
+:100FC000400C00206A0A0020480B00207C0B002007
+:100FD000E40B0020280A0020FF4A11547047FE4904
+:100FE000085C7047FD4852218170D021C1700021FA
+:100FF00001711C21417101218171F9484122027462
+:10100000417482741121C1747047F8B5F54CE27ACD
+:10101000002A2DD00246F14E403232707070022507
+:10102000002804D0C207D20F012A02D004E0002217
+:1010300002E00B780325B37082420ADA8B5C7355A9
+:101040008B186D1C5F782B466D1CF754921C8242E6
+:10105000F4DB0220E0700025A57000F0C9FE657188
+:10106000A078E1788842FBD300F0C7FE01206071D0
+:10107000F8BDF8B5044601F0E4F8DA4DC3B2E87AF9
+:1010800000282DD01846D54EC03030707370022025
+:10109000002B04D0D907C90F012902D004E0002198
+:1010A00002E022780320B27099420ADA625C32547C
+:1010B0006218401C57780246401CB754891C99425C
+:1010C000F4DB0220E8700024AC7000F091FE6C713B
+:1010D000A878E9788842FBD300F08FFE0120687180
+:1010E000F8BDC04AD27A002A32D0F8B50246BB4FCA
+:1010F00080323A7078700123002804D0C207D20FE2
+:10110000012A02D004E0002202E00B887B80022347
+:1011100082420CDA54000D5B5E00BD535B1C641808
+:1011200064885D007C535B1C921C8242F2DBAD4CF8
+:101130000220E0700025A57000F05AFE6571A078CD
+:10114000E1788842FBD300F058FE01206071F8BDC1
+:101150007047A44AD27A002A40D0F8B502469F4F81
+:1011600080323A700123002804D0C207D20F012A2E
+:1011700002D009E0002207E00B68DC17640EE318D8
+:101180005B021B0C7B800223824216DA94000D590D
+:10119000EE17760E75196D022D0C5E00BD536418A6
+:1011A00064685B1CE5176D0E2C196402240C5D004D
+:1011B0007C535B1C921C8242E8DB8A4C0220E0706C
+:1011C0000025A57000F014FE6571A078E1788842D2
+:1011D000FBD300F012FE01206071F8BD704781481A
+:1011E000C17A002903D080490978C17370477D48CE
+:1011F00000790028FBD1F0B5002083B001460090B3
+:10120000102084467948407A864600200B2903DA6C
+:101210000B20401AC007C00F45188D420FDD744ED9
+:10122000744B75484A00B45E9F5EBC422DD1734C2E
+:10123000A45E825E944228D1491C8D42F2DC28468D
+:101240000B2D21DA6B4A6D4B6B4C4100684D565E9D
+:101250006F5EB74218D15E5E675EBE4214D14D1913
+:101260000226AE5F8D180227EF5FBE4207D1CD1870
+:101270000226AE5F091902254D5FAE4201D0411C26
+:1012800003E0801C0B28E0DB01464B0058485B4C18
+:10129000C25E5848584EC75EE45EF65ED51B0196A6
+:1012A000A61B00D57642002D00DA6D42AE4507DD63
+:1012B000B64505DD501C03D0781C01D0491C26E042
+:1012C0000B2927D0501C34D0481C05077F1C2D0F3C
+:1012D0004248A035664685552511F026354016054D
+:1012E000360FAD19604445708270C470444DED5A9C
+:1012F0000571444D6D5C45713E48C2521630C45272
+:101300006046801D84460098401C491C00900B29B3
+:1013100000DA7AE70098062148433049C87372B66C
+:10132000304C607B00281ED000BF00BF62B603B007
+:10133000F0BD481C0607360F284D203660462E5457
+:10134000019865440611F02006403805000F361854
+:101350006E70AF700198E8702948C05A287129480A
+:10136000405C6871C8E762B60098002805D001208B
+:10137000207100F03DFD002060711B480078E07393
+:1013800003B0F0BDF8B50C0014491F4F054604D05A
+:10139000E207D20F012A02D003E0002201E0387BED
+:1013A0004855A24209DAD019067BAB18CE54407BCF
+:1013B0005B185870921CA242F5DB0A4E0020307177
+:1013C00000F01BFD012171731048052D5CD033DC4A
+:1013D000002D46D0012D1BE0840D0020840C002040
+:1013E000640D00201400002013000020780C002061
+:1013F000FE0B00208E0D0020A40D0020140C0020F8
+:101400002A0C0020400C0020000000502400002086
+:101410002FD0022D0FD1022C0DD93A7B7B7B1207E6
+:10142000120D1A4302603A7BBB7B120912011201B2
+:101430001A4342607173F8BD062D2DD0A02DFAD14C
+:10144000387BF072387B1A2829D0002002460346E8
+:10145000062B58D27B441B79DB189F445432394009
+:10146000474E00243473347100F0C7FCB470F4703C
+:10147000F8BD387B0028FBD131730024347100F0B3
+:10148000BCFCB470F470F8BD3A7B0272027A092A8F
+:10149000D0D209220272CDE73A7B4272CAE702201B
+:1014A00030730024347100F0A8FCB470F470514B18
+:1014B0000020C219127B81B25A54401C0528F8DB67
+:1014C000F8BD7B7B9B0701D5B1711CE0B2711AE0BE
+:1014D0007B7B5B0701D5F17115E0F27113E07B7B3B
+:1014E0001B0701D531720EE032720CE07B7BDB060C
+:1014F00001D5717207E0727205E07B7B9B0601D516
+:10150000B17200E0B272401C0828A0DBF8BDF8B54B
+:1015100005242407207AA67A07073F0F364D012FAE
+:1015200004D0082F29D0022F3BD056E0E8780028BD
+:1015300000D000266E7000F060FC01206871A82EBB
+:1015400015D02E48815D21738019417861738178AF
+:10155000A173C178E1730179217441796174817952
+:10156000A174C079E07468780830687035E021486B
+:10157000017A2173407A6073F5E769781F48425C0D
+:1015800022734018417861738178A173C178E17347
+:1015900001792174417961748179A174C079E07411
+:1015A000E1E7A87B01280BD0082809D0E07A0107E1
+:1015B000090F05D03046FFF7E5FE00202870E870DF
+:1015C000102E04D12879012801D100202871E87853
+:1015D000002802D0A878401CA87001202072AF73A8
+:1015E0001020207200202072F8BD0120052109077B
+:1015F00008727047840D002014000020840C002025
+:10160000F0B5FFB0C8B05B21C90010A800F00DFE16
+:10161000D9480421417000F003FC00F0FDFB00F00C
+:1016200011FC00200090D548417882784808099044
+:10163000D248C17850080A90D048027948080B90E7
+:10164000CE48417950080C90CC48827948080D90DA
+:1016500050080E90CA480178490801914178827873
+:1016600049080291C37851080391027959080491FD
+:10167000437951080591817958080690BF4802784E
+:101680004808079050080890FF2000990022013078
+:10169000401AC4B2019990014618BA4976183470BC
+:1016A000029E861976183470039E86197618347057
+:1016B000049E861976183470059E86197618347043
+:1016C000069E861976183470079E861971180C705C
+:1016D00008994618AC4976183470099E8619761810
+:1016E00034700A9E8619761834700B9E8619761807
+:1016F00034700C9E8619761834700D9E86197618F3
+:1017000034700E9E801940180470521C1A2AC1DBD6
+:1017100000F086FB00F080FB00F094FB0020864682
+:1017200070463421484310A9411870461A2250438C
+:10173000964A7546831800221A20ED01AC4666468B
+:1017400055007619924D75190426AE5F0027CF5FBC
+:10175000BE4202DDAD880D801C70891C5B1C521CD2
+:10176000401EECD17146491C8E460E29D8DB0099EB
+:10177000491C49B20091142986DB7F4902224A7034
+:10178000824E019B8101345CCD187E4B641EED18A6
+:101790002C7032183424A45C029D641E4D19ED187F
+:1017A0002C706824A45C039D641E4D19ED182C70E8
+:1017B0009C24A45C049D641E4D19ED182C70D0244B
+:1017C000A45C059D641E4D19ED182C70FF24053492
+:1017D000A45C069D641E4D19ED182C70FF2439344D
+:1017E000A45C079D641E4D19EB181C70089B927E2B
+:1017F000CD18654B521EED182A700122342415466F
+:101800006543624C2C1925185C4CAE7EA75C761E95
+:101810007F08CF19FF183E70A41840356478AD7B5F
+:1018200064086D1E0C19E4182570921C072AE5DB6C
+:10183000401C1A28A4DB574E307800281BD056A035
+:10184000FFF717FC514D00241A20604341191A205C
+:10185000FFF7DBFB641C0E2CF6DB307800280AD087
+:10186000002410AD3420604341191A20FFF739FCE1
+:10187000641C0E2CF6DB7FB048B0F0BDF8B5012833
+:1018800042D000203F4A0021484C83019A18342559
+:101890003E4E4D43AD192E5C394D143E6D5C6D08C6
+:1018A0005F19384D7D192E70275C0126BE40966168
+:1018B000491CC9B20729EAD3304E334F00213424E2
+:1018C000324D4C4364192418755CA47E6D08143C99
+:1018D0005D19ED192C70354C255C0124AC409463E6
+:1018E000491CC9B20729EAD3401CC0B21A28C9D37F
+:1018F000002400F095FA00F08FFA00F0A3FA641CBF
+:10190000E4B2322CF5D3F8BD00201E4A0021274B4B
+:101910008501AC1834221D4E4A439219165C184AB0
+:10192000525C5208AF18174ABA1816701F5C012291
+:101930001646BE40A661491CC9B20729EAD3002158
+:101940003423124E4B439B191B189E7E0B4B5B5C42
+:101950005B08EF180C4BFB181E70144B1E5C1346F3
+:10196000B340A363491CC9B20729E9D3401CC0B2E4
+:101970001A28CAD3BCE7000040000040E122000062
+:10198000DA2200000010004020100040BA0D0020B4
+:10199000004100403000002044656C617920706592
+:1019A00072206368616E6E656C000000C0220000EA
+:1019B000F0B50020844683B0F7480090F74841789E
+:1019C0000120884001900023F54C604619461F46CF
+:1019D0008001421812199772D772891C0A29F8DB04
+:1019E000F04A0021545CFF2564080619EC4C3619B6
+:1019F0003570565C4D007608AD1C86193419A572F9
+:101A0000555C0124AC40E318491C0729EADBE44992
+:101A100041188E46CB616246921CD2B202920A7580
+:101A2000DE4A6346D35C01229A408A6161461929E5
+:101A300002D0002908D00CE0DB498A69009B01260E
+:101A4000DB7F9E40324303E0D5498A69019B1A43FC
+:101A50008A610023D54A19463D464418A418A57248
+:101A6000E572891C0A29F8DB00210127D04CFF25EB
+:101A7000665C76088619B6183570665C4D00760887
+:101A8000ED1C8619B618B572655C3C46AC40E3188F
+:101A9000491C0729EADBC2497446E3638018029BAC
+:101AA0000375BE486246805C874070468763604627
+:101AB000192A07D000280FD0401C84461A2882DB40
+:101AC00003B0F0BDB848826B0099CB7F01219940EB
+:101AD0000A43826303B0F0BD886B019A10438863A8
+:101AE000012084466FE710B45221480701770C218A
+:101AF0004177962181771821C177AF48AD490180A0
+:101B000000214180AD4981801322C2800181428140
+:101B1000AB4B8381AB4CC481038244828182C282FD
+:101B2000018342838183C28310BC7047F0B40921D2
+:101B3000880741831E210176122141760021A24EA1
+:101B4000A24C0F2208464B009D1928800B191870D3
+:101B500068805870891C521EF5D100218C4663465E
+:101B600000210F229D01DF014B00FC18984BE41867
+:101B70002080984B6E18F318187060805870891C7C
+:101B8000521EF1D16146491C8C461229E7DBF0BC9C
+:101B9000704710B590488221417002214170422364
+:101BA00005221207D372482393724170FFF71AFA85
+:101BB00010BD0021012080078170C160F3210174F4
+:101BC000FF214174704710B584490F200860844993
+:101BD00000200860032100F0C0F80021012000F07F
+:101BE000BCF80221084600F0B8F80121032000F0FB
+:101BF000B4F80121042000F0B0F80221052000F023
+:101C0000ACF80021062000F0A8F87648016904220B
+:101C10001143016110BDF8B50120850768720024E9
+:101C2000EC80CC202871BA2068716B4EB4700F2004
+:101C300028729D21A9727F21E972332131706A498E
+:101C400071603472654908606548046003210020B2
+:101C500000F083F80021012000F07FF802210846FF
+:101C600000F07BF80121032000F077F80121042027
+:101C700000F073F80221052000F06FF80021062023
+:101C800000F06BF857480169042211430161AC7000
+:101C9000EC60F3202874FF206874822070700220AA
+:101CA0007070422205210907CA7248228A72707038
+:101CB000FFF798F9092068831E202876122068769D
+:101CC0000020414E414B0F21420095192C80C21833
+:101CD00014706C805470801C491EF5D1002084461D
+:101CE000624600200F219501D7014200BB18384AF7
+:101CF0009B181C80374A2E18B21814705C805470E0
+:101D0000801C491EF1D16046401C84461228E7DB46
+:101D10005221480701770C214177962181771821BC
+:101D2000C1772548234901804480244981801322BA
+:101D3000C28001814281224B8381224CC481038273
+:101D400044828182C282018342838183C283FFF7FE
+:101D50002FFEFEF7EDF962B6F8BD8307FF22DB0E1A
+:101D60009A408907090E994000280BDA0007000FF6
+:101D7000083883081B489B001818C36993430B431A
+:101D8000C3617047830819489B0018180368934380
+:101D90000B43036070470000B9220000C02200001E
+:101DA00000100040DA2200004016004020100040E1
+:101DB000E122000078080000200000401304000029
+:101DC0001306000013020000004900408024004078
+:101DD00000400040002000404000004000E100E0E2
+:101DE00000E200E000ED00E0FFFF0F0000E400E093
+:101DF000FB200121890748747047FF2001218907D2
+:101E000048747047924948064161924A0121117015
+:101E10000022C275C17570478F4901200872704752
+:101E20008C49002048708C4908720906087001200E
+:101E300008707047874901204870002001218907F8
+:101E400008707047834872B64178002905D062B6A1
+:101E500082480078002805D1704700BF00BF62B6F5
+:101E600030BFF0E770B57EA0FFF703F97F4D002487
+:101E7000E00141191A20FFF734F9641C0E2CF7DB3E
+:101E800070BD7449012088700020012189074870C5
+:101E90007047FFF7C0FE6F48002444706E4F3C72DD
+:101EA0003D062C7001262E703E72FFF7CBFF69486D
+:101EB00044703C722C702E703E72FFF7C3FFFFF728
+:101EC0009FFB644844703C722C702E703E72FFF78A
+:101ED000B9FF604804705E486861EE75644900228D
+:101EE0008A5664480378002B08D00470634E624819
+:101EF00033687568B668866045600360002A60D004
+:101F0000012A04D0022AE9D100F00AF9E6E70C70B0
+:101F10000120800744720F2303720121052212075A
+:101F200051721125C570C470037241725472BFF3AF
+:101F3000608F484E7470484A1472047001701172B8
+:101F400072B67078002832D062B644480078002813
+:101F50000CD043A0FFF78DF8444F0025E801C119CC
+:101F60001A20FFF7BEF86D1C0E2DF7DB74703A488F
+:101F7000047201060C7001220A70027272B6707847
+:101F8000002819D062B63548007800280CD034A05B
+:101F9000FFF76FF8354E0025E80181191A20FFF789
+:101FA000A0F86D1C0E2DF7DBFEF7C2F896E700BF18
+:101FB00000BF62B630BFC3E700BF00BF62B630BF2C
+:101FC000DCE7244D6C702448047206063470012747
+:101FD0003770FEF7CDF8FEF78EF9AC70747077703D
+:101FE00072B6A878002823D062B6FEF7CDFCFEF7C3
+:101FF000D0FF1948077272B6687800281DD062B603
+:102000001648007800280CD015A0FFF732F8174FBB
+:102010000026F001C1191A20FFF763F8761C0E2E76
+:10202000F7DB72B6287800280CD062B62C7055E722
+:1020300000BF00BF62B630BFD2E700BF00BF62B6CC
+:1020400030BFD8E700BF00BF62B630BFE9E700008D
+:10205000A086010031000020400000401A0000204E
+:1020600052617720496D61676500000004410040BE
+:102070002000002021000020780C002024000020F7
+:10208000F0B5FFB0B8B00120FFF7F8FB544C0027C3
+:102090003620394641435348534A081889180022CC
+:1020A0001A23FE01550075192D19AD8805800D8084
+:1020B000801C891C521C5B1EF4D17F1CFFB20E2FAA
+:1020C000E6D30020FFF7DAFB0020844634214843A2
+:1020D00069464118604636225043424A1A23801806
+:1020E00000226446E70154003D193D4C2C19A48898
+:1020F0000C800025455F26B2AC1B02D5741B048002
+:1021000000E00480891C801C521C5B1EEBD16046E1
+:10211000401CC0B284460E28D8D37FB038B0F0BD82
+:10212000F8B5BD208107887230490120487004202D
+:10213000FEF755FF00242E4D012828D00420FEF77D
+:102140004EFF02282DD00420FEF749FF03281DD1A1
+:102150000120FEF744FF06460220FEF740FF36212D
+:102160004843214976004718B85B4004410E0820D7
+:10217000FEF732FFB85BC1B20920FEF72DFF002148
+:102180000420FEF729FFFFF733FE2C70F8BDFFF7A0
+:1021900077FF00210420FEF71FFFFFF729FE2C70B8
+:1021A000F8BD0120FEF71BFF06460220FEF717FFD1
+:1021B000362148430B4976004718B85B4004410E6E
+:1021C0000820FEF709FFB85BC1B20920FEF704FF43
+:1021D00000210420FEF700FFFFF70AFE2C70F8BD77
+:1021E00000410040260F00201A120020400000404D
+:1021F0001900002070B500242546002801DA0124CA
+:102200004042002901DA0125494200F022F8AC429F
+:1022100000D04042002C00D0494270BDD2B201E053
+:102220000270401C491EFBD270470022F6E710B531
+:1022300004460846114602462046FFF7EFFF2046B7
+:1022400010BD0146002000E0401C0A5C002AFBD1C2
+:10225000704730B50B46014600202022012409E0DA
+:102260000D46D5409D4205D31D469540491B254648
+:10227000954040191546521E002DF1DC30BD00007E
+:10228000064C0125064E05E02046E36807C82B43AF
+:1022900098471034B442F7D3FDF716FFE822000048
+:1022A0000823000002E008C8121F08C1002AFAD162
+:1022B0007047002001E001C1121F002AFBD17047C6
+:1022C000000102030405060A0B0C0D0E0F1011127B
+:1022D000131415191A1B1C1D1E1F020406080C0ED0
+:1022E000100305070B0D0F1108230000000000204C
+:1022F00034000000A42200003C2300003400002031
+:10230000DC140000B2220000000000000000000009
+:1023100000000000000000000000000000000000BD
+:10232000000100000000000000010000D0020000D9
+:0C233000000500000F010000000000008C
+:00000001FF
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 new file mode 100644 index 0000000..cfa6580 --- /dev/null +++ b/include/linux/hsi_char.h @@ -0,0 +1,71 @@ +/* + * hsi_char.h + * + * HSI character driver public declaration header file. + * + * Copyright (C) 2009 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Andras Domokos <andras.domokos@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef HSI_CHAR_H +#define HSI_CHAR_H + +#define HSI_CHAR_BASE 'S' +#define CS_IOW(num, dtype) _IOW(HSI_CHAR_BASE, num, dtype) +#define CS_IOR(num, dtype) _IOR(HSI_CHAR_BASE, num, dtype) +#define CS_IOWR(num, dtype) _IOWR(HSI_CHAR_BASE, num, dtype) +#define CS_IO(num) _IO(HSI_CHAR_BASE, num) + +#define CS_SEND_BREAK CS_IO(1) +#define CS_FLUSH_RX CS_IO(2) +#define CS_FLUSH_TX CS_IO(3) +#define CS_BOOTSTRAP CS_IO(4) +#define CS_SET_ACWAKELINE CS_IOW(5, unsigned int) +#define CS_GET_ACWAKELINE CS_IOR(6, unsigned int) +#define CS_SET_RX CS_IOW(7, struct hsi_rx_config) +#define CS_GET_RX CS_IOW(8, struct hsi_rx_config) +#define CS_SET_TX CS_IOW(9, struct hsi_tx_config) +#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 HSI_MODE_SLEEP 0 +#define HSI_MODE_STREAM 1 +#define HSI_MODE_FRAME 2 + +#define HSI_ARBMODE_RR 0 +#define HSI_ARBMODE_PRIO 1 + +#define WAKE_UP 1 +#define WAKE_DOWN 0 + +struct hsi_tx_config { + __u32 mode; + __u32 flow; + __u32 frame_size; + __u32 channels; + __u32 divisor; + __u32 arb_mode; +}; + +struct hsi_rx_config { + __u32 mode; + __u32 flow; + __u32 frame_size; + __u32 channels; + __u32 divisor; /* not used for SSI */ + __u32 counters; +}; + +#endif /* HSI_CHAR_H */ diff --git a/include/linux/hsi_driver_if.h b/include/linux/hsi_driver_if.h new file mode 100644 index 0000000..547b30e --- /dev/null +++ b/include/linux/hsi_driver_if.h @@ -0,0 +1,181 @@ +/* + * hsi_driver_if.h + * + * Header for the HSI driver low level interface. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Author: Carlos Chinea <carlos.chinea@nokia.com> + * Author: Sebastien JAN <s-jan@ti.com> + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef __HSI_DRIVER_IF_H__ +#define __HSI_DRIVER_IF_H__ + +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/notifier.h> + +/* The number of ports handled by the driver (MAX:2). Reducing this value + * optimizes the driver memory footprint. + */ +#define HSI_MAX_PORTS 1 + +/* bit-field definition for allowed controller IDs and channels */ +#define ANY_HSI_CONTROLLER -1 + +/* HSR special divisor values set to control the auto-divisor Rx mode */ +#define HSI_HSR_DIVISOR_AUTO 0x1000 /* Activate auto Rx */ +#define HSI_SSR_DIVISOR_USE_TIMEOUT 0x1001 /* De-activate auto-Rx (SSI) */ + +enum { + HSI_EVENT_BREAK_DETECTED = 0, + HSI_EVENT_ERROR, + HSI_EVENT_PRE_SPEED_CHANGE, + HSI_EVENT_POST_SPEED_CHANGE, + HSI_EVENT_CAWAKE_UP, + HSI_EVENT_CAWAKE_DOWN, + HSI_EVENT_HSR_DATAAVAILABLE, +}; + +enum { + HSI_IOCTL_ACWAKE_DOWN = 0, /* Unset HST ACWAKE line for channel */ + HSI_IOCTL_ACWAKE_UP, /* Set HSI wakeup line (acwake) for channel */ + HSI_IOCTL_SEND_BREAK, /* Send a HW BREAK frame in FRAME mode */ + HSI_IOCTL_GET_ACWAKE, /* Get HST CAWAKE line status */ + HSI_IOCTL_FLUSH_RX, /* Force the HSR to idle state */ + HSI_IOCTL_FLUSH_TX, /* Force the HST to idle state */ + HSI_IOCTL_GET_CAWAKE, /* Get CAWAKE (HSR) line status */ + HSI_IOCTL_SET_RX, /* Set HSR configuration */ + HSI_IOCTL_GET_RX, /* Get HSR configuration */ + HSI_IOCTL_SET_TX, /* Set HST configuration */ + 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, +}; + +/* Forward references */ +struct hsi_device; +struct hsi_channel; + +/* DPS */ +struct hst_ctx { + u32 mode; + u32 flow; + u32 frame_size; + u32 divisor; + u32 arb_mode; + u32 channels; +}; + +struct hsr_ctx { + u32 mode; + u32 flow; + u32 frame_size; + u32 divisor; + u32 counters; + u32 channels; +}; + +struct port_ctx { + u32 sys_mpu_enable[2]; + struct hst_ctx hst; + struct hsr_ctx hsr; +}; + +/** + * struct 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 { + u32 sysconfig; + u32 gdd_gcr; + u32 dll; + struct port_ctx *pctx; +}; +/* END DPS */ + + +/** + * struct hsi_device - HSI device object (Virtual) + * @n_ctrl: associated HSI controller platform id number + * @n_p: port number + * @n_ch: channel number + * @ch: channel descriptor + * @device: associated device +*/ +struct hsi_device { + int n_ctrl; + unsigned int n_p; + unsigned int n_ch; + struct hsi_channel *ch; + struct device device; +}; + +#define to_hsi_device(dev) container_of(dev, struct hsi_device, device) + +/** + * struct hsi_device_driver - HSI driver instance container + * @ctrl_mask: bit-field indicating the supported HSI device ids + * @ch_mask: bit-field indicating enabled channels for this port + * @probe: probe callback (driver registering) + * @remove: remove callback (driver un-registering) + * @suspend: suspend callback + * @resume: resume callback + * @driver: associated device_driver object +*/ +struct hsi_device_driver { + unsigned long ctrl_mask; + unsigned long ch_mask[HSI_MAX_PORTS]; + int (*probe) (struct hsi_device *dev); + int (*remove) (struct hsi_device *dev); + int (*suspend) (struct hsi_device *dev, pm_message_t mesg); + int (*resume) (struct hsi_device *dev); + struct device_driver driver; + void *priv_data; + +}; + +#define to_hsi_device_driver(drv) container_of(drv, \ + struct hsi_device_driver, \ + driver) + +int hsi_register_driver(struct hsi_device_driver *driver); +void hsi_unregister_driver(struct hsi_device_driver *driver); +int hsi_open(struct hsi_device *dev); +int hsi_write(struct hsi_device *dev, u32 * addr, unsigned int size); +int hsi_write_cancel(struct hsi_device *dev); +int hsi_read(struct hsi_device *dev, u32 * addr, unsigned int size); +int hsi_read_cancel(struct hsi_device *dev); +int hsi_poll(struct hsi_device *dev); +int hsi_unpoll(struct hsi_device *dev); +int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg); +void hsi_close(struct hsi_device *dev); +void hsi_set_read_cb(struct hsi_device *dev, + void (*read_cb) (struct hsi_device *dev, + unsigned int size)); +void hsi_set_write_cb(struct hsi_device *dev, + void (*write_cb) (struct hsi_device *dev, + unsigned int size)); +void hsi_set_port_event_cb(struct hsi_device *dev, + void (*port_event_cb) (struct hsi_device *dev, + unsigned int event, + void *arg)); +#endif /* __HSI_DRIVER_IF_H__ */ diff --git a/include/linux/mpu.h b/include/linux/mpu.h new file mode 100644 index 0000000..6a48513 --- /dev/null +++ b/include/linux/mpu.h @@ -0,0 +1,327 @@ +/* + $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, + + 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/mms_ts.h b/include/linux/platform_data/mms_ts.h new file mode 100644 index 0000000..ddb4683 --- /dev/null +++ b/include/linux/platform_data/mms_ts.h @@ -0,0 +1,33 @@ +/* + * 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); +}; + +#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..a1a404a --- /dev/null +++ b/include/linux/platform_data/modem.h @@ -0,0 +1,90 @@ +/* + * 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, +}; + +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_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_cp_off; + unsigned gpio_slave_wakeup; + unsigned gpio_host_wakeup; + unsigned gpio_host_active; +#endif /*CONFIG_LTE_MODEM_CMC221*/ + + /* 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..275ed84 --- /dev/null +++ b/include/linux/platform_data/panel-s6e8aa0.h @@ -0,0 +1,69 @@ +/* + * 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 + +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 v0; + const u32 v1; + const u32 v15; + const u32 v35; + const u32 v59; + const u32 v87; + const u32 v171; + const u32 v255; +}; + +struct s6e8aa0_color_adj { + u32 mult[3]; + int rshift; +}; + +struct panel_s6e8aa0_data { + int reset_gpio; + void (* set_power)(bool enable); + + u16 factory_v255_regs[3]; + struct s6e8aa0_color_adj color_adj; + + const struct s6e8aa0_gamma_adj_points *gamma_adj_points; + const struct s6e8aa0_gamma_entry *gamma_table; + int gamma_table_size; +}; + +#endif diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c index 3d6a554..24eadb5 100644 --- a/sound/soc/omap/sdp4430.c +++ b/sound/soc/omap/sdp4430.c @@ -722,8 +722,9 @@ 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"); @@ -731,6 +732,8 @@ static int __init sdp4430_soc_init(void) 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) { |